Files
iag-dev-evaluator/src/actions/evaluations.ts
Froidefond Julien 27866091bf perf: optimisations DB — batch queries et index
- createEvaluation: remplace N create() par un createMany() (N→1 requête)
- updateEvaluation: regroupe les upserts en $transaction() parallèle
- Ajout d'index sur Evaluation.evaluatorId, Evaluation.templateId,
  EvaluationShare.userId et AuditLog.evaluationId

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-25 13:27:57 +01:00

219 lines
7.4 KiB
TypeScript

"use server";
import { auth } from "@/auth";
import { prisma } from "@/lib/db";
import { canAccessEvaluation } from "@/lib/evaluation-access";
import { getEvaluation } from "@/lib/server-data";
import { revalidatePath } from "next/cache";
export type ActionResult<T = void> = { success: true; data?: T } | { success: false; error: string };
export async function fetchEvaluation(id: string): Promise<ActionResult<Awaited<ReturnType<typeof getEvaluation>>>> {
const session = await auth();
if (!session?.user) return { success: false, error: "Non authentifié" };
const evaluation = await getEvaluation(id);
if (!evaluation) return { success: false, error: "Évaluation introuvable" };
return { success: true, data: evaluation };
}
export async function deleteEvaluation(id: string): Promise<ActionResult> {
const session = await auth();
if (!session?.user) return { success: false, error: "Non authentifié" };
const hasAccess = await canAccessEvaluation(id, session.user.id, session.user.role === "admin");
if (!hasAccess) return { success: false, error: "Accès refusé" };
try {
await prisma.evaluation.delete({ where: { id } });
revalidatePath("/dashboard");
return { success: true };
} catch (e) {
console.error(e);
return { success: false, error: "Erreur lors de la suppression" };
}
}
export async function createEvaluation(data: {
candidateName: string;
candidateRole: string;
candidateTeam?: string;
evaluationDate: string;
templateId: string;
}): Promise<ActionResult<{ id: string }>> {
const session = await auth();
if (!session?.user) return { success: false, error: "Non authentifié" };
const { candidateName, candidateRole, candidateTeam, evaluationDate, templateId } = data;
if (!candidateName || !candidateRole || !evaluationDate || !templateId) {
return { success: false, error: "Champs requis manquants" };
}
try {
const evaluatorName = session.user.name || session.user.email || "Évaluateur";
const template = await prisma.template.findUnique({
where: { id: templateId },
include: { dimensions: { orderBy: { orderIndex: "asc" } } },
});
if (!template) return { success: false, error: "Template introuvable" };
const evaluation = await prisma.evaluation.create({
data: {
candidateName,
candidateRole,
candidateTeam: candidateTeam || null,
evaluatorName,
evaluatorId: session.user.id,
evaluationDate: new Date(evaluationDate),
templateId,
status: "draft",
},
});
await prisma.dimensionScore.createMany({
data: template.dimensions.map((dim) => ({
evaluationId: evaluation.id,
dimensionId: dim.id,
})),
});
revalidatePath("/dashboard");
return { success: true, data: { id: evaluation.id } };
} catch (e) {
console.error(e);
return { success: false, error: "Erreur lors de la création" };
}
}
export interface UpdateEvaluationInput {
candidateName?: string;
candidateRole?: string;
candidateTeam?: string | null;
evaluatorName?: string;
evaluationDate?: string;
status?: string;
findings?: string | null;
recommendations?: string | null;
isPublic?: boolean;
dimensionScores?: {
dimensionId: string;
evaluationId: string;
score: number | null;
justification?: string | null;
examplesObserved?: string | null;
confidence?: string | null;
candidateNotes?: string | null;
}[];
}
export async function updateDimensionScore(
evaluationId: string,
dimensionId: string,
data: { score?: number | null; justification?: string | null; examplesObserved?: string | null; confidence?: string | null; candidateNotes?: string | null }
): Promise<ActionResult> {
const session = await auth();
if (!session?.user) return { success: false, error: "Non authentifié" };
const hasAccess = await canAccessEvaluation(evaluationId, session.user.id, session.user.role === "admin");
if (!hasAccess) return { success: false, error: "Accès refusé" };
try {
await prisma.dimensionScore.upsert({
where: { evaluationId_dimensionId: { evaluationId, dimensionId } },
update: data,
create: { evaluationId, dimensionId, ...data },
});
revalidatePath(`/evaluations/${evaluationId}`);
return { success: true };
} catch (e) {
return { success: false, error: e instanceof Error ? e.message : "Erreur" };
}
}
export async function updateEvaluation(id: string, data: UpdateEvaluationInput): Promise<ActionResult> {
const session = await auth();
if (!session?.user) return { success: false, error: "Non authentifié" };
const hasAccess = await canAccessEvaluation(id, session.user.id, session.user.role === "admin");
if (!hasAccess) return { success: false, error: "Accès refusé" };
const existing = await prisma.evaluation.findUnique({ where: { id } });
if (!existing) return { success: false, error: "Évaluation introuvable" };
try {
const {
candidateName,
candidateRole,
candidateTeam,
evaluatorName,
evaluationDate,
status,
findings,
recommendations,
isPublic,
dimensionScores,
} = data;
const updateData: Record<string, unknown> = {};
if (candidateName != null) updateData.candidateName = candidateName;
if (candidateRole != null) updateData.candidateRole = candidateRole;
if (candidateTeam !== undefined) updateData.candidateTeam = candidateTeam;
if (evaluatorName != null) updateData.evaluatorName = evaluatorName;
if (evaluationDate != null) {
const d = new Date(evaluationDate);
if (!isNaN(d.getTime())) updateData.evaluationDate = d;
}
if (status != null) updateData.status = status;
if (findings != null) updateData.findings = findings;
if (recommendations != null) updateData.recommendations = recommendations;
if (typeof isPublic === "boolean") updateData.isPublic = isPublic;
if (Object.keys(updateData).length > 0) {
await prisma.auditLog.create({
data: { evaluationId: id, action: "updated", newValue: JSON.stringify(updateData) },
});
await prisma.evaluation.update({ where: { id }, data: updateData as Record<string, unknown> });
}
if (dimensionScores && Array.isArray(dimensionScores)) {
const validScores = dimensionScores.filter((ds) => ds.dimensionId);
if (validScores.length > 0) {
await prisma.$transaction(
validScores.map((ds) =>
prisma.dimensionScore.upsert({
where: {
evaluationId_dimensionId: { evaluationId: id, dimensionId: ds.dimensionId },
},
update: {
score: ds.score,
justification: ds.justification,
examplesObserved: ds.examplesObserved,
confidence: ds.confidence,
candidateNotes: ds.candidateNotes,
},
create: {
evaluationId: id,
dimensionId: ds.dimensionId,
score: ds.score,
justification: ds.justification,
examplesObserved: ds.examplesObserved,
confidence: ds.confidence,
candidateNotes: ds.candidateNotes,
},
})
)
);
}
}
revalidatePath(`/evaluations/${id}`);
revalidatePath("/dashboard");
return { success: true };
} catch (e) {
console.error(e);
return { success: false, error: e instanceof Error ? e.message : "Erreur lors de la sauvegarde" };
}
}