feat: implementation du role Gestionnaire RH et refonte de la gestion des offres
This commit is contained in:
@@ -93,11 +93,20 @@ class CandidateController extends Controller
|
||||
public function store(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'birth_name' => 'nullable|string|max:255',
|
||||
'usage_name' => 'nullable|string|max:255',
|
||||
'first_name' => 'nullable|string|max:255',
|
||||
'address' => 'nullable|string|max:255',
|
||||
'zip_code' => 'nullable|string|max:10',
|
||||
'email' => 'required|string|email|max:255|unique:users',
|
||||
'phone' => 'nullable|string|max:20',
|
||||
'linkedin_url' => 'nullable|url|max:255',
|
||||
'city' => 'nullable|string|max:255',
|
||||
'birth_date' => 'nullable|date',
|
||||
'birth_place' => 'nullable|string|max:255',
|
||||
'nationality' => 'nullable|string|max:255',
|
||||
'current_situation' => 'nullable|string|max:255',
|
||||
'education_level' => 'nullable|string|max:255',
|
||||
'has_driving_license' => 'nullable|boolean',
|
||||
'cv' => 'nullable|mimes:pdf|max:5120',
|
||||
'cover_letter' => 'nullable|mimes:pdf|max:5120',
|
||||
'tenant_id' => 'nullable|exists:tenants,id',
|
||||
@@ -106,20 +115,34 @@ class CandidateController extends Controller
|
||||
|
||||
$password = Str::random(10);
|
||||
|
||||
$name = $request->first_name
|
||||
? ($request->first_name . ' ' . ($request->usage_name ?? ''))
|
||||
: $request->name;
|
||||
|
||||
$user = User::create([
|
||||
'name' => $request->name,
|
||||
'name' => $name,
|
||||
'email' => $request->email,
|
||||
'password' => Hash::make(Str::random(12)),
|
||||
'password' => Hash::make($password),
|
||||
'role' => 'candidate',
|
||||
'tenant_id' => auth()->user()->isSuperAdmin() ? $request->tenant_id : auth()->user()->tenant_id,
|
||||
'tenant_id' => (auth()->user()->isSuperAdmin() || auth()->user()->isGestionnaireRH()) ? $request->tenant_id : auth()->user()->tenant_id,
|
||||
]);
|
||||
|
||||
$candidate = $user->candidate()->create([
|
||||
'birth_name' => $request->birth_name,
|
||||
'usage_name' => $request->usage_name,
|
||||
'first_name' => $request->first_name,
|
||||
'address' => $request->address,
|
||||
'zip_code' => $request->zip_code,
|
||||
'phone' => $request->phone,
|
||||
'linkedin_url' => $request->linkedin_url,
|
||||
'city' => $request->city,
|
||||
'birth_date' => $request->birth_date,
|
||||
'birth_place' => $request->birth_place,
|
||||
'nationality' => $request->nationality,
|
||||
'current_situation' => $request->current_situation,
|
||||
'education_level' => $request->education_level,
|
||||
'has_driving_license' => $request->has_driving_license ?? false,
|
||||
'status' => 'en_attente',
|
||||
'tenant_id' => auth()->user()->isSuperAdmin() ? $request->tenant_id : auth()->user()->tenant_id,
|
||||
'tenant_id' => (auth()->user()->isSuperAdmin() || auth()->user()->isGestionnaireRH()) ? $request->tenant_id : auth()->user()->tenant_id,
|
||||
'job_position_id' => $request->job_position_id,
|
||||
]);
|
||||
|
||||
@@ -165,7 +188,7 @@ class CandidateController extends Controller
|
||||
]
|
||||
];
|
||||
|
||||
if (auth()->user()->isSuperAdmin()) {
|
||||
if (auth()->user()->isSuperAdmin() || auth()->user()->isGestionnaireRH()) {
|
||||
$data['tenants'] = \App\Models\Tenant::orderBy('name')->get();
|
||||
}
|
||||
|
||||
@@ -190,22 +213,42 @@ class CandidateController extends Controller
|
||||
public function update(Request $request, Candidate $candidate)
|
||||
{
|
||||
$request->validate([
|
||||
'birth_name' => 'nullable|string|max:255',
|
||||
'usage_name' => 'nullable|string|max:255',
|
||||
'first_name' => 'nullable|string|max:255',
|
||||
'address' => 'nullable|string|max:255',
|
||||
'zip_code' => 'nullable|string|max:10',
|
||||
'phone' => 'nullable|string|max:255',
|
||||
'city' => 'nullable|string|max:255',
|
||||
'birth_date' => 'nullable|date',
|
||||
'birth_place' => 'nullable|string|max:255',
|
||||
'nationality' => 'nullable|string|max:255',
|
||||
'current_situation' => 'nullable|string|max:255',
|
||||
'education_level' => 'nullable|string|max:255',
|
||||
'has_driving_license' => 'nullable|boolean',
|
||||
'email' => 'nullable|string|email|max:255|unique:users,email,' . $candidate->user_id,
|
||||
'linkedin_url' => 'nullable|url|max:255',
|
||||
'cv' => 'nullable|file|mimes:pdf|max:5120',
|
||||
'cover_letter' => 'nullable|file|mimes:pdf|max:5120',
|
||||
'name' => 'nullable|string|max:255',
|
||||
'email' => 'nullable|string|email|max:255|unique:users,email,' . $candidate->user_id,
|
||||
'phone' => 'nullable|string|max:255',
|
||||
'linkedin_url' => 'nullable|url|max:255',
|
||||
'city' => 'nullable|string|max:255',
|
||||
]);
|
||||
|
||||
// Update User info if name or email present
|
||||
if ($request->has('name') || $request->has('email')) {
|
||||
$candidate->user->update($request->only(['name', 'email']));
|
||||
if ($request->has('email')) {
|
||||
$candidate->user->update(['email' => $request->email]);
|
||||
}
|
||||
|
||||
if ($request->has('first_name') || $request->has('usage_name')) {
|
||||
$firstName = $request->first_name ?? $candidate->first_name;
|
||||
$usageName = $request->usage_name ?? $candidate->usage_name;
|
||||
$candidate->user->update(['name' => $firstName . ' ' . $usageName]);
|
||||
}
|
||||
|
||||
// Update Candidate info
|
||||
$candidate->update($request->only(['phone', 'linkedin_url', 'city']));
|
||||
$candidate->update($request->only([
|
||||
'birth_name', 'usage_name', 'first_name', 'address', 'zip_code',
|
||||
'phone', 'linkedin_url', 'city', 'birth_date', 'birth_place',
|
||||
'nationality', 'current_situation', 'education_level', 'has_driving_license'
|
||||
]));
|
||||
|
||||
if ($request->hasFile('cv')) {
|
||||
$this->replaceDocument($candidate, $request->file('cv'), 'cv');
|
||||
@@ -263,7 +306,7 @@ class CandidateController extends Controller
|
||||
|
||||
public function updateTenant(Request $request, Candidate $candidate)
|
||||
{
|
||||
if (!auth()->user()->isSuperAdmin()) {
|
||||
if (!auth()->user()->isSuperAdmin() && !auth()->user()->isGestionnaireRH()) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ class JobPositionController extends Controller
|
||||
$this->authorizeAdmin();
|
||||
|
||||
return Inertia::render('Admin/JobPositions/Index', [
|
||||
'jobPositions' => JobPosition::with(['tenant', 'quizzes'])->get(),
|
||||
'jobPositions' => JobPosition::with(['tenant', 'quizzes'])->withCount('candidates')->get(),
|
||||
'tenants' => \App\Models\Tenant::orderBy('name')->get(),
|
||||
'quizzes' => \App\Models\Quiz::all()
|
||||
]);
|
||||
@@ -33,6 +33,7 @@ class JobPositionController extends Controller
|
||||
'tenant_id' => 'nullable|exists:tenants,id',
|
||||
'quiz_ids' => 'nullable|array',
|
||||
'quiz_ids.*' => 'exists:quizzes,id',
|
||||
'expires_at' => 'nullable|date',
|
||||
]);
|
||||
|
||||
$jobPosition = JobPosition::create([
|
||||
@@ -42,7 +43,8 @@ class JobPositionController extends Controller
|
||||
'ai_prompt' => $request->ai_prompt,
|
||||
'ai_bypass_base_prompt' => $request->boolean('ai_bypass_base_prompt'),
|
||||
'fpt_metadata' => $request->fpt_metadata,
|
||||
'tenant_id' => auth()->user()->isSuperAdmin() ? $request->tenant_id : auth()->user()->tenant_id,
|
||||
'tenant_id' => (auth()->user()->isSuperAdmin() || auth()->user()->isGestionnaireRH()) ? $request->tenant_id : auth()->user()->tenant_id,
|
||||
'expires_at' => $request->expires_at,
|
||||
]);
|
||||
|
||||
$jobPosition->quizzes()->sync($request->input('quiz_ids', []));
|
||||
@@ -64,6 +66,7 @@ class JobPositionController extends Controller
|
||||
'tenant_id' => 'nullable|exists:tenants,id',
|
||||
'quiz_ids' => 'nullable|array',
|
||||
'quiz_ids.*' => 'exists:quizzes,id',
|
||||
'expires_at' => 'nullable|date',
|
||||
]);
|
||||
|
||||
$jobPosition->update([
|
||||
@@ -73,7 +76,8 @@ class JobPositionController extends Controller
|
||||
'ai_prompt' => $request->ai_prompt,
|
||||
'ai_bypass_base_prompt' => $request->boolean('ai_bypass_base_prompt'),
|
||||
'fpt_metadata' => $request->fpt_metadata,
|
||||
'tenant_id' => auth()->user()->isSuperAdmin() ? $request->tenant_id : auth()->user()->tenant_id,
|
||||
'tenant_id' => (auth()->user()->isSuperAdmin() || auth()->user()->isGestionnaireRH()) ? $request->tenant_id : auth()->user()->tenant_id,
|
||||
'expires_at' => $request->expires_at,
|
||||
]);
|
||||
|
||||
$jobPosition->quizzes()->sync($request->input('quiz_ids', []));
|
||||
|
||||
@@ -16,10 +16,17 @@ class PublicJobApplicationController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$jobs = JobPosition::with('tenant')->orderBy('created_at', 'desc')->get()->map(function($job) {
|
||||
$job->description = strip_tags(\Illuminate\Support\Str::markdown($job->description));
|
||||
return $job;
|
||||
});
|
||||
$jobs = JobPosition::with('tenant')
|
||||
->where(function($q) {
|
||||
$q->whereNull('expires_at')
|
||||
->orWhere('expires_at', '>=', now());
|
||||
})
|
||||
->orderBy('created_at', 'desc')
|
||||
->get()
|
||||
->map(function($job) {
|
||||
$job->description = strip_tags(\Illuminate\Support\Str::markdown($job->description));
|
||||
return $job;
|
||||
});
|
||||
|
||||
return Inertia::render('Public/Jobs/Index', [
|
||||
'jobs' => $jobs
|
||||
@@ -28,6 +35,10 @@ class PublicJobApplicationController extends Controller
|
||||
|
||||
public function show(JobPosition $jobPosition)
|
||||
{
|
||||
if ($jobPosition->expires_at && $jobPosition->expires_at->isPast()) {
|
||||
abort(404);
|
||||
}
|
||||
|
||||
$data = $jobPosition->toArray();
|
||||
$data['description_html'] = \Illuminate\Support\Str::markdown($jobPosition->description);
|
||||
|
||||
@@ -38,12 +49,26 @@ class PublicJobApplicationController extends Controller
|
||||
|
||||
public function store(Request $request, JobPosition $jobPosition)
|
||||
{
|
||||
if ($jobPosition->expires_at && $jobPosition->expires_at->isPast()) {
|
||||
return back()->withErrors(['error' => 'Cette offre a expiré.']);
|
||||
}
|
||||
|
||||
$request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'email' => 'required|string|email|max:255|unique:users',
|
||||
'phone' => 'nullable|string|max:20',
|
||||
'linkedin_url' => 'nullable|url|max:255',
|
||||
'city' => 'nullable|string|max:255',
|
||||
'birth_name' => 'required|string|max:255',
|
||||
'usage_name' => 'required|string|max:255',
|
||||
'first_name' => 'required|string|max:255',
|
||||
'address' => 'required|string|max:255',
|
||||
'zip_code' => 'required|string|max:10',
|
||||
'city' => 'required|string|max:255',
|
||||
'phone' => 'required|string|max:20',
|
||||
'email' => 'required|string|email|max:255|unique:users|confirmed',
|
||||
'birth_date' => 'required|date',
|
||||
'birth_place' => 'required|string|max:255',
|
||||
'nationality' => 'required|string|max:255',
|
||||
'current_situation' => 'required|string|max:255',
|
||||
'education_level' => 'required|string|max:255',
|
||||
'has_driving_license' => 'required|boolean',
|
||||
'privacy_policy' => 'accepted',
|
||||
'cv' => 'nullable|mimes:pdf|max:5120',
|
||||
'cover_letter' => 'nullable|mimes:pdf|max:5120',
|
||||
]);
|
||||
@@ -51,7 +76,7 @@ class PublicJobApplicationController extends Controller
|
||||
$password = Str::random(10);
|
||||
|
||||
$user = User::create([
|
||||
'name' => $request->name,
|
||||
'name' => $request->first_name . ' ' . $request->usage_name,
|
||||
'email' => $request->email,
|
||||
'password' => Hash::make($password),
|
||||
'role' => 'candidate',
|
||||
@@ -59,9 +84,19 @@ class PublicJobApplicationController extends Controller
|
||||
]);
|
||||
|
||||
$candidate = $user->candidate()->create([
|
||||
'phone' => $request->phone,
|
||||
'linkedin_url' => $request->linkedin_url,
|
||||
'birth_name' => $request->birth_name,
|
||||
'usage_name' => $request->usage_name,
|
||||
'first_name' => $request->first_name,
|
||||
'address' => $request->address,
|
||||
'zip_code' => $request->zip_code,
|
||||
'city' => $request->city,
|
||||
'phone' => $request->phone,
|
||||
'birth_date' => $request->birth_date,
|
||||
'birth_place' => $request->birth_place,
|
||||
'nationality' => $request->nationality,
|
||||
'current_situation' => $request->current_situation,
|
||||
'education_level' => $request->education_level,
|
||||
'has_driving_license' => $request->has_driving_license,
|
||||
'status' => 'en_attente',
|
||||
'tenant_id' => $jobPosition->tenant_id,
|
||||
'job_position_id' => $jobPosition->id,
|
||||
@@ -74,7 +109,7 @@ class PublicJobApplicationController extends Controller
|
||||
$this->storeDocument($candidate, $request->file('cover_letter'), 'cover_letter');
|
||||
}
|
||||
|
||||
// Auto-login the candidate so they can take the quiz immediately if they want
|
||||
// Auto-login
|
||||
Auth::login($user);
|
||||
|
||||
return redirect()->route('dashboard')->with('success', 'Votre candidature a bien été enregistrée. Voici votre mot de passe temporaire pour vous reconnecter : ' . $password);
|
||||
|
||||
@@ -10,7 +10,7 @@ class TenantController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
if (!auth()->user()->isSuperAdmin()) {
|
||||
if (!auth()->user()->isSuperAdmin() && !auth()->user()->isGestionnaireRH()) {
|
||||
abort(403, 'Unauthorized action.');
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ class TenantController extends Controller
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
if (!auth()->user()->isSuperAdmin()) {
|
||||
if (!auth()->user()->isSuperAdmin() && !auth()->user()->isGestionnaireRH()) {
|
||||
abort(403, 'Unauthorized action.');
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ class TenantController extends Controller
|
||||
|
||||
public function update(Request $request, Tenant $tenant)
|
||||
{
|
||||
if (!auth()->user()->isSuperAdmin()) {
|
||||
if (!auth()->user()->isSuperAdmin() && !auth()->user()->isGestionnaireRH()) {
|
||||
abort(403, 'Unauthorized action.');
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ class TenantController extends Controller
|
||||
|
||||
public function destroy(Tenant $tenant)
|
||||
{
|
||||
if (!auth()->user()->isSuperAdmin()) {
|
||||
if (!auth()->user()->isSuperAdmin() && !auth()->user()->isGestionnaireRH()) {
|
||||
abort(403, 'Unauthorized action.');
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ class UserController extends Controller
|
||||
abort(403, 'Unauthorized action.');
|
||||
}
|
||||
|
||||
$users = User::whereIn('role', ['admin', 'super_admin'])
|
||||
$users = User::whereIn('role', ['admin', 'super_admin', 'gestionnaire_rh'])
|
||||
->with('tenant')
|
||||
->orderBy('name')
|
||||
->get();
|
||||
@@ -40,7 +40,7 @@ class UserController extends Controller
|
||||
$request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'email' => 'required|string|email|max:255|unique:users',
|
||||
'role' => ['required', Rule::in(['admin', 'super_admin'])],
|
||||
'role' => ['required', Rule::in(['admin', 'super_admin', 'gestionnaire_rh'])],
|
||||
'tenant_id' => 'nullable|exists:tenants,id',
|
||||
]);
|
||||
|
||||
@@ -51,7 +51,7 @@ class UserController extends Controller
|
||||
'email' => $request->email,
|
||||
'password' => Hash::make($password),
|
||||
'role' => $request->role,
|
||||
'tenant_id' => $request->role === 'super_admin' ? null : $request->tenant_id,
|
||||
'tenant_id' => ($request->role === 'super_admin' || $request->role === 'gestionnaire_rh') ? null : $request->tenant_id,
|
||||
]);
|
||||
|
||||
return back()->with('success', 'Administrateur créé avec succès. Mot de passe généré : ' . $password);
|
||||
@@ -66,7 +66,7 @@ class UserController extends Controller
|
||||
$request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'email' => 'required|string|email|max:255|unique:users,email,' . $user->id,
|
||||
'role' => ['required', Rule::in(['admin', 'super_admin'])],
|
||||
'role' => ['required', Rule::in(['admin', 'super_admin', 'gestionnaire_rh'])],
|
||||
'tenant_id' => 'nullable|exists:tenants,id',
|
||||
]);
|
||||
|
||||
@@ -74,7 +74,7 @@ class UserController extends Controller
|
||||
'name' => $request->name,
|
||||
'email' => $request->email,
|
||||
'role' => $request->role,
|
||||
'tenant_id' => $request->role === 'super_admin' ? null : $request->tenant_id,
|
||||
'tenant_id' => ($request->role === 'super_admin' || $request->role === 'gestionnaire_rh') ? null : $request->tenant_id,
|
||||
]);
|
||||
|
||||
return back()->with('success', 'Administrateur mis à jour.');
|
||||
|
||||
24
app/Http/Middleware/RestrictHrManager.php
Normal file
24
app/Http/Middleware/RestrictHrManager.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class RestrictHrManager
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param Closure(Request): (Response) $next
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
if (auth()->check() && auth()->user()->isGestionnaireRH()) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
|
||||
use App\Traits\BelongsToTenant;
|
||||
|
||||
#[Fillable(['user_id', 'job_position_id', 'phone', 'linkedin_url', 'city', 'status', 'is_selected', 'sort_order', 'notes', 'cv_score', 'motivation_score', 'interview_score', 'interview_details', 'ai_analysis', 'tenant_id'])]
|
||||
#[Fillable(['user_id', 'job_position_id', 'birth_name', 'usage_name', 'first_name', 'address', 'zip_code', 'phone', 'linkedin_url', 'city', 'birth_date', 'birth_place', 'nationality', 'current_situation', 'education_level', 'has_driving_license', 'status', 'is_selected', 'sort_order', 'notes', 'cv_score', 'motivation_score', 'interview_score', 'interview_details', 'ai_analysis', 'tenant_id'])]
|
||||
class Candidate extends Model
|
||||
{
|
||||
use HasFactory, BelongsToTenant;
|
||||
@@ -31,6 +31,7 @@ class Candidate extends Model
|
||||
protected $casts = [
|
||||
'ai_analysis' => 'array',
|
||||
'is_selected' => 'boolean',
|
||||
'has_driving_license' => 'boolean',
|
||||
'interview_details' => 'array',
|
||||
];
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
|
||||
use App\Traits\BelongsToTenant;
|
||||
|
||||
#[Fillable(['title', 'description', 'requirements', 'ai_prompt', 'ai_bypass_base_prompt', 'gemini_cache_id', 'gemini_cache_expires_at', 'tenant_id', 'fpt_metadata'])]
|
||||
#[Fillable(['title', 'description', 'requirements', 'ai_prompt', 'ai_bypass_base_prompt', 'gemini_cache_id', 'gemini_cache_expires_at', 'tenant_id', 'fpt_metadata', 'expires_at'])]
|
||||
class JobPosition extends Model
|
||||
{
|
||||
use HasFactory, BelongsToTenant;
|
||||
@@ -19,6 +19,7 @@ class JobPosition extends Model
|
||||
'ai_bypass_base_prompt' => 'boolean',
|
||||
'gemini_cache_expires_at' => 'datetime',
|
||||
'fpt_metadata' => 'array',
|
||||
'expires_at' => 'datetime',
|
||||
];
|
||||
|
||||
public function candidates(): HasMany
|
||||
|
||||
@@ -19,7 +19,7 @@ class User extends Authenticatable
|
||||
|
||||
public function isAdmin(): bool
|
||||
{
|
||||
return in_array($this->role, ['admin', 'super_admin']);
|
||||
return in_array($this->role, ['admin', 'super_admin', 'gestionnaire_rh']);
|
||||
}
|
||||
|
||||
public function isSuperAdmin(): bool
|
||||
@@ -27,6 +27,11 @@ class User extends Authenticatable
|
||||
return $this->role === 'super_admin';
|
||||
}
|
||||
|
||||
public function isGestionnaireRH(): bool
|
||||
{
|
||||
return $this->role === 'gestionnaire_rh';
|
||||
}
|
||||
|
||||
public function isCandidate(): bool
|
||||
{
|
||||
return $this->role === 'candidate';
|
||||
|
||||
Reference in New Issue
Block a user