diff --git a/app/Http/Controllers/CandidateController.php b/app/Http/Controllers/CandidateController.php index f343603..c860088 100644 --- a/app/Http/Controllers/CandidateController.php +++ b/app/Http/Controllers/CandidateController.php @@ -129,6 +129,19 @@ class CandidateController extends Controller return back()->with('success', 'Notes mises à jour avec succès.'); } + public function updateScores(Request $request, Candidate $candidate) + { + $request->validate([ + 'cv_score' => 'nullable|numeric|min:0|max:20', + 'motivation_score' => 'nullable|numeric|min:0|max:10', + 'interview_score' => 'nullable|numeric|min:0|max:30', + ]); + + $candidate->update($request->only(['cv_score', 'motivation_score', 'interview_score'])); + + return back()->with('success', 'Notes mises à jour avec succès.'); + } + public function resetPassword(Candidate $candidate) { $password = Str::random(10); diff --git a/app/Models/Candidate.php b/app/Models/Candidate.php index 277e31f..feb1d95 100644 --- a/app/Models/Candidate.php +++ b/app/Models/Candidate.php @@ -9,11 +9,30 @@ use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Attributes\Fillable; use Illuminate\Database\Eloquent\Factories\HasFactory; -#[Fillable(['user_id', 'phone', 'linkedin_url', 'status', 'notes'])] +#[Fillable(['user_id', 'phone', 'linkedin_url', 'status', 'notes', 'cv_score', 'motivation_score', 'interview_score'])] class Candidate extends Model { use HasFactory; + protected $appends = ['weighted_score']; + + public function getWeightedScoreAttribute(): float + { + $cv = (float)$this->cv_score; + $motivation = (float)$this->motivation_score; + $interview = (float)$this->interview_score; + + // On récupère le meilleur test (ramené sur 20) + $bestAttempt = $this->attempts()->whereNotNull('finished_at')->get()->map(function($a) { + return $a->max_score > 0 ? ($a->score / $a->max_score) * 20 : 0; + })->max() ?? 0; + + $totalPoints = $cv + $motivation + $interview + $bestAttempt; + $maxPoints = 20 + 10 + 30 + 20; // Total potentiel = 80 + + return round(($totalPoints / $maxPoints) * 20, 2); + } + public function user(): BelongsTo { return $this->belongsTo(User::class); diff --git a/database/migrations/2026_03_20_164315_add_detailed_scores_to_candidates_table.php b/database/migrations/2026_03_20_164315_add_detailed_scores_to_candidates_table.php new file mode 100644 index 0000000..76fcae9 --- /dev/null +++ b/database/migrations/2026_03_20_164315_add_detailed_scores_to_candidates_table.php @@ -0,0 +1,30 @@ +decimal('cv_score', 5, 2)->nullable()->after('notes'); + $table->decimal('motivation_score', 5, 2)->nullable()->after('cv_score'); + $table->decimal('interview_score', 5, 2)->nullable()->after('motivation_score'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('candidates', function (Blueprint $table) { + $table->dropColumn(['cv_score', 'motivation_score', 'interview_score']); + }); + } +}; diff --git a/resources/js/Pages/Admin/Candidates/Show.vue b/resources/js/Pages/Admin/Candidates/Show.vue index 1c0ea1d..b9e0cd9 100644 --- a/resources/js/Pages/Admin/Candidates/Show.vue +++ b/resources/js/Pages/Admin/Candidates/Show.vue @@ -28,6 +28,12 @@ const notesForm = useForm({ notes: props.candidate.notes || '' }); +const scoreForm = useForm({ + cv_score: props.candidate.cv_score || 0, + motivation_score: props.candidate.motivation_score || 0, + interview_score: props.candidate.interview_score || 0, +}); + const isPreview = ref(false); const renderedNotes = computed(() => marked.parse(notesForm.notes || '')); @@ -85,6 +91,12 @@ const saveNotes = () => { }); }; +const saveScores = () => { + scoreForm.patch(route('admin.candidates.update-scores', props.candidate.id), { + preserveScroll: true, + }); +}; + const openPreview = (doc) => { selectedDocument.value = doc; }; @@ -216,8 +228,81 @@ const openPreview = (doc) => { - +
Note pondérée sur 20
+