authorizeAdmin(); $candidateName = $attempt->candidate->user->name; $quizTitle = $attempt->quiz->title; DB::transaction(function () use ($attempt, $candidateName, $quizTitle) { // Log the action AdminLog::create([ 'user_id' => auth()->id(), 'action' => 'DELETE_ATTEMPT', 'description' => "Suppression du test '{$quizTitle}' pour le candidat '{$candidateName}'." ]); // Delete attempt (cascades to answers) $attempt->delete(); // Re-evaluate candidate status if needed $candidate = $attempt->candidate; if ($candidate->attempts()->count() === 0) { $candidate->update(['status' => 'en_attente']); } }); return back()->with('success', 'Tentative de test supprimée et action journalisée.'); } private function authorizeAdmin() { if (!auth()->user()->isAdmin()) { abort(403); } } public function show(int $quizId) { // Bypass tenant global scope: candidates have no tenant_id // but should still access their assigned quizzes $quiz = Quiz::withoutGlobalScopes()->findOrFail($quizId); $candidate = auth()->user()->candidate; if (!$candidate) { abort(403, 'Seuls les candidats peuvent passer des quiz.'); } $attempt = Attempt::where('candidate_id', $candidate->id) ->where('quiz_id', $quiz->id) ->first(); if ($attempt && $attempt->finished_at) { return Inertia::render('Candidate/Thanks'); } if (!$attempt) { $attempt = Attempt::create([ 'candidate_id' => $candidate->id, 'quiz_id' => $quiz->id, 'started_at' => now(), ]); $candidate->update(['status' => 'en_cours']); } $quiz->load(['questions.options']); return Inertia::render('Candidate/QuizInterface', [ 'quiz' => $quiz, 'attempt' => $attempt->load('answers') ]); } public function saveAnswer(Request $request, Attempt $attempt) { $request->validate([ 'question_id' => 'required|exists:questions,id', 'option_id' => 'nullable|exists:options,id', 'text_content' => 'nullable|string', ]); Answer::updateOrCreate( [ 'attempt_id' => $attempt->id, 'question_id' => $request->question_id, ], [ 'option_id' => $request->option_id, 'text_content' => $request->text_content, ] ); return response()->json(['status' => 'success']); } public function finish(Attempt $attempt) { if ($attempt->finished_at) { return redirect()->route('dashboard'); } $this->recalculateScore($attempt); $attempt->update([ 'finished_at' => now(), ]); $attempt->candidate->update(['status' => 'termine']); return redirect()->route('dashboard'); } public function updateAnswerScore(Request $request, Answer $answer) { $this->authorizeAdmin(); $request->validate([ 'score' => 'required|numeric|min:0' ]); $answer->update(['score' => $request->score]); $this->recalculateScore($answer->attempt); return back()->with('success', 'Note mise à jour et score total recalculé.'); } private function recalculateScore(Attempt $attempt) { // Bypass tenant scope: candidates have no tenant_id $quiz = Quiz::withoutGlobalScopes() ->with(['questions.options']) ->find($attempt->quiz_id); $attempt->load(['answers.option']); $score = 0; $maxScore = 0; if (!$quiz) { return; } foreach ($quiz->questions as $question) { $maxScore += $question->points; $userAnswer = $attempt->answers->where('question_id', $question->id)->first(); if ($userAnswer) { if ($question->type === 'qcm') { if ($userAnswer->option && $userAnswer->option->is_correct) { $score += $question->points; } } else if ($question->type === 'open') { $score += (float) $userAnswer->score; } } } $attempt->update([ 'score' => $score, 'max_score' => $maxScore, ]); } }