From 74e6a562f05161797c389211bfe21536726289ba Mon Sep 17 00:00:00 2001 From: mohanmd Date: Wed, 8 Jul 2026 16:00:17 +0530 Subject: [PATCH] applicants filter option --- .../Controllers/Api/WorkerJobController.php | 134 +++++++++++++++++ .../Api/WorkerProfileController.php | 4 +- public/swagger.json | 135 ++++++++++++++++++ tests/Feature/WorkerJobApiTest.php | 110 ++++++++++++++ 4 files changed, 382 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/Api/WorkerJobController.php b/app/Http/Controllers/Api/WorkerJobController.php index f883ad9..284e54c 100644 --- a/app/Http/Controllers/Api/WorkerJobController.php +++ b/app/Http/Controllers/Api/WorkerJobController.php @@ -688,6 +688,140 @@ public function employerJobApplicants(Request $request, $id) $query->where('status', strtolower($request->status)); } + // Apply filters based on worker attributes + $query->whereHas('worker', function($q) use ($request) { + // Gender filter + if ($request->filled('gender')) { + $q->where('gender', strtolower($request->gender)); + } + + // Nationality filter + if ($request->filled('nationality')) { + $natInput = $request->nationality; + $natsArray = is_array($natInput) ? $natInput : array_filter(array_map('trim', explode(',', $natInput))); + if (!empty($natsArray)) { + $q->where(function($subQ) use ($natsArray) { + foreach ($natsArray as $n) { + $subQ->orWhere('nationality', 'like', "%{$n}%"); + } + }); + } + } + + // Main Profession filter + if ($request->filled('main_profession')) { + $q->where('main_profession', strtolower($request->main_profession)); + } + + // Age filter (integer or range e.g. "18-25") + if ($request->filled('age')) { + $ageVal = $request->age; + if (str_contains($ageVal, '-')) { + [$minAge, $maxAge] = array_map('intval', explode('-', $ageVal)); + $q->whereBetween('age', [$minAge, $maxAge]); + } else { + $q->where('age', $ageVal); + } + } + + // Experience filter + if ($request->filled('experience')) { + $q->where('experience', 'like', "%{$request->experience}%"); + } + + // Salary range filter + if ($request->filled('salary_min')) { + $q->where('salary', '>=', (float)$request->salary_min); + } + if ($request->filled('salary_max')) { + $q->where('salary', '<=', (float)$request->salary_max); + } + + // Preferred location filter + if ($request->filled('preferred_location')) { + $prefLoc = $request->preferred_location; + $locsArray = is_array($prefLoc) ? $prefLoc : array_filter(array_map('trim', explode(',', $prefLoc))); + if (!empty($locsArray)) { + $q->where(function($subQ) use ($locsArray) { + foreach ($locsArray as $l) { + $subQ->orWhere('preferred_location', 'like', "%{$l}%"); + } + }); + } + } + + // Job type filter (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))); + if (!empty($jobTypesArray)) { + $q->where(function($subQ) use ($jobTypesArray) { + foreach ($jobTypesArray as $jt) { + $subQ->orWhere('preferred_job_type', 'like', "%{$jt}%"); + } + }); + } + } + + // Accommodation filter (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))); + if (!empty($accsArray)) { + $q->where(function($subQ) use ($accsArray) { + foreach ($accsArray as $a) { + $normA = str_replace(['_', '-'], ' ', $a); + $subQ->orWhere('live_in_out', 'like', "%{$normA}%"); + } + }); + } + } + + // In country filter + 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) { + $q->where('in_country', $isInCountry); + } + } + } + + // Visa status filter + $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))); + if (!empty($visasArray)) { + $q->where(function($subQ) use ($visasArray) { + foreach ($visasArray as $v) { + $subQ->orWhere('visa_status', 'like', "%{$v}%"); + } + }); + } + } + + // Skills filter + if ($request->filled('skills')) { + $skills = $request->skills; + $skillsArray = is_array($skills) ? $skills : array_filter(array_map('trim', explode(',', $skills))); + if (!empty($skillsArray)) { + $q->whereHas('skills', function($skillQ) use ($skillsArray) { + $skillQ->whereIn('name', $skillsArray) + ->orWhereIn('id', array_filter($skillsArray, 'is_numeric')); + }); + } + } + }); + $sortBy = $request->input('sort_by', 'created_at'); $sortOrder = $request->input('sort_order', 'desc'); diff --git a/app/Http/Controllers/Api/WorkerProfileController.php b/app/Http/Controllers/Api/WorkerProfileController.php index dbf7e5b..decb880 100644 --- a/app/Http/Controllers/Api/WorkerProfileController.php +++ b/app/Http/Controllers/Api/WorkerProfileController.php @@ -70,6 +70,7 @@ public function updateProfile(Request $request) $validator = Validator::make($request->all(), [ 'name' => 'nullable|string|max:255', 'phone' => 'nullable|string|max:50|unique:workers,phone,' . $worker->id, + 'main_profession' => 'nullable|string|max:100', 'age' => [ 'nullable', function ($attribute, $value, $fail) { @@ -154,7 +155,8 @@ function ($attribute, $value, $fail) { 'gender', 'live_in_out', 'preferred_location', - 'fcm_token' + 'fcm_token', + 'main_profession' ]); // Filter out null inputs if we want to support partial updates diff --git a/public/swagger.json b/public/swagger.json index 9a9f2ad..78a437c 100644 --- a/public/swagger.json +++ b/public/swagger.json @@ -7102,6 +7102,141 @@ "schema": { "type": "integer" } + }, + { + "name": "search", + "in": "query", + "description": "Search term matching worker name or nationality.", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "status", + "in": "query", + "description": "Filter by job application status (applied, shortlisted, hired, etc.).", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "gender", + "in": "query", + "description": "Filter by worker gender (male, female, other).", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "nationality", + "in": "query", + "description": "Filter by worker nationality (comma-separated list or array).", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "main_profession", + "in": "query", + "description": "Filter by worker main profession.", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "age", + "in": "query", + "description": "Filter by worker age (exact number or range, e.g. 18-25).", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "experience", + "in": "query", + "description": "Filter by worker experience.", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "salary_min", + "in": "query", + "description": "Filter by worker minimum expected monthly salary.", + "required": false, + "schema": { + "type": "number" + } + }, + { + "name": "salary_max", + "in": "query", + "description": "Filter by worker maximum expected monthly salary.", + "required": false, + "schema": { + "type": "number" + } + }, + { + "name": "preferred_location", + "in": "query", + "description": "Filter by worker preferred location (comma-separated list or array).", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "job_type", + "in": "query", + "description": "Filter by worker preferred job type (full-time, part-time, contract, etc.).", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "live_in_out", + "in": "query", + "description": "Filter by worker accommodation type (live_in, live_out).", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "in_country", + "in": "query", + "description": "Filter by worker presence in country (true/false, yes/no).", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "visa_status", + "in": "query", + "description": "Filter by worker visa status (Tourist Visa, Residence Visa, etc.).", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "skills", + "in": "query", + "description": "Filter by worker skills (comma-separated names or IDs).", + "required": false, + "schema": { + "type": "string" + } } ], "responses": { diff --git a/tests/Feature/WorkerJobApiTest.php b/tests/Feature/WorkerJobApiTest.php index 2260bb4..809f0f3 100644 --- a/tests/Feature/WorkerJobApiTest.php +++ b/tests/Feature/WorkerJobApiTest.php @@ -240,6 +240,116 @@ public function test_worker_job_apis() ]); } + public function test_employer_view_applicants_filtering() + { + // 1. Create a job post + $job = JobPost::create([ + 'employer_id' => $this->employer->id, + 'title' => 'Filter Test Job', + 'main_profession' => 'Plumber', + 'location' => 'Dubai', + 'salary' => 2000, + 'workers_needed' => 5, + 'job_type' => 'Full Time', + 'start_date' => now()->addDays(5)->format('Y-m-d'), + 'description' => 'Filter test job description', + 'status' => 'active', + ]); + + // 2. Create worker A: Male, Indian, Salary 2000, Age 25, Experience 2 Years + $workerA = Worker::create([ + 'name' => 'Worker A', + 'email' => 'workera@example.com', + 'password' => bcrypt('password'), + 'phone' => '971500000010', + 'nationality' => 'Indian', + 'gender' => 'male', + 'age' => 25, + 'salary' => 2000, + 'experience' => '2 Years', + 'status' => 'active', + 'api_token' => 'worker_a_token', + 'availability' => 'Immediate', + 'religion' => 'Christian', + 'bio' => 'Bio info', + 'passport_status' => 'valid', + 'main_profession' => 'Plumber', + ]); + + // 3. Create worker B: Female, Filipino, Salary 3000, Age 30, Experience 5 Years + $workerB = Worker::create([ + 'name' => 'Worker B', + 'email' => 'workerb@example.com', + 'password' => bcrypt('password'), + 'phone' => '971500000020', + 'nationality' => 'Filipino', + 'gender' => 'female', + 'age' => 30, + 'salary' => 3000, + 'experience' => '5 Years', + 'status' => 'active', + 'api_token' => 'worker_b_token', + 'availability' => 'Immediate', + 'religion' => 'Christian', + 'bio' => 'Bio info', + 'passport_status' => 'valid', + 'main_profession' => 'Plumber', + ]); + + // 4. Submit applications + JobApplication::create([ + 'job_id' => $job->id, + 'worker_id' => $workerA->id, + 'status' => 'applied' + ]); + + JobApplication::create([ + 'job_id' => $job->id, + 'worker_id' => $workerB->id, + 'status' => 'applied' + ]); + + // Test filter by gender=female (should return only Worker B) + $response = $this->getJson("/api/employers/jobs/{$job->id}/applicants?gender=female", [ + 'Authorization' => 'Bearer employer_api_token' + ]); + $response->assertStatus(200); + $response->assertJsonCount(1, 'data.applicants'); + $response->assertJsonPath('data.applicants.0.worker.name', 'Worker B'); + + // Test filter by nationality=Indian (should return only Worker A) + $response = $this->getJson("/api/employers/jobs/{$job->id}/applicants?nationality=Indian", [ + 'Authorization' => 'Bearer employer_api_token' + ]); + $response->assertStatus(200); + $response->assertJsonCount(1, 'data.applicants'); + $response->assertJsonPath('data.applicants.0.worker.name', 'Worker A'); + + // Test filter by salary_max=2500 (should return only Worker A) + $response = $this->getJson("/api/employers/jobs/{$job->id}/applicants?salary_max=2500", [ + 'Authorization' => 'Bearer employer_api_token' + ]); + $response->assertStatus(200); + $response->assertJsonCount(1, 'data.applicants'); + $response->assertJsonPath('data.applicants.0.worker.name', 'Worker A'); + + // Test filter by age range "28-35" (should return only Worker B) + $response = $this->getJson("/api/employers/jobs/{$job->id}/applicants?age=28-35", [ + 'Authorization' => 'Bearer employer_api_token' + ]); + $response->assertStatus(200); + $response->assertJsonCount(1, 'data.applicants'); + $response->assertJsonPath('data.applicants.0.worker.name', 'Worker B'); + + // Test filter by experience="5 Years" (should return only Worker B) + $response = $this->getJson("/api/employers/jobs/{$job->id}/applicants?experience=5%20Years", [ + 'Authorization' => 'Bearer employer_api_token' + ]); + $response->assertStatus(200); + $response->assertJsonCount(1, 'data.applicants'); + $response->assertJsonPath('data.applicants.0.worker.name', 'Worker B'); + } + public function test_employer_job_apis_and_access_restrictions() { // 1. List jobs via API (premium plan by default)