diff --git a/app/Http/Controllers/AttemptController.php b/app/Http/Controllers/AttemptController.php index 98b19a2..27bcef6 100644 --- a/app/Http/Controllers/AttemptController.php +++ b/app/Http/Controllers/AttemptController.php @@ -110,26 +110,9 @@ class AttemptController extends Controller return redirect()->route('dashboard'); } - $attempt->load(['quiz.questions.options', 'answers']); - - $score = 0; - $maxScore = $attempt->quiz->questions->sum('points'); - - foreach ($attempt->quiz->questions as $question) { - if ($question->type === 'qcm') { - $userAnswer = $attempt->answers->where('question_id', $question->id)->first(); - if ($userAnswer && $userAnswer->option_id) { - $option = $question->options->where('id', $userAnswer->option_id)->first(); - if ($option && $option->is_correct) { - $score += $question->points; - } - } - } - } + $this->recalculateScore($attempt); $attempt->update([ - 'score' => $score, - 'max_score' => $maxScore, 'finished_at' => now(), ]); @@ -137,4 +120,47 @@ class AttemptController extends Controller 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, + ]); + } } diff --git a/app/Models/Answer.php b/app/Models/Answer.php index 1a0e5a8..6af006c 100644 --- a/app/Models/Answer.php +++ b/app/Models/Answer.php @@ -8,11 +8,15 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Attributes\Fillable; use Illuminate\Database\Eloquent\Factories\HasFactory; -#[Fillable(['attempt_id', 'question_id', 'option_id', 'text_content'])] +#[Fillable(['attempt_id', 'question_id', 'option_id', 'text_content', 'score'])] class Answer extends Model { use HasFactory; + protected $casts = [ + 'score' => 'float', + ]; + public function attempt(): BelongsTo { return $this->belongsTo(Attempt::class); diff --git a/database/migrations/2026_03_22_210217_add_score_to_answers_table.php b/database/migrations/2026_03_22_210217_add_score_to_answers_table.php new file mode 100644 index 0000000..ddcd46c --- /dev/null +++ b/database/migrations/2026_03_22_210217_add_score_to_answers_table.php @@ -0,0 +1,28 @@ +decimal('score', 8, 2)->nullable()->after('text_content'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('answers', function (Blueprint $table) { + $table->dropColumn('score'); + }); + } +}; diff --git a/resources/js/Pages/Admin/Candidates/Show.vue b/resources/js/Pages/Admin/Candidates/Show.vue index d37c021..7983f34 100644 --- a/resources/js/Pages/Admin/Candidates/Show.vue +++ b/resources/js/Pages/Admin/Candidates/Show.vue @@ -100,6 +100,14 @@ const saveScores = () => { const openPreview = (doc) => { selectedDocument.value = doc; }; + +const updateAnswerScore = (answerId, score) => { + router.patch(route('admin.answers.update-score', answerId), { + score: score + }, { + preserveScroll: true, + }); +};