merge: responsable emprunts consultation api
This commit is contained in:
@@ -1,7 +1,14 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { AppError } from '../errors/app-error';
|
||||
import { getDashboardResponsable } from '../services/responsable.service';
|
||||
import { toDashboardResponsableResponse } from '../dtos/responsable.dto';
|
||||
import {
|
||||
getDashboardResponsable,
|
||||
listerEmpruntsResponsable,
|
||||
parseStatutEmprunt,
|
||||
} from '../services/responsable.service';
|
||||
import {
|
||||
toDashboardResponsableResponse,
|
||||
toEmpruntResponsableResponse,
|
||||
} from '../dtos/responsable.dto';
|
||||
|
||||
export async function getDashboard(req: Request, res: Response): Promise<void> {
|
||||
const user = req.user;
|
||||
@@ -12,3 +19,14 @@ export async function getDashboard(req: Request, res: Response): Promise<void> {
|
||||
const dashboard = await getDashboardResponsable(user.id, user.roleCode, user.campusId);
|
||||
res.json({ data: toDashboardResponsableResponse(dashboard) });
|
||||
}
|
||||
|
||||
export async function getEmprunts(req: Request, res: Response): Promise<void> {
|
||||
const user = req.user;
|
||||
if (!user) {
|
||||
throw new AppError(401, 'Authentification requise');
|
||||
}
|
||||
|
||||
const statut = parseStatutEmprunt(req.query.statut);
|
||||
const emprunts = await listerEmpruntsResponsable(user.roleCode, user.campusId, { statut });
|
||||
res.json({ data: emprunts.map(toEmpruntResponsableResponse) });
|
||||
}
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { ActiviteResponsable, StatutCount } from '../repositories/responsable.repository';
|
||||
import {
|
||||
ActiviteResponsable,
|
||||
EmpruntResponsable,
|
||||
StatutCount,
|
||||
} from '../repositories/responsable.repository';
|
||||
import { DashboardResponsableData } from '../services/responsable.service';
|
||||
|
||||
export interface RepartitionStatutResponse {
|
||||
@@ -34,6 +38,31 @@ export interface DashboardResponsableResponse {
|
||||
activiteRecente: ActiviteResponsableResponse[];
|
||||
}
|
||||
|
||||
export interface EmpruntResponsableResponse {
|
||||
id: number;
|
||||
statut: string;
|
||||
dateEmprunt: Date;
|
||||
dateRetourPrevue: Date;
|
||||
dateRetourReelle: Date | null;
|
||||
etudiant: {
|
||||
id: number;
|
||||
nom: string;
|
||||
prenom: string;
|
||||
email: string;
|
||||
classe: string | null;
|
||||
};
|
||||
materiel: {
|
||||
id: number;
|
||||
nom: string;
|
||||
reference: string;
|
||||
statut: string;
|
||||
categorie: {
|
||||
id: number;
|
||||
nom: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
function toRepartition(rows: StatutCount[]): RepartitionStatutResponse {
|
||||
const parStatut: Record<string, number> = {};
|
||||
let total = 0;
|
||||
@@ -81,3 +110,32 @@ export function toDashboardResponsableResponse(
|
||||
activiteRecente: data.activiteRecente.map(toActiviteResponse),
|
||||
};
|
||||
}
|
||||
|
||||
export function toEmpruntResponsableResponse(
|
||||
emprunt: EmpruntResponsable,
|
||||
): EmpruntResponsableResponse {
|
||||
return {
|
||||
id: emprunt.id,
|
||||
statut: emprunt.statut,
|
||||
dateEmprunt: emprunt.dateEmprunt,
|
||||
dateRetourPrevue: emprunt.dateRetourPrevue,
|
||||
dateRetourReelle: emprunt.dateRetourReelle,
|
||||
etudiant: {
|
||||
id: emprunt.utilisateur.id,
|
||||
nom: emprunt.utilisateur.nom,
|
||||
prenom: emprunt.utilisateur.prenom,
|
||||
email: emprunt.utilisateur.email,
|
||||
classe: emprunt.utilisateur.classe,
|
||||
},
|
||||
materiel: {
|
||||
id: emprunt.materiel.id,
|
||||
nom: emprunt.materiel.nom,
|
||||
reference: emprunt.materiel.reference,
|
||||
statut: emprunt.materiel.statut,
|
||||
categorie: {
|
||||
id: emprunt.materiel.categorie.id,
|
||||
nom: emprunt.materiel.categorie.nom,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -14,6 +14,17 @@ export type ActiviteResponsable = Prisma.HistoriqueGetPayload<{
|
||||
};
|
||||
}>;
|
||||
|
||||
export type EmpruntResponsable = Prisma.EmpruntGetPayload<{
|
||||
include: {
|
||||
utilisateur: true;
|
||||
materiel: { include: { categorie: true } };
|
||||
};
|
||||
}>;
|
||||
|
||||
export interface EmpruntResponsableFiltres {
|
||||
statut?: string;
|
||||
}
|
||||
|
||||
function mapStatutCounts(rows: Array<{ statut: string; _count: { _all: number } }>): StatutCount[] {
|
||||
return rows.map((row) => ({ statut: row.statut, count: row._count._all }));
|
||||
}
|
||||
@@ -69,3 +80,20 @@ export function findActiviteRecente(
|
||||
take: limite,
|
||||
});
|
||||
}
|
||||
|
||||
export function findEmpruntsResponsable(
|
||||
campusId: number,
|
||||
filtres: EmpruntResponsableFiltres,
|
||||
): Promise<EmpruntResponsable[]> {
|
||||
return prisma.emprunt.findMany({
|
||||
where: {
|
||||
campusId,
|
||||
...(filtres.statut ? { statut: filtres.statut } : {}),
|
||||
},
|
||||
include: {
|
||||
utilisateur: true,
|
||||
materiel: { include: { categorie: true } },
|
||||
},
|
||||
orderBy: { dateEmprunt: 'desc' },
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Router } from 'express';
|
||||
import { getDashboard } from '../controllers/responsable.controller';
|
||||
import { getDashboard, getEmprunts } from '../controllers/responsable.controller';
|
||||
|
||||
export const responsableRoutes: Router = Router();
|
||||
|
||||
responsableRoutes.get('/dashboard', getDashboard);
|
||||
responsableRoutes.get('/emprunts', getEmprunts);
|
||||
|
||||
@@ -5,9 +5,12 @@ import {
|
||||
countMaterielsParStatut,
|
||||
countNotificationsNonLues,
|
||||
findActiviteRecente,
|
||||
findEmpruntsResponsable,
|
||||
StatutCount,
|
||||
ActiviteResponsable,
|
||||
EmpruntResponsable,
|
||||
} from '../repositories/responsable.repository';
|
||||
import { STATUT_EMPRUNT, StatutEmprunt } from '../models/enums';
|
||||
|
||||
export interface DashboardResponsableData {
|
||||
materiels: StatutCount[];
|
||||
@@ -17,14 +20,22 @@ export interface DashboardResponsableData {
|
||||
activiteRecente: ActiviteResponsable[];
|
||||
}
|
||||
|
||||
export interface ListerEmpruntsResponsableFiltres {
|
||||
statut?: StatutEmprunt;
|
||||
}
|
||||
|
||||
function verifierResponsable(roleCode: string): void {
|
||||
if (roleCode !== 'RESPONSABLE') {
|
||||
throw new AppError(403, 'Acces reserve au responsable materiel');
|
||||
}
|
||||
}
|
||||
|
||||
export async function getDashboardResponsable(
|
||||
utilisateurId: number,
|
||||
roleCode: string,
|
||||
campusId: number,
|
||||
): Promise<DashboardResponsableData> {
|
||||
if (roleCode !== 'RESPONSABLE') {
|
||||
throw new AppError(403, 'Acces reserve au responsable materiel');
|
||||
}
|
||||
verifierResponsable(roleCode);
|
||||
|
||||
const [materiels, emprunts, anomalies, notificationsNonLues, activiteRecente] =
|
||||
await Promise.all([
|
||||
@@ -43,3 +54,22 @@ export async function getDashboardResponsable(
|
||||
activiteRecente,
|
||||
};
|
||||
}
|
||||
|
||||
export function parseStatutEmprunt(value: unknown): StatutEmprunt | undefined {
|
||||
if (value === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
if (typeof value !== 'string' || !(STATUT_EMPRUNT as readonly string[]).includes(value)) {
|
||||
throw new AppError(400, 'statut invalide');
|
||||
}
|
||||
return value as StatutEmprunt;
|
||||
}
|
||||
|
||||
export function listerEmpruntsResponsable(
|
||||
roleCode: string,
|
||||
campusId: number,
|
||||
filtres: ListerEmpruntsResponsableFiltres,
|
||||
): Promise<EmpruntResponsable[]> {
|
||||
verifierResponsable(roleCode);
|
||||
return findEmpruntsResponsable(campusId, filtres);
|
||||
}
|
||||
|
||||
@@ -332,6 +332,14 @@ En terminal interactif (VS Code), `migrate dev` fonctionne normalement (taper `y
|
||||
- Activité récente exposée depuis l'historique, avec utilisateur, matériel et emprunt associé.
|
||||
- Vérifié au runtime : Karim (`RESPONSABLE`) obtient 200 avec KPIs ; Lucas (`ETUDIANT`) obtient 403.
|
||||
|
||||
### Étape 35 — API responsable : consultation des emprunts
|
||||
- Branche dédiée `feat/responsable-emprunts-api` créée après merge du dashboard responsable dans `develop`.
|
||||
- Endpoint `GET /api/responsable/emprunts` ajouté, protégé par rôle `RESPONSABLE`.
|
||||
- Filtrage par campus du responsable appliqué systématiquement (RG27).
|
||||
- Filtre optionnel `statut` ajouté pour RG28 : `EN_COURS`, `EN_RETARD`, `CLOTURE`, `RETOUR_NON_CONFORME`, `ANNULE`.
|
||||
- Réponse enrichie avec l'étudiant, le matériel, la catégorie, les dates et le statut.
|
||||
- Vérifié au runtime : Karim obtient les emprunts du campus, `statut=EN_COURS` filtre correctement, statut invalide renvoie 400, Lucas obtient 403.
|
||||
|
||||
---
|
||||
|
||||
*Dernière mise à jour : 2026-07-15 — Premier endpoint responsable ajouté et testé.*
|
||||
*Dernière mise à jour : 2026-07-15 — Dashboard et consultation des emprunts responsable ajoutés et testés.*
|
||||
|
||||
Reference in New Issue
Block a user