id ?? null); if (!$sessId) { $user = User::where('role', 'employer')->first(); if ($user) { session(['user' => (object)[ 'id' => $user->id, 'name' => $user->name, 'email' => $user->email, 'role' => 'employer', 'subscription_status' => $user->subscription_status ?? 'active', ]]); return $user; } } else { return User::find($sessId); } return null; } public function index(Request $request) { $user = $this->resolveCurrentUser(); $employerId = $user ? $user->id : 2; // 1. Get job posts created by this employer $jobIds = JobPost::where('employer_id', $employerId)->pluck('id'); // Fetch applications for those jobs $applications = JobApplication::whereIn('job_id', $jobIds) ->with(['worker', 'jobPost']) ->get(); $selectedWorkers = $applications->map(function ($app) { $w = $app->worker; if (!$w) return null; // Map DB status to UI friendly status $status = 'Review'; $dbStatusLower = strtolower($app->status ?? ''); if ($dbStatusLower === 'hired') $status = 'Hired'; elseif ($dbStatusLower === 'rejected') $status = 'Rejected'; elseif ($dbStatusLower === 'shortlisted' || $dbStatusLower === 'offer_sent' || $dbStatusLower === 'offer sent') $status = 'Shortlisted'; elseif ($dbStatusLower === 'applied' || $dbStatusLower === 'review' || $dbStatusLower === 'reviewing') $status = 'Review'; else $status = ucfirst($app->status); return [ 'id' => $app->id, // standard application id 'worker_id' => $w->id, 'name' => $w->name, 'nationality' => $w->nationality, 'salary' => (int)$w->salary, 'status' => $status, 'applied_at' => $app->created_at->format('M d, Y'), 'preferred_location' => $w->preferred_location, 'preferred_job_type' => $w->preferred_job_type, 'live_in_out' => $w->live_in_out, 'in_country' => (bool)$w->in_country, 'visa_status' => $w->visa_status, 'main_profession' => $w->main_profession, ]; })->filter()->values()->toArray(); // 2. Fetch direct hiring offers sent by this employer $directOffers = JobOffer::where('employer_id', $employerId) ->with('worker') ->get(); // Get worker IDs of all hired job applications for this employer to prevent duplication $hiredAppWorkerIds = collect($applications) ->filter(fn($app) => strtolower($app->status ?? '') === 'hired') ->pluck('worker_id') ->toArray(); $directWorkers = $directOffers->map(function ($offer) use ($hiredAppWorkerIds) { $w = $offer->worker; if (!$w) return null; // Avoid duplicate hired entries if worker was hired via standard application $dbOfferStatusLower = strtolower($offer->status ?? ''); if (($dbOfferStatusLower === 'accepted' || $dbOfferStatusLower === 'hired') && in_array($w->id, $hiredAppWorkerIds)) { return null; } // Map DB offer status to UI friendly status $status = 'Offer Sent'; if ($dbOfferStatusLower === 'accepted' || $dbOfferStatusLower === 'hired') $status = 'Hired'; elseif ($dbOfferStatusLower === 'rejected') $status = 'Rejected'; elseif ($dbOfferStatusLower === 'pending' || $dbOfferStatusLower === 'offer sent' || $dbOfferStatusLower === 'offer_sent') $status = 'Offer Sent'; return [ 'id' => 'offer_' . $offer->id, // Unique string key prefix to prevent clashes 'worker_id' => $w->id, 'name' => $w->name, 'nationality' => $w->nationality, 'salary' => (int)$offer->salary, 'status' => $status, 'applied_at' => $offer->created_at->format('M d, Y'), 'preferred_location' => $w->preferred_location, 'preferred_job_type' => $w->preferred_job_type, 'live_in_out' => $w->live_in_out, 'in_country' => (bool)$w->in_country, 'visa_status' => $w->visa_status, 'main_profession' => $w->main_profession, ]; })->filter()->values()->toArray(); // Merge standard job applications with direct hiring offers for unified tracking $mergedCandidates = array_merge($selectedWorkers, $directWorkers); // Only display Hired candidates in the candidates pipeline $mergedCandidates = array_values(array_filter($mergedCandidates, function ($w) { return $w && strtolower($w['status'] ?? '') === 'hired'; })); // Apply filters: preferred_location, job_type, live_in_out, nationality, in_country, visa_status if ($request->filled('preferred_location')) { $prefLoc = $request->preferred_location; $locsArray = is_array($prefLoc) ? $prefLoc : array_filter(array_map('trim', explode(',', $prefLoc))); $locsArray = array_map('strtolower', $locsArray); $locationMapping = [ 'dubai' => ['dubai', 'marina', 'barsha', 'nahda', 'jumeirah', 'deira', 'downtown', 'silicon', 'sports', 'motor', 'jlt', 'jbr', 'meydan', 'ranches', 'bay', 'mirdif', 'quoz'], 'abu dhabi' => ['abu dhabi', 'yas', 'khalifa', 'reem', 'saadiyat', 'raha', 'mussafah', 'zahiyah', 'karamah'], 'oman' => ['oman', 'muscat', 'salalah', 'sohar', 'nizwa', 'sur', 'ibri', 'rustaq'] ]; $mergedCandidates = array_values(array_filter($mergedCandidates, function ($c) use ($locsArray, $locationMapping) { if (!isset($c['preferred_location'])) return false; $wLoc = strtolower($c['preferred_location']); foreach ($locsArray as $l) { if ($l === 'all') return true; $allowedKeywords = $locationMapping[$l] ?? [$l]; foreach ($allowedKeywords as $keyword) { if (str_contains($wLoc, $keyword)) { return true; } } } return false; })); } $jobTypeParam = $request->input('job_type') ?? $request->input('preferred_job_type'); if ($jobTypeParam) { $jobTypesArray = is_array($jobTypeParam) ? $jobTypeParam : array_filter(array_map('trim', explode(',', $jobTypeParam))); $jobTypesArray = array_map('strtolower', $jobTypesArray); $mergedCandidates = array_values(array_filter($mergedCandidates, function ($c) use ($jobTypesArray) { if (!isset($c['preferred_job_type'])) return false; $wJobType = strtolower($c['preferred_job_type']); foreach ($jobTypesArray as $jt) { if (str_contains($wJobType, $jt)) { return true; } } return false; })); } $accParam = $request->input('live_in_out') ?? $request->input('accommodation_type') ?? $request->input('accomadation_type'); if ($accParam) { $accsArray = is_array($accParam) ? $accParam : array_filter(array_map('trim', explode(',', $accParam))); $accsArray = array_map(function($val) { return str_replace(['_', '-'], ' ', strtolower($val)); }, $accsArray); $mergedCandidates = array_values(array_filter($mergedCandidates, function ($c) use ($accsArray) { if (!isset($c['live_in_out'])) return false; $wAcc = str_replace(['_', '-'], ' ', strtolower($c['live_in_out'])); foreach ($accsArray as $a) { if (str_contains($wAcc, $a)) { return true; } } return false; })); } if ($request->filled('nationality')) { $natInput = $request->nationality; $natsArray = is_array($natInput) ? $natInput : array_filter(array_map('trim', explode(',', $natInput))); $natsArray = array_map('strtolower', $natsArray); $mergedCandidates = array_values(array_filter($mergedCandidates, function ($c) use ($natsArray) { if (!isset($c['nationality'])) return false; $wNat = strtolower($c['nationality']); foreach ($natsArray as $n) { if (str_contains($wNat, $n) || $wNat === $n) { return true; } } return false; })); } if ($request->filled('gender')) { $gender = strtolower($request->gender); $mergedCandidates = array_values(array_filter($mergedCandidates, function ($c) use ($gender) { return isset($c['gender']) && strtolower($c['gender']) === $gender; })); } if ($request->has('in_country')) { $inCountryVal = $request->input('in_country'); if ($inCountryVal !== null && $inCountryVal !== '') { $isInCountry = filter_var($inCountryVal, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE); if ($isInCountry === null) { $strVal = strtolower(trim($inCountryVal)); if ($strVal === 'in' || $strVal === 'in_country' || $strVal === 'yes') { $isInCountry = true; } elseif ($strVal === 'out' || $strVal === 'out_country' || $strVal === 'no') { $isInCountry = false; } } if ($isInCountry !== null) { $mergedCandidates = array_values(array_filter($mergedCandidates, function ($c) use ($isInCountry) { return isset($c['in_country']) && (bool)$c['in_country'] === $isInCountry; })); } } } if ($request->has('out_country')) { $outCountryVal = $request->input('out_country'); if ($outCountryVal !== null && $outCountryVal !== '') { $isOutCountry = filter_var($outCountryVal, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE); if ($isOutCountry === null) { $strVal = strtolower(trim($outCountryVal)); if ($strVal === 'out' || $strVal === 'out_country' || $strVal === 'yes') { $isOutCountry = true; } elseif ($strVal === 'in' || $strVal === 'in_country' || $strVal === 'no') { $isOutCountry = false; } } if ($isOutCountry !== null) { $mergedCandidates = array_values(array_filter($mergedCandidates, function ($c) use ($isOutCountry) { return isset($c['in_country']) && (bool)$c['in_country'] !== $isOutCountry; })); } } } $visaParam = $request->input('visa_status') ?? $request->input('next_visa_type') ?? $request->input('visa_type'); if ($visaParam) { $visasArray = is_array($visaParam) ? $visaParam : array_filter(array_map('trim', explode(',', $visaParam))); $visasArray = array_map('strtolower', $visasArray); $mergedCandidates = array_values(array_filter($mergedCandidates, function ($c) use ($visasArray) { if (!isset($c['visa_status'])) return false; $isInCountry = isset($c['in_country']) && (bool)$c['in_country']; if (!$isInCountry) { return false; } $wVisa = strtolower($c['visa_status']); foreach ($visasArray as $v) { if (str_contains($wVisa, $v)) { return true; } } return false; })); } if ($request->filled('main_profession')) { $mainProfession = strtolower($request->main_profession); $mergedCandidates = array_values(array_filter($mergedCandidates, function ($c) use ($mainProfession) { return isset($c['main_profession']) && strtolower($c['main_profession']) === $mainProfession; })); } $nationalitiesResponse = app(\App\Http\Controllers\Api\WorkerAuthController::class)->nationalities(new \Illuminate\Http\Request(['per_page' => 500])); $nationalitiesData = json_decode($nationalitiesResponse->getContent(), true); $allNationalities = collect($nationalitiesData['data']['nationalities'] ?? [])->pluck('name')->filter()->toArray(); return Inertia::render('Employer/SelectedCandidates', [ 'selectedWorkers' => $mergedCandidates, 'allNationalities' => $allNationalities, ]); } private function checkJobAccess($user) { if (!$user) { return false; } $sub = \Illuminate\Support\Facades\DB::table('subscriptions') ->where('user_id', $user->id) ->where('status', 'active') ->latest('id') ->first(); $planId = $sub ? $sub->plan_id : 'basic'; return $planId !== 'basic'; } public function updateStatus(Request $request, $id) { $request->validate([ 'status' => 'required|string|in:Applied,Review,review,Reviewing,reviewing,Shortlisted,Contacted,Interview Scheduled,Selected,Rejected,Hired,Offer Sent,applied,shortlisted,contacted,interview scheduled,interview_scheduled,selected,rejected,hired,offer sent,offer_sent,hire_requested', 'notes' => 'nullable|string', ]); // If this is a direct hiring offer response if (is_string($id) && str_starts_with($id, 'offer_')) { $offerId = substr($id, 6); $offer = JobOffer::findOrFail($offerId); $dbStatus = 'pending'; if (in_array(strtolower($request->status), ['hired', 'accepted'])) { // Under two-way confirmation, it must NOT immediately set status to accepted/Hired. // It should set offer status to 'pending' (waiting for worker confirmation). $dbStatus = 'pending'; } elseif (in_array(strtolower($request->status), ['rejected'])) { $dbStatus = 'rejected'; } elseif (in_array(strtolower($request->status), ['offer sent', 'offer_sent', 'pending'])) { $dbStatus = 'pending'; } $offer->update(['status' => $dbStatus]); // Notify worker about the hire offer if it is set to pending if ($dbStatus === 'pending') { $offer->worker->notify(new \App\Notifications\GenericNotification( "Direct Hiring Offer", ($offer->employer->name ?? "Employer") . " wants to hire you. Do you want to accept this job offer?", [ 'type' => 'direct_hire_request', 'offer_id' => $offer->id, 'status' => 'pending', ] )); } return back()->with('success', 'Hire request initiated successfully. Awaiting candidate confirmation.'); } // Standard job application status update $app = JobApplication::findOrFail($id); $user = $this->resolveCurrentUser(); if (!$user) { return redirect()->route('employer.login'); } $statusLower = strtolower($request->status); $isDirectHiringAction = in_array($statusLower, ['hired', 'hire_requested']); if (!$isDirectHiringAction && !$this->checkJobAccess($user)) { return redirect()->route('employer.subscription') ->with('error', 'Your current plan does not support the Job Apply module. Please upgrade to a Premium plan to access this feature.'); } $dbStatus = 'applied'; $statusLower = strtolower($request->status); if ($statusLower === 'hired' || $statusLower === 'hire_requested') { $dbStatus = 'hire_requested'; } elseif ($statusLower === 'rejected') { $dbStatus = 'rejected'; } elseif ($statusLower === 'shortlisted' || $statusLower === 'offer sent' || $statusLower === 'offer_sent') { $dbStatus = 'shortlisted'; } elseif ($statusLower === 'contacted') { $dbStatus = 'contacted'; } elseif ($statusLower === 'interview scheduled' || $statusLower === 'interview_scheduled') { $dbStatus = 'interview_scheduled'; } elseif ($statusLower === 'selected') { $dbStatus = 'selected'; } elseif ($statusLower === 'reviewing' || $statusLower === 'applied' || $statusLower === 'review') { $dbStatus = 'applied'; } // Contact Limit check: if moving to 'shortlisted', 'contacted', 'interview_scheduled', 'selected', 'hired', 'hire_requested' $contactStatuses = ['shortlisted', 'contacted', 'interview_scheduled', 'selected', 'hired', 'hire_requested']; if (in_array($dbStatus, $contactStatuses)) { if (!User::canContactWorker($user->id, $app->worker_id)) { $activeSub = \App\Models\Subscription::where('user_id', $user->id)->where('status', 'active')->first(); $planId = $activeSub ? $activeSub->plan_id : 'basic'; $plan = \App\Models\Plan::find($planId); $maxContacts = $plan ? $plan->max_contact_people : 10; // Web: contact limit block redirect return redirect()->route('employer.subscription') ->with('error', 'You have reached your contacted workers limit of ' . $maxContacts . ' under your active plan. Please upgrade or renew your subscription to contact more workers.'); } } // Update application status & history $history = $app->status_history ?: []; $history[] = [ 'status' => $dbStatus, 'timestamp' => now()->toIso8601String(), 'notes' => $request->input('notes'), ]; $empStatus = strtolower($request->status); if ($empStatus === 'applied' || $empStatus === 'reviewing' || $empStatus === 'review') { $empStatus = 'review'; } elseif ($empStatus === 'hire_requested') { $empStatus = 'hired'; } $updateData = [ 'status' => $dbStatus, 'employer_status' => $empStatus, 'status_history' => $history, ]; if ($request->filled('notes')) { $updateData['notes'] = $request->notes; } $app->update($updateData); // If shortlisted, automatically add to Saved Workers (Shortlist table) if not already saved if ($dbStatus === 'shortlisted') { $exists = \App\Models\Shortlist::where('employer_id', $user->id) ->where('worker_id', $app->worker_id) ->exists(); if (!$exists) { \App\Models\Shortlist::create([ 'employer_id' => $user->id, 'worker_id' => $app->worker_id, ]); } } if ($dbStatus === 'rejected') { if ($app->worker) { $app->worker->update(['status' => 'active']); } } // Trigger notifications $this->triggerStatusNotification($app, $dbStatus); return back()->with('success', 'Candidate application status updated successfully to ' . $request->status); } private function triggerStatusNotification($application, $status) { $worker = $application->worker; $job = $application->jobPost; $employer = $job ? $job->employer : null; // When status is updated: notify worker if ($worker) { $title = "Job Application Update"; $body = ""; $type = 'job_application_status_update'; switch (strtolower($status)) { case 'shortlisted': $body = "You have been shortlisted for the job: " . ($job->title ?? 'Job Post'); $type = 'shortlisted'; break; case 'applied': case 'review': case 'reviewing': $body = "Your application is under review for the job: " . ($job->title ?? 'Job Post'); $type = 'review'; break; case 'contacted': $body = "An employer wants to contact you regarding the job: " . ($job->title ?? 'Job Post'); $type = 'contacted'; break; case 'interview scheduled': case 'interview_scheduled': $body = "An interview has been scheduled for the job: " . ($job->title ?? 'Job Post'); $type = 'interview_scheduled'; break; case 'selected': $body = "Congratulations! You have been selected for the job: " . ($job->title ?? 'Job Post'); $type = 'selected'; break; case 'rejected': $body = "Your application for the job: " . ($job->title ?? 'Job Post') . " has been reviewed and rejected."; $type = 'rejected'; break; case 'hire_requested': $title = "Action Required: Confirm Hiring"; $body = "An employer wants to hire you for the job: " . ($job->title ?? 'Job Post') . ". Please confirm."; $type = 'hire_request'; break; case 'hired': $body = "Congratulations! You have been hired for the job: " . ($job->title ?? 'Job Post'); $type = 'hired'; break; default: $body = "Your application status for the job: " . ($job->title ?? 'Job Post') . " has changed to " . ucfirst($status); break; } $worker->notify(new \App\Notifications\GenericNotification( $title, $body, [ 'type' => $type, 'job_id' => $job->id ?? '', 'application_id' => $application->id, 'status' => $status, ] )); } } }