Skip to content

Commit

Permalink
update valider affectation rapport
Browse files Browse the repository at this point in the history
  • Loading branch information
achorein committed Dec 21, 2024
1 parent 805e317 commit 2e98875
Show file tree
Hide file tree
Showing 10 changed files with 152 additions and 41 deletions.
2 changes: 1 addition & 1 deletion apiv2/src/admin/core/sejours/jeune/Jeune.gateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export interface JeuneGateway {
): Promise<JeuneModel[]>;
findBySessionId(sessionId: string): Promise<JeuneModel[]>;
update(jeune: JeuneModel): Promise<JeuneModel>;
bulkUpdate(jeunes: { original: JeuneModel; updated: JeuneModel }[]): Promise<number>;
bulkUpdate(jeunesUpdated: JeuneModel[]): Promise<number>;
create(jeune: CreateJeuneModel): Promise<JeuneModel>;
countAffectedByLigneDeBus(ligneDeBusId: string);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { Test, TestingModule } from "@nestjs/testing";
import { Logger } from "@nestjs/common";

import { FileGateway } from "@shared/core/File.gateway";
import { TaskGateway } from "@task/core/Task.gateway";

import { PointDeRassemblementGateway } from "../pointDeRassemblement/PointDeRassemblement.gateway";
import { AffectationService } from "./Affectation.service";
import { SessionGateway } from "../session/Session.gateway";
import { JeuneGateway } from "../../jeune/Jeune.gateway";
import { LigneDeBusGateway } from "../ligneDeBus/LigneDeBus.gateway";
import { SejourGateway } from "../sejour/Sejour.gateway";
import { CentreGateway } from "../centre/Centre.gateway";
import { PlanDeTransportGateway } from "../PlanDeTransport/PlanDeTransport.gateway";

describe("AffectationService", () => {
let affectationService: AffectationService;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
AffectationService,
Logger,
{
provide: TaskGateway,
useValue: {},
},
{
provide: SessionGateway,
useValue: {},
},
{
provide: JeuneGateway,
useValue: {},
},
{
provide: LigneDeBusGateway,
useValue: {},
},
{
provide: PointDeRassemblementGateway,
useValue: {},
},
{
provide: SejourGateway,
useValue: {},
},
{
provide: CentreGateway,
useValue: {},
},
{
provide: PlanDeTransportGateway,
useValue: {},
},
],
}).compile();

affectationService = module.get<AffectationService>(AffectationService);
});

it("should correctly format the percentage", () => {
expect(affectationService.formatPourcent(0.5)).toBe("50.00%");
expect(affectationService.formatPourcent(0.1234)).toBe("12.34%");
expect(affectationService.formatPourcent(0)).toBe("0.00%");
expect(affectationService.formatPourcent(1)).toBe("100.00%");
expect(affectationService.formatPourcent(null as any)).toBe("");
expect(affectationService.formatPourcent(undefined as any)).toBe("");
expect(affectationService.formatPourcent(NaN)).toBe("");
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -141,4 +141,11 @@ export class AffectationService {
}
return ligneDeBus;
}

formatPourcent(value: number): string {
if (!value && value !== 0) {
return "";
}
return (value * 100).toFixed(2) + "%";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { LigneDeBusModel } from "../ligneDeBus/LigneDeBus.model";
import { CentreModel } from "../centre/Centre.model";
import { PointDeRassemblementModel } from "../pointDeRassemblement/PointDeRassemblement.model";
import { PointDeRassemblementGateway } from "../pointDeRassemblement/PointDeRassemblement.gateway";
import { AffectationService } from "./Affectation.service";

describe("SimulationAffectationHTSService", () => {
let simulationAffectationHTSService: SimulationAffectationHTSService;
Expand All @@ -26,6 +27,12 @@ describe("SimulationAffectationHTSService", () => {
providers: [
SimulationAffectationHTSService,
Logger,
{
provide: AffectationService,
useValue: {
formatPourcent: jest.fn().mockReturnValue("50.00%"),
},
},
{
provide: FileGateway,
useValue: {
Expand Down Expand Up @@ -866,14 +873,4 @@ describe("SimulationAffectationHTSService", () => {
expect(result.jeuneIntraDepartementList).toBeInstanceOf(Array);
});
});

it("should correctly format the percentage", () => {
expect(simulationAffectationHTSService.formatPourcent(0.5)).toBe("50.00%");
expect(simulationAffectationHTSService.formatPourcent(0.1234)).toBe("12.34%");
expect(simulationAffectationHTSService.formatPourcent(0)).toBe("0.00%");
expect(simulationAffectationHTSService.formatPourcent(1)).toBe("100.00%");
expect(simulationAffectationHTSService.formatPourcent(null as any)).toBe("");
expect(simulationAffectationHTSService.formatPourcent(undefined as any)).toBe("");
expect(simulationAffectationHTSService.formatPourcent(NaN)).toBe("");
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { RouteXLS } from "@admin/core/referentiel/routes/ReferentielRoutesModel"
import { SejourModel } from "../sejour/Sejour.model";
import { JeuneModel } from "../../jeune/Jeune.model";
import { CentreModel } from "../centre/Centre.model";
import { AffectationService } from "./Affectation.service";

type JeuneRapport = Pick<
JeuneModel,
Expand Down Expand Up @@ -134,6 +135,7 @@ export const RAPPORT_SHEETS = {
@Injectable()
export class SimulationAffectationHTSService {
constructor(
private readonly affectationService: AffectationService,
@Inject(TaskGateway) private readonly taskGateway: TaskGateway,
@Inject(FileGateway) private readonly fileService: FileGateway,
private readonly logger: Logger,
Expand Down Expand Up @@ -1030,7 +1032,9 @@ export class SimulationAffectationHTSService {
chefDeCentreReferentId: sejour.chefDeCentreReferentId,
placesRestantes: sejour.placesRestantes,
placesTotal: sejour.placesTotal,
tauxRemplissage: this.formatPourcent(1 - (sejour.placesRestantes || 0) / (sejour.placesTotal || 0)),
tauxRemplissage: this.affectationService.formatPourcent(
1 - (sejour.placesRestantes || 0) / (sejour.placesTotal || 0),
),
region: centre?.region,
departement: centre?.departement,
ville: centre?.ville,
Expand Down Expand Up @@ -1060,11 +1064,11 @@ export class SimulationAffectationHTSService {
departement: centre.departement,
ville: centre.ville,
codePostal: centre.codePostal,
tauxRemplissage: this.formatPourcent(stats.tauxRemplissage),
tauxGarcon: this.formatPourcent(stats.tauxGarcon),
tauxFille: this.formatPourcent(1 - stats.tauxGarcon),
tauxQVP: this.formatPourcent(stats.tauxQVP),
tauxPSH: this.formatPourcent(stats.tauxPSH),
tauxRemplissage: this.affectationService.formatPourcent(stats.tauxRemplissage),
tauxGarcon: this.affectationService.formatPourcent(stats.tauxGarcon),
tauxFille: this.affectationService.formatPourcent(1 - stats.tauxGarcon),
tauxQVP: this.affectationService.formatPourcent(stats.tauxQVP),
tauxPSH: this.affectationService.formatPourcent(stats.tauxPSH),
...centreSejourList.reduce((acc, sejour, index) => {
acc[`sejour_id_${index + 1}`] = sejour.id;
acc[`sejour_places-occupées_${index + 1}`] =
Expand Down Expand Up @@ -1093,10 +1097,12 @@ export class SimulationAffectationHTSService {
pointDeRassemblementIds: ligne.pointDeRassemblementIds.join(", "),
placesOccupeesJeunes: ligne.placesOccupeesJeunes,
capaciteJeunes: ligne.capaciteJeunes,
tauxRemplissageLigne: this.formatPourcent(ligne.placesOccupeesJeunes / ligne.capaciteJeunes),
tauxRemplissageLigne: this.affectationService.formatPourcent(
ligne.placesOccupeesJeunes / ligne.capaciteJeunes,
),
centreId: ligne.centreId,
centreNom: centreLigne?.nom,
tauxRemplissageCentre: this.formatPourcent(tauxRemplissageCentre),
tauxRemplissageCentre: this.affectationService.formatPourcent(tauxRemplissageCentre),
} as RapportData["ligneDeBusList"][0];
});

Expand Down Expand Up @@ -1218,13 +1224,6 @@ export class SimulationAffectationHTSService {
return itemA.departement?.localeCompare(itemB.departement!) || 0;
}

formatPourcent(value: number): string {
if (!value && value !== 0) {
return "";
}
return (value * 100).toFixed(2) + "%";
}

randomizeArray(array: any[], returnIndexes: boolean = false): any[] {
const result = returnIndexes ? Array.from({ length: array.length }, (_, i) => i) : [...array];
let currentIndex = result.length;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,13 +150,16 @@ describe("ValiderAffectationHTS", () => {
expect(result.analytics.errors).toEqual(0);
expect(result.analytics.jeunesAffected).toEqual(1);
expect(result.rapportData[0]).toEqual({
centreId: "657334e06e801c0816d4550c",
centreNom: "",
dateNaissance: undefined,
departement: undefined,
email: undefined,
error: "",
genre: "garçon",
id: "jeune1",
ligneDeBusId: "65f9c8bb735e0e12a4213c18",
ligneDeBusNumeroLigne: "IDF078036",
nom: undefined,
parent1Email: undefined,
parent1Nom: undefined,
Expand All @@ -169,6 +172,7 @@ describe("ValiderAffectationHTS", () => {
"places restantes après l'inscription (centre)": 13,
"places totale (centre)": 126,
pointDeRassemblementId: "6398797d3bc18708cc3981f6",
pointDeRassemblementMatricule: "",
prenom: undefined,
psh: "non",
qpv: "non",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import { JeuneModel } from "../../jeune/Jeune.model";
import { AffectationService } from "./Affectation.service";
import { SejourModel } from "../sejour/Sejour.model";
import { ValiderAffectationHTSTaskParameters } from "./ValiderAffectationHTSTask.model";
import { LigneDeBusModel } from "../ligneDeBus/LigneDeBus.model";
import { PointDeRassemblementModel } from "../pointDeRassemblement/PointDeRassemblement.model";

export type ValiderAffectationRapportData = Array<
Pick<
Expand Down Expand Up @@ -46,7 +48,11 @@ export type ValiderAffectationRapportData = Array<
| "parent2Nom"
| "parent2Email"
| "parent2Telephone"
| "centreId"
> & {
ligneDeBusNumeroLigne: string;
pointDeRassemblementMatricule: string;
centreNom: string;
"places restantes après l'inscription (centre)": string | number;
"places totale (centre)": string | number;
error?: string;
Expand Down Expand Up @@ -110,7 +116,7 @@ export class ValiderAffectationHTS implements UseCase<ValiderAffectationHTSResul
jeunesAffected: 0,
errors: 0,
};
const updateQueries: { original: JeuneModel; updated: JeuneModel }[] = [];
const updateQueries: JeuneModel[] = [];
const rapportData: Array<ReturnType<typeof this.formatJeuneRapport>> = [];

// Traitement des jeunes
Expand All @@ -130,7 +136,9 @@ export class ValiderAffectationHTS implements UseCase<ValiderAffectationHTSResul
}
if (!sejour.placesRestantes || sejour.placesRestantes < 0) {
console.error(`🚩 plus de place pour ce sejour: ${sejour.id} (jeune: ${jeune.id})`);
rapportData.push(this.formatJeuneRapport(jeune, sejour, "plus de place pour ce sejour"));
rapportData.push(
this.formatJeuneRapport(jeune, sejour, ligneDeBus, pdr, "plus de place pour ce sejour"),
);
analytics.errors += 1;
continue;
}
Expand All @@ -140,7 +148,13 @@ export class ValiderAffectationHTS implements UseCase<ValiderAffectationHTSResul
`🚩 Le jeune (${jeune?.id}) a changé de cohort depuis la simulation (sejour: ${sejour?.sessionName}, jeune: ${jeune?.sessionNom})`,
);
rapportData.push(
this.formatJeuneRapport(jeune, sejour, "jeune ayant changé de cohorte depuis la simulation"),
this.formatJeuneRapport(
jeune,
sejour,
ligneDeBus,
pdr,
"jeune ayant changé de cohorte depuis la simulation",
),
);
analytics.errors += 1;
continue;
Expand Down Expand Up @@ -181,8 +195,6 @@ export class ValiderAffectationHTS implements UseCase<ValiderAffectationHTSResul
transportInfoGivenByLocal: undefined, // Metropole
});

updateQueries.push({ original: jeune, updated: jeuneUpdated });

this.logger.log(
`🚀 Jeune affecté: ${jeune.id}, centre: ${jeuneUpdated.centreId}, sejour: ${
jeuneUpdated.sejourId
Expand All @@ -191,21 +203,25 @@ export class ValiderAffectationHTS implements UseCase<ValiderAffectationHTSResul
} (${analytics.jeunesAffected + 1}/${jeuneAAffecterList.length})`,
);

rapportData.push(this.formatJeuneRapport(jeuneUpdated, sejour));
updateQueries.push(jeuneUpdated);
rapportData.push(this.formatJeuneRapport(jeuneUpdated, sejour, ligneDeBus, pdr));
analytics.jeunesAffected += 1;
}

this.cls.set("user", { firstName: `Affectation ${session.nom}` });

await this.jeuneGateway.bulkUpdate(updateQueries);

this.logger.log(`Mise à jour des places dans les séjours`);
// mise à jour des placesRestantes dans les centres
for (const sejour of sejoursList) {
// TODO: bulkUpdate
await this.sejoursGateway.update(sejour);
}
this.logger.log(`Mise à jour des places dans les lignes de bus et PDT`);
// mise à jour des placesOccupeesJeunes dans les bus
for (const ligneDeBus of ligneDeBusList) {
// TODO: bulkUpdate
await this.affectationService.syncPlaceDisponiblesLigneDeBus(ligneDeBus);
}

Expand All @@ -232,7 +248,13 @@ export class ValiderAffectationHTS implements UseCase<ValiderAffectationHTSResul
};
}

formatJeuneRapport(jeune: JeuneModel, sejour?: SejourModel, error = ""): ValiderAffectationRapportData[0] {
formatJeuneRapport(
jeune: JeuneModel,
sejour: SejourModel,
ligneDeBus?: LigneDeBusModel,
pdr?: PointDeRassemblementModel,
error = "",
): ValiderAffectationRapportData[0] {
return {
id: jeune.id,
statut: jeune.statut,
Expand All @@ -246,7 +268,11 @@ export class ValiderAffectationHTS implements UseCase<ValiderAffectationHTSResul
sessionId: sejour?.id || "",
sessionNom: jeune.sessionNom,
ligneDeBusId: jeune.ligneDeBusId || "",
ligneDeBusNumeroLigne: ligneDeBus?.numeroLigne || "",
pointDeRassemblementId: jeune.pointDeRassemblementId || "",
pointDeRassemblementMatricule: pdr?.matricule || "",
centreId: sejour?.centreId || "",
centreNom: sejour?.centreNom || "",
"places restantes après l'inscription (centre)": sejour?.placesRestantes || "",
"places totale (centre)": sejour?.placesTotal || "",
error,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,15 @@ export class JeuneRepository implements JeuneGateway {
return JeuneMapper.toModel(retrievedJeune);
}

async bulkUpdate(jeunes: { original: JeuneModel; updated: JeuneModel }[]): Promise<number> {
const jeunesEntity = jeunes.map((jeune) => ({
original: JeuneMapper.toEntity(jeune.original),
updated: JeuneMapper.toEntity(jeune.updated),
async bulkUpdate(jeunesUpdated: JeuneModel[]): Promise<number> {
const jeunesOriginal = await this.findByIds(jeunesUpdated.map((jeune) => jeune.id));
if (jeunesOriginal.length !== jeunesUpdated.length) {
throw new FunctionalException(FunctionalExceptionCode.NOT_FOUND);
}

const jeunesEntity = jeunesUpdated.map((updated) => ({
original: JeuneMapper.toEntity(jeunesOriginal.find(({ id }) => updated.id === id)!),
updated: JeuneMapper.toEntity(updated),
}));

const user = this.cls.get("user");
Expand Down
7 changes: 5 additions & 2 deletions apiv2/src/admin/infra/task/AdminTask.consumer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,11 @@ export class AdminTaskConsumer extends WorkerHost {
// TODO: handle errors with partial results for all tasks
if (validationResult.analytics.jeunesAffected === 0) {
await this.adminTaskRepository.update(task.id, {
// @ts-expect-error mise à jour uniquement des results
"metadata.results": results,
...task,
metadata: {
...task.metadata,
results,
},
});
await this.adminTaskRepository.toFailed(job.data.id, "Aucun jeune n'a été affecté");
return ConsumerResponse.FAILURE;
Expand Down
3 changes: 1 addition & 2 deletions apiv2/src/infra/Queue.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ import { QueueName } from "@shared/infra/Queue";
url: config.getOrThrow("broker.url"),
},
prefix: config.getOrThrow("broker.queuePrefix"),
lockDuration: 1000 * 60 * 15, // 30 minutes
maxStalledCount: 0, // no retry
lockDuration: 1000 * 60 * 5, // 5 minutes
}),
}),
BullModule.registerQueue({
Expand Down

0 comments on commit 2e98875

Please sign in to comment.