364 lines
11 KiB
TypeScript
364 lines
11 KiB
TypeScript
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 } };
|
|
checklists: { include: { elements: true } };
|
|
};
|
|
}>;
|
|
|
|
export type EmpruntAvecChecklists = Prisma.EmpruntGetPayload<{
|
|
include: { checklists: { include: { elements: true } } };
|
|
}>;
|
|
|
|
const STATUTS_EN_COURS: StatutEmprunt[] = ['EN_COURS', 'EN_RETARD'];
|
|
const STATUTS_BLOQUANTS: StatutEmprunt[] = [
|
|
'EN_ATTENTE_VALIDATION_DEPART',
|
|
'EN_COURS',
|
|
'EN_RETARD',
|
|
'EN_ATTENTE_VALIDATION_RETOUR',
|
|
];
|
|
|
|
export function findEnCoursParUtilisateur(utilisateurId: number): Promise<EmpruntAvecMateriel[]> {
|
|
return prisma.emprunt.findMany({
|
|
where: {
|
|
utilisateurId,
|
|
statut: { in: STATUTS_EN_COURS },
|
|
},
|
|
include: {
|
|
materiel: { include: { categorie: true } },
|
|
checklists: { include: { elements: true } },
|
|
},
|
|
orderBy: { dateRetourPrevue: 'asc' },
|
|
});
|
|
}
|
|
|
|
export function findByIdAvecChecklists(id: number): Promise<EmpruntAvecChecklists | null> {
|
|
return prisma.emprunt.findUnique({
|
|
where: { id },
|
|
include: { checklists: { include: { elements: true } } },
|
|
});
|
|
}
|
|
|
|
export interface SignalerTentativeRetourData {
|
|
empruntId: number;
|
|
utilisateurId: number;
|
|
materielId: number;
|
|
campusId: number;
|
|
sallePretId: number;
|
|
posteEmpruntId: number;
|
|
responsablesIds: number[];
|
|
description: string;
|
|
}
|
|
|
|
export function signalerTentativeRetour(data: SignalerTentativeRetourData): Promise<boolean> {
|
|
return prisma.$transaction(async (tx) => {
|
|
const emprunt = await tx.emprunt.findFirst({
|
|
where: {
|
|
id: data.empruntId,
|
|
utilisateurId: data.utilisateurId,
|
|
statut: { in: STATUTS_EN_COURS },
|
|
},
|
|
select: { id: true },
|
|
});
|
|
if (!emprunt) {
|
|
throw new AppError(409, 'Emprunt non restituable');
|
|
}
|
|
|
|
const dejaSignalee = await tx.historique.findFirst({
|
|
where: {
|
|
empruntId: data.empruntId,
|
|
action: 'TENTATIVE_MODIFICATION_RETOUR',
|
|
},
|
|
select: { id: true },
|
|
});
|
|
if (dejaSignalee) {
|
|
return false;
|
|
}
|
|
|
|
if (data.responsablesIds.length > 0) {
|
|
await tx.notification.createMany({
|
|
data: data.responsablesIds.map((responsableId) => ({
|
|
utilisateurId: responsableId,
|
|
titre: 'Tentative de modification au retour',
|
|
message: data.description,
|
|
type: 'TENTATIVE_MODIFICATION_RETOUR',
|
|
})),
|
|
});
|
|
}
|
|
|
|
await tx.historique.create({
|
|
data: {
|
|
utilisateurId: data.utilisateurId,
|
|
empruntId: data.empruntId,
|
|
materielId: data.materielId,
|
|
campusId: data.campusId,
|
|
sallePretId: data.sallePretId,
|
|
posteEmpruntId: data.posteEmpruntId,
|
|
action: 'TENTATIVE_MODIFICATION_RETOUR',
|
|
description: data.description,
|
|
dateAction: new Date(),
|
|
},
|
|
});
|
|
|
|
return true;
|
|
});
|
|
}
|
|
|
|
interface ChecklistElementData {
|
|
accessoireId?: number;
|
|
nomElement: string;
|
|
etat: string;
|
|
quantiteConstatee: number;
|
|
commentaire?: string;
|
|
}
|
|
|
|
export interface CreerEmpruntData {
|
|
utilisateurId: number;
|
|
materielId: number;
|
|
categorieId: number;
|
|
campusId: number;
|
|
sallePretId: number;
|
|
posteEmpruntId: number;
|
|
dateRetourPrevue: Date;
|
|
modeIdentification: string;
|
|
commentaireDepart?: string;
|
|
elements: ChecklistElementData[];
|
|
statutEmprunt: StatutEmprunt;
|
|
statutMateriel: string;
|
|
responsablesIds: number[];
|
|
descriptionEcart?: string;
|
|
}
|
|
|
|
export function creerEmpruntComplet(data: CreerEmpruntData): Promise<EmpruntAvecMateriel> {
|
|
return prisma.$transaction(
|
|
async (tx) => {
|
|
const empruntBloquant = await tx.emprunt.findFirst({
|
|
where: {
|
|
utilisateurId: data.utilisateurId,
|
|
statut: { in: STATUTS_BLOQUANTS },
|
|
materiel: { categorieId: data.categorieId },
|
|
},
|
|
select: { id: true },
|
|
});
|
|
if (empruntBloquant) {
|
|
throw new AppError(
|
|
409,
|
|
'Un materiel de cette categorie est deja emprunte ou en attente de confirmation',
|
|
);
|
|
}
|
|
|
|
const reservation = await tx.materiel.updateMany({
|
|
where: { id: data.materielId, statut: 'DISPONIBLE' },
|
|
data: { statut: data.statutMateriel },
|
|
});
|
|
if (reservation.count === 0) {
|
|
throw new AppError(409, 'Materiel non disponible');
|
|
}
|
|
|
|
const maintenant = new Date();
|
|
const emprunt = await tx.emprunt.create({
|
|
data: {
|
|
utilisateurId: data.utilisateurId,
|
|
materielId: data.materielId,
|
|
campusId: data.campusId,
|
|
sallePretId: data.sallePretId,
|
|
posteEmpruntId: data.posteEmpruntId,
|
|
dateEmprunt: maintenant,
|
|
dateRetourPrevue: data.dateRetourPrevue,
|
|
statut: data.statutEmprunt,
|
|
modeIdentificationEmprunt: data.modeIdentification,
|
|
commentaireDepart: data.commentaireDepart ?? null,
|
|
},
|
|
});
|
|
|
|
await tx.checklist.create({
|
|
data: {
|
|
empruntId: emprunt.id,
|
|
utilisateurId: data.utilisateurId,
|
|
type: 'DEPART',
|
|
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,
|
|
})),
|
|
},
|
|
},
|
|
});
|
|
|
|
if (data.descriptionEcart) {
|
|
if (data.responsablesIds.length > 0) {
|
|
await tx.notification.createMany({
|
|
data: data.responsablesIds.map((responsableId) => ({
|
|
utilisateurId: responsableId,
|
|
titre: 'Ecart signale au depart',
|
|
message: data.descriptionEcart as string,
|
|
type: 'ECART_DEPART',
|
|
})),
|
|
});
|
|
}
|
|
await tx.historique.create({
|
|
data: {
|
|
utilisateurId: data.utilisateurId,
|
|
empruntId: emprunt.id,
|
|
materielId: data.materielId,
|
|
campusId: data.campusId,
|
|
sallePretId: data.sallePretId,
|
|
posteEmpruntId: data.posteEmpruntId,
|
|
action: 'ECART_DEPART_SIGNALE',
|
|
description: data.descriptionEcart,
|
|
dateAction: maintenant,
|
|
},
|
|
});
|
|
} else {
|
|
await tx.historique.create({
|
|
data: {
|
|
utilisateurId: data.utilisateurId,
|
|
empruntId: emprunt.id,
|
|
materielId: data.materielId,
|
|
campusId: data.campusId,
|
|
sallePretId: data.sallePretId,
|
|
posteEmpruntId: data.posteEmpruntId,
|
|
action: 'EMPRUNT_AUTOMATIQUE',
|
|
description: `Emprunt #${emprunt.id} active avec une checklist de depart inchangee`,
|
|
dateAction: maintenant,
|
|
},
|
|
});
|
|
}
|
|
|
|
return tx.emprunt.findUniqueOrThrow({
|
|
where: { id: emprunt.id },
|
|
include: {
|
|
materiel: { include: { categorie: true } },
|
|
checklists: { include: { elements: true } },
|
|
},
|
|
});
|
|
},
|
|
{ isolationLevel: Prisma.TransactionIsolationLevel.Serializable },
|
|
);
|
|
}
|
|
|
|
export interface RestituerEmpruntData {
|
|
empruntId: number;
|
|
utilisateurId: number;
|
|
materielId: number;
|
|
statutEmprunt: string;
|
|
statutMateriel: string;
|
|
modeIdentification: string;
|
|
commentaireRetour?: string;
|
|
elements: ChecklistElementData[];
|
|
campusId: number;
|
|
sallePretId: number;
|
|
posteEmpruntId: number;
|
|
responsablesIds: number[];
|
|
descriptionEcart?: string;
|
|
}
|
|
|
|
export function restituerEmpruntComplet(data: RestituerEmpruntData): Promise<EmpruntAvecMateriel> {
|
|
return prisma.$transaction(async (tx) => {
|
|
const maintenant = new Date();
|
|
|
|
const transition = await tx.emprunt.updateMany({
|
|
where: {
|
|
id: data.empruntId,
|
|
utilisateurId: data.utilisateurId,
|
|
statut: { in: STATUTS_EN_COURS },
|
|
},
|
|
data: {
|
|
statut: data.statutEmprunt,
|
|
...(data.statutEmprunt === 'CLOTURE' ? { dateRetourReelle: maintenant } : {}),
|
|
modeIdentificationRetour: data.modeIdentification,
|
|
commentaireRetour: data.commentaireRetour ?? null,
|
|
},
|
|
});
|
|
if (transition.count === 0) {
|
|
throw new AppError(409, 'Emprunt non restituable');
|
|
}
|
|
|
|
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.materiel.update({
|
|
where: { id: data.materielId },
|
|
data: { statut: data.statutMateriel },
|
|
});
|
|
|
|
if (data.descriptionEcart) {
|
|
const tentativeDejaSignalee = await tx.historique.findFirst({
|
|
where: {
|
|
empruntId: data.empruntId,
|
|
action: 'TENTATIVE_MODIFICATION_RETOUR',
|
|
},
|
|
select: { id: true },
|
|
});
|
|
if (!tentativeDejaSignalee && data.responsablesIds.length > 0) {
|
|
await tx.notification.createMany({
|
|
data: data.responsablesIds.map((responsableId) => ({
|
|
utilisateurId: responsableId,
|
|
titre: 'Changement signale au retour',
|
|
message: data.descriptionEcart as string,
|
|
type: 'ECART_RETOUR',
|
|
})),
|
|
});
|
|
}
|
|
await tx.historique.create({
|
|
data: {
|
|
utilisateurId: data.utilisateurId,
|
|
empruntId: data.empruntId,
|
|
materielId: data.materielId,
|
|
campusId: data.campusId,
|
|
sallePretId: data.sallePretId,
|
|
posteEmpruntId: data.posteEmpruntId,
|
|
action: 'ECART_RETOUR_SIGNALE',
|
|
description: data.descriptionEcart,
|
|
dateAction: maintenant,
|
|
},
|
|
});
|
|
} else {
|
|
await tx.historique.create({
|
|
data: {
|
|
utilisateurId: data.utilisateurId,
|
|
empruntId: data.empruntId,
|
|
materielId: data.materielId,
|
|
campusId: data.campusId,
|
|
sallePretId: data.sallePretId,
|
|
posteEmpruntId: data.posteEmpruntId,
|
|
action: 'RESTITUTION_AUTOMATIQUE',
|
|
description: `Emprunt #${data.empruntId} cloture avec une checklist de retour inchangee`,
|
|
dateAction: maintenant,
|
|
},
|
|
});
|
|
}
|
|
|
|
return tx.emprunt.findUniqueOrThrow({
|
|
where: { id: data.empruntId },
|
|
include: {
|
|
materiel: { include: { categorie: true } },
|
|
checklists: { include: { elements: true } },
|
|
},
|
|
});
|
|
});
|
|
}
|