migrant-web/app/Http/Controllers/Api/EmployerWorkerController.php
2026-05-29 17:49:37 +05:30

360 lines
14 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 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 = $jobTypes[$w->id % 4];
// Availability status: Active / Hidden / Hired
$availabilityStatus = ucfirst($w->status === 'active' ? 'Active' : ($w->status === 'hidden' ? 'Hidden' : ($w->status === 'Hired' ? 'Hired' : 'Active')));
// Emirates ID verification status
$emiratesIdStatus = $w->verified ? 'Emirates ID Verified' : 'EID Vetting Pending';
// 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
$visaStatusesList = ['Residence Visa', 'Tourist Visa', 'Employment Visa', 'Cancelled Visa', 'Own Visa'];
$visaStatus = $visaStatusesList[$w->id % 5];
// 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];
$rating = 4.0 + (($w->id * 3) % 10) / 10.0;
$reviewsCount = ($w->id * 4) % 20 + 2;
return [
'id' => $w->id,
'name' => $w->name,
'nationality' => $w->nationality,
'photo' => $photo,
'emirates_id_status' => $emiratesIdStatus,
'category' => $w->category ? $w->category->name : 'Domestic Worker',
'skills' => $mappedSkills,
'availability_status' => $availabilityStatus,
'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,
'bio' => $w->bio,
'rating' => $rating,
'reviews_count' => $reviewsCount,
];
}
/**
* 1. GET /api/employers/workers
* List of workers available for hiring.
*/
public function getWorkers(Request $request)
{
try {
$query = Worker::with(['category', 'skills'])
->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}%");
});
}
// Apply category filter if provided
if ($request->filled('category_id')) {
$query->where('category_id', $request->category_id);
}
// Apply nationality filter if provided
if ($request->filled('nationality')) {
$query->where('nationality', $request->nationality);
}
// Apply salary filters
if ($request->filled('min_salary')) {
$query->where('salary', '>=', $request->min_salary);
}
if ($request->filled('max_salary')) {
$query->where('salary', '<=', $request->max_salary);
}
$dbWorkers = $query->latest()->get();
$formattedWorkers = $dbWorkers->map(function ($w) {
return $this->formatWorker($w);
});
return response()->json([
'success' => true,
'data' => [
'workers' => $formattedWorkers
]
], 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');
$applications = JobApplication::whereIn('job_id', $jobIds)
->with(['worker.category', 'jobPost'])
->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);
return [
'id' => $app->id,
'worker_id' => $w->id,
'name' => $w->name,
'nationality' => $w->nationality,
'category' => $w->category ? $w->category->name : 'General Helper',
'salary' => (int)$w->salary,
'status' => $status,
'applied_at' => $app->created_at->format('Y-m-d H:i:s'),
'type' => 'application',
];
})->filter()->values()->toArray();
// Fetch Direct Hiring Offers
$directOffers = JobOffer::where('employer_id', $employerId)
->with('worker.category')
->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';
return [
'id' => 'offer_' . $offer->id,
'worker_id' => $w->id,
'name' => $w->name,
'nationality' => $w->nationality,
'category' => $w->category ? $w->category->name : 'General Helper',
'salary' => (int)$offer->salary,
'status' => $status,
'applied_at' => $offer->created_at->format('Y-m-d H:i:s'),
'type' => 'direct_offer',
];
})->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';
}));
return response()->json([
'success' => true,
'data' => [
'candidates' => $mergedCandidates
]
], 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);
}
}
}