Skip to content

Commit

Permalink
fix(backend): add new rules for search
Browse files Browse the repository at this point in the history
  • Loading branch information
pYassine committed Nov 15, 2024
1 parent 808f8b2 commit 3be665a
Show file tree
Hide file tree
Showing 32 changed files with 1,203 additions and 653 deletions.
30 changes: 30 additions & 0 deletions packages/backend/src/_migrations/1731347018702-auto-migration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { MigrationInterface, QueryRunner } from "typeorm";
import { domifaConfig } from "../config";

export class AutoMigration1731347018702 implements MigrationInterface {
name = "AutoMigration1731347018702";

public async up(queryRunner: QueryRunner): Promise<void> {
if (
domifaConfig().envId === "prod" ||
domifaConfig().envId === "preprod" ||
domifaConfig().envId === "local"
) {
await queryRunner.query(
`CREATE TABLE typeorm_metadata ( type varchar(255), schema varchar(255), name varchar(255), value text );`
);
}
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`DROP INDEX "public"."IDX_57133463f2311234ecf27157fa"`
);
await queryRunner.query(
`ALTER TABLE "usager" DROP COLUMN "nom_prenom_surnom_ref"`
);
await queryRunner.query(
`ALTER TABLE "usager" ADD "nom_prenom_surnom_ref" character varying NOT NULL`
);
}
}
33 changes: 33 additions & 0 deletions packages/backend/src/_migrations/1731349553247-auto-migration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { MigrationInterface, QueryRunner } from "typeorm";
import { domifaConfig } from "../config";

export class AutoMigration1731349553247 implements MigrationInterface {
name = "AutoMigration1731349553247";

public async up(queryRunner: QueryRunner): Promise<void> {
if (
domifaConfig().envId === "prod" ||
domifaConfig().envId === "preprod" ||
domifaConfig().envId === "local"
) {
await queryRunner.query(
`ALTER TABLE "usager" DROP COLUMN "nom_prenom_ref"`
);
await queryRunner.query(
`ALTER TABLE "usager" ADD "nom_prenom_surnom_ref" character varying NULL`
);
await queryRunner.query(
`CREATE INDEX "IDX_57133463f2311234ecf27157fa" ON "usager" ("nom_prenom_surnom_ref") `
);
}
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`DROP INDEX "public"."IDX_57133463f2311234ecf27157fa"`
);
await queryRunner.query(
`ALTER TABLE "usager" DROP COLUMN "nom_prenom_surnom_ref"`
);
}
}
104 changes: 104 additions & 0 deletions packages/backend/src/_migrations/1731349672897-manual-migration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { MigrationInterface, QueryRunner } from "typeorm";

import { domifaConfig } from "../config";
import { dataCompare } from "../util";

const batchSize = 5000;
export class ManualMigration1731349672897 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
if (
domifaConfig().envId === "prod" ||
domifaConfig().envId === "preprod" ||
domifaConfig().envId === "local"
) {
const totalCount = await queryRunner.query(`
SELECT COUNT(*) as count
FROM usager
WHERE nom_prenom_surnom_ref IS NULL OR nom_prenom_surnom_ref = ''
`);

const totalRecords = parseInt(totalCount[0].count);
let processedRecords = 0;

while ((await this.getUsagersToUpdate(queryRunner)) > 0) {
await queryRunner.startTransaction();

try {
// Récupère un lot d'usagers
const usagers = await queryRunner.query(
`SELECT uuid, ref, nom, prenom, surnom FROM usager WHERE usager_nom_prenom_surnom_ref IS NULL OR usager_nom_prenom_surnom_ref = '' LIMIT ${batchSize}`
);

// Exécute les updates en une seule requête
if (usagers.length > 0) {
for (const usager of usagers) {
const parts = [
usager.nom,
usager.prenom,
usager.surnom,
usager.ref,
]
.filter(Boolean)
.map((part) => dataCompare.cleanString(part.toString()));

const nom_prenom_surnom_ref = parts.join(" ");

await queryRunner.query(
`UPDATE usager set nom_prenom_surnom_ref = $1 where uuid=$2`,
[nom_prenom_surnom_ref, usager.uuid]
);
}
}

// Valide la transaction
await queryRunner.commitTransaction();

processedRecords += usagers.length;
console.log(
`Progression: ${Math.min(
processedRecords,
totalRecords
)}/${totalRecords} enregistrements traités`
);
} catch (error) {
await queryRunner.rollbackTransaction();
console.error(
`Erreur lors du traitement du lot ${processedRecords}-${
processedRecords + batchSize
}:`,
error
);
throw error;
}

// Petite pause entre les lots
await new Promise((resolve) => setTimeout(resolve, 100));
}
}
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.startTransaction();

try {
await queryRunner.query(`
UPDATE usager
SET nom_prenom_surnom_ref = NULL
`);

await queryRunner.commitTransaction();
} catch (error) {
await queryRunner.rollbackTransaction();
throw error;
}
}

private async getUsagersToUpdate(queryRunner: QueryRunner): Promise<number> {
const total = await queryRunner.query(`
SELECT COUNT(*) as count
FROM usager
WHERE nom_prenom_surnom_ref IS NULL OR nom_prenom_surnom_ref = ''
`);
return parseInt(total[0].count);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { UsagerEntretienTable } from "./UsagerEntretienTable.typeorm";
import {
BeforeInsert,
BeforeUpdate,
Column,
Entity,
Index,
Expand Down Expand Up @@ -29,6 +30,7 @@ import {
Usager,
UsagerDecisionStatut,
} from "@domifa/common";
import { dataCompare } from "../../../util";

// https://typeorm.io/#/entities/column-types-for-postgres
@Entity({ name: "usager" })
Expand All @@ -52,13 +54,12 @@ export class UsagerTable
@JoinColumn({ name: "structureId", referencedColumnName: "id" })
public structureId!: number;

@Index()
@Column({
nullable: false,
name: "nom_prenom_ref",
select: false,
})
@Index()
public nom_prenom_ref: string;
public nom_prenom_surnom_ref: string;

@Column({ type: "text", nullable: false })
public nom!: string;
Expand Down Expand Up @@ -156,9 +157,16 @@ export class UsagerTable
> | null;

@BeforeInsert()
@BeforeUpdate()
nameToUpperCase() {
this.nom = this.nom.trim();
this.prenom = this.prenom.trim();

const parts = [this.nom, this.prenom, this.surnom, this.ref]
.filter(Boolean)
.map((part) => dataCompare.cleanString(part.toString()));

this.nom_prenom_surnom_ref = parts.join(" ");
}

public constructor(entity?: Partial<UsagerTable>) {
Expand Down
82 changes: 71 additions & 11 deletions packages/backend/src/usagers/controllers/usagers.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { usagerDocsRepository } from "./../../database/services/usager/usagerDoc

import { messageSmsRepository } from "./../../database/services/message-sms/messageSmsRepository.service";
import {
BadRequestException,
Body,
Controller,
Delete,
Expand Down Expand Up @@ -59,11 +60,13 @@ import {
ETAPE_DOCUMENTS,
CerfaDocType,
UsagerDecision,
getUsagerDeadlines,
} from "@domifa/common";
import { UsagerHistoryStateService } from "../services/usagerHistoryState.service";
import { domifaConfig } from "../../config";
import { FileManagerService } from "../../util/file-manager/file-manager.service";
import { Not } from "typeorm";
import { isDateString } from "class-validator";

@Controller("usagers")
@ApiTags("usagers")
Expand Down Expand Up @@ -151,22 +154,79 @@ export class UsagersController {
@Post("search-radies")
@AllowUserStructureRoles(...USER_STRUCTURE_ROLE_ALL)
public async searchInRadies(
@Body() { searchString }: SearchUsagerDto,
@Body() search: SearchUsagerDto,
@CurrentUser() user: UserStructureAuthenticated
) {
const search = dataCompare.cleanString(searchString);

return usagerRepository
.createQueryBuilder()
const query = usagerRepository
.createQueryBuilder("usager")
.select(joinSelectFields(USAGER_LIGHT_ATTRIBUTES))
.where(
`"structureId" = :structureId and statut = 'RADIE' AND nom_prenom_ref ILIKE :search`,
.where(`"structureId" = :structureId and statut = 'RADIE'`, {
structureId: user.structureId,
});

if (search.searchString && search.searchStringField === "DEFAULT") {
query.andWhere(`nom_prenom_surnom_ref ILIKE :str`, {
str: `%${dataCompare.cleanString(search.searchString)}%`,
});
}

if (search.searchString && search.searchStringField === "DATE_NAISSANCE") {
if (!isDateString(search.searchString)) {
throw new BadRequestException("SEARCH_PARAMS_ERROR");
}
query.andWhere(`DATE("dateNaissance") = DATE(:date)`, {
date: new Date(search.searchString),
});
}

if (search?.lastInteractionDate) {
const deadlines = getUsagerDeadlines();
const date = deadlines[search.lastInteractionDate].value;
console.log({ date, deadlines });
query.andWhere(
` ("lastInteraction"->>'dateInteraction')::timestamp >= :date`,
{
structureId: user.structureId,
search: `%${search}%`,
date,
}
)
.getRawMany();
);
}

if (search?.echeance) {
const deadlines = getUsagerDeadlines();
const now = new Date();
const deadline = deadlines[search.echeance];

if (search.echeance === "EXCEEDED") {
query.andWhere(`(decision->>'dateDecision')::timestamp <= :now`, {
now,
});
} else if (search.echeance.startsWith("NEXT_")) {
query.andWhere(
`(decision->>'dateDecision')::timestamp <= :deadline AND (decision->>'dateDecision')::timestamp >= :now`,
{
deadline: deadline.value,
now,
}
);
} else if (search?.echeance.startsWith("PREVIOUS_")) {
query.andWhere(`(decision->>'dateDecision')::timestamp <= :deadline`, {
deadline: deadline.value,
now,
});
}
}

if (
!search?.searchString &&
!search?.echeance &&
!search?.lastInteractionDate
) {
query.take(100);
}

const results = await query.getRawMany();

return results;
}

@Post()
Expand Down
28 changes: 25 additions & 3 deletions packages/backend/src/usagers/dto/search-usager.dto.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,43 @@
import { ApiProperty } from "@nestjs/swagger";
import { IsNotEmpty, IsString, MinLength } from "class-validator";
import { IsIn, IsOptional, IsString, MinLength } from "class-validator";
import {
LowerCaseTransform,
StripTagsTransform,
Trim,
} from "../../_common/decorators";
import {
UsagersFilterCriteriaDernierPassage,
UsagersFilterCriteriaEcheance,
} from "@domifa/common";

export class SearchUsagerDto {
@ApiProperty({
example: "dupuis",
description: "Nom ou prénom",
})
@IsNotEmpty()
@IsOptional()
@IsString()
@Trim()
@MinLength(3)
@MinLength(2)
@StripTagsTransform()
@LowerCaseTransform()
public searchString!: string;

@IsOptional()
@IsIn(["DEFAULT", "DATE_NAISSANCE"])
public readonly searchStringField: "DEFAULT" | "DATE_NAISSANCE";

@IsIn([
"EXCEEDED",
"NEXT_TWO_WEEKS",
"NEXT_TWO_MONTHS",
"PREVIOUS_YEAR",
"PREVIOUS_TWO_YEARS",
])
@IsOptional()
public readonly echeance: UsagersFilterCriteriaEcheance;

@IsIn(["PREVIOUS_TWO_MONTHS", "PREVIOUS_THREE_MONTHS"])
@IsOptional()
public readonly lastInteractionDate: UsagersFilterCriteriaDernierPassage;
}
1 change: 1 addition & 0 deletions packages/common/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export * from "./import";
export * from "./interactions";
export * from "./message-sms";
export * from "./pagination";
export * from "./search";
export * from "./stats";
export * from "./structure";
export * from "./structure-doc";
Expand Down
Loading

0 comments on commit 3be665a

Please sign in to comment.