import { Prisma } from '@prisma/client'; import { prisma } from '../db/prisma'; import { StatutEmprunt } from '../models/enums'; import { AppError } from '../errors/app-error'; export type EmpruntAvecMateriel = Prisma.EmpruntGetPayload<{ include: { materiel: { include: { categorie: true } } }; }>; export type EmpruntAvecChecklists = Prisma.EmpruntGetPayload<{ include: { checklists: { include: { elements: true } } }; }>; const STATUTS_EN_COURS: StatutEmprunt[] = ['EN_COURS', 'EN_RETARD']; export function findEnCoursParUtilisateur(utilisateurId: number): Promise { return prisma.emprunt.findMany({ where: { utilisateurId, statut: { in: STATUTS_EN_COURS }, }, include: { materiel: { include: { categorie: true } } }, orderBy: { dateRetourPrevue: 'asc' }, }); } export function findByIdAvecChecklists(id: number): Promise { return prisma.emprunt.findUnique({ where: { id }, include: { checklists: { include: { elements: true } } }, }); } interface ChecklistElementData { accessoireId?: number; nomElement: string; etat: string; quantiteConstatee: number; commentaire?: string; } export interface CreerEmpruntData { utilisateurId: number; materielId: number; campusId: number; sallePretId: number; posteEmpruntId: number; dateRetourPrevue: Date; modeIdentification: string; commentaireDepart?: string; elements: ChecklistElementData[]; } /* Création atomique : réservation du matériel (DISPONIBLE -> EMPRUNTE), emprunt, checklist de départ et ses éléments. Si une étape échoue, tout est annulé. */ export function creerEmpruntComplet(data: CreerEmpruntData): Promise { return prisma.$transaction(async (tx) => { // Réservation atomique : empêche un double emprunt du même matériel (RG07). const reservation = await tx.materiel.updateMany({ where: { id: data.materielId, statut: 'DISPONIBLE' }, data: { statut: 'EMPRUNTE' }, }); if (reservation.count === 0) { throw new AppError(409, 'Materiel non disponible'); } const emprunt = await tx.emprunt.create({ data: { utilisateurId: data.utilisateurId, materielId: data.materielId, campusId: data.campusId, sallePretId: data.sallePretId, posteEmpruntId: data.posteEmpruntId, dateEmprunt: new Date(), dateRetourPrevue: data.dateRetourPrevue, statut: 'EN_COURS', modeIdentificationEmprunt: data.modeIdentification, commentaireDepart: data.commentaireDepart ?? null, }, }); await tx.checklist.create({ data: { empruntId: emprunt.id, utilisateurId: data.utilisateurId, type: 'DEPART', dateVerification: new Date(), elements: { create: data.elements.map((element) => ({ accessoireId: element.accessoireId ?? null, nomElement: element.nomElement, etat: element.etat, quantiteConstatee: element.quantiteConstatee, commentaire: element.commentaire ?? null, })), }, }, }); return tx.emprunt.findUniqueOrThrow({ where: { id: emprunt.id }, include: { materiel: { include: { categorie: true } } }, }); }); } export interface RestituerEmpruntData { empruntId: number; utilisateurId: number; materielId: number; statutEmprunt: string; statutMateriel: string; modeIdentification: string; commentaireRetour?: string; elements: ChecklistElementData[]; anomalie?: { type: string; description: string; responsablesIds: number[]; }; } /* Restitution atomique : checklist de retour, mise à jour de l'emprunt et du matériel, et — si non conforme — création de l'anomalie et des notifications. */ export function restituerEmpruntComplet(data: RestituerEmpruntData): Promise { return prisma.$transaction(async (tx) => { const maintenant = new Date(); await tx.checklist.create({ data: { empruntId: data.empruntId, utilisateurId: data.utilisateurId, type: 'RETOUR', dateVerification: maintenant, elements: { create: data.elements.map((element) => ({ accessoireId: element.accessoireId ?? null, nomElement: element.nomElement, etat: element.etat, quantiteConstatee: element.quantiteConstatee, commentaire: element.commentaire ?? null, })), }, }, }); await tx.emprunt.update({ where: { id: data.empruntId }, data: { statut: data.statutEmprunt, dateRetourReelle: maintenant, modeIdentificationRetour: data.modeIdentification, commentaireRetour: data.commentaireRetour ?? null, }, }); await tx.materiel.update({ where: { id: data.materielId }, data: { statut: data.statutMateriel }, }); if (data.anomalie) { const infoAnomalie = data.anomalie; const anomalie = await tx.anomalie.create({ data: { empruntId: data.empruntId, materielId: data.materielId, utilisateurId: data.utilisateurId, type: infoAnomalie.type, description: infoAnomalie.description, statut: 'DETECTEE', detecteeAutomatiquement: true, dateDetection: maintenant, }, }); if (infoAnomalie.responsablesIds.length > 0) { await tx.notification.createMany({ data: infoAnomalie.responsablesIds.map((responsableId) => ({ utilisateurId: responsableId, anomalieId: anomalie.id, titre: 'Nouvelle anomalie detectee', message: infoAnomalie.description, type: 'ANOMALIE', })), }); } } return tx.emprunt.findUniqueOrThrow({ where: { id: data.empruntId }, include: { materiel: { include: { categorie: true } } }, }); }); }