Skip to content

Commit

Permalink
feat(backend/referentiels): mise à jour d'un statut d'une action et r…
Browse files Browse the repository at this point in the history
…ecalcul du score, ajout de tRPC et des tests associés
  • Loading branch information
dthib committed Dec 11, 2024
1 parent 2c915db commit c6da387
Show file tree
Hide file tree
Showing 15 changed files with 710 additions and 52 deletions.
2 changes: 1 addition & 1 deletion backend/src/auth/models/private-utilisateur-droit.table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { boolean, integer, pgTable, serial, uuid } from 'drizzle-orm/pg-core';
import { collectiviteTable } from '../../collectivites/models/collectivite.table';
import { createdAt, modifiedAt } from '../../common/models/column.helpers';
import { invitationTable } from './invitation.table';
import { niveauAccessEnum, NiveauAcces } from './niveau-acces.enum';
import { NiveauAcces, niveauAccessEnum } from './niveau-acces.enum';

export const utilisateurDroitTable = pgTable('private_utilisateur_droit', {
id: serial('id').primaryKey(),
Expand Down
33 changes: 33 additions & 0 deletions backend/src/referentiels/compute-score/compute-score.router.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Injectable } from '@nestjs/common';
import z from 'zod';
import { TrpcService } from '../../trpc/trpc.service';
import { getReferentielScoresRequestSchema } from '../models/get-referentiel-scores.request';
import { ReferentielType } from '../models/referentiel.enum';
import ReferentielsScoringService from '../services/referentiels-scoring.service';

export const computeScoreRequestSchema = z.object({
referentielId: z.nativeEnum(ReferentielType),
collectiviteId: z.number().int(),
parameters: getReferentielScoresRequestSchema,
});

@Injectable()
export class ComputeScoreRouter {
constructor(
private readonly trpc: TrpcService,
private readonly service: ReferentielsScoringService
) {}

router = this.trpc.router({
computeScore: this.trpc.authedProcedure
.input(computeScoreRequestSchema)
.query(({ input, ctx }) => {
return this.service.computeScoreForCollectivite(
input.referentielId,
input.collectiviteId,
input.parameters,
ctx.user
);
}),
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -149,13 +149,13 @@ export class ReferentielsScoringController {
@Get(
'collectivites/:collectivite_id/referentiels/:referentiel_id/score-snapshots'
)
async getReferentielScoreSnapshots(
async listSummary(
@Param('collectivite_id') collectiviteId: number,
@Param('referentiel_id') referentielId: ReferentielType,
@Query() parameters: GetScoreSnapshotsRequestClass,
@TokenInfo() tokenInfo: AuthUser
): Promise<GetScoreSnapshotsResponseClass> {
return this.referentielsScoringSnapshotsService.getScoreSnapshots(
return this.referentielsScoringSnapshotsService.listSummary(
collectiviteId,
referentielId,
parameters
Expand All @@ -171,12 +171,22 @@ export class ReferentielsScoringController {
@Param('referentiel_id') referentielId: ReferentielType,
@Param('snapshot_ref') snapshotRef: string,
@TokenInfo() tokenInfo: AuthenticatedUser
): Promise<GetReferentielScoresResponseClass> {
return this.referentielsScoringSnapshotsService.getFullScoreSnapshot(
collectiviteId,
referentielId,
snapshotRef
);
) {
if (
snapshotRef ===
ReferentielsScoringSnapshotsService.SCORE_COURANT_SNAPSHOT_REF
) {
return this.referentielsScoringService.getOrCreateCurrentScore(
collectiviteId,
referentielId
);
} else {
return this.referentielsScoringSnapshotsService.get(
collectiviteId,
referentielId,
snapshotRef
);
}
}

@Delete(
Expand All @@ -188,7 +198,7 @@ export class ReferentielsScoringController {
@Param('snapshot_ref') snapshotRef: string,
@TokenInfo() tokenInfo: AuthUser
): Promise<void> {
return this.referentielsScoringSnapshotsService.deleteScoreSnapshot(
return this.referentielsScoringSnapshotsService.delete(
collectiviteId,
referentielId,
snapshotRef,
Expand Down
18 changes: 12 additions & 6 deletions backend/src/referentiels/models/get-referentiel-scores.request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ export const getReferentielScoresRequestSchema = extendApi(
),

avecReferentielsOrigine: z
.enum(['true', 'false'])
.transform((value) => value === 'true')
.union([
z.enum(['true', 'false']).transform((value) => value === 'true'),
z.boolean(),
])
.optional()
.describe(
`Indique si les scores des actions doivent être calculés à partir des avancement dans les référentiels d'origine. Utilisé pour le bac à sable lors de la création de nouveaux référentiels à partir de référentiels existants`
Expand Down Expand Up @@ -46,8 +48,10 @@ export const getReferentielScoresRequestSchema = extendApi(
.optional()
.describe(`Année de l'audit pour le jalon`),
snapshot: z
.enum(['true', 'false'])
.transform((value) => value === 'true')
.union([
z.enum(['true', 'false']).transform((value) => value === 'true'),
z.boolean(),
])
.optional()
.describe(
`Indique si le score doit être sauvegardé. Si c'est le cas, l'utilisateur doit avoir le droit d'écriture sur la collectivité`
Expand All @@ -59,8 +63,10 @@ export const getReferentielScoresRequestSchema = extendApi(
`Nom du snapshot de score à sauvegarder. Ne peut être défini que pour une date personnalisée, sinon un nom par défaut est utilisé`
),
snapshotForceUpdate: z
.enum(['true', 'false'])
.transform((value) => value === 'true')
.union([
z.enum(['true', 'false']).transform((value) => value === 'true'),
z.boolean(),
])
.optional()
.describe(`Force l'update du snapshot même si il existe déjà`),
})
Expand Down
12 changes: 12 additions & 0 deletions backend/src/referentiels/referentiels.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,16 @@ import { CommonModule } from '../common/common.module';
import { ConfigurationModule } from '../config/configuration.module';
import { PersonnalisationsModule } from '../personnalisations/personnalisations.module';
import { SheetModule } from '../spreadsheets/sheet.module';
import { ComputeScoreRouter } from './compute-score/compute-score.router';
import { ReferentielsScoringController } from './controllers/referentiels-scoring.controller';
import { ReferentielsController } from './controllers/referentiels.controller';
import LabellisationService from './services/labellisation.service';
import ReferentielsScoringSnapshotsService from './services/referentiels-scoring-snapshots.service';
import ReferentielsScoringService from './services/referentiels-scoring.service';
import ReferentielsService from './services/referentiels.service';
import { ScoreSnapshotsRouter } from './snapshots/score-snaphots.router';
import { UpdateActionStatutRouter } from './update-action-statut/update-action-statut.router';
import { UpdateActionStatutService } from './update-action-statut/update-action-statut.service';

@Module({
imports: [
Expand All @@ -26,12 +30,20 @@ import ReferentielsService from './services/referentiels.service';
LabellisationService,
ReferentielsScoringSnapshotsService,
ReferentielsScoringService,
UpdateActionStatutService,
UpdateActionStatutRouter,
ComputeScoreRouter,
ScoreSnapshotsRouter,
],
exports: [
ReferentielsService,
LabellisationService,
ReferentielsScoringSnapshotsService,
ReferentielsScoringService,
UpdateActionStatutService,
UpdateActionStatutRouter,
ComputeScoreRouter,
ScoreSnapshotsRouter,
],
controllers: [ReferentielsController, ReferentielsScoringController],
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export default class ReferentielsScoringSnapshotsService {

slugifyName(name: string): string {
if (name) {
return slugify(name, {
return slugify(name.toLowerCase(), {
replacement: '-',
remove: /[*+~.()'"!:@]/g,
});
Expand Down Expand Up @@ -303,7 +303,7 @@ export default class ReferentielsScoringSnapshotsService {
let scoreSnapshots: ScoreSnapshotType[] = [];
try {
if (snapshotForceUpdate) {
const existingSnapshot = await this.getScoreSnapshotInfo(
const existingSnapshot = await this.getSummary(
createScoreSnapshot.collectiviteId,
createScoreSnapshot.referentielId as ReferentielType,
createScoreSnapshot.ref!,
Expand Down Expand Up @@ -352,7 +352,7 @@ export default class ReferentielsScoringSnapshotsService {
return scoreSnapshot;
}

async getScoreSnapshots(
async listSummary(
collectiviteId: number,
referentielId: ReferentielType,
parameters: GetScoreSnapshotsRequestType
Expand Down Expand Up @@ -393,7 +393,7 @@ export default class ReferentielsScoringSnapshotsService {
return getScoreSnapshotsResponseType;
}

async getScoreSnapshotInfo(
async getSummary(
collectiviteId: number,
referentielId: ReferentielType,
snapshotRef: string,
Expand Down Expand Up @@ -433,11 +433,12 @@ export default class ReferentielsScoringSnapshotsService {
return result.length ? result[0] : null;
}

async getFullScoreSnapshot(
async get(
collectiviteId: number,
referentielId: ReferentielType,
snapshotRef: string
): Promise<GetReferentielScoresResponseType> {
snapshotRef: string,
doNotThrowIfNotFound?: boolean
): Promise<GetReferentielScoresResponseType | null> {
const result = (await this.databaseService.db
.select()
.from(scoreSnapshotTable)
Expand All @@ -450,9 +451,13 @@ export default class ReferentielsScoringSnapshotsService {
)) as ScoreSnapshotType[];

if (!result.length) {
throw new NotFoundException(
`Aucun snapshot de score avec la référence ${snapshotRef} n'a été trouvé pour la collectivité ${collectiviteId} et le referentiel ${referentielId}`
);
if (!doNotThrowIfNotFound) {
throw new NotFoundException(
`Aucun snapshot de score avec la référence ${snapshotRef} n'a été trouvé pour la collectivité ${collectiviteId} et le referentiel ${referentielId}`
);
} else {
return null;
}
}

const fullScores = result[0].referentielScores;
Expand All @@ -467,7 +472,7 @@ export default class ReferentielsScoringSnapshotsService {
return fullScores;
}

async deleteScoreSnapshot(
async delete(
collectiviteId: number,
referentielId: ReferentielType,
snapshotRef: string,
Expand All @@ -479,7 +484,7 @@ export default class ReferentielsScoringSnapshotsService {
NiveauAcces.EDITION
);

const snapshotInfo = await this.getScoreSnapshotInfo(
const snapshotInfo = await this.getSummary(
collectiviteId,
referentielId,
snapshotRef
Expand Down
24 changes: 24 additions & 0 deletions backend/src/referentiels/services/referentiels-scoring.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,30 @@ export default class ReferentielsScoringService {
private readonly labellisationService: LabellisationService
) {}

async getOrCreateCurrentScore(
collectiviteId: number,
referentielId: ReferentielType
) {
let currentScore = await this.referentielsScoringSnapshotsService.get(
collectiviteId,
referentielId,
ReferentielsScoringSnapshotsService.SCORE_COURANT_SNAPSHOT_REF,
true
);
if (!currentScore) {
currentScore = await this.computeScoreForCollectivite(
referentielId,
collectiviteId,
{
mode: ComputeScoreMode.RECALCUL,
snapshot: true,
snapshotForceUpdate: true,
}
);
}
return currentScore;
}

async checkCollectiviteAndReferentielWithAccess(
collectiviteId: number,
referentielId: ReferentielType,
Expand Down
12 changes: 12 additions & 0 deletions backend/src/referentiels/services/referentiels.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,18 @@ export default class ReferentielsService {
);
}

getReferentielIdFromActionId(actionId: string): ReferentielType {
const referentielId = actionId.split('_')[0];
if (
!Object.values(ReferentielType).includes(referentielId as ReferentielType)
) {
throw new UnprocessableEntityException(
`Invalid referentiel id ${referentielId} for action ${actionId}`
);
}
return referentielId as ReferentielType;
}

getLevelFromActionId(actionId: string): number {
const level = actionId.split('.').length;
if (level === 1) {
Expand Down
84 changes: 84 additions & 0 deletions backend/src/referentiels/snapshots/score-snaphots.router.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { Injectable } from '@nestjs/common';
import z from 'zod';
import { TrpcService } from '../../trpc/trpc.service';
import { ReferentielType } from '../models/referentiel.enum';
import { ScoreJalon } from '../models/score-jalon.enum';
import ReferentielsScoringSnapshotsService from '../services/referentiels-scoring-snapshots.service';
import ReferentielsScoringService from '../services/referentiels-scoring.service';

export const getScoreSnapshotInfosTrpcRequestSchema = z.object({
referentielId: z.nativeEnum(ReferentielType),
collectiviteId: z.number().int(),
parameters: z.object({
typesJalon: z.nativeEnum(ScoreJalon).array(),
}),
});

export const getFullScoreSnapshotTrpcRequestSchema = z.object({
referentielId: z.nativeEnum(ReferentielType),
collectiviteId: z.number().int(),
snapshotRef: z.string(),
});

@Injectable()
export class ScoreSnapshotsRouter {
constructor(
private readonly trpc: TrpcService,
private readonly service: ReferentielsScoringSnapshotsService,
private readonly referentielsScoringService: ReferentielsScoringService
) {}

router = this.trpc.router({
listSummary: this.trpc.authedProcedure
.input(getScoreSnapshotInfosTrpcRequestSchema)
.query(({ input, ctx }) => {
return this.service.listSummary(
input.collectiviteId,
input.referentielId,
input.parameters
);
}),
getCurrentFullScore: this.trpc.authedProcedure
.input(
z.object({
referentielId: z.nativeEnum(ReferentielType),
collectiviteId: z.number().int(),
})
)
.query(({ input, ctx }) => {
return this.referentielsScoringService.getOrCreateCurrentScore(
input.collectiviteId,
input.referentielId
);
}),
get: this.trpc.authedProcedure
.input(getFullScoreSnapshotTrpcRequestSchema)
.query(({ input, ctx }) => {
if (
input.snapshotRef ===
ReferentielsScoringSnapshotsService.SCORE_COURANT_SNAPSHOT_REF
) {
return this.referentielsScoringService.getOrCreateCurrentScore(
input.collectiviteId,
input.referentielId
);
} else {
return this.service.get(
input.collectiviteId,
input.referentielId,
input.snapshotRef
);
}
}),
delete: this.trpc.authedProcedure
.input(getFullScoreSnapshotTrpcRequestSchema)
.query(({ input, ctx }) => {
return this.service.delete(
input.collectiviteId,
input.referentielId,
input.snapshotRef,
ctx.user
);
}),
});
}
Loading

0 comments on commit c6da387

Please sign in to comment.