Files
HRM-System/app/Http/Controllers/CandidateController.php
2026-04-13 08:16:56 +08:00

458 lines
19 KiB
PHP

<?php
namespace App\Http\Controllers;
use App\Models\AttendancePolicy;
use App\Models\Branch;
use App\Models\Candidate;
use App\Models\JobPosting;
use App\Models\CandidateSource;
use App\Models\Department;
use App\Models\Designation;
use App\Models\DocumentType;
use App\Models\Employee;
use App\Models\Offer;
use App\Models\Shift;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator;
use Inertia\Inertia;
class CandidateController extends Controller
{
public function index(Request $request)
{
if (Auth::user()->can('manage-candidates')) {
$query = Candidate::with(['job', 'source', 'referralEmployee'])->where(function ($q) {
if (Auth::user()->can('manage-any-candidates')) {
$q->whereIn('created_by', getCompanyAndUsersId());
} elseif (Auth::user()->can('manage-own-candidates')) {
$q->where('created_by', Auth::id());
} else {
$q->whereRaw('1 = 0');
}
});
if ($request->has('search') && !empty($request->search)) {
$query->where(function ($q) use ($request) {
$q->where('first_name', 'like', '%' . $request->search . '%')
->orWhere('last_name', 'like', '%' . $request->search . '%')
->orWhere('email', 'like', '%' . $request->search . '%');
});
}
if ($request->has('status') && !empty($request->status) && $request->status !== 'all') {
$query->where('status', $request->status);
}
if ($request->has('job_id') && !empty($request->job_id) && $request->job_id !== 'all') {
$query->where('job_id', $request->job_id);
}
if ($request->has('source_id') && !empty($request->source_id) && $request->source_id !== 'all') {
$query->where('source_id', $request->source_id);
}
// Handle sorting
$sortField = $request->get('sort_field', 'created_at');
$sortDirection = $request->get('sort_direction', 'desc');
// Validate sort field
$allowedSortFields = ['first_name', 'application_date', 'created_at'];
if (!in_array($sortField, $allowedSortFields)) {
$sortField = 'created_at';
}
$query->orderBy($sortField, $sortDirection);
$candidates = $query->paginate($request->per_page ?? 10);
$jobPostings = JobPosting::whereIn('created_by', getCompanyAndUsersId())
->select('id', 'title', 'job_code')
->get();
$sources = CandidateSource::whereIn('created_by', getCompanyAndUsersId())
->where('status', 'active')
->select('id', 'name')
->get();
$employees = User::with('employee')
->where('type', 'employee')
->whereIn('created_by', getCompanyAndUsersId())
->where('status', 'active')
->select('id', 'name')
->get()
->map(function ($user) {
return [
'id' => $user->id,
'name' => $user->name,
'employee_id' => $user->employee->employee_id ?? ''
];
});
return Inertia::render('hr/recruitment/candidates/index', [
'candidates' => $candidates,
'jobPostings' => $jobPostings,
'sources' => $sources,
'employees' => $employees,
'filters' => $request->all(['search', 'status', 'job_id', 'source_id', 'sort_field', 'sort_direction', 'per_page']),
]);
} else {
return redirect()->back()->with('error', __('Permission Denied.'));
}
}
public function store(Request $request)
{
$validator = Validator::make($request->all(), [
'job_id' => 'required|exists:job_postings,id',
'source_id' => 'required|exists:candidate_sources,id',
'first_name' => 'required|string|max:255',
'last_name' => 'required|string|max:255',
'email' => 'required|email|max:255',
'phone' => 'nullable|string|max:20',
'current_company' => 'nullable|string|max:255',
'current_position' => 'nullable|string|max:255',
'experience_years' => 'required|integer|min:0',
'current_salary' => 'nullable|numeric|min:0',
'expected_salary' => 'nullable|numeric|min:0',
'notice_period' => 'nullable|string|max:255',
'skills' => 'nullable|string',
'education' => 'nullable|string',
'portfolio_url' => 'nullable|string',
'linkedin_url' => 'nullable|string',
'referral_employee_id' => 'nullable|exists:users,id',
'application_date' => 'required|date',
]);
if ($validator->fails()) {
return redirect()->back()->withErrors($validator)->withInput();
}
Candidate::create([
'job_id' => $request->job_id,
'source_id' => $request->source_id,
'first_name' => $request->first_name,
'last_name' => $request->last_name,
'email' => $request->email,
'phone' => $request->phone,
'current_company' => $request->current_company,
'current_position' => $request->current_position,
'experience_years' => $request->experience_years,
'current_salary' => $request->current_salary,
'expected_salary' => $request->expected_salary,
'notice_period' => $request->notice_period,
'skills' => $request->skills,
'education' => $request->education,
'portfolio_url' => $request->portfolio_url ?: null,
'linkedin_url' => $request->linkedin_url ?: null,
'referral_employee_id' => $request->referral_employee_id ?: null,
'application_date' => $request->application_date,
'created_by' => creatorId(),
]);
return redirect()->back()->with('success', __('Candidate created successfully'));
}
public function update(Request $request, Candidate $candidate)
{
if (!in_array($candidate->created_by, getCompanyAndUsersId())) {
return redirect()->back()->with('error', 'You do not have permission to update this candidate');
}
$validator = Validator::make($request->all(), [
'job_id' => 'required|exists:job_postings,id',
'source_id' => 'required|exists:candidate_sources,id',
'first_name' => 'required|string|max:255',
'last_name' => 'required|string|max:255',
'email' => 'required|email|max:255',
'phone' => 'nullable|string|max:20',
'current_company' => 'nullable|string|max:255',
'current_position' => 'nullable|string|max:255',
'experience_years' => 'required|integer|min:0',
'current_salary' => 'nullable|numeric|min:0',
'expected_salary' => 'nullable|numeric|min:0',
'notice_period' => 'nullable|string|max:255',
'skills' => 'nullable|string',
'education' => 'nullable|string',
'portfolio_url' => 'nullable|string',
'linkedin_url' => 'nullable|string',
'referral_employee_id' => 'nullable|exists:users,id',
'application_date' => 'required|date',
]);
if ($validator->fails()) {
return redirect()->back()->withErrors($validator)->withInput();
}
$candidate->update($request->only([
'job_id',
'source_id',
'first_name',
'last_name',
'email',
'phone',
'current_company',
'current_position',
'experience_years',
'current_salary',
'expected_salary',
'notice_period',
'skills',
'education',
'portfolio_url',
'linkedin_url',
'referral_employee_id',
'application_date'
]));
return redirect()->back()->with('success', __('Candidate updated successfully'));
}
public function destroy(Candidate $candidate)
{
if (!in_array($candidate->created_by, getCompanyAndUsersId())) {
return redirect()->back()->with('error', 'You do not have permission to delete this candidate');
}
$candidate->delete();
return redirect()->back()->with('success', __('Candidate deleted successfully'));
}
public function show(Candidate $candidate)
{
if (!in_array($candidate->created_by, getCompanyAndUsersId())) {
return abort(404);
}
$candidate->load([
'job.location',
'job.jobType',
'source',
'referralEmployee',
'branch',
'department'
]);
return Inertia::render('hr/recruitment/candidates/show', [
'candidate' => $candidate,
]);
}
public function updateStatus(Request $request, Candidate $candidate)
{
if (!in_array($candidate->created_by, getCompanyAndUsersId())) {
return redirect()->back()->with('error', 'You do not have permission to update this candidate');
}
$validator = Validator::make($request->all(), [
'status' => 'required|in:New,Screening,Interview,Offer,Hired,Rejected',
]);
if ($validator->fails()) {
return redirect()->back()->withErrors($validator);
}
$candidate->update(['status' => $request->status]);
return redirect()->back()->with('success', __('Candidate status updated successfully'));
}
public function convertToEmployee(Candidate $candidate)
{
try {
if (Auth::user()->can('convert-to-employee')) {
if (!in_array($candidate->created_by, getCompanyAndUsersId())) {
return redirect()->back()->with('error', __('You do not have permission to convert this candidate'));
}
if ($candidate->status !== 'Hired') {
return redirect()->back()->with('error', __('Only hired candidates can be converted to employees'));
}
if ($candidate->is_employee) {
return redirect()->back()->with('error', __('This candidate has already been converted to an employee'));
}
// Check if candidate has an accepted offer
$acceptedOffer = Offer::where('candidate_id', $candidate->id)
->where('status', 'Accepted')
->first();
if (!$acceptedOffer) {
return redirect()->back()->with('error', __('Candidate must have an accepted offer before conversion to employee'));
}
// Get data needed for employee creation form
$branches = Branch::whereIn('created_by', getCompanyAndUsersId())
->where('status', 'active')
->get(['id', 'name']);
$departments = Department::with('branch')
->whereIn('created_by', getCompanyAndUsersId())
->where('status', 'active')
->get(['id', 'name', 'branch_id']);
$designations = Designation::with('department')
->whereIn('created_by', getCompanyAndUsersId())
->where('status', 'active')
->get(['id', 'name', 'department_id']);
$documentTypes = DocumentType::whereIn('created_by', getCompanyAndUsersId())
->get(['id', 'name', 'is_required']);
$shifts = Shift::whereIn('created_by', getCompanyAndUsersId())
->where('status', 'active')
->get(['id', 'name', 'start_time', 'end_time']);
$attendancePolicies = AttendancePolicy::whereIn('created_by', getCompanyAndUsersId())
->where('status', 'active')
->get(['id', 'name']);
return Inertia::render('hr/recruitment/candidates/convert-to-employee', [
'candidate' => $candidate->load(['job', 'source']),
'branches' => $branches,
'departments' => $departments,
'designations' => $designations,
'documentTypes' => $documentTypes,
'shifts' => $shifts,
'attendancePolicies' => $attendancePolicies,
'generatedEmployeeId' => Employee::generateEmployeeId(),
]);
} else {
return redirect()->back()->with('error', __('Permission Denied.'));
}
} catch (\Exception $e) {
\Log::error('Convert to employee page load failed: ' . $e->getMessage());
return redirect()->back()->with('error', __('Failed to load conversion page: :message', ['message' => $e->getMessage()]));
}
}
public function storeEmployee(Request $request)
{
try {
// Validate the request
$validator = Validator::make($request->all(), [
'candidate_id' => 'required|exists:candidates,id',
'name' => 'required|string|max:255',
'email' => 'required|email|max:255|unique:users,email',
'password' => 'required|string|min:8',
'phone' => 'required|string|max:20',
'date_of_birth' => 'required|date',
'gender' => 'required|in:male,female,other',
'branch_id' => 'required|exists:branches,id',
'department_id' => 'required|exists:departments,id',
'designation_id' => 'required|exists:designations,id',
'date_of_joining' => 'required|date',
'employment_type' => 'required|string|max:50',
'address_line_1' => 'required|string|max:255',
'city' => 'required|string|max:100',
'state' => 'required|string|max:100',
'country' => 'required|string|max:100',
'postal_code' => 'required|string|max:20',
'emergency_contact_name' => 'required|string|max:255',
'emergency_contact_relationship' => 'required|string|max:100',
'emergency_contact_number' => 'required|string|max:20',
'bank_name' => 'required|string|max:255',
'account_holder_name' => 'required|string|max:255',
'account_number' => 'required|string|max:50',
'salary' => 'required|numeric|min:0',
]);
if ($validator->fails()) {
return redirect()->back()->withErrors($validator)->withInput();
}
// Get candidate
$candidate = \App\Models\Candidate::findOrFail($request->candidate_id);
// Check permissions and status
if (!in_array($candidate->created_by, getCompanyAndUsersId())) {
return redirect()->back()->with('error', 'Permission denied');
}
if ($candidate->status !== 'Hired' || $candidate->is_employee) {
return redirect()->back()->with('error', 'Invalid candidate status for conversion');
}
// Create User
$user = \App\Models\User::create([
'name' => $request->name,
'email' => $request->email,
'password' => \Illuminate\Support\Facades\Hash::make($request->password),
'type' => 'employee',
'lang' => 'en',
'avatar' => $request->profile_image,
'created_by' => creatorId(),
]);
// Assign Employee role
if (isSaas()) {
$employeeRole = \Spatie\Permission\Models\Role::where('created_by', createdBy())->where('name', 'employee')->first();
} else {
$employeeRole = \Spatie\Permission\Models\Role::where('name', 'employee')->first();
}
if ($employeeRole) {
$user->assignRole($employeeRole);
}
// Create Employee
$employee = \App\Models\Employee::create([
'user_id' => $user->id,
'employee_id' => \App\Models\Employee::generateEmployeeId(),
'biometric_emp_id' => $request->biometric_emp_id,
'phone' => $request->phone,
'date_of_birth' => $request->date_of_birth,
'gender' => $request->gender,
'branch_id' => $request->branch_id,
'department_id' => $request->department_id,
'designation_id' => $request->designation_id,
'shift_id' => $request->shift_id,
'attendance_policy_id' => $request->attendance_policy_id,
'date_of_joining' => $request->date_of_joining,
'employment_type' => $request->employment_type,
'employee_status' => $request->employee_status ?? 'active',
'address_line_1' => $request->address_line_1,
'address_line_2' => $request->address_line_2,
'city' => $request->city,
'state' => $request->state,
'country' => $request->country,
'postal_code' => $request->postal_code,
'emergency_contact_name' => $request->emergency_contact_name,
'emergency_contact_relationship' => $request->emergency_contact_relationship,
'emergency_contact_number' => $request->emergency_contact_number,
'bank_name' => $request->bank_name,
'account_holder_name' => $request->account_holder_name,
'account_number' => $request->account_number,
'bank_identifier_code' => $request->bank_identifier_code,
'bank_branch' => $request->bank_branch,
'tax_payer_id' => $request->tax_payer_id,
'created_by' => creatorId(),
]);
// Handle documents
if ($request->has('documents') && is_array($request->documents)) {
foreach ($request->documents as $document) {
if (isset($document['file_path']) && !empty($document['file_path'])) {
\App\Models\EmployeeDocument::create([
'employee_id' => $employee->user_id,
'document_type_id' => $document['document_type_id'],
'file_path' => $document['file_path'],
'expiry_date' => $document['expiry_date'] ?? null,
'verification_status' => 'pending',
'created_by' => creatorId(),
]);
}
}
}
// Mark candidate as converted
$candidate->update(['is_employee' => true]);
return redirect()->route('hr.employees.index')->with('success', __('Candidate converted to employee successfully'));
} catch (\Exception $e) {
\Log::error('Candidate to employee conversion failed: ' . $e->getMessage());
return redirect()->back()->with('error', __('Failed to convert candidate: :message', ['message' => $e->getMessage()]))->withInput();
}
}
}