migrant-web/app/Http/Controllers/Employer/WorkerController.php
2026-07-10 14:57:13 +05:30

585 lines
25 KiB
PHP

<?php
namespace App\Http\Controllers\Employer;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Inertia\Inertia;
use App\Models\User;
use App\Models\Worker;
use App\Models\Shortlist;
use App\Models\JobOffer;
use App\Models\Review;
use App\Models\ProfileView;
class WorkerController extends Controller
{
private function resolveCurrentUser()
{
$sess = session('user');
$sessId = is_array($sess) ? ($sess['id'] ?? null) : ($sess->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;
$dbWorkers = Worker::with(['skills', 'documents'])
->where('status', '!=', 'Hired')
->where('status', '!=', 'hidden')
->get();
$workers = $dbWorkers->map(function ($w) {
$isPending = str_contains(strtolower($w->passport_status), 'pending');
if ($isPending || $w->status === 'hidden' || $w->status === 'Hired') {
return null;
}
// Map languages from database comma-separated string
$langs = $w->language ? array_map('trim', explode(',', $w->language)) : ['English'];
// Preferred job types: full-time / part-time / live-in / live-out
$jobTypes = ['full-time', 'part-time', 'live-in', 'live-out'];
$preferredJobType = $w->preferred_job_type ?? $jobTypes[$w->id % 4];
// Emirates ID verification status (now dynamic passport status)
$emiratesIdStatus = $w->emirates_id_status;
// Map skills dynamically from database
$mappedSkills = $w->skills->pluck('name')->toArray();
if (empty($mappedSkills)) {
$mappedSkills = ['cooking', 'cleaning'];
}
// Visa status
$visaStatus = $w->visa_status;
$photo = null;
$dbReviews = \App\Models\Review::where('worker_id', $w->id)->get();
$reviewsCount = $dbReviews->count();
$rating = $reviewsCount > 0 ? round($dbReviews->avg('rating'), 1) : 0.0;
return [
'id' => $w->id,
'name' => $w->name,
'phone' => $w->phone,
'gender' => $w->gender ?? 'Female',
'nationality' => $w->nationality,
'photo' => $photo,
'emirates_id_status' => $emiratesIdStatus,
'passport_status' => $w->passport_status,
'category' => 'Domestic Worker',
'main_profession' => $w->main_profession,
'skills' => $mappedSkills,
'visa_status' => $visaStatus,
'experience' => $w->experience,
'salary' => (int) $w->salary,
'religion' => $w->religion,
'languages' => $langs,
'age' => $w->age,
'verified' => (bool) $w->verified,
'preferred_job_type' => $preferredJobType,
'live_in_out' => $w->live_in_out ?? 'Live-in',
'bio' => $w->bio,
'rating' => $rating,
'reviews_count' => $reviewsCount,
'preferred_location' => $w->preferred_location,
'in_country' => (bool) $w->in_country,
'document_expiry_status' => $w->document_expiry_status,
'document_expiry_days' => $w->document_expiry_days,
'visa_expiry_date' => $w->visa_expiry_date,
];
})->filter()->values()->toArray();
// Apply request 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']
];
$workers = array_values(array_filter($workers, 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);
$workers = array_values(array_filter($workers, 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);
$workers = array_values(array_filter($workers, 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);
$workers = array_values(array_filter($workers, 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('main_profession')) {
$mainProfession = strtolower($request->main_profession);
$workers = array_values(array_filter($workers, function ($c) use ($mainProfession) {
return isset($c['main_profession']) && strtolower($c['main_profession']) === $mainProfession;
}));
}
if ($request->filled('gender')) {
$gender = strtolower($request->gender);
$workers = array_values(array_filter($workers, 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) {
$workers = array_values(array_filter($workers, 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) {
$workers = array_values(array_filter($workers, 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);
$workers = array_values(array_filter($workers, 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;
}));
}
$shortlistedIds = Shortlist::where('employer_id', $employerId)->pluck('worker_id')->toArray();
$nationalitiesResponse = app(\App\Http\Controllers\Api\WorkerAuthController::class)->nationalities(new \Illuminate\Http\Request(['per_page' => 500]));
$nationalitiesData = json_decode($nationalitiesResponse->getContent(), true);
$dbNationalities = collect($nationalitiesData['data']['nationalities'] ?? [])->pluck('name')->filter()->toArray();
$filtersMetadata = [
'nationalities' => array_merge(['All Nationalities'], $dbNationalities),
'professions' => ['All Professions', 'Housemaid', 'Nanny', 'Cook', 'Driver', 'Caregiver'],
'experienceLevels' => ['All Experience', '1-2 Years', '3-5 Years', '5+ Years'],
'religions' => ['All Religions', 'Christian', 'Muslim', 'Hindu', 'Buddhist'],
'languages' => ['All Languages', 'English', 'Arabic', 'Hindi', 'Tagalog'],
'workTypes' => ['All Types', 'full-time', 'part-time', 'live-in', 'live-out'],
'skills' => ['All Skills', 'cooking', 'driving', 'childcare', 'cleaning', 'elderly care', 'gardening'],
'visaStatuses' => ['All Visa Statuses', 'Residence Visa', 'Tourist Visa', 'Employment Visa'],
];
return Inertia::render('Employer/Workers/Index', [
'initialWorkers' => $workers,
'initialShortlistedIds' => $shortlistedIds,
'filtersMetadata' => $filtersMetadata,
]);
}
public function show($id)
{
$w = Worker::with(['skills', 'documents'])->findOrFail($id);
$isPending = str_contains(strtolower($w->passport_status), 'pending');
if ($w->status === 'hidden' || $isPending) {
abort(404);
}
// Record employer profile view (Requirement 3)
$user = $this->resolveCurrentUser();
if ($user) {
ProfileView::updateOrCreate(
['employer_id' => $user->id, 'worker_id' => $w->id],
['updated_at' => now()]
);
}
$langs = $w->language ? array_map('trim', explode(',', $w->language)) : ['English'];
// Preferred job types: full-time / part-time / live-in / live-out
$jobTypes = ['full-time', 'part-time', 'live-in', 'live-out'];
$preferredJobType = $jobTypes[$w->id % 4];
$emiratesIdStatus = $w->emirates_id_status;
// Map skills dynamically from database
$mappedSkills = $w->skills->pluck('name')->toArray();
if (empty($mappedSkills)) {
$mappedSkills = ['cooking', 'cleaning'];
}
$photo = null;
// Fetch dynamic database reviews (Requirement 1)
$dbReviews = Review::where('worker_id', $w->id)->with('employer')->latest()->get();
$reviews = $dbReviews->map(function ($rev) {
return [
'id' => $rev->id,
'employer_name' => $rev->employer->name ?? 'Employer',
'rating' => $rev->rating,
'date' => $rev->created_at->format('M d, Y'),
'comment' => $rev->comment,
];
})->toArray();
$reviewsCount = count($reviews);
$rating = $reviewsCount > 0 ? round($dbReviews->avg('rating'), 1) : 0;
$simDb = Worker::query()
->where('id', '!=', $w->id)
->where('status', '!=', 'Hired')
->where('status', '!=', 'hidden')
->limit(3)
->get();
$similarWorkers = $simDb->map(function ($sw) {
$isPending = str_contains(strtolower($sw->passport_status), 'pending');
if ($isPending || $sw->status === 'hidden' || $sw->status === 'Hired') {
return null;
}
return [
'id' => $sw->id,
'name' => $sw->name,
'nationality' => $sw->nationality,
'category' => 'Helper',
'salary' => (int) $sw->salary,
'rating' => 4.7,
'verified' => (bool) $sw->verified,
];
})->filter()->values()->toArray();
$visaStatus = $w->visa_status;
$waitingForReviewOfferOrApp = JobOffer::where('employer_id', $user ? $user->id : 0)
->where('worker_id', $w->id)
->where('status', 'waiting_for_review')
->exists() ||
\App\Models\JobApplication::where('worker_id', $w->id)
->whereHas('jobPost', function($q) use ($user) {
$q->where('employer_id', $user ? $user->id : 0);
})->where('status', 'waiting_for_review')
->exists();
$pendingOfferOrApp = JobOffer::where('employer_id', $user ? $user->id : 0)
->where('worker_id', $w->id)
->where('status', 'pending')
->exists() ||
\App\Models\JobApplication::where('worker_id', $w->id)
->whereHas('jobPost', function($q) use ($user) {
$q->where('employer_id', $user ? $user->id : 0);
})->where('status', 'hire_requested')
->exists();
$worker = [
'id' => $w->id,
'name' => $w->name,
'phone' => $w->phone,
'gender' => $w->gender ?? 'Female',
'nationality' => $w->nationality,
'photo' => $photo,
'emirates_id_status' => $emiratesIdStatus,
'passport_status' => $w->passport_status,
'category' => 'Domestic Worker',
'main_profession' => $w->main_profession,
'skills' => $mappedSkills,
'visa_status' => $visaStatus,
'experience' => $w->experience,
'salary' => (int) $w->salary,
'religion' => $w->religion,
'languages' => $langs,
'age' => $w->age,
'verified' => (bool) $w->verified,
'preferred_job_type' => $preferredJobType,
'live_in_out' => $w->live_in_out ?? 'Live-in',
'bio' => $w->bio,
'rating' => $rating,
'reviews_count' => $reviewsCount,
'reviews' => $reviews,
'similar_workers' => $similarWorkers,
'status' => $w->status,
'availability_status' => $pendingOfferOrApp ? 'Pending Confirmation' : ($waitingForReviewOfferOrApp ? 'Waiting for Review' : ($w->status === 'Hired' ? 'Hired' : $w->status)),
'document_expiry_status' => $w->document_expiry_status,
'document_expiry_days' => $w->document_expiry_days,
'visa_expiry_date' => $w->visa_expiry_date,
'in_country' => (bool) $w->in_country,
'preferred_location' => $w->preferred_location,
'documents' => $w->documents->map(function ($doc) {
return [
'id' => $doc->id,
'type' => $doc->type,
'number' => $doc->number,
'issue_date' => $doc->issue_date,
'expiry_date' => $doc->expiry_date,
'ocr_accuracy' => $doc->ocr_accuracy,
'file_path' => $doc->file_path ? url($doc->file_path) : null,
'ocr_data' => $doc->ocr_data,
];
})->toArray(),
];
return Inertia::render('Employer/Workers/Show', [
'worker' => $worker,
]);
}
public function sendOffer(Request $request, $id)
{
$request->validate([
'work_date' => 'required|date',
'location' => 'required|string|max:255',
'salary' => 'required|numeric|min:0',
'notes' => 'nullable|string|max:1000',
]);
$user = $this->resolveCurrentUser();
$employerId = $user ? $user->id : 2;
if (!User::canContactWorker($employerId, $id)) {
$activeSub = \App\Models\Subscription::where('user_id', $employerId)->where('status', 'active')->first();
$planId = $activeSub ? $activeSub->plan_id : 'basic';
$plan = \App\Models\Plan::find($planId);
$maxContacts = $plan ? $plan->max_contact_people : 10;
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.');
}
$worker = Worker::findOrFail($id);
JobOffer::create([
'employer_id' => $employerId,
'worker_id' => $worker->id,
'work_date' => $request->work_date,
'location' => $request->location,
'salary' => $request->salary,
'notes' => $request->notes,
'status' => 'pending',
]);
return redirect()->route('employer.hiring.success', ['id' => $id])
->with('success', 'Hiring offer has been successfully dispatched to the worker!');
}
public function markHired(Request $request, $id)
{
$user = $this->resolveCurrentUser();
$employerId = $user ? $user->id : 2;
if (!User::canContactWorker($employerId, $id)) {
$activeSub = \App\Models\Subscription::where('user_id', $employerId)->where('status', 'active')->first();
$planId = $activeSub ? $activeSub->plan_id : 'basic';
$plan = \App\Models\Plan::find($planId);
$maxContacts = $plan ? $plan->max_contact_people : 10;
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.');
}
$worker = Worker::findOrFail($id);
// Check if there is an existing job offer for this employer and worker
$offer = JobOffer::where('employer_id', $employerId)
->where('worker_id', $worker->id)
->first();
// Also check if there's an existing job application
$jobIds = \App\Models\JobPost::where('employer_id', $employerId)->pluck('id');
$application = \App\Models\JobApplication::whereIn('job_id', $jobIds)
->where('worker_id', $worker->id)
->first();
// Determine if this is first click ("Hire") or second click ("Confirm Hire")
// If they have an existing offer or application with status 'waiting_for_review', then it's the second click.
$isWaitingForReview = false;
if ($offer && $offer->status === 'waiting_for_review') {
$isWaitingForReview = true;
} elseif ($application && $application->status === 'waiting_for_review') {
$isWaitingForReview = true;
}
if (!$isWaitingForReview) {
// Step 1: Transition to 'waiting_for_review'
if ($offer) {
$offer->update(['status' => 'waiting_for_review']);
} elseif ($application) {
$application->update(['status' => 'waiting_for_review']);
$history = $application->status_history ?: [];
$history[] = [
'status' => 'waiting_for_review',
'timestamp' => now()->toIso8601String(),
'notes' => 'Candidate status moved to waiting for review.',
];
$application->update(['status_history' => $history]);
} else {
// Create a new direct offer in 'waiting_for_review' status
$offer = JobOffer::create([
'employer_id' => $employerId,
'worker_id' => $worker->id,
'work_date' => now()->format('Y-m-d'),
'location' => 'Dubai',
'salary' => $worker->salary,
'notes' => 'Direct sponsoring matching initiated.',
'status' => 'waiting_for_review',
]);
}
return back()->with('success', 'Candidate status updated to Waiting for Review.');
} else {
// Step 2: Confirm hire (Transition to pending/hire_requested and send notifications)
if ($offer) {
$offer->update(['status' => 'pending']);
} elseif ($application) {
$application->update(['status' => 'hire_requested']);
$history = $application->status_history ?: [];
$history[] = [
'status' => 'hire_requested',
'timestamp' => now()->toIso8601String(),
'notes' => 'Hire request initiated by employer.',
];
$application->update(['status_history' => $history]);
}
// Notify the worker
if ($offer) {
$worker->notify(new \App\Notifications\GenericNotification(
"Direct Hiring Offer",
($user->name ?? "Employer") . " wants to hire you. Do you want to accept this job offer?",
[
'type' => 'direct_hire_request',
'offer_id' => $offer->id,
'status' => 'pending',
]
));
} else {
$worker->notify(new \App\Notifications\GenericNotification(
"Action Required: Confirm Hiring",
"Employer " . ($user->name ?? "Employer") . " wants to hire you for '" . ($application->jobPost->title ?? 'Job Post') . "'. Please confirm.",
[
'type' => 'hire_request',
'job_id' => $application->jobPost->id ?? '',
'application_id' => $application->id,
'status' => 'hire_requested',
]
));
}
return back()->with('success', 'Hire request initiated successfully! Awaiting candidate confirmation.');
}
}
}