feat(backend): add emprunt restitution with automatic anomaly detection
This commit is contained in:
@@ -1,14 +1,24 @@
|
||||
import { AppError } from '../errors/app-error';
|
||||
import { CreerEmpruntRequest } from '../dtos/emprunt.dto';
|
||||
import {
|
||||
CreerEmpruntRequest,
|
||||
RestituerEmpruntRequest,
|
||||
ChecklistElementRequest,
|
||||
} from '../dtos/emprunt.dto';
|
||||
import {
|
||||
findEnCoursParUtilisateur,
|
||||
creerEmpruntComplet,
|
||||
findByIdAvecChecklists,
|
||||
restituerEmpruntComplet,
|
||||
EmpruntAvecMateriel,
|
||||
RestituerEmpruntData,
|
||||
} from '../repositories/emprunt.repository';
|
||||
import { findById as findMaterielById } from '../repositories/materiel.repository';
|
||||
import { findById as findPosteById } from '../repositories/poste-emprunt.repository';
|
||||
import { findResponsablesParCampus } from '../repositories/utilisateur.repository';
|
||||
import { StatutEmprunt } from '../models/enums';
|
||||
|
||||
const DUREE_EMPRUNT_JOURS = 14;
|
||||
const STATUTS_RESTITUABLES: readonly string[] = ['EN_COURS', 'EN_RETARD'];
|
||||
|
||||
/* RG15 : un étudiant ne consulte que ses propres emprunts non restitués. */
|
||||
export function listerMesEmpruntsEnCours(utilisateurId: number): Promise<EmpruntAvecMateriel[]> {
|
||||
@@ -57,3 +67,116 @@ export async function creerEmprunt(
|
||||
elements: request.elements,
|
||||
});
|
||||
}
|
||||
|
||||
interface ElementDepart {
|
||||
accessoireId: number | null;
|
||||
nomElement: string;
|
||||
etat: string;
|
||||
}
|
||||
|
||||
interface ResultatComparaison {
|
||||
conforme: boolean;
|
||||
auMoinsUnDeteriore: boolean;
|
||||
details: string[];
|
||||
}
|
||||
|
||||
function trouverRetour(
|
||||
depart: ElementDepart,
|
||||
retour: ChecklistElementRequest[],
|
||||
): ChecklistElementRequest | undefined {
|
||||
if (depart.accessoireId !== null) {
|
||||
const parId = retour.find((element) => element.accessoireId === depart.accessoireId);
|
||||
if (parId) {
|
||||
return parId;
|
||||
}
|
||||
}
|
||||
return retour.find((element) => element.nomElement === depart.nomElement);
|
||||
}
|
||||
|
||||
/* RG17 : compare la checklist de départ à celle de retour. Un élément présent au
|
||||
départ mais absent ou détérioré au retour rend la restitution non conforme (RG20). */
|
||||
function comparerChecklists(
|
||||
depart: ElementDepart[],
|
||||
retour: ChecklistElementRequest[],
|
||||
): ResultatComparaison {
|
||||
const details: string[] = [];
|
||||
let auMoinsUnDeteriore = false;
|
||||
|
||||
for (const elementDepart of depart) {
|
||||
if (elementDepart.etat !== 'PRESENT') {
|
||||
continue;
|
||||
}
|
||||
|
||||
const elementRetour = trouverRetour(elementDepart, retour);
|
||||
if (!elementRetour || elementRetour.etat === 'ABSENT') {
|
||||
details.push(`${elementDepart.nomElement} manquant au retour`);
|
||||
} else if (elementRetour.etat === 'DETERIORE') {
|
||||
details.push(`${elementDepart.nomElement} deteriore au retour`);
|
||||
auMoinsUnDeteriore = true;
|
||||
}
|
||||
}
|
||||
|
||||
return { conforme: details.length === 0, auMoinsUnDeteriore, details };
|
||||
}
|
||||
|
||||
/* RG14-RG20 : restitution d'un emprunt avec comparaison des checklists, clôture
|
||||
ou passage en non conforme, et création automatique d'anomalie + notifications. */
|
||||
export async function restituerEmprunt(
|
||||
utilisateurId: number,
|
||||
empruntId: number,
|
||||
request: RestituerEmpruntRequest,
|
||||
): Promise<EmpruntAvecMateriel> {
|
||||
const emprunt = await findByIdAvecChecklists(empruntId);
|
||||
if (!emprunt) {
|
||||
throw new AppError(404, 'Emprunt introuvable');
|
||||
}
|
||||
if (emprunt.utilisateurId !== utilisateurId) {
|
||||
throw new AppError(403, 'Cet emprunt ne vous appartient pas');
|
||||
}
|
||||
if (!STATUTS_RESTITUABLES.includes(emprunt.statut)) {
|
||||
throw new AppError(409, 'Emprunt non restituable');
|
||||
}
|
||||
|
||||
const checklistDepart = emprunt.checklists.find((checklist) => checklist.type === 'DEPART');
|
||||
if (!checklistDepart) {
|
||||
throw new AppError(409, 'Checklist de depart introuvable');
|
||||
}
|
||||
|
||||
const comparaison = comparerChecklists(
|
||||
checklistDepart.elements.map((element) => ({
|
||||
accessoireId: element.accessoireId,
|
||||
nomElement: element.nomElement,
|
||||
etat: element.etat,
|
||||
})),
|
||||
request.elements,
|
||||
);
|
||||
|
||||
const statutEmprunt: StatutEmprunt = comparaison.conforme ? 'CLOTURE' : 'RETOUR_NON_CONFORME';
|
||||
|
||||
let statutMateriel = 'DISPONIBLE';
|
||||
if (!comparaison.conforme) {
|
||||
statutMateriel = comparaison.auMoinsUnDeteriore ? 'DETERIORE' : 'NON_CONFORME';
|
||||
}
|
||||
|
||||
const donnees: RestituerEmpruntData = {
|
||||
empruntId,
|
||||
utilisateurId,
|
||||
materielId: emprunt.materielId,
|
||||
statutEmprunt,
|
||||
statutMateriel,
|
||||
modeIdentification: request.modeIdentification,
|
||||
commentaireRetour: request.commentaireRetour,
|
||||
elements: request.elements,
|
||||
};
|
||||
|
||||
if (!comparaison.conforme) {
|
||||
const responsables = await findResponsablesParCampus(emprunt.campusId);
|
||||
donnees.anomalie = {
|
||||
type: 'RETOUR_NON_CONFORME',
|
||||
description: `Retour non conforme : ${comparaison.details.join(', ')}.`,
|
||||
responsablesIds: responsables.map((responsable) => responsable.id),
|
||||
};
|
||||
}
|
||||
|
||||
return restituerEmpruntComplet(donnees);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user