applicants filter option

This commit is contained in:
mohanmd 2026-07-08 16:00:17 +05:30
parent 166e7393c6
commit 74e6a562f0
4 changed files with 382 additions and 1 deletions

View File

@ -688,6 +688,140 @@ public function employerJobApplicants(Request $request, $id)
$query->where('status', strtolower($request->status)); $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'); $sortBy = $request->input('sort_by', 'created_at');
$sortOrder = $request->input('sort_order', 'desc'); $sortOrder = $request->input('sort_order', 'desc');

View File

@ -70,6 +70,7 @@ public function updateProfile(Request $request)
$validator = Validator::make($request->all(), [ $validator = Validator::make($request->all(), [
'name' => 'nullable|string|max:255', 'name' => 'nullable|string|max:255',
'phone' => 'nullable|string|max:50|unique:workers,phone,' . $worker->id, 'phone' => 'nullable|string|max:50|unique:workers,phone,' . $worker->id,
'main_profession' => 'nullable|string|max:100',
'age' => [ 'age' => [
'nullable', 'nullable',
function ($attribute, $value, $fail) { function ($attribute, $value, $fail) {
@ -154,7 +155,8 @@ function ($attribute, $value, $fail) {
'gender', 'gender',
'live_in_out', 'live_in_out',
'preferred_location', 'preferred_location',
'fcm_token' 'fcm_token',
'main_profession'
]); ]);
// Filter out null inputs if we want to support partial updates // Filter out null inputs if we want to support partial updates

View File

@ -7102,6 +7102,141 @@
"schema": { "schema": {
"type": "integer" "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": { "responses": {

View File

@ -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() public function test_employer_job_apis_and_access_restrictions()
{ {
// 1. List jobs via API (premium plan by default) // 1. List jobs via API (premium plan by default)