diff --git a/app/Domains/Management/Recruitment/Http/Controllers/CandidateAssessmentController.php b/app/Domains/Management/Recruitment/Http/Controllers/CandidateAssessmentController.php
index ddb61dcd9..591bc9f8c 100644
--- a/app/Domains/Management/Recruitment/Http/Controllers/CandidateAssessmentController.php
+++ b/app/Domains/Management/Recruitment/Http/Controllers/CandidateAssessmentController.php
@@ -3,17 +3,30 @@
 namespace App\Domains\Management\Recruitment\Http\Controllers;
 
 use App\Core\Traits\InteractWithResponse;
+use App\Domains\Management\Recruitment\Models\Candidate;
 use App\Domains\Management\Recruitment\Models\CandidateAssessment;
+use App\Domains\Management\Recruitment\Models\JobOpening;
 use App\Domains\Management\Recruitment\Http\Resources\CandidateAssessmentResource;
+use App\Domains\Management\Recruitment\Services\EvaluationFormService;
 use Illuminate\Http\JsonResponse;
 
 class CandidateAssessmentController
 {
     use InteractWithResponse;
 
+    public function __construct(
+        protected EvaluationFormService $evaluationFormService
+    ) {}
+
     public function index(): JsonResponse
     {
-        $assessments = CandidateAssessment::with(['candidate', 'hiringStage', 'jobOpening', 'assessor', 'approvedByUser'])
+        $assessments = CandidateAssessment::with([
+                'candidate',
+                'hiringStage.evaluationCriteria',
+                'jobOpening',
+                'assessor',
+                'approvedByUser',
+            ])
             ->orderByDesc('created_at')
             ->paginate(15);
 
@@ -24,77 +37,188 @@ public function index(): JsonResponse
         );
     }
 
+    /**
+     * Return the dynamic evaluation form schema for a candidate at their current stage.
+     * Query params: candidate_id, job_opening_id
+     */
+    public function getFormSchema(): JsonResponse
+    {
+        $candidateId  = request('candidate_id');
+        $jobOpeningId = request('job_opening_id');
+
+        $candidate = Candidate::find($candidateId);
+        $job       = JobOpening::find($jobOpeningId);
+
+        if (!$candidate || !$job) {
+            return $this->sendFailedResponse('المرشح أو الوظيفة غير موجودة', 404);
+        }
+
+        try {
+            $schema = $this->evaluationFormService->buildFormForCandidate($candidate, $job);
+            return $this->sendSuccessResponse($schema, 'تم جلب نموذج التقييم بنجاح');
+        } catch (\Exception $e) {
+            return $this->sendFailedResponse($e->getMessage());
+        }
+    }
+
+    /**
+     * Create a new assessment (draft or submitted).
+     */
     public function store(): JsonResponse
     {
         $validated = request()->validate([
-            'candidate_id' => 'required|exists:candidates,id',
-            'hiring_stage_id' => 'required|exists:hiring_stages,id',
-            'job_opening_id' => 'required|exists:job_openings,id',
-            'assessor_id' => 'required|exists:users,id',
-            'assessment_date' => 'required|date',
-            'availability_date' => 'nullable|date',
-            'expected_salary' => 'nullable|numeric|min:0',
-            'scores' => 'required|array',
-            'comments' => 'nullable|string',
-            'status' => 'nullable|in:pending,completed,approved,rejected',
-            'approved_by' => 'nullable|exists:users,id',
-            'approved_at' => 'nullable|date',
+            'candidate_id'             => 'required|exists:candidates,id',
+            'job_opening_id'           => 'required|exists:job_openings,id',
+            'overall_rating'           => 'nullable|integer|min:1|max:10',
+            'availability_date'        => 'nullable|date',
+            'expected_salary'          => 'nullable|numeric|min:0',
+            'position_expected_salary' => 'nullable|numeric|min:0',
+            'general_notes'            => 'nullable|string',
+            'criteria_values'          => 'nullable|array',
+            'skill_verifications'      => 'nullable|array',
+            'scores'                   => 'nullable|array',
+            'comments'                 => 'nullable|string',
+            'save_mode'                => 'in:draft,submitted',
         ]);
 
-        $assessment = CandidateAssessment::create($validated);
+        $candidate = Candidate::find($validated['candidate_id']);
+        $job       = JobOpening::find($validated['job_opening_id']);
 
-        return $this->sendSuccessResponse(
-            new CandidateAssessmentResource($assessment->load(['candidate', 'hiringStage', 'jobOpening', 'assessor', 'approvedByUser'])),
-            'تم إنشاء التقييم بنجاح',
-            201
-        );
+        try {
+            $assessment = $this->evaluationFormService->createEvaluation(
+                $candidate,
+                $job,
+                $validated,
+                auth()->user()
+            );
+
+            return $this->sendSuccessResponse(
+                new CandidateAssessmentResource(
+                    $assessment->load(['candidate', 'hiringStage.evaluationCriteria', 'jobOpening', 'assessor', 'approvedByUser'])
+                ),
+                'تم إنشاء التقييم بنجاح',
+                201
+            );
+        } catch (\Illuminate\Validation\ValidationException $e) {
+            return $this->sendFailedResponse($e->errors(), 422);
+        } catch (\Exception $e) {
+            return $this->sendFailedResponse($e->getMessage());
+        }
     }
 
     public function show(CandidateAssessment $assessment): JsonResponse
     {
         return $this->sendSuccessResponse(
-            new CandidateAssessmentResource($assessment->load(['candidate', 'hiringStage', 'jobOpening', 'assessor', 'approvedByUser'])),
+            new CandidateAssessmentResource(
+                $assessment->load(['candidate', 'hiringStage.evaluationCriteria', 'jobOpening', 'assessor', 'approvedByUser'])
+            ),
             'تم جلب تفاصيل التقييم بنجاح'
         );
     }
 
+    /**
+     * Update an assessment — only allowed while still in draft mode.
+     */
     public function update(CandidateAssessment $assessment): JsonResponse
     {
+        if ($assessment->isSubmitted()) {
+            return $this->sendFailedResponse('لا يمكن تعديل التقييم المُقدَّم', 422);
+        }
+
         $validated = request()->validate([
-            'scores' => 'required|array',
-            'notes' => 'nullable|string',
+            'overall_rating'           => 'nullable|integer|min:1|max:10',
+            'availability_date'        => 'nullable|date',
+            'expected_salary'          => 'nullable|numeric|min:0',
+            'position_expected_salary' => 'nullable|numeric|min:0',
+            'general_notes'            => 'nullable|string',
+            'criteria_values'          => 'nullable|array',
+            'skill_verifications'      => 'nullable|array',
+            'scores'                   => 'nullable|array',
+            'comments'                 => 'nullable|string',
+            'save_mode'                => 'in:draft,submitted',
         ]);
 
-        if ($assessment->status !== 'pending') {
-            return $this->sendFailedResponse('لا يمكن تحديث التقييم المعتمد');
+        try {
+            $mode = $validated['save_mode'] ?? 'draft';
+            unset($validated['save_mode']);
+
+            $assessment = $this->evaluationFormService->saveEvaluation(
+                $assessment,
+                $validated,
+                $mode,
+                auth()->user()
+            );
+
+            return $this->sendSuccessResponse(
+                new CandidateAssessmentResource(
+                    $assessment->load(['candidate', 'hiringStage.evaluationCriteria', 'jobOpening', 'assessor', 'approvedByUser'])
+                ),
+                'تم تحديث التقييم بنجاح'
+            );
+        } catch (\Illuminate\Validation\ValidationException $e) {
+            return $this->sendFailedResponse($e->errors(), 422);
+        } catch (\Exception $e) {
+            return $this->sendFailedResponse($e->getMessage());
         }
+    }
 
-        $assessment->update($validated);
+    /**
+     * Submit (approve) an evaluation — moves it from draft to submitted.
+     * Only the responsible user of the current stage can do this.
+     */
+    public function submitEvaluation(CandidateAssessment $assessment): JsonResponse
+    {
+        if ($assessment->isSubmitted()) {
+            return $this->sendFailedResponse('التقييم مُقدَّم بالفعل', 422);
+        }
 
-        return $this->sendSuccessResponse(
-            new CandidateAssessmentResource($assessment),
-            'تم تحديث التقييم بنجاح'
-        );
+        $extra = request()->only([
+            'overall_rating',
+            'availability_date',
+            'expected_salary',
+            'position_expected_salary',
+            'general_notes',
+            'criteria_values',
+            'skill_verifications',
+            'comments',
+        ]);
+
+        try {
+            $assessment = $this->evaluationFormService->saveEvaluation(
+                $assessment,
+                $extra,
+                'submitted',
+                auth()->user()
+            );
+
+            return $this->sendSuccessResponse(
+                new CandidateAssessmentResource(
+                    $assessment->load(['candidate', 'hiringStage.evaluationCriteria', 'jobOpening', 'assessor', 'approvedByUser'])
+                ),
+                'تم تقديم التقييم بنجاح'
+            );
+        } catch (\Illuminate\Validation\ValidationException $e) {
+            return $this->sendFailedResponse($e->errors(), 422);
+        } catch (\Exception $e) {
+            return $this->sendFailedResponse($e->getMessage());
+        }
     }
 
     public function destroy(CandidateAssessment $assessment): JsonResponse
     {
-        if ($assessment->status === 'approved') {
-            return $this->sendFailedResponse('لا يمكن حذف التقييم المعتمد');
+        if ($assessment->isSubmitted()) {
+            return $this->sendFailedResponse('لا يمكن حذف التقييم المُقدَّم', 422);
         }
 
         $assessment->delete();
 
-        return $this->sendSuccessResponse(
-            null,
-            'تم حذف التقييم بنجاح'
-        );
+        return $this->sendSuccessResponse(null, 'تم حذف التقييم بنجاح');
     }
 
     public function approve(CandidateAssessment $assessment): JsonResponse
     {
         $assessment->update([
-            'status' => 'approved',
+            'status'      => 'approved',
             'approved_by' => auth()->id(),
             'approved_at' => now(),
         ]);
@@ -110,7 +234,7 @@ public function reject(CandidateAssessment $assessment): JsonResponse
         $reason = request('reason', 'تم الرفض دون تحديد السبب');
 
         $assessment->update([
-            'status' => 'rejected',
+            'status'           => 'rejected',
             'rejection_reason' => $reason,
         ]);
 
@@ -122,13 +246,22 @@ public function reject(CandidateAssessment $assessment): JsonResponse
 
     public function getCandidateAssessments(): JsonResponse
     {
-        $candidateId = request('candidate_id');
+        $candidateId  = request('candidate_id');
+        $jobOpeningId = request('job_opening_id');
 
-        $assessments = CandidateAssessment::whereHas('candidate', function ($query) use ($candidateId) {
-            $query->where('id', $candidateId);
-        })
-            ->orderByDesc('created_at')
-            ->get();
+        $query = CandidateAssessment::with([
+                'hiringStage.evaluationCriteria',
+                'jobOpening',
+                'assessor',
+                'approvedByUser',
+            ])
+            ->where('candidate_id', $candidateId);
+
+        if ($jobOpeningId) {
+            $query->where('job_opening_id', $jobOpeningId);
+        }
+
+        $assessments = $query->orderByDesc('created_at')->get();
 
         return $this->sendSuccessResponse(
             CandidateAssessmentResource::collection($assessments),
diff --git a/app/Domains/Management/Recruitment/Http/Controllers/JobOpeningController.php b/app/Domains/Management/Recruitment/Http/Controllers/JobOpeningController.php
index 1d0d57909..8c105b1f6 100644
--- a/app/Domains/Management/Recruitment/Http/Controllers/JobOpeningController.php
+++ b/app/Domains/Management/Recruitment/Http/Controllers/JobOpeningController.php
@@ -4,11 +4,14 @@
 
 use App\Core\Traits\InteractWithResponse;
 use App\Domains\Management\Recruitment\Models\Candidate;
+use App\Domains\Management\Recruitment\Models\HiringStage;
 use App\Domains\Management\Recruitment\Models\JobOpening;
+use App\Domains\Management\Recruitment\Models\CandidateStageTransition;
 use App\Domains\Management\Recruitment\Http\Requests\CreateJobOpeningRequest;
 use App\Domains\Management\Recruitment\Http\Requests\FilterCandidatesRequest;
 use App\Domains\Management\Recruitment\Services\FavoriteService;
 use App\Domains\Management\Recruitment\Services\JobOpeningService;
+use App\Domains\Management\Recruitment\Services\CandidateService;
 use App\Domains\Management\Recruitment\Http\Resources\JobCandidatesResource;
 use App\Domains\Management\Recruitment\Http\Resources\JobOpeningResource;
 use Illuminate\Http\JsonResponse;
@@ -317,4 +320,123 @@ public function getPublished(): JsonResponse
             'تم جلب الوظائف المنشورة بنجاح'
         );
     }
+
+    // ── Candidate Quick Actions (vacancy-scoped) ─────────────────────────────
+
+    /**
+     * Move a candidate to a specific stage within this vacancy.
+     * POST /job-openings/{job}/candidates/{candidate}/move-stage
+     */
+    public function moveCandidateStage(JobOpening $job, Candidate $candidate): JsonResponse
+    {
+        $stageId           = request('stage_id');
+        $nextStepDate      = request('next_step_date');
+        $responsibleUserId = request('responsible_user_id');
+        $notes             = request('notes');
+
+        $stage = HiringStage::find($stageId);
+        if (!$stage || $stage->job_opening_id !== $job->id) {
+            return $this->sendFailedResponse('المرحلة غير موجودة أو لا تنتمي لهذه الوظيفة', 404);
+        }
+
+        $application = $candidate->application($job);
+        if (!$application) {
+            return $this->sendFailedResponse('لا يوجد طلب تقديم لهذه الوظيفة', 404);
+        }
+        if (!$application->canAdvance()) {
+            return $this->sendFailedResponse('لا يمكن نقل مرشح مرفوض', 422);
+        }
+
+        $oldStageId = $application->current_stage_id;
+
+        $application->update([
+            'current_stage_id'    => $stageId,
+            'next_step_date'      => $nextStepDate,
+            'responsible_user_id' => $responsibleUserId,
+        ]);
+
+        CandidateStageTransition::create([
+            'candidate_id'        => $candidate->id,
+            'job_opening_id'      => $job->id,
+            'application_id'      => $application->id,
+            'from_stage_id'       => $oldStageId,
+            'to_stage_id'         => $stageId,
+            'responsible_user_id' => $responsibleUserId ?? auth()->id(),
+            'notes'               => $notes,
+        ]);
+
+        return $this->sendSuccessResponse(
+            null,
+            'تم نقل المرشح للمرحلة بنجاح'
+        );
+    }
+
+    /**
+     * Assign an interview manager to a candidate's application within this vacancy.
+     * POST /job-openings/{job}/candidates/{candidate}/assign-interviewer
+     */
+    public function assignInterviewer(JobOpening $job, Candidate $candidate): JsonResponse
+    {
+        $responsibleUserId = request()->validate([
+            'responsible_user_id' => 'required|exists:users,id',
+        ])['responsible_user_id'];
+
+        $application = $candidate->application($job);
+        if (!$application) {
+            return $this->sendFailedResponse('لا يوجد طلب تقديم لهذه الوظيفة', 404);
+        }
+
+        $application->update(['responsible_user_id' => $responsibleUserId]);
+
+        return $this->sendSuccessResponse(
+            null,
+            'تم تعيين مدير المقابلة بنجاح'
+        );
+    }
+
+    /**
+     * Reject a candidate within this specific vacancy.
+     * POST /job-openings/{job}/candidates/{candidate}/reject
+     */
+    public function rejectCandidate(JobOpening $job, Candidate $candidate): JsonResponse
+    {
+        $reason = request('reason', 'لم يتم تحديد السبب');
+
+        $application = $candidate->application($job);
+        if (!$application) {
+            return $this->sendFailedResponse('لا يوجد طلب تقديم لهذه الوظيفة', 404);
+        }
+        if ($application->isRejected()) {
+            return $this->sendFailedResponse('المرشح مرفوض بالفعل', 422);
+        }
+
+        $application->reject($reason);
+
+        return $this->sendSuccessResponse(
+            null,
+            'تم رفض المرشح بنجاح'
+        );
+    }
+
+    /**
+     * Mark a candidate as preliminarily accepted within this vacancy.
+     * POST /job-openings/{job}/candidates/{candidate}/preliminary-accept
+     */
+    public function preliminaryAccept(JobOpening $job, Candidate $candidate): JsonResponse
+    {
+        $application = $candidate->application($job);
+        if (!$application) {
+            return $this->sendFailedResponse('لا يوجد طلب تقديم لهذه الوظيفة', 404);
+        }
+        if ($application->isRejected()) {
+            return $this->sendFailedResponse('لا يمكن قبول مرشح مرفوض', 422);
+        }
+
+        $application->markAsInitiallyAccepted();
+
+        return $this->sendSuccessResponse(
+            null,
+            'تم القبول الأولي للمرشح بنجاح'
+        );
+    }
 }
diff --git a/app/Domains/Management/Recruitment/Http/Controllers/StageEvaluationCriteriaController.php b/app/Domains/Management/Recruitment/Http/Controllers/StageEvaluationCriteriaController.php
new file mode 100644
index 000000000..a428cf8ae
--- /dev/null
+++ b/app/Domains/Management/Recruitment/Http/Controllers/StageEvaluationCriteriaController.php
@@ -0,0 +1,101 @@
+<?php
+
+namespace App\Domains\Management\Recruitment\Http\Controllers;
+
+use App\Core\Traits\InteractWithResponse;
+use App\Domains\Management\Recruitment\Models\HiringStage;
+use App\Domains\Management\Recruitment\Models\StageEvaluationCriteria;
+use App\Domains\Management\Recruitment\Http\Resources\StageEvaluationCriteriaResource;
+use App\Domains\Management\Recruitment\Services\StageEvaluationCriteriaService;
+use Illuminate\Http\JsonResponse;
+use Illuminate\Http\Request;
+
+class StageEvaluationCriteriaController
+{
+    use InteractWithResponse;
+
+    public function __construct(
+        protected StageEvaluationCriteriaService $service
+    ) {}
+
+    public function index(HiringStage $hiringStage): JsonResponse
+    {
+        $criteria = $this->service->getCriteriaForStage($hiringStage);
+
+        return $this->sendSuccessResponse(
+            StageEvaluationCriteriaResource::collection($criteria),
+            'تم جلب معايير التقييم بنجاح'
+        );
+    }
+
+    public function store(Request $request, HiringStage $hiringStage): JsonResponse
+    {
+        $validated = $request->validate([
+            'label'       => 'required|string|max:255',
+            'input_type'  => 'required|in:numeric,yes_no,dropdown,free_text',
+            'options'     => 'nullable|array',
+            'options.*'   => 'required_if:input_type,dropdown',
+            'is_required' => 'boolean',
+            'order'       => 'nullable|integer|min:0',
+        ]);
+
+        $criterion = $this->service->addCriterion($hiringStage, $validated);
+
+        return $this->sendSuccessResponse(
+            new StageEvaluationCriteriaResource($criterion),
+            'تم إضافة المعيار بنجاح',
+            201
+        );
+    }
+
+    public function show(HiringStage $hiringStage, StageEvaluationCriteria $criterion): JsonResponse
+    {
+        abort_if($criterion->hiring_stage_id !== $hiringStage->id, 404, 'المعيار لا ينتمي لهذه المرحلة');
+
+        return $this->sendSuccessResponse(
+            new StageEvaluationCriteriaResource($criterion),
+            'تم جلب تفاصيل المعيار بنجاح'
+        );
+    }
+
+    public function update(Request $request, HiringStage $hiringStage, StageEvaluationCriteria $criterion): JsonResponse
+    {
+        abort_if($criterion->hiring_stage_id !== $hiringStage->id, 404, 'المعيار لا ينتمي لهذه المرحلة');
+
+        $validated = $request->validate([
+            'label'       => 'sometimes|string|max:255',
+            'input_type'  => 'sometimes|in:numeric,yes_no,dropdown,free_text',
+            'options'     => 'nullable|array',
+            'is_required' => 'boolean',
+            'order'       => 'nullable|integer|min:0',
+        ]);
+
+        $criterion = $this->service->updateCriterion($criterion, $validated);
+
+        return $this->sendSuccessResponse(
+            new StageEvaluationCriteriaResource($criterion),
+            'تم تحديث المعيار بنجاح'
+        );
+    }
+
+    public function destroy(HiringStage $hiringStage, StageEvaluationCriteria $criterion): JsonResponse
+    {
+        abort_if($criterion->hiring_stage_id !== $hiringStage->id, 404, 'المعيار لا ينتمي لهذه المرحلة');
+
+        $this->service->deleteCriterion($criterion);
+
+        return $this->sendSuccessResponse(null, 'تم حذف المعيار بنجاح');
+    }
+
+    public function reorder(Request $request, HiringStage $hiringStage): JsonResponse
+    {
+        $request->validate([
+            'ids'   => 'required|array',
+            'ids.*' => 'required|integer|exists:stage_evaluation_criteria,id',
+        ]);
+
+        $this->service->reorderCriteria($hiringStage, $request->get('ids'));
+
+        return $this->sendSuccessResponse(null, 'تم إعادة ترتيب المعايير بنجاح');
+    }
+}
diff --git a/app/Domains/Management/Recruitment/Http/Resources/CandidateAssessmentResource.php b/app/Domains/Management/Recruitment/Http/Resources/CandidateAssessmentResource.php
index f1ceba18f..c8b4f51f4 100644
--- a/app/Domains/Management/Recruitment/Http/Resources/CandidateAssessmentResource.php
+++ b/app/Domains/Management/Recruitment/Http/Resources/CandidateAssessmentResource.php
@@ -6,37 +6,86 @@
 
 class CandidateAssessmentResource extends JsonResource
 {
-    public function toArray($request)
+    public function toArray($request): array
     {
         return [
-            'id' => $this->id,
-            'candidate_id' => $this->candidate_id,
-            'candidate' => new CandidateResource($this->whenLoaded('candidate')),
-            'hiring_stage_id' => $this->hiring_stage_id,
-            'hiring_stage' => new HiringStageResource($this->whenLoaded('hiringStage')),
-            'job_opening_id' => $this->job_opening_id,
-            'job_opening' => new JobOpeningResource($this->whenLoaded('jobOpening')),
-            'assessor_id' => $this->assessor_id,
-            'assessor' => [
-                'id' => $this->assessor?->id,
-                'name' => $this->assessor?->name,
+            'id'                      => $this->id,
+            'candidate_id'            => $this->candidate_id,
+            'candidate'               => new CandidateResource($this->whenLoaded('candidate')),
+            'hiring_stage_id'         => $this->hiring_stage_id,
+            'hiring_stage'            => new HiringStageResource($this->whenLoaded('hiringStage')),
+            'job_opening_id'          => $this->job_opening_id,
+            'job_opening'             => new JobOpeningResource($this->whenLoaded('jobOpening')),
+            'assessor_id'             => $this->assessor_id,
+            'assessor'                => [
+                'id'    => $this->assessor?->id,
+                'name'  => $this->assessor?->name,
                 'email' => $this->assessor?->email,
             ],
-            'assessment_date' => $this->assessment_date,
-            'scores' => $this->scores,
-            'average_score' => $this->getAverageScore(),
-            'is_passed' => $this->isPassed(),
-            'comments' => $this->comments,
-            'status' => $this->status,
-            'approved_by' => $this->approved_by,
-            'approved_by_user' => [
-                'id' => $this->approvedByUser?->id,
-                'name' => $this->approvedByUser?->name,
+            'assessment_date'         => $this->assessment_date,
+            // Standard evaluation fields
+            'overall_rating'          => $this->overall_rating,
+            'availability_date'       => $this->availability_date,
+            'expected_salary'         => $this->expected_salary,
+            'position_expected_salary'=> $this->position_expected_salary,
+            'general_notes'           => $this->general_notes,
+            // Dynamic criteria — full Q&A pairs for assessments tab
+            'criteria_values'         => $this->criteria_values,
+            'criteria_answers'        => $this->buildCriteriaAnswers(),
+            'skill_verifications'     => $this->skill_verifications,
+            // Legacy scores field
+            'scores'                  => $this->scores,
+            'average_score'           => $this->getAverageScore(),
+            'is_passed'               => $this->isPassed(),
+            'comments'                => $this->comments,
+            'save_mode'               => $this->save_mode,
+            'status'                  => $this->status,
+            'approved_by'             => $this->approved_by,
+            'approved_by_user'        => [
+                'id'    => $this->approvedByUser?->id,
+                'name'  => $this->approvedByUser?->name,
                 'email' => $this->approvedByUser?->email,
             ],
-            'approved_at' => $this->approved_at,
-            'created_at' => $this->created_at,
-            'updated_at' => $this->updated_at,
+            'approved_at'             => $this->approved_at,
+            'created_at'              => $this->created_at,
+            'updated_at'              => $this->updated_at,
         ];
     }
+
+    /**
+     * Build full Q&A pairs by resolving criterion labels from the stage's criteria.
+     * Used in the assessments tab to show question+answer for each criterion.
+     */
+    private function buildCriteriaAnswers(): array
+    {
+        $values  = $this->criteria_values ?? [];
+        $answers = [];
+
+        // If the hiringStage relation with evaluationCriteria is loaded, use it
+        if ($this->relationLoaded('hiringStage') && $this->hiringStage?->relationLoaded('evaluationCriteria')) {
+            foreach ($this->hiringStage->evaluationCriteria as $criterion) {
+                $answers[] = [
+                    'criterion_id' => $criterion->id,
+                    'label'        => $criterion->label,
+                    'input_type'   => $criterion->input_type,
+                    'is_required'  => $criterion->is_required,
+                    'value'        => $values[$criterion->id] ?? null,
+                ];
+            }
+            return $answers;
+        }
+
+        // Fallback: return raw id→value pairs if stage not loaded
+        foreach ($values as $criterionId => $value) {
+            $answers[] = [
+                'criterion_id' => $criterionId,
+                'label'        => null,
+                'input_type'   => null,
+                'is_required'  => null,
+                'value'        => $value,
+            ];
+        }
+
+        return $answers;
+    }
 }
diff --git a/app/Domains/Management/Recruitment/Http/Resources/HiringStageResource.php b/app/Domains/Management/Recruitment/Http/Resources/HiringStageResource.php
index 93045ccb3..b990a6aa0 100644
--- a/app/Domains/Management/Recruitment/Http/Resources/HiringStageResource.php
+++ b/app/Domains/Management/Recruitment/Http/Resources/HiringStageResource.php
@@ -10,19 +10,20 @@ class HiringStageResource extends JsonResource
     public function toArray(Request $request): array
     {
         return [
-            'id' => $this->id,
-            'job_opening_id' => $this->job_opening_id,
-            'stage_name' => $this->stage_name,
-            'order' => $this->order,
-            'is_enabled' => $this->is_enabled,
-            'assessment_form_id' => $this->assessment_form_id,
-            'candidate_count' => $this->candidate_count,
-            'job_opening' => $this->whenLoaded('jobOpening', fn() => new JobOpeningResource($this->jobOpening)),
-            'applications_count' => $this->applications()->count(),
-            'assessments_count' => $this->assessments()->count(),
-            'assessments' => CandidateAssessmentResource::collection($this->whenLoaded('assessments')),
-            'created_at' => $this->created_at,
-            'updated_at' => $this->updated_at,
+            'id'                  => $this->id,
+            'job_opening_id'      => $this->job_opening_id,
+            'stage_name'          => $this->stage_name,
+            'order'               => $this->order,
+            'is_enabled'          => $this->is_enabled,
+            'assessment_form_id'  => $this->assessment_form_id,
+            'candidate_count'     => $this->candidate_count,
+            'job_opening'         => $this->whenLoaded('jobOpening', fn() => new JobOpeningResource($this->jobOpening)),
+            'evaluation_criteria' => StageEvaluationCriteriaResource::collection($this->whenLoaded('evaluationCriteria')),
+            'applications_count'  => $this->applications()->count(),
+            'assessments_count'   => $this->assessments()->count(),
+            'assessments'         => CandidateAssessmentResource::collection($this->whenLoaded('assessments')),
+            'created_at'          => $this->created_at,
+            'updated_at'          => $this->updated_at,
         ];
     }
 }
diff --git a/app/Domains/Management/Recruitment/Http/Resources/JobCandidatesResource.php b/app/Domains/Management/Recruitment/Http/Resources/JobCandidatesResource.php
index 75d388fd6..dd8272149 100644
--- a/app/Domains/Management/Recruitment/Http/Resources/JobCandidatesResource.php
+++ b/app/Domains/Management/Recruitment/Http/Resources/JobCandidatesResource.php
@@ -12,48 +12,83 @@ class JobCandidatesResource extends JsonResource
     public function __construct($resource, $job = null, $user = null)
     {
         parent::__construct($resource);
-        $this->job = $job;
+        $this->job  = $job;
         $this->user = $user;
     }
 
-    public function toArray($request)
+    public function toArray($request): array
     {
-        $application = $this->application($this->job);
-        $atsService = app(\App\Domains\Management\Recruitment\Services\ATSEngineService::class);
+        $application    = $this->application($this->job);
+        $atsService     = app(\App\Domains\Management\Recruitment\Services\ATSEngineService::class);
         $favoriteService = app(\App\Domains\Management\Recruitment\Services\FavoriteService::class);
 
+        // Available stages for the vacancy (for move-to-stage action)
+        $availableStages = $this->when($this->job, function () {
+            return $this->job->hiringStages()
+                ->where('is_enabled', true)
+                ->orderBy('order')
+                ->get(['id', 'stage_name', 'order'])
+                ->toArray();
+        });
+
+        // Interview managers from job opening JSON field
+        $interviewManagers = $this->when($this->job, function () {
+            $managerIds = $this->job->interview_managers ?? [];
+            if (empty($managerIds)) return [];
+            return \App\Domains\Core\User\Models\User::whereIn('id', $managerIds)
+                ->get(['id', 'name', 'email'])
+                ->toArray();
+        });
+
         return [
-            'id' => $this->id,
-            'full_name' => $this->full_name,
-            'email' => $this->email,
-            'phone' => $this->phone,
-            'city' => $this->city,
-            'years_of_experience' => $this->years_of_experience,
-            'expected_salary' => $this->expected_salary,
-            'available_date' => $this->available_date,
-            'match_score' => $this->match_score,
-            'match_rating' => $this->match_score >= 80 ? 'excellent' : ($this->match_score >= 60 ? 'suitable' : ($this->match_score >= 40 ? 'fair' : 'poor')),
-            'match_color' => $this->match_score !== null ? $atsService->getMatchColor($this->match_score) : null,
-            'match_details_url' => $this->when($this->job, function () {
+            'id'                 => $this->id,
+            'full_name'          => $this->full_name,
+            'email'              => $this->email,
+            'phone'              => $this->phone,
+            'city'               => $this->city,
+            'years_of_experience'=> $this->years_of_experience,
+            'expected_salary'    => $this->expected_salary,
+            'available_date'     => $this->available_date,
+            'match_score'        => $this->match_score,
+            'match_rating'       => $this->match_score >= 80 ? 'excellent' : ($this->match_score >= 60 ? 'suitable' : ($this->match_score >= 40 ? 'fair' : 'poor')),
+            'match_color'        => $this->match_score !== null ? $atsService->getMatchColor($this->match_score) : null,
+            'match_details_url'  => $this->when($this->job, function () {
                 return url("/api/recruitment/job-openings/{$this->job->id}/candidates/{$this->id}/match-details");
             }),
-            'current_stage' => $this->when($application && $application->currentStage, function () use ($application) {
+            'current_stage'      => $this->when($application && $application->currentStage, function () use ($application) {
                 return [
-                    'id' => $application->currentStage->id,
+                    'id'         => $application->currentStage->id,
                     'stage_name' => $application->currentStage->stage_name,
-                    'order' => $application->currentStage->order,
+                    'order'      => $application->currentStage->order,
                 ];
             }),
             'application_status' => $application ? $application->status : null,
-            'application_id' => $application ? $application->id : null,
-            'is_favorite' => $this->when($this->job && $this->user, function () use ($favoriteService) {
+            'application_id'     => $application ? $application->id : null,
+            'is_favorite'        => $this->when($this->job && $this->user, function () use ($favoriteService) {
                 return $favoriteService->isFavorite($this->resource, $this->job, $this->user);
             }),
-            'can_advance' => $application ? $application->canAdvance() : false,
-            'status' => $this->status,
-            'status_label' => $this->status?->label(),
-            'status_color' => $this->status?->color(),
-            'created_at' => $this->created_at,
+            'can_advance'        => $application ? $application->canAdvance() : false,
+            'status'             => $this->status,
+            'status_label'       => $this->status?->label(),
+            'status_color'       => $this->status?->color(),
+            // ── Quick Actions ──────────────────────────────────────────────
+            'quick_actions'      => $this->when($this->job, function () use ($application, $availableStages, $interviewManagers) {
+                $isRejected = $application?->isRejected() ?? false;
+                return [
+                    'can_move_stage'          => !$isRejected && $application !== null,
+                    'can_reject'              => !$isRejected && $application !== null,
+                    'can_preliminary_accept'  => !$isRejected && $application !== null,
+                    'can_assign_interviewer'  => $application !== null,
+                    'can_create_job_offer'    => !$isRejected && $application !== null,
+                    'available_stages'        => $availableStages,
+                    'interview_managers'      => $interviewManagers,
+                    'move_stage_url'          => url("/api/recruitment/job-openings/{$this->job->id}/candidates/{$this->id}/move-stage"),
+                    'assign_interviewer_url'  => url("/api/recruitment/job-openings/{$this->job->id}/candidates/{$this->id}/assign-interviewer"),
+                    'reject_url'              => url("/api/recruitment/job-openings/{$this->job->id}/candidates/{$this->id}/reject"),
+                    'preliminary_accept_url'  => url("/api/recruitment/job-openings/{$this->job->id}/candidates/{$this->id}/preliminary-accept"),
+                ];
+            }),
+            'created_at'         => $this->created_at,
         ];
     }
 }
diff --git a/app/Domains/Management/Recruitment/Http/Resources/StageEvaluationCriteriaResource.php b/app/Domains/Management/Recruitment/Http/Resources/StageEvaluationCriteriaResource.php
new file mode 100644
index 000000000..b70f6824a
--- /dev/null
+++ b/app/Domains/Management/Recruitment/Http/Resources/StageEvaluationCriteriaResource.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace App\Domains\Management\Recruitment\Http\Resources;
+
+use Illuminate\Http\Resources\Json\JsonResource;
+
+class StageEvaluationCriteriaResource extends JsonResource
+{
+    public function toArray($request): array
+    {
+        return [
+            'id'              => $this->id,
+            'hiring_stage_id' => $this->hiring_stage_id,
+            'label'           => $this->label,
+            'input_type'      => $this->input_type,
+            'options'         => $this->options,
+            'is_required'     => $this->is_required,
+            'order'           => $this->order,
+            'created_at'      => $this->created_at,
+            'updated_at'      => $this->updated_at,
+        ];
+    }
+}
diff --git a/app/Domains/Management/Recruitment/Models/CandidateAssessment.php b/app/Domains/Management/Recruitment/Models/CandidateAssessment.php
index da34f88b2..7db816a8c 100644
--- a/app/Domains/Management/Recruitment/Models/CandidateAssessment.php
+++ b/app/Domains/Management/Recruitment/Models/CandidateAssessment.php
@@ -18,8 +18,14 @@ class CandidateAssessment extends Model
         'job_opening_id',
         'assessor_id',
         'assessment_date',
+        'overall_rating',
         'availability_date',
         'expected_salary',
+        'position_expected_salary',
+        'general_notes',
+        'criteria_values',
+        'skill_verifications',
+        'save_mode',
         'scores',
         'comments',
         'status',
@@ -28,15 +34,28 @@ class CandidateAssessment extends Model
     ];
 
     protected $casts = [
-        'scores' => 'array',
-        'assessment_date' => 'datetime',
-        'availability_date' => 'date',
-        'expected_salary' => 'decimal:2',
-        'approved_at' => 'datetime',
-        'created_at' => 'datetime',
-        'updated_at' => 'datetime',
+        'scores'                   => 'array',
+        'criteria_values'          => 'array',
+        'skill_verifications'      => 'array',
+        'assessment_date'          => 'datetime',
+        'availability_date'        => 'date',
+        'expected_salary'          => 'decimal:2',
+        'position_expected_salary' => 'decimal:2',
+        'approved_at'              => 'datetime',
+        'created_at'               => 'datetime',
+        'updated_at'               => 'datetime',
     ];
 
+    public function isDraft(): bool
+    {
+        return $this->save_mode === 'draft';
+    }
+
+    public function isSubmitted(): bool
+    {
+        return $this->save_mode === 'submitted';
+    }
+
     public function candidate()
     {
         return $this->belongsTo(Candidate::class);
diff --git a/app/Domains/Management/Recruitment/Models/HiringStage.php b/app/Domains/Management/Recruitment/Models/HiringStage.php
index 357e6bc70..6b70a53c9 100644
--- a/app/Domains/Management/Recruitment/Models/HiringStage.php
+++ b/app/Domains/Management/Recruitment/Models/HiringStage.php
@@ -41,6 +41,11 @@ public function assessments()
         return $this->hasMany(CandidateAssessment::class);
     }
 
+    public function evaluationCriteria()
+    {
+        return $this->hasMany(StageEvaluationCriteria::class)->orderBy('order');
+    }
+
     public static function createDefaultStages(JobOpening $jobOpening): void
     {
         $stages = [
diff --git a/app/Domains/Management/Recruitment/Models/StageEvaluationCriteria.php b/app/Domains/Management/Recruitment/Models/StageEvaluationCriteria.php
new file mode 100644
index 000000000..586ee4f77
--- /dev/null
+++ b/app/Domains/Management/Recruitment/Models/StageEvaluationCriteria.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace App\Domains\Management\Recruitment\Models;
+
+use Illuminate\Database\Eloquent\Factories\HasFactory;
+use Illuminate\Database\Eloquent\Model;
+
+class StageEvaluationCriteria extends Model
+{
+    use HasFactory;
+
+    protected $table = 'stage_evaluation_criteria';
+
+    protected $fillable = [
+        'hiring_stage_id',
+        'label',
+        'input_type',
+        'options',
+        'is_required',
+        'order',
+    ];
+
+    protected $casts = [
+        'options'     => 'array',
+        'is_required' => 'boolean',
+        'created_at'  => 'datetime',
+        'updated_at'  => 'datetime',
+    ];
+
+    public function hiringStage()
+    {
+        return $this->belongsTo(HiringStage::class);
+    }
+}
diff --git a/app/Domains/Management/Recruitment/Routes/api.php b/app/Domains/Management/Recruitment/Routes/api.php
index dfd032576..0215d9503 100644
--- a/app/Domains/Management/Recruitment/Routes/api.php
+++ b/app/Domains/Management/Recruitment/Routes/api.php
@@ -10,6 +10,7 @@
 use App\Domains\Management\Recruitment\Http\Controllers\PublicApplicationController;
 use App\Domains\Management\Recruitment\Http\Controllers\JobTitleController;
 use App\Domains\Management\Recruitment\Http\Controllers\HiringStageController;
+use App\Domains\Management\Recruitment\Http\Controllers\StageEvaluationCriteriaController;
 use App\Domains\Management\Recruitment\Http\Controllers\DepartmentController;
 use App\Domains\Management\Recruitment\Http\Controllers\BulkActionController;
 use App\Domains\Management\Recruitment\Http\Controllers\InterviewController;
@@ -54,6 +55,22 @@
             ->middleware('permission:recruitment.hiring_stages.toggle_status')->name('recruitment.hiring_stages.toggle_status');
         Route::delete('{hiringStage}', [HiringStageController::class, 'destroy'])
             ->middleware('permission:recruitment.hiring_stages.destroy')->name('recruitment.hiring_stages.destroy');
+
+        // Evaluation Criteria (per-stage, scoped to vacancy via stage)
+        Route::prefix('{hiringStage}/criteria')->group(function () {
+            Route::get('/', [StageEvaluationCriteriaController::class, 'index'])
+                ->name('recruitment.hiring_stages.criteria.index');
+            Route::post('/', [StageEvaluationCriteriaController::class, 'store'])
+                ->name('recruitment.hiring_stages.criteria.store');
+            Route::post('/reorder', [StageEvaluationCriteriaController::class, 'reorder'])
+                ->name('recruitment.hiring_stages.criteria.reorder');
+            Route::get('{criterion}', [StageEvaluationCriteriaController::class, 'show'])
+                ->name('recruitment.hiring_stages.criteria.show');
+            Route::patch('{criterion}', [StageEvaluationCriteriaController::class, 'update'])
+                ->name('recruitment.hiring_stages.criteria.update');
+            Route::delete('{criterion}', [StageEvaluationCriteriaController::class, 'destroy'])
+                ->name('recruitment.hiring_stages.criteria.destroy');
+        });
     });
 
     // Job Titles
@@ -140,17 +157,23 @@
         // Job Questions
         Route::prefix('{job}/questions')->group(function () {
             Route::get('/', [JobQuestionController::class, 'index']);
-                // ->middleware('permission:recruitment.job_openings.view_candidates')->name('recruitment.job_openings.questions.index');
             Route::post('/', [JobQuestionController::class, 'store']);
-                // ->middleware('permission:recruitment.job_openings.update')->name('recruitment.job_openings.questions.store');
             Route::post('/reorder', [JobQuestionController::class, 'reorder']);
-                // ->middleware('permission:recruitment.job_openings.update')->name('recruitment.job_openings.questions.reorder');
             Route::get('{question}', [JobQuestionController::class, 'show']);
-                // ->middleware('permission:recruitment.job_openings.view_candidates')->name('recruitment.job_openings.questions.show');
             Route::patch('{question}', [JobQuestionController::class, 'update']);
-                // ->middleware('permission:recruitment.job_openings.update')->name('recruitment.job_openings.questions.update');
             Route::delete('{question}', [JobQuestionController::class, 'destroy']);
-                // ->middleware('permission:recruitment.job_openings.update')->name('recruitment.job_openings.questions.destroy');
+        });
+
+        // Candidate Quick Actions (vacancy-scoped)
+        Route::prefix('{job}/candidates/{candidate}')->group(function () {
+            Route::post('move-stage', [JobOpeningController::class, 'moveCandidateStage'])
+                ->name('recruitment.job_openings.candidates.move_stage');
+            Route::post('assign-interviewer', [JobOpeningController::class, 'assignInterviewer'])
+                ->name('recruitment.job_openings.candidates.assign_interviewer');
+            Route::post('reject', [JobOpeningController::class, 'rejectCandidate'])
+                ->name('recruitment.job_openings.candidates.reject');
+            Route::post('preliminary-accept', [JobOpeningController::class, 'preliminaryAccept'])
+                ->name('recruitment.job_openings.candidates.preliminary_accept');
         });
     });
 
@@ -216,6 +239,9 @@
 
     // Candidate Assessments
     Route::prefix('assessments')->group(function () {
+        // Form schema endpoint — returns dynamic form for a candidate at their current stage
+        Route::get('form-schema', [CandidateAssessmentController::class, 'getFormSchema'])
+            ->name('recruitment.assessments.form_schema');
         Route::get('/', [CandidateAssessmentController::class, 'index'])
             ->middleware('permission:recruitment.assessments.index')->name('recruitment.assessments.index');
         Route::post('/', [CandidateAssessmentController::class, 'store'])
@@ -224,6 +250,8 @@
             ->middleware('permission:recruitment.assessments.show')->name('recruitment.assessments.show');
         Route::patch('{assessment}', [CandidateAssessmentController::class, 'update'])
             ->middleware('permission:recruitment.assessments.update')->name('recruitment.assessments.update');
+        Route::post('{assessment}/submit', [CandidateAssessmentController::class, 'submitEvaluation'])
+            ->name('recruitment.assessments.submit');
         Route::delete('{assessment}', [CandidateAssessmentController::class, 'destroy'])
             ->middleware('permission:recruitment.assessments.destroy')->name('recruitment.assessments.destroy');
         Route::post('{assessment}/approve', [CandidateAssessmentController::class, 'approve'])
diff --git a/app/Domains/Management/Recruitment/Services/ATSEngineService.php b/app/Domains/Management/Recruitment/Services/ATSEngineService.php
index d58880b4b..ccdd5caa4 100644
--- a/app/Domains/Management/Recruitment/Services/ATSEngineService.php
+++ b/app/Domains/Management/Recruitment/Services/ATSEngineService.php
@@ -156,4 +156,59 @@ private function extractSkills(Candidate $candidate): array
         // In a real scenario, extract from CV or profile
         return [];
     }
+
+    /**
+     * Update the candidate's ATS match score after an evaluation is submitted.
+     * Incorporates skill verifications and numeric criteria scores.
+     */
+    public function incorporateEvaluationData(\App\Domains\Management\Recruitment\Models\CandidateAssessment $assessment): void
+    {
+        $candidate = $assessment->candidate;
+        $job       = $assessment->jobOpening;
+
+        if (!$candidate || !$job) {
+            return;
+        }
+
+        // Update candidate skills from verified skill verifications
+        $verifications = $assessment->skill_verifications ?? [];
+        $verifiedSkills = collect($verifications)
+            ->filter(fn($v) => $v === true || $v === 1 || $v === 'true')
+            ->keys()
+            ->toArray();
+
+        // Update overall rating influence on match_score
+        $overallRating   = $assessment->overall_rating ?? 0; // 1..10
+        $ratingScore     = $overallRating * 10; // convert to 0-100
+
+        // Compute criteria bonus (numeric criteria average)
+        $criteriaValues  = $assessment->criteria_values ?? [];
+        $numericValues   = collect($criteriaValues)->filter(fn($v) => is_numeric($v));
+        $criteriaAverage = $numericValues->isNotEmpty() ? $numericValues->avg() : null;
+
+        // Build updated score
+        $baseScore  = $this->calculateMatchScore($candidate, $job);
+        $bonusScore = 0;
+
+        if ($criteriaAverage !== null) {
+            // Criteria average is out of 10 — convert and blend 20%
+            $bonusScore += ($criteriaAverage / 10 * 100) * 0.2;
+        }
+
+        // Overall rating blends 20%
+        $bonusScore += $ratingScore * 0.2;
+
+        // Skill verifications: verified skills count toward skills match (40%)
+        if (!empty($verifiedSkills) && !empty($job->required_skills)) {
+            $requiredSkills      = $job->required_skills;
+            $verifiedCount       = count(array_intersect($verifiedSkills, $requiredSkills));
+            $skillBonus          = ($verifiedCount / count($requiredSkills)) * 100 * 0.4;
+            $newScore            = (int) min(100, ($baseScore['percentage'] * 0.6) + $bonusScore + $skillBonus);
+        } else {
+            $newScore = (int) min(100, ($baseScore['percentage'] * 0.6) + $bonusScore);
+        }
+
+        $candidate->update(['match_score' => $newScore]);
+    }
 }
+
diff --git a/app/Domains/Management/Recruitment/Services/EvaluationFormService.php b/app/Domains/Management/Recruitment/Services/EvaluationFormService.php
new file mode 100644
index 000000000..b5660a3b8
--- /dev/null
+++ b/app/Domains/Management/Recruitment/Services/EvaluationFormService.php
@@ -0,0 +1,180 @@
+<?php
+
+namespace App\Domains\Management\Recruitment\Services;
+
+use App\Domains\Core\User\Models\User;
+use App\Domains\Management\Recruitment\Models\Candidate;
+use App\Domains\Management\Recruitment\Models\CandidateAssessment;
+use App\Domains\Management\Recruitment\Models\HiringStage;
+use App\Domains\Management\Recruitment\Models\JobOpening;
+use Illuminate\Validation\ValidationException;
+
+class EvaluationFormService
+{
+    public function __construct(
+        protected ATSEngineService $atsEngine
+    ) {}
+
+    /**
+     * Build the dynamic evaluation form schema for a candidate at their current stage.
+     * Returns standard fields + custom criteria for that stage.
+     */
+    public function buildFormForCandidate(Candidate $candidate, JobOpening $job): array
+    {
+        $application = $candidate->application($job);
+
+        if (!$application) {
+            throw new \RuntimeException('المرشح لا يملك طلباً لهذه الوظيفة');
+        }
+
+        $stage = $application->currentStage;
+        if (!$stage) {
+            throw new \RuntimeException('المرشح غير موجود في أي مرحلة حالياً');
+        }
+
+        $criteria = $stage->evaluationCriteria()->get()->map(fn($c) => [
+            'criterion_id' => $c->id,
+            'label'        => $c->label,
+            'input_type'   => $c->input_type,
+            'options'      => $c->options,
+            'is_required'  => $c->is_required,
+        ])->values()->toArray();
+
+        $requiredSkills = $job->required_skills ?? [];
+
+        return [
+            'stage' => [
+                'id'         => $stage->id,
+                'stage_name' => $stage->stage_name,
+                'order'      => $stage->order,
+            ],
+            'standard_fields' => [
+                ['field' => 'overall_rating',           'label' => 'التقييم العام (1-10)',         'input_type' => 'numeric',    'is_required' => true],
+                ['field' => 'availability_date',        'label' => 'تاريخ توفر المرشح',            'input_type' => 'date',       'is_required' => true],
+                ['field' => 'expected_salary',          'label' => 'الراتب المتوقع للمرشح',        'input_type' => 'numeric',    'is_required' => true],
+                ['field' => 'position_expected_salary', 'label' => 'الراتب المتوقع للوظيفة',       'input_type' => 'numeric',    'is_required' => false],
+                ['field' => 'general_notes',            'label' => 'ملاحظات عامة',                 'input_type' => 'free_text',  'is_required' => false],
+            ],
+            'custom_criteria'    => $criteria,
+            'skill_verifications' => collect($requiredSkills)->map(fn($skill) => [
+                'skill'       => $skill,
+                'is_required' => true,
+            ])->values()->toArray(),
+        ];
+    }
+
+    /**
+     * Save an evaluation in draft or submitted mode.
+     * Only the responsible user for the stage can submit.
+     */
+    public function saveEvaluation(
+        CandidateAssessment $assessment,
+        array $data,
+        string $mode,
+        ?User $user = null
+    ): CandidateAssessment {
+        if ($mode === 'submitted') {
+            // Validate required standard fields
+            $this->validateSubmitRequirements($data, $assessment);
+
+            // Only responsible_user_id of the application can submit
+            if ($user) {
+                $application = CandidateAssessment::with('hiringStage')->find($assessment->id);
+                $candidate   = \App\Domains\Management\Recruitment\Models\Candidate::find($assessment->candidate_id);
+                $job         = \App\Domains\Management\Recruitment\Models\JobOpening::find($assessment->job_opening_id);
+                $app         = $candidate ? $candidate->application($job) : null;
+
+                if ($app && $app->responsible_user_id && $app->responsible_user_id !== $user->id) {
+                    throw new \RuntimeException('فقط المسؤول المعين على هذه المرحلة يمكنه تقديم التقييم');
+                }
+            }
+
+            $data['status']    = 'submitted';
+            $data['save_mode'] = 'submitted';
+
+            if (!isset($data['approved_by'])) {
+                $data['approved_by'] = $user?->id;
+                $data['approved_at'] = now();
+            }
+        } else {
+            $data['save_mode'] = 'draft';
+            $data['status']    = 'draft';
+        }
+
+        $assessment->update($data);
+
+        // Feed into ATS engine on submission
+        if ($mode === 'submitted') {
+            $this->atsEngine->incorporateEvaluationData($assessment);
+        }
+
+        return $assessment->fresh();
+    }
+
+    /**
+     * Create a new assessment record (draft) for a candidate at their current stage.
+     */
+    public function createEvaluation(Candidate $candidate, JobOpening $job, array $data, ?User $assessor = null): CandidateAssessment
+    {
+        $application = $candidate->application($job);
+        if (!$application) {
+            throw new \RuntimeException('المرشح لا يملك طلباً لهذه الوظيفة');
+        }
+
+        $stage = $application->currentStage;
+        if (!$stage) {
+            throw new \RuntimeException('المرشح غير محدد في مرحلة');
+        }
+
+        // Enforce: only the form for this stage may be used
+        if (isset($data['hiring_stage_id']) && (int)$data['hiring_stage_id'] !== $stage->id) {
+            throw new \RuntimeException('لا يمكن استخدام نموذج مرحلة أخرى');
+        }
+
+        $mode = $data['save_mode'] ?? 'draft';
+        unset($data['save_mode']);
+
+        $assessment = CandidateAssessment::create(array_merge($data, [
+            'candidate_id'  => $candidate->id,
+            'job_opening_id' => $job->id,
+            'hiring_stage_id' => $stage->id,
+            'assessor_id'   => $assessor?->id ?? ($data['assessor_id'] ?? null),
+            'assessment_date' => now(),
+            'save_mode'     => 'draft',
+            'status'        => 'draft',
+        ]));
+
+        return $this->saveEvaluation($assessment, [], $mode, $assessor);
+    }
+
+    private function validateSubmitRequirements(array $data, CandidateAssessment $assessment): void
+    {
+        $merged = array_merge($assessment->toArray(), $data);
+        $errors = [];
+
+        if (empty($merged['overall_rating'])) {
+            $errors['overall_rating'] = 'التقييم العام مطلوب عند التقديم';
+        }
+        if (empty($merged['availability_date'])) {
+            $errors['availability_date'] = 'تاريخ التوفر مطلوب عند التقديم';
+        }
+        if (empty($merged['expected_salary'])) {
+            $errors['expected_salary'] = 'الراتب المتوقع مطلوب عند التقديم';
+        }
+
+        // Validate required custom criteria
+        $stage = \App\Domains\Management\Recruitment\Models\HiringStage::with('evaluationCriteria')->find($assessment->hiring_stage_id);
+        if ($stage) {
+            $criteriaValues = $merged['criteria_values'] ?? [];
+            foreach ($stage->evaluationCriteria as $criterion) {
+                if ($criterion->is_required && empty($criteriaValues[$criterion->id])) {
+                    $errors["criteria_values.{$criterion->id}"] = "المعيار '{$criterion->label}' مطلوب";
+                }
+            }
+        }
+
+        if (!empty($errors)) {
+            throw ValidationException::withMessages($errors);
+        }
+    }
+}
diff --git a/app/Domains/Management/Recruitment/Services/StageEvaluationCriteriaService.php b/app/Domains/Management/Recruitment/Services/StageEvaluationCriteriaService.php
new file mode 100644
index 000000000..d935bf0c3
--- /dev/null
+++ b/app/Domains/Management/Recruitment/Services/StageEvaluationCriteriaService.php
@@ -0,0 +1,47 @@
+<?php
+
+namespace App\Domains\Management\Recruitment\Services;
+
+use App\Domains\Management\Recruitment\Models\HiringStage;
+use App\Domains\Management\Recruitment\Models\StageEvaluationCriteria;
+use Illuminate\Database\Eloquent\Collection;
+
+class StageEvaluationCriteriaService
+{
+    public function getCriteriaForStage(HiringStage $stage): Collection
+    {
+        return $stage->evaluationCriteria()->get();
+    }
+
+    public function addCriterion(HiringStage $stage, array $data): StageEvaluationCriteria
+    {
+        // Auto-assign order if not provided
+        if (!isset($data['order'])) {
+            $maxOrder = $stage->evaluationCriteria()->max('order') ?? 0;
+            $data['order'] = $maxOrder + 1;
+        }
+
+        return $stage->evaluationCriteria()->create($data);
+    }
+
+    public function updateCriterion(StageEvaluationCriteria $criterion, array $data): StageEvaluationCriteria
+    {
+        $criterion->update($data);
+        return $criterion->fresh();
+    }
+
+    public function deleteCriterion(StageEvaluationCriteria $criterion): bool
+    {
+        return $criterion->delete();
+    }
+
+    public function reorderCriteria(HiringStage $stage, array $orderedIds): bool
+    {
+        foreach ($orderedIds as $index => $criterionId) {
+            StageEvaluationCriteria::where('id', $criterionId)
+                ->where('hiring_stage_id', $stage->id)
+                ->update(['order' => $index + 1]);
+        }
+        return true;
+    }
+}
diff --git a/database/migrations/2026_04_04_153700_create_stage_evaluation_criteria_table.php b/database/migrations/2026_04_04_153700_create_stage_evaluation_criteria_table.php
new file mode 100644
index 000000000..bfc38c4ea
--- /dev/null
+++ b/database/migrations/2026_04_04_153700_create_stage_evaluation_criteria_table.php
@@ -0,0 +1,27 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+    public function up(): void
+    {
+        Schema::create('stage_evaluation_criteria', function (Blueprint $table) {
+            $table->id();
+            $table->foreignId('hiring_stage_id')->constrained()->onDelete('cascade');
+            $table->string('label', 255);
+            $table->enum('input_type', ['numeric', 'yes_no', 'dropdown', 'free_text'])->default('free_text');
+            $table->json('options')->nullable(); // For dropdown: array of {value, label}
+            $table->boolean('is_required')->default(true);
+            $table->integer('order')->default(0);
+            $table->timestamps();
+        });
+    }
+
+    public function down(): void
+    {
+        Schema::dropIfExists('stage_evaluation_criteria');
+    }
+};
diff --git a/database/migrations/2026_04_04_153701_alter_candidate_assessments_add_evaluation_fields.php b/database/migrations/2026_04_04_153701_alter_candidate_assessments_add_evaluation_fields.php
new file mode 100644
index 000000000..22187ba5a
--- /dev/null
+++ b/database/migrations/2026_04_04_153701_alter_candidate_assessments_add_evaluation_fields.php
@@ -0,0 +1,33 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+    public function up(): void
+    {
+        Schema::table('candidate_assessments', function (Blueprint $table) {
+            // These columns did not exist yet
+            $table->decimal('position_expected_salary', 12, 2)->nullable()->after('expected_salary');
+            $table->text('general_notes')->nullable()->after('position_expected_salary');
+            $table->json('criteria_values')->nullable()->after('general_notes');
+            $table->json('skill_verifications')->nullable()->after('criteria_values');
+            $table->enum('save_mode', ['draft', 'submitted'])->default('draft')->after('skill_verifications');
+        });
+    }
+
+    public function down(): void
+    {
+        Schema::table('candidate_assessments', function (Blueprint $table) {
+            $table->dropColumn([
+                'position_expected_salary',
+                'general_notes',
+                'criteria_values',
+                'skill_verifications',
+                'save_mode',
+            ]);
+        });
+    }
+};
diff --git a/public/api-docs.yaml b/public/api-docs.yaml
index fc2065688..1e02f10ca 100644
--- a/public/api-docs.yaml
+++ b/public/api-docs.yaml
@@ -2989,6 +2989,11 @@ components:
               start_time: { type: string }
               end_time: { type: string }
               max_break_minutes: { type: integer }
+        users:
+          type: array
+          items:
+            type: integer
+          description: List of user IDs to associate with this shift (Optional)
 
     RewardPenaltyInput:
       type: object
