1050 lines
46 KiB
PHP
1050 lines
46 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Api;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use Illuminate\Http\Request;
|
|
use App\Models\User;
|
|
use App\Models\Worker;
|
|
use App\Models\WorkerCategory;
|
|
use App\Models\Shortlist;
|
|
use App\Models\JobOffer;
|
|
use App\Models\JobApplication;
|
|
use App\Models\JobPost;
|
|
use App\Models\Review;
|
|
use App\Models\ProfileView;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Validator;
|
|
|
|
class EmployerWorkerController extends Controller
|
|
{
|
|
/**
|
|
* Helper to map worker DB models to API presentation format.
|
|
*/
|
|
private function formatWorker(Worker $w)
|
|
{
|
|
// Map languages with country names
|
|
$langs = ($w->id % 2 === 0) ? ['English', 'Arabic'] : (($w->id % 3 === 0) ? ['English', 'Tagalog'] : ['English', 'Hindi', 'Arabic']);
|
|
|
|
// 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 (dynamic passport status)
|
|
$emiratesIdStatus = $w->emirates_id_status;
|
|
|
|
// Exact Skills mapping: cooking, driving, childcare, cleaning, elderly care, gardening
|
|
$skillsList = ['cooking', 'driving', 'childcare', 'cleaning', 'elderly care', 'gardening'];
|
|
$mappedSkills = [
|
|
$skillsList[$w->id % 6],
|
|
$skillsList[($w->id + 2) % 6]
|
|
];
|
|
|
|
// Visa status
|
|
$visaStatus = $w->visa_status;
|
|
|
|
// Optional profile photos
|
|
$photos = [
|
|
'https://images.unsplash.com/photo-1573496359142-b8d87734a5a2?auto=format&fit=crop&q=80&w=200',
|
|
'https://images.unsplash.com/photo-1534528741775-53994a69daeb?auto=format&fit=crop&q=80&w=200',
|
|
'https://images.unsplash.com/photo-1544005313-94ddf0286df2?auto=format&fit=crop&q=80&w=200',
|
|
null
|
|
];
|
|
$photo = $photos[$w->id % 4];
|
|
|
|
$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,
|
|
'nationality' => $w->nationality,
|
|
'photo' => $photo,
|
|
'emirates_id_status' => $emiratesIdStatus,
|
|
'passport_status' => $w->passport_status,
|
|
'skills' => $mappedSkills,
|
|
'visa_status' => $visaStatus,
|
|
'experience' => $w->experience,
|
|
'religion' => $w->religion,
|
|
'languages' => $langs,
|
|
'age' => $w->age,
|
|
'gender' => $w->gender,
|
|
'verified' => (bool)$w->verified,
|
|
'preferred_job_type' => $preferredJobType,
|
|
'bio' => $w->bio,
|
|
'rating' => $rating,
|
|
'reviews_count' => $reviewsCount,
|
|
'preferred_location' => $w->preferred_location,
|
|
'live_in_out' => $w->live_in_out,
|
|
'in_country' => (bool)$w->in_country,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* 1. GET /api/employers/workers
|
|
* List of workers available for hiring.
|
|
*/
|
|
public function getWorkers(Request $request)
|
|
{
|
|
try {
|
|
$query = Worker::with(['category', 'skills', 'documents'])
|
|
->where('status', '!=', 'Hired')
|
|
->where('status', '!=', 'hidden');
|
|
|
|
// Apply search filter if provided
|
|
if ($request->filled('search')) {
|
|
$search = $request->search;
|
|
$query->where(function($q) use ($search) {
|
|
$q->where('name', 'like', "%{$search}%")
|
|
->orWhere('nationality', 'like', "%{$search}%")
|
|
->orWhere('religion', 'like', "%{$search}%");
|
|
});
|
|
}
|
|
|
|
$dbWorkers = $query->latest()->get();
|
|
|
|
$formattedWorkers = $dbWorkers->map(function ($w) {
|
|
$isPending = str_contains(strtolower($w->passport_status), 'pending');
|
|
if ($isPending || $w->status === 'hidden' || $w->status === 'Hired') {
|
|
return null;
|
|
}
|
|
return $this->formatWorker($w);
|
|
})->filter()->values();
|
|
|
|
$workersArray = $formattedWorkers->toArray();
|
|
|
|
// Apply filters: gender, skills, language/languages, nationality, preferred_location, job_type, live_in_out, in_country, visa_status
|
|
if ($request->filled('gender')) {
|
|
$gender = strtolower($request->gender);
|
|
$workersArray = array_values(array_filter($workersArray, function ($c) use ($gender) {
|
|
return isset($c['gender']) && strtolower($c['gender']) === $gender;
|
|
}));
|
|
}
|
|
|
|
if ($request->filled('nationality')) {
|
|
$natInput = $request->nationality;
|
|
$natsArray = is_array($natInput) ? $natInput : array_filter(array_map('trim', explode(',', $natInput)));
|
|
$natsArray = array_map('strtolower', $natsArray);
|
|
|
|
$workersArray = array_values(array_filter($workersArray, 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('skills')) {
|
|
$skills = $request->skills;
|
|
$skillsArray = is_array($skills) ? $skills : array_filter(array_map('trim', explode(',', $skills)));
|
|
$skillsArray = array_map('strtolower', $skillsArray);
|
|
|
|
$workersArray = array_values(array_filter($workersArray, function ($c) use ($skillsArray) {
|
|
if (!isset($c['skills']) || !is_array($c['skills'])) return false;
|
|
foreach ($c['skills'] as $s) {
|
|
if (in_array(strtolower($s), $skillsArray)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}));
|
|
}
|
|
|
|
$langParam = $request->input('language') ?? $request->input('languages');
|
|
if ($langParam) {
|
|
$langsArray = is_array($langParam) ? $langParam : array_filter(array_map('trim', explode(',', $langParam)));
|
|
$langsArray = array_map('strtolower', $langsArray);
|
|
|
|
$workersArray = array_values(array_filter($workersArray, function ($c) use ($langsArray) {
|
|
if (!isset($c['languages']) || !is_array($c['languages'])) return false;
|
|
foreach ($c['languages'] as $l) {
|
|
if (in_array(strtolower($l), $langsArray)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}));
|
|
}
|
|
|
|
// Filter: preferred_location
|
|
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']
|
|
];
|
|
|
|
$workersArray = array_values(array_filter($workersArray, 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;
|
|
}));
|
|
}
|
|
|
|
// Filter: job_type / preferred_job_type
|
|
$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);
|
|
|
|
$workersArray = array_values(array_filter($workersArray, 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;
|
|
}));
|
|
}
|
|
|
|
// Filter: accommodation_type / accomadation_type / live_in_out
|
|
$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);
|
|
|
|
$workersArray = array_values(array_filter($workersArray, 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;
|
|
}));
|
|
}
|
|
|
|
// Filter: in country / out country (in_country / out_country)
|
|
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) {
|
|
$workersArray = array_values(array_filter($workersArray, 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) {
|
|
$workersArray = array_values(array_filter($workersArray, function ($c) use ($isOutCountry) {
|
|
return isset($c['in_country']) && (bool)$c['in_country'] !== $isOutCountry;
|
|
}));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Filter: visa_status / next_visa_type / visa_type (applicable when in country)
|
|
$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);
|
|
|
|
$workersArray = array_values(array_filter($workersArray, 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;
|
|
}));
|
|
}
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'data' => [
|
|
'workers' => $workersArray
|
|
]
|
|
], 200);
|
|
|
|
} catch (\Exception $e) {
|
|
logger()->error('Mobile API Employer Get Workers Failure: ' . $e->getMessage());
|
|
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'An error occurred while fetching the worker list.',
|
|
'error' => app()->environment('local') ? $e->getMessage() : 'Internal Server Error'
|
|
], 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 2. GET /api/employers/candidates
|
|
* List of candidates representing standard job applications and direct hiring offers.
|
|
*/
|
|
public function getCandidates(Request $request)
|
|
{
|
|
/** @var User $employer */
|
|
$employer = $request->attributes->get('employer');
|
|
|
|
try {
|
|
$employerId = $employer->id;
|
|
|
|
// Fetch Job Applications
|
|
$jobIds = JobPost::where('employer_id', $employerId)->pluck('id');
|
|
$applicationsQuery = JobApplication::whereIn('job_id', $jobIds)
|
|
->with(['worker.category', 'jobPost']);
|
|
|
|
// Fetch Direct Hiring Offers
|
|
$directOffersQuery = JobOffer::where('employer_id', $employerId)
|
|
->with('worker.category');
|
|
|
|
// Apply search filter if provided
|
|
if ($request->filled('search')) {
|
|
$search = $request->search;
|
|
$applicationsQuery->whereHas('worker', function ($q) use ($search) {
|
|
$q->where('name', 'like', "%{$search}%")
|
|
->orWhere('nationality', 'like', "%{$search}%")
|
|
->orWhere('religion', 'like', "%{$search}%");
|
|
});
|
|
$directOffersQuery->whereHas('worker', function ($q) use ($search) {
|
|
$q->where('name', 'like', "%{$search}%")
|
|
->orWhere('nationality', 'like', "%{$search}%")
|
|
->orWhere('religion', 'like', "%{$search}%");
|
|
});
|
|
}
|
|
|
|
$applications = $applicationsQuery->get();
|
|
|
|
$selectedWorkers = $applications->map(function ($app) {
|
|
$w = $app->worker;
|
|
if (!$w) return null;
|
|
|
|
$status = 'Reviewing';
|
|
if ($app->status === 'hired') $status = 'Hired';
|
|
elseif ($app->status === 'rejected') $status = 'Rejected';
|
|
elseif ($app->status === 'shortlisted' || $app->status === 'offer_sent') $status = 'Offer Sent';
|
|
elseif ($app->status === 'applied') $status = 'Reviewing';
|
|
else $status = ucfirst($app->status);
|
|
|
|
$langs = ($w->id % 2 === 0) ? ['English', 'Arabic'] : (($w->id % 3 === 0) ? ['English', 'Tagalog'] : ['English', 'Hindi', 'Arabic']);
|
|
$skillsList = ['cooking', 'driving', 'childcare', 'cleaning', 'elderly care', 'gardening'];
|
|
$mappedSkills = [
|
|
$skillsList[$w->id % 6],
|
|
$skillsList[($w->id + 2) % 6]
|
|
];
|
|
$jobTypes = ['full-time', 'part-time', 'live-in', 'live-out'];
|
|
$preferredJobType = $w->preferred_job_type ?? $jobTypes[$w->id % 4];
|
|
|
|
return [
|
|
'id' => $app->id,
|
|
'worker_id' => $w->id,
|
|
'name' => $w->name,
|
|
'nationality' => $w->nationality,
|
|
'status' => $status,
|
|
'applied_at' => $app->created_at->format('Y-m-d H:i:s'),
|
|
'type' => 'application',
|
|
'skills' => $mappedSkills,
|
|
'languages' => $langs,
|
|
'availability' => $w->availability ?? 'Immediate',
|
|
'preferred_location' => $w->preferred_location,
|
|
'preferred_job_type' => $preferredJobType,
|
|
'live_in_out' => $w->live_in_out,
|
|
'in_country' => (bool)$w->in_country,
|
|
'visa_status' => $w->visa_status,
|
|
'gender' => $w->gender,
|
|
];
|
|
})->filter()->values()->toArray();
|
|
|
|
// Fetch Direct Hiring Offers
|
|
$directOffers = $directOffersQuery->get();
|
|
|
|
$directWorkers = $directOffers->map(function ($offer) {
|
|
$w = $offer->worker;
|
|
if (!$w) return null;
|
|
|
|
$status = 'Offer Sent';
|
|
if ($offer->status === 'accepted') $status = 'Hired';
|
|
elseif ($offer->status === 'rejected') $status = 'Rejected';
|
|
elseif ($offer->status === 'pending') $status = 'Offer Sent';
|
|
|
|
$langs = ($w->id % 2 === 0) ? ['English', 'Arabic'] : (($w->id % 3 === 0) ? ['English', 'Tagalog'] : ['English', 'Hindi', 'Arabic']);
|
|
$skillsList = ['cooking', 'driving', 'childcare', 'cleaning', 'elderly care', 'gardening'];
|
|
$mappedSkills = [
|
|
$skillsList[$w->id % 6],
|
|
$skillsList[($w->id + 2) % 6]
|
|
];
|
|
$jobTypes = ['full-time', 'part-time', 'live-in', 'live-out'];
|
|
$preferredJobType = $w->preferred_job_type ?? $jobTypes[$w->id % 4];
|
|
|
|
return [
|
|
'id' => 'offer_' . $offer->id,
|
|
'worker_id' => $w->id,
|
|
'name' => $w->name,
|
|
'nationality' => $w->nationality,
|
|
'status' => $status,
|
|
'applied_at' => $offer->created_at->format('Y-m-d H:i:s'),
|
|
'type' => 'direct_offer',
|
|
'skills' => $mappedSkills,
|
|
'languages' => $langs,
|
|
'availability' => $w->availability ?? 'Immediate',
|
|
'preferred_location' => $w->preferred_location,
|
|
'preferred_job_type' => $preferredJobType,
|
|
'live_in_out' => $w->live_in_out,
|
|
'in_country' => (bool)$w->in_country,
|
|
'visa_status' => $w->visa_status,
|
|
'gender' => $w->gender,
|
|
];
|
|
})->filter()->values()->toArray();
|
|
|
|
// Merge and sort
|
|
$mergedCandidates = array_merge($selectedWorkers, $directWorkers);
|
|
|
|
// Only display Hired candidates in the candidates pipeline (exclude active states)
|
|
$mergedCandidates = array_values(array_filter($mergedCandidates, function ($c) {
|
|
return $c && $c['status'] === 'Hired';
|
|
}));
|
|
|
|
// Apply filters: gender, skills, languages, nationality, availability, preferred_location, job_type, live_in_out, in_country, visa_status
|
|
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->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('availability')) {
|
|
$availability = strtolower($request->availability);
|
|
$mergedCandidates = array_values(array_filter($mergedCandidates, function ($c) use ($availability) {
|
|
return isset($c['availability']) && strtolower($c['availability']) === $availability;
|
|
}));
|
|
}
|
|
|
|
if ($request->filled('skills')) {
|
|
$skills = $request->skills;
|
|
$skillsArray = is_array($skills) ? $skills : array_filter(array_map('trim', explode(',', $skills)));
|
|
$skillsArray = array_map('strtolower', $skillsArray);
|
|
|
|
$mergedCandidates = array_values(array_filter($mergedCandidates, function ($c) use ($skillsArray) {
|
|
if (!isset($c['skills']) || !is_array($c['skills'])) return false;
|
|
foreach ($c['skills'] as $s) {
|
|
if (in_array(strtolower($s), $skillsArray)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}));
|
|
}
|
|
|
|
if ($request->filled('languages')) {
|
|
$languages = $request->languages;
|
|
$langsArray = is_array($languages) ? $languages : array_filter(array_map('trim', explode(',', $languages)));
|
|
$langsArray = array_map('strtolower', $langsArray);
|
|
|
|
$mergedCandidates = array_values(array_filter($mergedCandidates, function ($c) use ($langsArray) {
|
|
if (!isset($c['languages']) || !is_array($c['languages'])) return false;
|
|
foreach ($c['languages'] as $l) {
|
|
if (in_array(strtolower($l), $langsArray)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}));
|
|
}
|
|
|
|
// Filter: preferred_location
|
|
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);
|
|
|
|
$mergedCandidates = array_values(array_filter($mergedCandidates, function ($c) use ($locsArray) {
|
|
if (!isset($c['preferred_location'])) return false;
|
|
$wLoc = strtolower($c['preferred_location']);
|
|
foreach ($locsArray as $l) {
|
|
if (str_contains($wLoc, $l)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}));
|
|
}
|
|
|
|
// Filter: job_type / preferred_job_type
|
|
$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;
|
|
}));
|
|
}
|
|
|
|
// Filter: accommodation_type / accomadation_type / live_in_out
|
|
$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;
|
|
}));
|
|
}
|
|
|
|
// Filter: in country / out country (in_country / out_country)
|
|
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;
|
|
}));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Filter: visa_status / next_visa_type / visa_type (applicable when in country)
|
|
$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;
|
|
}));
|
|
}
|
|
|
|
$page = (int)$request->input('page', 1);
|
|
$perPage = (int)$request->input('per_page', 15);
|
|
$total = count($mergedCandidates);
|
|
$offset = ($page - 1) * $perPage;
|
|
$paginatedCandidates = array_slice($mergedCandidates, $offset, $perPage);
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'data' => [
|
|
'candidates' => $paginatedCandidates,
|
|
'pagination' => [
|
|
'total' => $total,
|
|
'per_page' => $perPage,
|
|
'current_page' => $page,
|
|
'last_page' => max(1, (int)ceil($total / $perPage)),
|
|
]
|
|
]
|
|
], 200);
|
|
|
|
} catch (\Exception $e) {
|
|
logger()->error('Mobile API Employer Get Candidates Failure: ' . $e->getMessage());
|
|
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'An error occurred while fetching the candidate list.',
|
|
'error' => app()->environment('local') ? $e->getMessage() : 'Internal Server Error'
|
|
], 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 3. POST /api/employers/candidates/hire or POST /api/employers/candidates/{id}/hire
|
|
* Update status from active (or reviewing/pending) to hired.
|
|
*/
|
|
public function hireCandidate(Request $request, $id = null)
|
|
{
|
|
/** @var User $employer */
|
|
$employer = $request->attributes->get('employer');
|
|
|
|
// Let the client pass either route parameter id OR body parameter identifier
|
|
$targetId = $id ?: $request->input('id') ?: $request->input('worker_id') ?: $request->input('application_id') ?: $request->input('offer_id');
|
|
|
|
if (!$targetId) {
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'Candidate or Worker identifier is required.'
|
|
], 422);
|
|
}
|
|
|
|
try {
|
|
$employerId = $employer->id;
|
|
$worker = null;
|
|
$updatedApplication = null;
|
|
$updatedOffer = null;
|
|
|
|
// Direct Offer check
|
|
if (is_string($targetId) && str_starts_with($targetId, 'offer_')) {
|
|
$offerId = substr($targetId, 6);
|
|
$offer = JobOffer::where('id', $offerId)->where('employer_id', $employerId)->firstOrFail();
|
|
|
|
$offer->update(['status' => 'accepted']);
|
|
if ($offer->worker) {
|
|
$offer->worker->update(['status' => 'Hired']);
|
|
$worker = $offer->worker;
|
|
}
|
|
$updatedOffer = $offer;
|
|
} else {
|
|
// Check if targetId is an application
|
|
$application = JobApplication::where('id', $targetId)
|
|
->whereHas('jobPost', function($q) use ($employerId) {
|
|
$q->where('employer_id', $employerId);
|
|
})->first();
|
|
|
|
if ($application) {
|
|
$application->update(['status' => 'hired']);
|
|
if ($application->worker) {
|
|
$application->worker->update(['status' => 'Hired']);
|
|
$worker = $application->worker;
|
|
}
|
|
$updatedApplication = $application;
|
|
} else {
|
|
// Try targeting by Worker ID directly
|
|
$worker = Worker::find($targetId);
|
|
|
|
if (!$worker) {
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'Worker, application, or job offer not found with the provided identifier.'
|
|
], 404);
|
|
}
|
|
|
|
$worker->update(['status' => 'Hired']);
|
|
|
|
// Find existing direct offer
|
|
$offer = JobOffer::where('employer_id', $employerId)
|
|
->where('worker_id', $worker->id)
|
|
->first();
|
|
|
|
if ($offer) {
|
|
$offer->update(['status' => 'accepted']);
|
|
$updatedOffer = $offer;
|
|
} else {
|
|
// Find existing application
|
|
$jobIds = JobPost::where('employer_id', $employerId)->pluck('id');
|
|
$application = JobApplication::whereIn('job_id', $jobIds)
|
|
->where('worker_id', $worker->id)
|
|
->first();
|
|
|
|
if ($application) {
|
|
$application->update(['status' => 'hired']);
|
|
$updatedApplication = $application;
|
|
} else {
|
|
// Create a new direct job offer and accept it
|
|
$updatedOffer = 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 finalized via mobile API.',
|
|
'status' => 'accepted',
|
|
]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'message' => 'Candidate successfully marked as Hired!',
|
|
'data' => [
|
|
'worker_id' => $worker ? $worker->id : null,
|
|
'worker_status' => $worker ? $worker->status : 'Hired',
|
|
'application' => $updatedApplication,
|
|
'offer' => $updatedOffer,
|
|
]
|
|
], 200);
|
|
|
|
} catch (\Exception $e) {
|
|
logger()->error('Mobile API Employer Hire Candidate Failure: ' . $e->getMessage());
|
|
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'An error occurred while marking the candidate as hired.',
|
|
'error' => app()->environment('local') ? $e->getMessage() : 'Internal Server Error'
|
|
], 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 4. GET /api/employers/workers/{id}
|
|
* Get detailed worker profile, mirroring the web detail view.
|
|
*/
|
|
public function getWorkerDetail(Request $request, $id)
|
|
{
|
|
/** @var User $employer */
|
|
$employer = $request->attributes->get('employer');
|
|
$w = Worker::with(['category', 'skills', 'documents'])->find($id);
|
|
|
|
if (!$w) {
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'Worker profile not found.'
|
|
], 404);
|
|
}
|
|
|
|
try {
|
|
$isPending = str_contains(strtolower($w->passport_status), 'pending');
|
|
if ($w->status === 'hidden' || $isPending) {
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'Worker profile not found.'
|
|
], 404);
|
|
}
|
|
|
|
// Record employer profile view (Requirement 3)
|
|
if ($employer) {
|
|
ProfileView::updateOrCreate(
|
|
['employer_id' => $employer->id, 'worker_id' => $w->id],
|
|
['updated_at' => now()]
|
|
);
|
|
}
|
|
|
|
// Map languages
|
|
$langs = ($w->id % 2 === 0) ? ['English', 'Arabic'] : (($w->id % 3 === 0) ? ['English', 'Tagalog'] : ['English', 'Hindi', 'Arabic']);
|
|
|
|
// Preferred job types
|
|
$jobTypes = ['full-time', 'part-time', 'live-in', 'live-out'];
|
|
$preferredJobType = $jobTypes[$w->id % 4];
|
|
|
|
// Emirates ID status
|
|
$emiratesIdStatus = $w->verified ? 'Emirates ID Verified' : 'EID Verification Pending';
|
|
|
|
// Skills mapping
|
|
$skillsList = ['cooking', 'driving', 'childcare', 'cleaning', 'elderly care', 'gardening'];
|
|
$mappedSkills = [
|
|
$skillsList[$w->id % 6],
|
|
$skillsList[($w->id + 2) % 6]
|
|
];
|
|
|
|
// Visa status
|
|
$visaStatusesList = ['Residence Visa', 'Tourist Visa', 'Employment Visa', 'Cancelled Visa', 'Own Visa'];
|
|
$visaStatus = $visaStatusesList[$w->id % 5];
|
|
|
|
// Profile photo
|
|
$photos = [
|
|
'https://images.unsplash.com/photo-1573496359142-b8d87734a5a2?auto=format&fit=crop&q=80&w=200',
|
|
'https://images.unsplash.com/photo-1534528741775-53994a69daeb?auto=format&fit=crop&q=80&w=200',
|
|
'https://images.unsplash.com/photo-1544005313-94ddf0286df2?auto=format&fit=crop&q=80&w=200',
|
|
null
|
|
];
|
|
$photo = $photos[$w->id % 4];
|
|
|
|
// 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);
|
|
if ($reviewsCount > 0) {
|
|
$rating = round($dbReviews->avg('rating'), 1);
|
|
} else {
|
|
$rating = 0.0;
|
|
$reviewsCount = 0;
|
|
$reviews = [];
|
|
}
|
|
|
|
// Similar workers matching web view
|
|
$simDb = Worker::with('category')
|
|
->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,
|
|
'rating' => 4.7,
|
|
'verified' => (bool)$sw->verified,
|
|
];
|
|
})->filter()->values()->toArray();
|
|
|
|
// Check if there is an existing conversation between this employer and this worker
|
|
$conversation = \App\Models\Conversation::where('employer_id', $employer->id)
|
|
->where('worker_id', $w->id)
|
|
->first();
|
|
|
|
$workerProfile = [
|
|
'id' => $w->id,
|
|
'name' => $w->name,
|
|
'nationality' => $w->nationality,
|
|
'photo' => $photo,
|
|
'emirates_id_status' => $emiratesIdStatus,
|
|
'skills' => $mappedSkills,
|
|
'visa_status' => $visaStatus,
|
|
'experience' => $w->experience,
|
|
'experience_years' => 5,
|
|
'religion' => $w->religion,
|
|
'languages' => $langs,
|
|
'age' => $w->age,
|
|
'gender' => $w->gender,
|
|
'verified' => (bool)$w->verified,
|
|
'preferred_job_type' => $preferredJobType,
|
|
'bio' => $w->bio,
|
|
'rating' => $rating,
|
|
'reviews_count' => $reviewsCount,
|
|
'reviews' => $reviews,
|
|
'similar_workers' => $similarWorkers,
|
|
'conversation_id' => $conversation ? $conversation->id : null,
|
|
];
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'data' => [
|
|
'worker' => $workerProfile
|
|
]
|
|
], 200);
|
|
|
|
} catch (\Exception $e) {
|
|
logger()->error('Mobile API Employer Get Worker Detail Failure: ' . $e->getMessage());
|
|
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'An error occurred while fetching the worker profile.',
|
|
'error' => app()->environment('local') ? $e->getMessage() : 'Internal Server Error'
|
|
], 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* GET /api/employers/shortlist
|
|
* Retrieve the list of shortlisted workers for the authenticated employer.
|
|
*/
|
|
public function getShortlist(Request $request)
|
|
{
|
|
/** @var User $employer */
|
|
$employer = $request->attributes->get('employer');
|
|
|
|
try {
|
|
$page = (int)$request->input('page', 1);
|
|
$perPage = (int)$request->input('per_page', 15);
|
|
|
|
$shortlists = Shortlist::where('employer_id', $employer->id)
|
|
->with(['worker.category', 'worker.skills'])
|
|
->get();
|
|
|
|
$formattedWorkers = $shortlists->map(function ($s) {
|
|
if ($s->worker) {
|
|
return $this->formatWorker($s->worker);
|
|
}
|
|
return null;
|
|
})->filter()->values();
|
|
|
|
$workersArray = $formattedWorkers->toArray();
|
|
$total = count($workersArray);
|
|
$offset = ($page - 1) * $perPage;
|
|
$paginatedWorkers = array_slice($workersArray, $offset, $perPage);
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'data' => [
|
|
'workers' => $paginatedWorkers,
|
|
'pagination' => [
|
|
'total' => $total,
|
|
'per_page' => $perPage,
|
|
'current_page' => $page,
|
|
'last_page' => max(1, (int)ceil($total / $perPage)),
|
|
]
|
|
]
|
|
], 200);
|
|
|
|
} catch (\Exception $e) {
|
|
logger()->error('Mobile API Employer Get Shortlist Failure: ' . $e->getMessage());
|
|
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'An error occurred while fetching the shortlist.',
|
|
'error' => app()->environment('local') ? $e->getMessage() : 'Internal Server Error'
|
|
], 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* POST /api/employers/shortlist
|
|
* Toggle a worker's shortlist status for the authenticated employer.
|
|
*/
|
|
public function toggleShortlist(Request $request)
|
|
{
|
|
/** @var User $employer */
|
|
$employer = $request->attributes->get('employer');
|
|
|
|
$validator = Validator::make($request->all(), [
|
|
'worker_id' => 'required|exists:workers,id',
|
|
]);
|
|
|
|
if ($validator->fails()) {
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'Validation error.',
|
|
'errors' => $validator->errors()
|
|
], 422);
|
|
}
|
|
|
|
try {
|
|
$workerId = $request->worker_id;
|
|
|
|
$existing = Shortlist::where('employer_id', $employer->id)
|
|
->where('worker_id', $workerId)
|
|
->first();
|
|
|
|
if ($existing) {
|
|
$existing->delete();
|
|
$action = 'removed';
|
|
} else {
|
|
Shortlist::create([
|
|
'employer_id' => $employer->id,
|
|
'worker_id' => $workerId,
|
|
]);
|
|
$action = 'added';
|
|
}
|
|
|
|
$shortlistedIds = Shortlist::where('employer_id', $employer->id)->pluck('worker_id')->toArray();
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'message' => "Worker successfully {$action} from shortlist.",
|
|
'data' => [
|
|
'action' => $action,
|
|
'shortlisted_ids' => $shortlistedIds
|
|
]
|
|
], 200);
|
|
|
|
} catch (\Exception $e) {
|
|
logger()->error('Mobile API Employer Toggle Shortlist Failure: ' . $e->getMessage());
|
|
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'An error occurred while toggling the shortlist.',
|
|
'error' => app()->environment('local') ? $e->getMessage() : 'Internal Server Error'
|
|
], 500);
|
|
}
|
|
}
|
|
}
|
|
|