Files
RecruIT/app/Http/Controllers/AttemptController.php

173 lines
5.0 KiB
PHP

<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Attempt;
use App\Models\Answer;
use App\Models\Quiz;
use App\Models\Option;
use App\Models\AdminLog;
use Illuminate\Support\Facades\DB;
use Inertia\Inertia;
class AttemptController extends Controller
{
public function destroy(Attempt $attempt)
{
$this->authorizeAdmin();
$candidateName = $attempt->candidate->user->name;
// Bypass tenant scope: admin may delete attempts for cross-tenant quizzes
$quiz = Quiz::withoutGlobalScopes()->find($attempt->quiz_id);
$quizTitle = $quiz?->title ?? "Quiz #{$attempt->quiz_id}";
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(Quiz $quiz)
{
$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']);
}
// Reload quiz with questions FRESHLY (avoid any cached state from model binding)
$quizData = Quiz::with(['questions' => function($q) {
$q->orderBy('id')->with('options');
}])
->find($quiz->id);
return Inertia::render('Candidate/QuizInterface', [
'quiz' => $quizData,
'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)
{
$attempt->load(['quiz.questions.options', 'answers.option']);
$score = 0;
$maxScore = 0;
foreach ($attempt->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,
]);
}
}