Skip to content

Commit 8575522

Browse files
tacxoualainabbas
andauthored
Fusion identities (#36)
* api * Update identities.service.ts * save * update --------- Co-authored-by: Alain Abbas <alain.abbas@libertech.fr>
1 parent d55067f commit 8575522

File tree

5 files changed

+215
-6
lines changed

5 files changed

+215
-6
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { ApiProperty } from '@nestjs/swagger';
2+
import { IsString } from 'class-validator';
3+
4+
export class FusionDto {
5+
@IsString()
6+
@ApiProperty({ example: '66d80ab41821baca9bf965b2', description: 'Id of the first identity', type: String })
7+
public id1: string;
8+
9+
@IsString()
10+
@ApiProperty({ example: '66d80ab41821baca9bf965b2', description: 'Id of the second identity', type: String })
11+
public id2: string;
12+
}

src/management/identities/_enums/states.enum.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@ export enum IdentityState {
77
TO_CREATE = -1,
88
TO_COMPLETE = -2,
99
ON_ERROR = -3,
10+
DONT_SYNC = -99,
1011
}

src/management/identities/_schemas/identities.schema.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { IdentityLifecycle } from './../_enums/lifecycle.enum';
22
import { inetOrgPerson, inetOrgPersonSchema } from './_parts/inetOrgPerson.part';
33
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
4-
import { Document } from 'mongoose';
4+
import {Document, Types} from 'mongoose';
55
import { AbstractSchema } from '~/_common/abstracts/schemas/abstract.schema';
66
import { IdentityState } from '../_enums/states.enum';
77
import { AdditionalFieldsPart, AdditionalFieldsPartSchema } from './_parts/additionalFields.part.schema';
@@ -42,7 +42,15 @@ export class Identities extends AbstractSchema {
4242
@Prop({
4343
type: Object,
4444
})
45-
public customFields?: { [key: string]: MixedValue }
45+
public customFields?: { [key: string]: MixedValue };
46+
47+
//pour les identités fusionnées ont met les deux identités sources
48+
@Prop({ type: Array, of: Types.ObjectId, required: true, default: [] })
49+
public srcFusionId: Types.ObjectId[];
50+
51+
//pour les identités qui on servit à une fusion on met la destination (la nouvelle identité fusionnée)
52+
@Prop({ type: Types.ObjectId, required: false })
53+
public destFusionId: Types.ObjectId;
4654
}
4755

4856
export const IdentitiesSchema = SchemaFactory.createForClass(Identities).plugin(AutoIncrementPlugin, <AutoIncrementPluginOptions>{

src/management/identities/identities.controller.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
UploadedFile,
1515
UseInterceptors,
1616
} from '@nestjs/common';
17-
import { ApiOperation, ApiParam, ApiTags, PartialType } from '@nestjs/swagger';
17+
import {ApiOperation, ApiParam, ApiResponse, ApiTags, PartialType} from '@nestjs/swagger';
1818
import {
1919
FilterOptions,
2020
FilterSchema,
@@ -47,6 +47,7 @@ import { join } from 'node:path';
4747
import { omit } from 'radash';
4848
import { TransformersFilestorageService } from '~/core/filestorage/_services/transformers-filestorage.service';
4949
import { Public } from '~/_common/decorators/public.decorator';
50+
import {FusionDto} from "~/management/identities/_dto/fusion.dto";
5051

5152
@ApiTags('management/identities')
5253
@Controller('identities')
@@ -360,4 +361,28 @@ export class IdentitiesController extends AbstractController {
360361
});
361362
await this.transformerService.transform(mime, res, data, stream, parent);
362363
}
364+
@Get('duplicates')
365+
@ApiOperation({ summary: 'Renvoie la liste des doublons supposés' })
366+
public async getDoublons(@Res() res: Response): Promise<Response> {
367+
const data = await this._service.searchDoubles();
368+
const total = data.length;
369+
return res.status(HttpStatus.OK).json({
370+
statusCode: HttpStatus.OK,
371+
data,
372+
total,
373+
});
374+
}
375+
@Post('fusion')
376+
@ApiOperation({ summary: 'fusionne les deux identités' })
377+
@ApiResponse({ status: HttpStatus.OK })
378+
public async fusion(
379+
@Body() body: FusionDto,
380+
@Res() res: Response,
381+
): Promise<Response> {
382+
const newId = await this._service.fusion(body.id1, body.id2);
383+
return res.status(HttpStatus.OK).json({
384+
statusCode: HttpStatus.OK,
385+
newId,
386+
});
387+
}
363388
}

src/management/identities/identities.service.ts

Lines changed: 166 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import { IdentityState } from './_enums/states.enum';
2222
import { Identities } from './_schemas/identities.schema';
2323
import { IdentitiesValidationService } from './validations/identities.validation.service';
2424
import { FactorydriveService } from '@the-software-compagny/nestjs_module_factorydrive';
25+
import {ApiBadRequestResponse} from "@nestjs/swagger";
26+
import {InitStatesEnum} from "~/management/identities/_enums/init-state.enum";
2527

2628
@Injectable()
2729
export class IdentitiesService extends AbstractServiceSchema {
@@ -320,17 +322,17 @@ export class IdentitiesService extends AbstractServiceSchema {
320322

321323
public transformNullsToString(obj) {
322324
if (obj === null) {
323-
return "";
325+
return '';
324326
}
325327

326328
if (Array.isArray(obj)) {
327329
return obj.map(this.transformNullsToString);
328330
}
329331

330-
if (typeof obj === 'object') {
332+
if (typeof obj === 'object' && !(obj instanceof Types.ObjectId)) {
331333
for (const key in obj) {
332334
if (obj[key] === null) {
333-
obj[key] = "";
335+
obj[key] = '';
334336
} else if (typeof obj[key] === 'object') {
335337
console.log('key', key);
336338
obj[key] = this.transformNullsToString(obj[key]);
@@ -340,4 +342,165 @@ export class IdentitiesService extends AbstractServiceSchema {
340342

341343
return obj;
342344
}
345+
public async searchDoubles() {
346+
const agg1 = [
347+
{
348+
$match: {
349+
state: { $ne: IdentityState.SYNCED },
350+
destFusionId: { $eq: null },
351+
},
352+
},
353+
{
354+
$addFields: {
355+
test: {
356+
$concat: [
357+
'$additionalFields.attributes.supannPerson.supannOIDCDatedeNaissance',
358+
'$inetOrgPerson.givenName',
359+
],
360+
},
361+
},
362+
},
363+
{
364+
$group: {
365+
_id: '$test',
366+
n: {
367+
$sum: 1,
368+
},
369+
list: {
370+
$addToSet: {
371+
_id: '$_id',
372+
uid: '$inetOrgPerson.uid',
373+
cn: '$inetOrgPerson.cn',
374+
employeeNumber: '$inetOrgPerson.employeeNumber',
375+
departmentNumber: '$inetOrgPerson.departmentNumber',
376+
},
377+
},
378+
},
379+
},
380+
{
381+
$match: {
382+
n: {
383+
$gt: 1,
384+
},
385+
},
386+
},
387+
];
388+
const agg2 = [
389+
{
390+
$match: {
391+
state: { $ne: IdentityState.SYNCED },
392+
destFusionId: { $eq: null },
393+
},
394+
},
395+
{
396+
$group: {
397+
_id: '$inetOrgPerson.uid',
398+
n: {
399+
$sum: 1,
400+
},
401+
list: {
402+
$addToSet: {
403+
_id: '$_id',
404+
uid: '$inetOrgPerson.uid',
405+
cn: '$inetOrgPerson.cn',
406+
employeeNumber: '$inetOrgPerson.employeeNumber',
407+
departmentNumber: '$inetOrgPerson.departmentNumber',
408+
},
409+
},
410+
},
411+
},
412+
{
413+
$match: {
414+
n: {
415+
$gt: 1,
416+
},
417+
},
418+
},
419+
];
420+
const result1 = await this._model.aggregate(agg1);
421+
const result2 = await this._model.aggregate(agg2);
422+
const result3 = result1.map((x) => {
423+
const k = x.list[0]._id + '/' + x.list[1]._id;
424+
const k1 = x.list[1]._id + '/' + x.list[0]._id;
425+
return { k1: k1, k: k, key1: x.list[0]._id, key2: x.list[1]._id, data: x.list };
426+
});
427+
const result4 = result2.map((x) => {
428+
const k = x.list[0]._id + '/' + x.list[1]._id;
429+
const k1 = x.list[1]._id + '/' + x.list[0]._id;
430+
return { k1: k1, k: k, key1: x.list[0]._id, key2: x.list[1]._id, data: x.list };
431+
});
432+
result4.forEach((x) => {
433+
const r = result3.find((o) => o.k === x.k);
434+
const r1 = result3.find((o) => o.k1 === x.k);
435+
if (r === undefined && r1 === undefined) {
436+
result3.push(x);
437+
}
438+
});
439+
return result3;
440+
}
441+
//fusionne les deux identités id2 > id1 les champs presents dans id2 et non present dans id1 seront ajoutés
442+
// retourne l'id de na nouvelle identité créée
443+
public async fusion(id1, id2) {
444+
let identity1 = null;
445+
let identity2 = null;
446+
try {
447+
identity1 = await this.findById(id1);
448+
} catch (error) {
449+
throw new BadRequestException('Id1 not found');
450+
}
451+
try {
452+
identity2 = await this.findById(id2);
453+
} catch (error) {
454+
throw new BadRequestException('Id2 not found');
455+
}
456+
//test si une ou les deux entités ont deja été fusionnées
457+
const x=identity1.destFusionId
458+
if (identity1.destFusionId !== undefined && identity1.destFusionId !== null) {
459+
throw new BadRequestException('Id1 already fusionned');
460+
}
461+
if (identity2.destFusionId !== undefined && identity2.destFusionId !== null) {
462+
throw new BadRequestException('Id2 already fusionned');
463+
}
464+
const plainIdentity1 = toPlainAndCrush(identity1.toJSON(), {
465+
excludePrefixes: ['_id', 'fingerprint', 'metadata'],
466+
});
467+
const plainIdentity2 = toPlainAndCrush(identity2.toJSON(), {
468+
excludePrefixes: ['_id', 'fingerprint', 'metadata'],
469+
});
470+
const newObj = construct({ ...plainIdentity2, ...plainIdentity1 });
471+
//const newIdentity = await this.create(newObj);
472+
newObj.inetOrgPerson.employeeType = 'FUSION';
473+
newObj.inetOrgPerson.employeeNumber =
474+
identity1.inetOrgPerson.employeeNumber + '/' + identity2.inetOrgPerson.employeeNumber;
475+
identity2.inetOrgPerson.departmentNumber.forEach((depN) => {
476+
newObj.inetOrgPerson.departmentNumber.push(depN);
477+
});
478+
// si supann est present
479+
if (
480+
identity1.additionalFields.objectClasses.includes('supannPerson') &&
481+
identity2.additionalFields.objectClasses.includes('supannPerson')
482+
) {
483+
identity2.additionalFields.attributes.supannPerson.supannTypeEntiteAffectation.forEach((depN) => {
484+
newObj.additionalFields.attributes.supannPerson.supannTypeEntiteAffectation.push(depN);
485+
});
486+
// supannRefId
487+
identity2.additionalFields.attributes.supannPerson.supannRefId.forEach((depN) => {
488+
newObj.additionalFields.attributes.supannPerson.supannRefId.push(depN);
489+
});
490+
}
491+
newObj.state = IdentityState.TO_VALIDATE;
492+
newObj.srcFusionId = [identity1._id, identity2._id];
493+
const newIdentity = await this.create(newObj);
494+
//MAJ identite1
495+
identity1.destFusionId = newIdentity._id;
496+
identity1.state = IdentityState.DONT_SYNC;
497+
// modification identité1
498+
super.update(identity1._id, identity1);
499+
//Maj Identity2
500+
identity2.destFusionId = newIdentity._id;
501+
identity2.state = IdentityState.DONT_SYNC;
502+
// modification identité1
503+
super.update(identity2._id, identity2);
504+
return newIdentity._id;
505+
}
343506
}

0 commit comments

Comments
 (0)