diff --git a/src/admin/admin.controller.spec.ts b/src/admin/admin.controller.spec.ts index 26b7d5b8b9ecdf2aa5acde316dd5abfecda90638..d5af5352ceb3950fce9459dbc131e41796bd9de5 100644 --- a/src/admin/admin.controller.spec.ts +++ b/src/admin/admin.controller.spec.ts @@ -190,4 +190,11 @@ describe('AdminController', () => { } }); }); + + it('should get pending structure list for admin', async () => { + expect((await controller.getAdminStructuresList()).inClaim.length).toBe(2); + expect((await controller.getAdminStructuresList()).toClaim.length).toEqual(2); + expect((await controller.getAdminStructuresList()).claimed.length).toEqual(0); + expect((await controller.getAdminStructuresList()).incomplete.length).toEqual(2); + }); }); diff --git a/src/admin/admin.controller.ts b/src/admin/admin.controller.ts index 4ce035f7188d6537d5a9235c049fec7db19fa763..ad0a493f351f0a25a9b66e18930f632f3c9f4966 100644 --- a/src/admin/admin.controller.ts +++ b/src/admin/admin.controller.ts @@ -1,6 +1,17 @@ import { ApiOperation, ApiParam } from '@nestjs/swagger'; import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard'; -import { Body, Delete, Param, Controller, Get, Post, UseGuards, HttpStatus, HttpException } from '@nestjs/common'; +import { + Body, + Delete, + Param, + Controller, + Get, + Post, + UseGuards, + HttpStatus, + HttpException, + Logger, +} from '@nestjs/common'; import { NewsletterSubscription } from '../newsletter/newsletter-subscription.schema'; import { NewsletterService } from '../newsletter/newsletter.service'; import { StructuresService } from '../structures/services/structures.service'; @@ -8,9 +19,12 @@ import { Roles } from '../users/decorators/roles.decorator'; import { RolesGuard } from '../users/guards/roles.guard'; import { UsersService } from '../users/users.service'; import { PendingStructureDto } from './dto/pending-structure.dto'; +import { validate } from 'class-validator'; +import { Structure } from '../structures/schemas/structure.schema'; @Controller('admin') export class AdminController { + private readonly logger = new Logger(AdminController.name); constructor( private usersService: UsersService, private structuresService: StructuresService, @@ -36,12 +50,13 @@ export class AdminController { @Get('adminStructuresList') @ApiOperation({ description: 'Get pending structre for validation' }) public async getAdminStructuresList(): Promise<any> { - const structuresList = { claimed: [], inClaim: [], toClaim: [] }; + const structuresList = { claimed: [], inClaim: [], toClaim: [], incomplete: [] }; structuresList.inClaim = await this.getPendingAttachments(); structuresList.toClaim = (await this.structuresService.findAllUnclaimed()).filter( (demand) => !structuresList.inClaim.find((elem) => elem.structureId == demand.structureId) ); - structuresList.claimed = (await this.structuresService.findAll()) + const allStructures = await this.structuresService.findAll(); + structuresList.claimed = allStructures .filter( (demand) => !structuresList.inClaim.find((elem) => elem.structureId == demand.id) && @@ -50,9 +65,23 @@ export class AdminController { .map((structure) => { return { structureId: structure.id, structureName: structure.structureName }; }); + structuresList.incomplete = await Promise.all( + allStructures.map(async (struct) => { + const validity = await validate(new Structure(struct)); + if (validity.length > 0) { + this.logger.debug(`getAdminStructuresList - validation failed. errors: ${validity.toString()}`); + return { structureId: struct.id, structureName: struct.structureName }; + } else { + this.logger.debug('getAdminStructuresList - validation succeed'); + return null; + } + }) + ); + structuresList.incomplete = structuresList.incomplete.filter((structure) => structure); structuresList.claimed.sort((a, b) => a.structureName.localeCompare(b.structureName)); structuresList.inClaim.sort((a, b) => a.structureName.localeCompare(b.structureName)); structuresList.toClaim.sort((a, b) => a.structureName.localeCompare(b.structureName)); + structuresList.incomplete.sort((a, b) => a.structureName.localeCompare(b.structureName)); return structuresList; } diff --git a/src/structures/schemas/structure.schema.ts b/src/structures/schemas/structure.schema.ts index a4745ffb328a51351752218b39009360e845f029..1ac967167569fdb89c4ba11b87309363f4c4ad7e 100644 --- a/src/structures/schemas/structure.schema.ts +++ b/src/structures/schemas/structure.schema.ts @@ -1,4 +1,6 @@ import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; +import { Type } from 'class-transformer'; +import { ArrayNotEmpty, IsNotEmpty, ValidateNested } from 'class-validator'; import { Document } from 'mongoose'; import { Address } from './address.schema'; import { Week } from './week.schema'; @@ -7,6 +9,50 @@ export type StructureDocument = Structure & Document; @Schema({ timestamps: true }) export class Structure { + constructor(data: Partial<Structure> = {}) { + this.structureName = data.structureName; + this.createdAt = data.createdAt; + this.updatedAt = data.updatedAt; + this.numero = data.numero; + this.structureType = data.structureType; + this.description = data.description; + this.lockdownActivity = data.lockdownActivity; + this.address = data.address; + this.contactPhone = data.contactPhone; + this.contactMail = data.contactMail; + this.website = data.website; + this.facebook = data.facebook; + this.twitter = data.twitter; + this.instagram = data.instagram; + this.linkedin = data.linkedin; + this.pmrAccess = data.pmrAccess; + this.accessModality = data.accessModality; + this.otherDescription = data.otherDescription; + this.labelsQualifications = data.labelsQualifications; + this.publics = data.publics; + this.exceptionalClosures = data.exceptionalClosures; + this.publicsAccompaniment = data.publicsAccompaniment; + this.proceduresAccompaniment = data.proceduresAccompaniment; + this.remoteAccompaniment = data.remoteAccompaniment; + this.baseSkills = data.baseSkills; + this.accessRight = data.accessRight; + this.socialAndProfessional = data.socialAndProfessional; + this.parentingHelp = data.parentingHelp; + this.exceptionalClosures = data.exceptionalClosures; + this.digitalCultureSecurity = data.digitalCultureSecurity; + this.equipmentsAndServices = data.equipmentsAndServices; + this.freeWorkShop = data.freeWorkShop; + this.nbTablets = data.nbTablets; + this.nbPrinters = data.nbPrinters; + this.nbComputers = data.nbComputers; + this.nbNumericTerminal = data.nbNumericTerminal; + this.nbScanners = data.nbScanners; + this.hours = data.hours; + this.coord = data.coord; + this.deletedAt = data.deletedAt; + this.accountVerified = data.accountVerified; + } + @Prop() numero: string; @@ -17,9 +63,11 @@ export class Structure { updatedAt: string; @Prop() + @IsNotEmpty() structureName: string; @Prop() + @IsNotEmpty() structureType: string; @Prop() @@ -29,13 +77,18 @@ export class Structure { lockdownActivity: string; @Prop() + @ValidateNested({ each: true }) + @Type(() => Address) address: Address; @Prop() + @IsNotEmpty() contactPhone: string; @Prop() + @IsNotEmpty() contactMail: string; + @Prop() website: string; @@ -52,9 +105,11 @@ export class Structure { linkedin: string; @Prop() + @IsNotEmpty() pmrAccess: boolean; @Prop() + @ArrayNotEmpty() accessModality: string[]; @Prop() @@ -64,6 +119,7 @@ export class Structure { labelsQualifications: string[]; @Prop() + @ArrayNotEmpty() publics: string[]; @Prop() @@ -76,6 +132,7 @@ export class Structure { proceduresAccompaniment: string[]; @Prop() + @IsNotEmpty() remoteAccompaniment: boolean; @Prop() @@ -97,21 +154,26 @@ export class Structure { equipmentsAndServices: string[]; @Prop() + @IsNotEmpty() freeWorkShop: boolean; @Prop() nbComputers: number; @Prop() + @IsNotEmpty() nbPrinters: number; @Prop() + @IsNotEmpty() nbTablets: number; @Prop() + @IsNotEmpty() nbNumericTerminal: number; @Prop() + @IsNotEmpty() nbScanners: number; @Prop() diff --git a/test/mock/services/structures.mock.service.ts b/test/mock/services/structures.mock.service.ts index 276dfd05e723b0aaf0e74b3d991818fb55f7f007..a3432c034ab8ecb859199518b728d59adf6b5c8c 100644 --- a/test/mock/services/structures.mock.service.ts +++ b/test/mock/services/structures.mock.service.ts @@ -329,4 +329,310 @@ export class StructuresServiceMock { throw new HttpException('Invalid structure id', HttpStatus.BAD_REQUEST); } + + findAll() { + return [ + { + _id: '6093ba0e2ab5775cfc01abce', + coord: [4.8498155, 45.7514817], + equipmentsAndServices: ['wifiEnAccesLibre'], + digitalCultureSecurity: [], + parentingHelp: [], + socialAndProfessional: [], + accessRight: [], + baseSkills: [], + proceduresAccompaniment: [], + publicsAccompaniment: [], + publics: ['adultes'], + labelsQualifications: [], + accessModality: ['telephoneVisio'], + structureType: null, + structureName: 'a', + description: null, + lockdownActivity: null, + address: { + numero: null, + street: 'Rue Alphonse Daudet', + commune: 'Lyon 7ème Arrondissement', + }, + contactMail: '', + contactPhone: '', + website: '', + facebook: null, + twitter: null, + instagram: null, + linkedin: null, + hours: { + monday: { + open: false, + time: [], + }, + tuesday: { + open: false, + time: [], + }, + wednesday: { + open: false, + time: [], + }, + thursday: { + open: false, + time: [], + }, + friday: { + open: false, + time: [], + }, + saturday: { + open: false, + time: [], + }, + sunday: { + open: false, + time: [], + }, + }, + pmrAccess: false, + exceptionalClosures: null, + otherDescription: null, + nbComputers: 1, + nbPrinters: 1, + nbTablets: 1, + nbNumericTerminal: 1, + nbScanners: 1, + freeWorkShop: false, + accountVerified: true, + createdAt: '2021-05-06T09:42:38.000Z', + updatedAt: '2021-05-06T09:42:50.000Z', + __v: 0, + }, + { + _id: '6093ba0e2ab5775cfc01fffe', + coord: [4.8498155, 45.7514817], + equipmentsAndServices: ['wifiEnAccesLibre'], + digitalCultureSecurity: [], + parentingHelp: [], + socialAndProfessional: [], + accessRight: [], + baseSkills: [], + proceduresAccompaniment: [], + publicsAccompaniment: [], + publics: ['adultes'], + labelsQualifications: [], + accessModality: ['telephoneVisio'], + structureType: null, + structureName: 'a', + description: null, + lockdownActivity: null, + address: { + numero: null, + street: 'Rue Alphonse Daudet', + commune: 'Lyon 7ème Arrondissement', + }, + contactMail: '', + contactPhone: '', + website: '', + facebook: null, + twitter: null, + instagram: null, + linkedin: null, + hours: { + monday: { + open: false, + time: [], + }, + tuesday: { + open: false, + time: [], + }, + wednesday: { + open: false, + time: [], + }, + thursday: { + open: false, + time: [], + }, + friday: { + open: false, + time: [], + }, + saturday: { + open: false, + time: [], + }, + sunday: { + open: false, + time: [], + }, + }, + pmrAccess: false, + exceptionalClosures: null, + otherDescription: null, + nbComputers: 1, + nbPrinters: 1, + nbTablets: 1, + nbNumericTerminal: 1, + nbScanners: 1, + freeWorkShop: false, + accountVerified: true, + createdAt: '2021-05-06T09:42:38.000Z', + updatedAt: '2021-05-06T09:42:50.000Z', + __v: 0, + }, + ]; + } + + findAllUnclaimed() { + return [ + { + _id: '6093ba0e2ab5775cfc01abcd', + coord: [4.8498155, 45.7514817], + equipmentsAndServices: ['wifiEnAccesLibre'], + digitalCultureSecurity: [], + parentingHelp: [], + socialAndProfessional: [], + accessRight: [], + baseSkills: [], + proceduresAccompaniment: [], + publicsAccompaniment: [], + publics: ['adultes'], + labelsQualifications: [], + accessModality: ['telephoneVisio'], + structureType: null, + structureName: 'a', + description: null, + lockdownActivity: null, + address: { + numero: null, + street: 'Rue Alphonse Daudet', + commune: 'Lyon 7ème Arrondissement', + }, + contactMail: '', + contactPhone: '', + website: '', + facebook: null, + twitter: null, + instagram: null, + linkedin: null, + hours: { + monday: { + open: false, + time: [], + }, + tuesday: { + open: false, + time: [], + }, + wednesday: { + open: false, + time: [], + }, + thursday: { + open: false, + time: [], + }, + friday: { + open: false, + time: [], + }, + saturday: { + open: false, + time: [], + }, + sunday: { + open: false, + time: [], + }, + }, + pmrAccess: false, + exceptionalClosures: null, + otherDescription: null, + nbComputers: 1, + nbPrinters: 1, + nbTablets: 1, + nbNumericTerminal: 1, + nbScanners: 1, + freeWorkShop: false, + accountVerified: true, + createdAt: '2021-05-06T09:42:38.000Z', + updatedAt: '2021-05-06T09:42:50.000Z', + __v: 0, + }, + { + _id: '6093ba0e2ab5775cfc01ffff', + coord: [4.8498155, 45.7514817], + equipmentsAndServices: ['wifiEnAccesLibre'], + digitalCultureSecurity: [], + parentingHelp: [], + socialAndProfessional: [], + accessRight: [], + baseSkills: [], + proceduresAccompaniment: [], + publicsAccompaniment: [], + publics: ['adultes'], + labelsQualifications: [], + accessModality: ['telephoneVisio'], + structureType: null, + structureName: 'a', + description: null, + lockdownActivity: null, + address: { + numero: null, + street: 'Rue Alphonse Daudet', + commune: 'Lyon 7ème Arrondissement', + }, + contactMail: '', + contactPhone: '', + website: '', + facebook: null, + twitter: null, + instagram: null, + linkedin: null, + hours: { + monday: { + open: false, + time: [], + }, + tuesday: { + open: false, + time: [], + }, + wednesday: { + open: false, + time: [], + }, + thursday: { + open: false, + time: [], + }, + friday: { + open: false, + time: [], + }, + saturday: { + open: false, + time: [], + }, + sunday: { + open: false, + time: [], + }, + }, + pmrAccess: false, + exceptionalClosures: null, + otherDescription: null, + nbComputers: 1, + nbPrinters: 1, + nbTablets: 1, + nbNumericTerminal: 1, + nbScanners: 1, + freeWorkShop: false, + accountVerified: true, + createdAt: '2021-05-06T09:42:38.000Z', + updatedAt: '2021-05-06T09:42:50.000Z', + __v: 0, + }, + ]; + } } diff --git a/test/mock/services/user.mock.service.ts b/test/mock/services/user.mock.service.ts index 1b44c701fded2d30f041bf14f3ce1c6624f77018..cd00bb6011952deba844c48bc3c9e5fe170805c8 100644 --- a/test/mock/services/user.mock.service.ts +++ b/test/mock/services/user.mock.service.ts @@ -110,7 +110,7 @@ export class UsersServiceMock { role: 0, name: 'DUPONT', surname: 'Pauline', - structuresLink: ['abcdefgh', '18sfqfq'], + structuresLink: ['abcdefgh', '18sfqfq', '6093ba0e2ab5775cfc01fffe'], }); }); } @@ -124,8 +124,8 @@ export class UsersServiceMock { return [ { structureOutdatedMailSent: [], - pendingStructuresLink: ['6001a48e16b08100062e4180'], - structuresLink: [], + pendingStructuresLink: ['6001a48e16b08100062e4180', '6093ba0e2ab5775cfc01fffe'], + structuresLink: ['6093ba0e2ab5775cfc01fffe'], newEmail: null, changeEmailToken: null, role: 0, @@ -150,8 +150,8 @@ export class UsersServiceMock { return [ { structureOutdatedMailSent: [], - pendingStructuresLink: ['6001a48e16b08100062e4180'], - structuresLink: [], + pendingStructuresLink: ['6001a48e16b08100062e4180', '6093ba0e2ab5775cfc01fffe'], + structuresLink: ['6093ba0e2ab5775cfc01fffe'], newEmail: null, changeEmailToken: null, role: 0, @@ -169,8 +169,8 @@ export class UsersServiceMock { }, { structureOutdatedMailSent: [], - pendingStructuresLink: ['6001a3c216b08100062e4169', '601d6aa2c94cf895c4e23860'], - structuresLink: ['6037aa17d8189c0014f62421'], + pendingStructuresLink: ['6001a3c216b08100062e4169', '601d6aa2c94cf895c4e23860', '6093ba0e2ab5775cfc01fffe'], + structuresLink: ['6037aa17d8189c0014f62421', '6093ba0e2ab5775cfc01fffe'], newEmail: null, changeEmailToken: null, role: 0,