feat(backend): add responsable anomalies status cycle
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { AppError } from '../errors/app-error';
|
||||
import {
|
||||
changerStatutAnomalieResponsableService,
|
||||
getDashboardResponsable,
|
||||
listerAnomaliesResponsable,
|
||||
listerHistoriqueResponsable,
|
||||
@@ -9,6 +10,7 @@ import {
|
||||
listerEmpruntsResponsable,
|
||||
marquerNotificationResponsableLue,
|
||||
marquerToutesNotificationsResponsableLues,
|
||||
parseAnomalieId,
|
||||
parseCategorieId,
|
||||
parseLu,
|
||||
parseNotificationId,
|
||||
@@ -27,6 +29,13 @@ import {
|
||||
toNotificationResponsableResponse,
|
||||
} from '../dtos/responsable.dto';
|
||||
|
||||
function asBodyObject(value: unknown): Record<string, unknown> {
|
||||
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
||||
throw new AppError(400, 'Corps de requete invalide');
|
||||
}
|
||||
return value as Record<string, unknown>;
|
||||
}
|
||||
|
||||
function getHistoriqueFiltres(req: Request) {
|
||||
const action = typeof req.query.action === 'string' ? req.query.action : undefined;
|
||||
return {
|
||||
@@ -102,6 +111,30 @@ export async function getAnomalies(req: Request, res: Response): Promise<void> {
|
||||
res.json({ data: anomalies.map(toAnomalieResponsableResponse) });
|
||||
}
|
||||
|
||||
export async function patchAnomalieStatut(req: Request, res: Response): Promise<void> {
|
||||
const user = req.user;
|
||||
if (!user) {
|
||||
throw new AppError(401, 'Authentification requise');
|
||||
}
|
||||
|
||||
const anomalieId = parseAnomalieId(req.params.id);
|
||||
const body = asBodyObject(req.body);
|
||||
const statut = parseStatutAnomalie(body.statut);
|
||||
if (!statut) {
|
||||
throw new AppError(400, 'statut requis');
|
||||
}
|
||||
const observation = typeof body.observation === 'string' ? body.observation : undefined;
|
||||
|
||||
const anomalie = await changerStatutAnomalieResponsableService(
|
||||
user.id,
|
||||
user.roleCode,
|
||||
user.campusId,
|
||||
anomalieId,
|
||||
{ statut, observation },
|
||||
);
|
||||
res.json({ data: toAnomalieResponsableResponse(anomalie) });
|
||||
}
|
||||
|
||||
export async function getNotifications(req: Request, res: Response): Promise<void> {
|
||||
const user = req.user;
|
||||
if (!user) {
|
||||
|
||||
@@ -86,6 +86,14 @@ export interface HistoriqueResponsableFiltres {
|
||||
dateFin?: Date;
|
||||
}
|
||||
|
||||
export interface ChangerStatutAnomalieData {
|
||||
anomalieId: number;
|
||||
campusId: number;
|
||||
responsableId: number;
|
||||
statut: string;
|
||||
observation?: string;
|
||||
}
|
||||
|
||||
function mapStatutCounts(rows: Array<{ statut: string; _count: { _all: number } }>): StatutCount[] {
|
||||
return rows.map((row) => ({ statut: row.statut, count: row._count._all }));
|
||||
}
|
||||
@@ -208,6 +216,62 @@ export function findAnomaliesResponsable(
|
||||
});
|
||||
}
|
||||
|
||||
export function findAnomalieResponsableById(
|
||||
campusId: number,
|
||||
anomalieId: number,
|
||||
): Promise<AnomalieResponsable | null> {
|
||||
return prisma.anomalie.findFirst({
|
||||
where: {
|
||||
id: anomalieId,
|
||||
emprunt: { campusId },
|
||||
},
|
||||
include: {
|
||||
emprunt: true,
|
||||
etudiant: true,
|
||||
materiel: { include: { categorie: true } },
|
||||
traitePar: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function changerStatutAnomalieResponsable(
|
||||
data: ChangerStatutAnomalieData,
|
||||
): Promise<AnomalieResponsable> {
|
||||
return prisma.$transaction(async (tx) => {
|
||||
const anomalie = await tx.anomalie.update({
|
||||
where: { id: data.anomalieId },
|
||||
data: {
|
||||
statut: data.statut,
|
||||
traiteeParId: data.responsableId,
|
||||
...(data.observation !== undefined ? { observation: data.observation } : {}),
|
||||
...(data.statut === 'RESOLUE' || data.statut === 'CLOTUREE'
|
||||
? { dateResolution: new Date() }
|
||||
: {}),
|
||||
},
|
||||
include: {
|
||||
emprunt: true,
|
||||
etudiant: true,
|
||||
materiel: { include: { categorie: true } },
|
||||
traitePar: true,
|
||||
},
|
||||
});
|
||||
|
||||
await tx.historique.create({
|
||||
data: {
|
||||
utilisateurId: data.responsableId,
|
||||
empruntId: anomalie.empruntId,
|
||||
materielId: anomalie.materielId,
|
||||
campusId: data.campusId,
|
||||
action: 'TRAITEMENT_ANOMALIE',
|
||||
description: `Anomalie #${anomalie.id} passee au statut ${data.statut}`,
|
||||
dateAction: new Date(),
|
||||
},
|
||||
});
|
||||
|
||||
return anomalie;
|
||||
});
|
||||
}
|
||||
|
||||
export function findNotificationsResponsable(
|
||||
utilisateurId: number,
|
||||
filtres: NotificationResponsableFiltres,
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
getHistoriqueCsv,
|
||||
getMateriels,
|
||||
getNotifications,
|
||||
patchAnomalieStatut,
|
||||
patchNotificationLue,
|
||||
patchNotificationsLues,
|
||||
} from '../controllers/responsable.controller';
|
||||
@@ -17,6 +18,7 @@ responsableRoutes.get('/dashboard', getDashboard);
|
||||
responsableRoutes.get('/emprunts', getEmprunts);
|
||||
responsableRoutes.get('/materiels', getMateriels);
|
||||
responsableRoutes.get('/anomalies', getAnomalies);
|
||||
responsableRoutes.patch('/anomalies/:id/statut', patchAnomalieStatut);
|
||||
responsableRoutes.get('/notifications', getNotifications);
|
||||
responsableRoutes.patch('/notifications/lu-toutes', patchNotificationsLues);
|
||||
responsableRoutes.patch('/notifications/:id/lu', patchNotificationLue);
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { AppError } from '../errors/app-error';
|
||||
import {
|
||||
changerStatutAnomalieResponsable,
|
||||
countAnomaliesParStatut,
|
||||
countEmpruntsParStatut,
|
||||
countMaterielsParStatut,
|
||||
countNotificationsNonLues,
|
||||
findActiviteRecente,
|
||||
findAnomalieResponsableById,
|
||||
findAnomaliesResponsable,
|
||||
findEmpruntsResponsable,
|
||||
findHistoriqueResponsable,
|
||||
@@ -65,6 +67,18 @@ export interface ListerHistoriqueResponsableFiltres {
|
||||
dateFin?: Date;
|
||||
}
|
||||
|
||||
export interface ChangerStatutAnomalieResponsableRequest {
|
||||
statut: StatutAnomalie;
|
||||
observation?: string;
|
||||
}
|
||||
|
||||
const TRANSITIONS_ANOMALIE: Record<StatutAnomalie, StatutAnomalie[]> = {
|
||||
DETECTEE: ['EN_COURS_TRAITEMENT'],
|
||||
EN_COURS_TRAITEMENT: ['RESOLUE'],
|
||||
RESOLUE: ['CLOTUREE'],
|
||||
CLOTUREE: [],
|
||||
};
|
||||
|
||||
function verifierResponsable(roleCode: string): void {
|
||||
if (roleCode !== 'RESPONSABLE') {
|
||||
throw new AppError(403, 'Acces reserve au responsable materiel');
|
||||
@@ -153,6 +167,19 @@ export function parseNotificationId(value: unknown): number {
|
||||
return parsed;
|
||||
}
|
||||
|
||||
export function parseAnomalieId(value: unknown): number {
|
||||
if (typeof value !== 'string') {
|
||||
throw new AppError(400, 'anomalieId invalide');
|
||||
}
|
||||
|
||||
const parsed = Number(value);
|
||||
if (!Number.isInteger(parsed) || parsed <= 0) {
|
||||
throw new AppError(400, 'anomalieId invalide');
|
||||
}
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
export function parsePositiveIntQuery(value: unknown, champ: string): number | undefined {
|
||||
if (value === undefined) {
|
||||
return undefined;
|
||||
@@ -264,3 +291,31 @@ export function listerHistoriqueResponsable(
|
||||
verifierResponsable(roleCode);
|
||||
return findHistoriqueResponsable(campusId, filtres);
|
||||
}
|
||||
|
||||
export async function changerStatutAnomalieResponsableService(
|
||||
responsableId: number,
|
||||
roleCode: string,
|
||||
campusId: number,
|
||||
anomalieId: number,
|
||||
data: ChangerStatutAnomalieResponsableRequest,
|
||||
): Promise<AnomalieResponsable> {
|
||||
verifierResponsable(roleCode);
|
||||
|
||||
const anomalie = await findAnomalieResponsableById(campusId, anomalieId);
|
||||
if (!anomalie) {
|
||||
throw new AppError(404, 'Anomalie introuvable');
|
||||
}
|
||||
|
||||
const statutActuel = anomalie.statut as StatutAnomalie;
|
||||
if (!TRANSITIONS_ANOMALIE[statutActuel]?.includes(data.statut)) {
|
||||
throw new AppError(400, `Transition invalide depuis ${anomalie.statut} vers ${data.statut}`);
|
||||
}
|
||||
|
||||
return changerStatutAnomalieResponsable({
|
||||
anomalieId,
|
||||
campusId,
|
||||
responsableId,
|
||||
statut: data.statut,
|
||||
observation: data.observation,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user