From 25510d1b5e7ab2744455a20890fb5ee36b8a1799 Mon Sep 17 00:00:00 2001 From: mohanmd Date: Tue, 23 Jun 2026 18:45:47 +0530 Subject: [PATCH 1/4] employer dashboard worker count --- .../Api/EmployerProfileController.php | 3 + .../Api/EmployerWorkerController.php | 78 ++++++++-- .../Controllers/Employer/WorkerController.php | 1 - public/swagger.json | 142 +++--------------- tests/Feature/EmployerProfileApiTest.php | 47 ++++++ tests/Feature/EmployerWorkerFilterApiTest.php | 83 ++++++++++ 6 files changed, 224 insertions(+), 130 deletions(-) diff --git a/app/Http/Controllers/Api/EmployerProfileController.php b/app/Http/Controllers/Api/EmployerProfileController.php index ce4980d..322acd0 100644 --- a/app/Http/Controllers/Api/EmployerProfileController.php +++ b/app/Http/Controllers/Api/EmployerProfileController.php @@ -309,6 +309,8 @@ public function getDashboard(Request $request) $savedCandidates = \App\Models\Shortlist::where('employer_id', $employer->id)->count(); + $totalWorkers = \App\Models\Worker::where('status', 'active')->count(); + // Resolve plan $sub = \Illuminate\Support\Facades\DB::table('subscriptions') ->where('user_id', $employer->id) @@ -375,6 +377,7 @@ public function getDashboard(Request $request) 'contacted_workers_count' => $contactedWorkersCount, 'total_hired_workers' => $totalHiredWorkers, 'saved_candidates' => $savedCandidates, + 'total_workers' => $totalWorkers, ], 'current_plan' => $currentPlan, 'recent_announcements' => $recentAnnouncements, diff --git a/app/Http/Controllers/Api/EmployerWorkerController.php b/app/Http/Controllers/Api/EmployerWorkerController.php index 54649e5..4db4198 100644 --- a/app/Http/Controllers/Api/EmployerWorkerController.php +++ b/app/Http/Controllers/Api/EmployerWorkerController.php @@ -44,6 +44,7 @@ private function formatWorker(Worker $w) return [ 'id' => $w->id, 'name' => $w->name, + 'email' => $w->email, 'phone' => $w->phone, 'nationality' => $w->nationality, 'age' => $w->age, @@ -57,6 +58,8 @@ private function formatWorker(Worker $w) 'language' => $w->language, 'languages' => $langs, 'preferred_location' => $w->preferred_location, + 'country' => $w->country, + 'city' => $w->city, 'area' => $w->area, 'live_in_out' => $w->live_in_out, 'gender' => $w->gender, @@ -71,7 +74,6 @@ private function formatWorker(Worker $w) 'visa_expiry_date' => $w->visa_expiry_date, 'bio' => $w->bio, 'photo' => $photo, - 'religion' => $w->religion, 'rating' => $rating, 'reviews_count' => $reviewsCount, 'skills' => $w->skills->map(function ($s) { @@ -88,6 +90,11 @@ private function formatWorker(Worker $w) ]; })->toArray(), 'documents' => $w->documents->map(function ($doc) { + $ocrData = $doc->ocr_data; + if ($doc->type === 'visa' && is_array($ocrData)) { + unset($ocrData['sponsor']); + unset($ocrData['valid_until']); + } return [ 'id' => $doc->id, 'worker_id' => $doc->worker_id, @@ -97,7 +104,7 @@ private function formatWorker(Worker $w) '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, + 'ocr_data' => $ocrData, 'created_at' => $doc->created_at?->toISOString(), 'updated_at' => $doc->updated_at?->toISOString(), ]; @@ -976,25 +983,74 @@ public function getWorkerDetail(Request $request, $id) $workerProfile = [ 'id' => $w->id, 'name' => $w->name, + 'email' => $w->email, + 'phone' => $w->phone, '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, + 'salary' => $w->salary, + 'experience' => $w->experience, 'verified' => (bool)$w->verified, + 'status' => $w->status, + 'created_at' => $w->created_at?->toISOString(), + 'updated_at' => $w->updated_at?->toISOString(), + 'deleted_at' => $w->deleted_at?->toISOString(), + 'language' => $w->language, + 'languages' => $langs, + 'preferred_location' => $w->preferred_location, + 'country' => $w->country, + 'city' => $w->city, + 'area' => $w->area, + 'live_in_out' => $w->live_in_out, + 'gender' => $w->gender, + 'in_country' => (bool)$w->in_country, + 'visa_status' => $visaStatus, 'preferred_job_type' => $preferredJobType, + 'fcm_token' => $w->fcm_token, + 'passport_status' => $w->passport_status, + 'emirates_id_status' => $emiratesIdStatus, + 'document_expiry_days' => $w->document_expiry_days, + 'document_expiry_status' => $w->document_expiry_status, + 'visa_expiry_date' => $w->visa_expiry_date, 'bio' => $w->bio, + 'photo' => $photo, 'rating' => $rating, 'reviews_count' => $reviewsCount, 'reviews' => $reviews, 'similar_workers' => $similarWorkers, 'conversation_id' => $conversation ? $conversation->id : null, + 'skills' => $w->skills->map(function ($s) { + return [ + 'id' => $s->id, + 'name' => $s->name, + 'created_at' => $s->created_at?->toISOString(), + 'updated_at' => $s->updated_at?->toISOString(), + 'image_path' => $s->image_path, + 'pivot' => [ + 'worker_id' => $s->pivot->worker_id, + 'skill_id' => $s->pivot->skill_id, + ] + ]; + })->toArray(), + 'documents' => $w->documents->map(function ($doc) { + $ocrData = $doc->ocr_data; + if ($doc->type === 'visa' && is_array($ocrData)) { + unset($ocrData['sponsor']); + unset($ocrData['valid_until']); + } + return [ + 'id' => $doc->id, + 'worker_id' => $doc->worker_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' => $ocrData, + 'created_at' => $doc->created_at?->toISOString(), + 'updated_at' => $doc->updated_at?->toISOString(), + ]; + })->toArray(), ]; return response()->json([ diff --git a/app/Http/Controllers/Employer/WorkerController.php b/app/Http/Controllers/Employer/WorkerController.php index 2632dc4..e2c0371 100644 --- a/app/Http/Controllers/Employer/WorkerController.php +++ b/app/Http/Controllers/Employer/WorkerController.php @@ -370,7 +370,6 @@ public function show($id) 'skills' => $mappedSkills, 'visa_status' => $visaStatus, 'experience' => $w->experience, - 'experience_years' => 5, 'salary' => (int) $w->salary, 'religion' => $w->religion, 'languages' => $langs, diff --git a/public/swagger.json b/public/swagger.json index 0a6834f..adc1825 100644 --- a/public/swagger.json +++ b/public/swagger.json @@ -4198,123 +4198,33 @@ "type": "object", "properties": { "worker": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "example": 1 + "allOf": [ + { + "$ref": "#/components/schemas/Worker" }, - "name": { - "type": "string", - "example": "Jane Doe" - }, - "nationality": { - "type": "string", - "example": "Filipino" - }, - "photo": { - "type": "string", - "example": "https://images.unsplash.com/photo-1573496359142-b8d87734a5a2?auto=format&fit=crop&q=80&w=200" - }, - "emirates_id_status": { - "type": "string", - "example": "Passport Verified", - "description": "Deprecated - Use passport_status instead" - }, - "passport_status": { - "type": "string", - "example": "Passport Verified" - }, - "category": { - "type": "string", - "example": "Domestic Worker" - }, - "skills": { - "type": "array", - "items": { - "type": "string" - }, - "example": [ - "childcare", - "cooking" - ] - }, - "availability_status": { - "type": "string", - "example": "Active" - }, - "visa_status": { - "type": "string", - "example": "Residence Visa" - }, - "experience": { - "type": "string", - "example": "5 Years" - }, - "experience_years": { - "type": "integer", - "example": 5 - }, - "salary": { - "type": "integer", - "example": 2500 - }, - "religion": { - "type": "string", - "example": "Christian" - }, - "languages": { - "type": "array", - "items": { - "type": "string" - }, - "example": [ - "English", - "Tagalog" - ] - }, - "age": { - "type": "integer", - "example": 28 - }, - "verified": { - "type": "boolean", - "example": true - }, - "preferred_job_type": { - "type": "string", - "example": "live-in" - }, - "bio": { - "type": "string", - "example": "Experienced and caring domestic worker specialing in childcare and housekeeping..." - }, - "rating": { - "type": "number", - "example": 4.3 - }, - "reviews_count": { - "type": "integer", - "example": 6 - }, - "reviews": { - "type": "array", - "items": { - "type": "object" + { + "type": "object", + "properties": { + "reviews": { + "type": "array", + "items": { + "type": "object" + } + }, + "similar_workers": { + "type": "array", + "items": { + "type": "object" + } + }, + "conversation_id": { + "type": "integer", + "nullable": true, + "example": 4 + } } - }, - "similar_workers": { - "type": "array", - "items": { - "type": "object" - } - }, - "conversation_id": { - "type": "integer", - "nullable": true, - "example": 4 } - } + ] } } } @@ -5738,10 +5648,6 @@ "type": "string", "example": "4 Years" }, - "religion": { - "type": "string", - "example": "Muslim" - }, "bio": { "type": "string", "example": "Certified infant caregiver and housekeeper with excellent cooking skills." diff --git a/tests/Feature/EmployerProfileApiTest.php b/tests/Feature/EmployerProfileApiTest.php index 106906c..01540d5 100644 --- a/tests/Feature/EmployerProfileApiTest.php +++ b/tests/Feature/EmployerProfileApiTest.php @@ -241,4 +241,51 @@ public function test_employer_dashboard_returns_only_two_charity_drives() $this->assertFalse($titles->contains('Charity Drive 1')); $this->assertFalse($titles->contains('Pending Charity Drive')); } + + public function test_employer_dashboard_returns_total_workers_active() + { + // Delete any existing workers seeded or created to make count precise + \App\Models\Worker::query()->delete(); + + // Create 3 active workers + \App\Models\Worker::create([ + 'name' => 'Active 1', 'email' => 'a1@test.com', 'phone' => '+971501110001', + 'password' => bcrypt('password'), 'status' => 'active', 'nationality' => 'Indian', + 'preferred_location' => 'Dubai', 'age' => 25, 'salary' => 1500, 'experience' => '3 Years', + 'availability' => 'Immediate', 'religion' => 'Christian', 'bio' => 'experienced worker', + ]); + \App\Models\Worker::create([ + 'name' => 'Active 2', 'email' => 'a2@test.com', 'phone' => '+971501110002', + 'password' => bcrypt('password'), 'status' => 'active', 'nationality' => 'Indian', + 'preferred_location' => 'Dubai', 'age' => 26, 'salary' => 1500, 'experience' => '3 Years', + 'availability' => 'Immediate', 'religion' => 'Christian', 'bio' => 'experienced worker', + ]); + \App\Models\Worker::create([ + 'name' => 'Active 3', 'email' => 'a3@test.com', 'phone' => '+971501110003', + 'password' => bcrypt('password'), 'status' => 'active', 'nationality' => 'Indian', + 'preferred_location' => 'Dubai', 'age' => 27, 'salary' => 1500, 'experience' => '3 Years', + 'availability' => 'Immediate', 'religion' => 'Christian', 'bio' => 'experienced worker', + ]); + + // Create 2 inactive/hidden workers + \App\Models\Worker::create([ + 'name' => 'Inactive 1', 'email' => 'i1@test.com', 'phone' => '+971502220001', + 'password' => bcrypt('password'), 'status' => 'hidden', 'nationality' => 'Indian', + 'preferred_location' => 'Dubai', 'age' => 28, 'salary' => 1500, 'experience' => '3 Years', + 'availability' => 'Immediate', 'religion' => 'Christian', 'bio' => 'experienced worker', + ]); + \App\Models\Worker::create([ + 'name' => 'Inactive 2', 'email' => 'i2@test.com', 'phone' => '+971502220002', + 'password' => bcrypt('password'), 'status' => 'Hired', 'nationality' => 'Indian', + 'preferred_location' => 'Dubai', 'age' => 29, 'salary' => 1500, 'experience' => '3 Years', + 'availability' => 'Immediate', 'religion' => 'Christian', 'bio' => 'experienced worker', + ]); + + $response = $this->withHeaders([ + 'Authorization' => 'Bearer ' . $this->token, + ])->getJson('/api/employers/dashboard'); + + $response->assertStatus(200); + $this->assertEquals(3, $response->json('data.stats.total_workers')); + } } diff --git a/tests/Feature/EmployerWorkerFilterApiTest.php b/tests/Feature/EmployerWorkerFilterApiTest.php index 9f07315..433fe79 100644 --- a/tests/Feature/EmployerWorkerFilterApiTest.php +++ b/tests/Feature/EmployerWorkerFilterApiTest.php @@ -532,4 +532,87 @@ public function test_worker_list_api_has_full_details_and_pagination() $this->assertEquals('passport', $matchingWorker['documents'][0]['type']); $this->assertEquals('visa', $matchingWorker['documents'][1]['type']); } + + public function test_worker_detail_api_has_full_details_without_religion_and_cleaned_ocr_data() + { + $worker = Worker::create([ + 'name' => 'Detail Worker Test', + 'email' => 'detail_test@example.com', + 'phone' => '+971509999777', + 'password' => bcrypt('password'), + 'nationality' => 'Indian', + 'verified' => true, + 'status' => 'active', + 'preferred_location' => 'Dubai Marina', + 'live_in_out' => 'live_out', + 'gender' => 'male', + 'in_country' => true, + 'visa_status' => 'Tourist Visa', + 'preferred_job_type' => 'full-time', + 'fcm_token' => 'fcm_token_example', + 'age' => 36, + 'salary' => 1500.00, + 'experience' => '4 Years', + 'language' => 'English, Arabic', + 'availability' => 'Immediate', + 'religion' => 'Christian', + 'bio' => 'Experienced helper', + ]); + + $doc1 = WorkerDocument::create([ + 'worker_id' => $worker->id, + 'type' => 'passport', + 'number' => 'YB98239279327', + 'issue_date' => '2017-07-05', + 'expiry_date' => '2022-07-05', + 'ocr_accuracy' => 99, + 'ocr_data' => ['authority' => 'FMP LLC'], + ]); + + $doc2 = WorkerDocument::create([ + 'worker_id' => $worker->id, + 'type' => 'visa', + 'number' => '343434', + 'issue_date' => '2022-02-16', + 'expiry_date' => '2024-02-15', + 'ocr_accuracy' => 99, + 'ocr_data' => [ + 'sponsor' => 'some sponsor', + 'sponsor_name' => 'some sponsor name', + 'valid_until' => '2024-02-15', + 'expiry_date' => '2024-02-15' + ], + ]); + + $response = $this->withHeaders([ + 'Authorization' => 'Bearer ' . $this->token, + ])->getJson('/api/employers/workers/' . $worker->id); + + $response->assertStatus(200); + $wData = $response->json('data.worker'); + + $this->assertNotNull($wData); + $this->assertEquals('+971509999777', $wData['phone']); + $this->assertEquals('detail_test@example.com', $wData['email']); + $this->assertEquals('1500.00', $wData['salary']); + $this->assertEquals('active', $wData['status']); + $this->assertEquals('English, Arabic', $wData['language']); + $this->assertEquals('fcm_token_example', $wData['fcm_token']); + $this->assertEquals('2024-02-15', $wData['visa_expiry_date']); + + // Assert religion is removed + $this->assertArrayNotHasKey('religion', $wData); + + // Check documents object array + $this->assertIsArray($wData['documents']); + $this->assertCount(2, $wData['documents']); + + // Assert visa ocr_data is cleaned up + $visaDoc = collect($wData['documents'])->firstWhere('type', 'visa'); + $this->assertNotNull($visaDoc); + $this->assertArrayNotHasKey('sponsor', $visaDoc['ocr_data']); + $this->assertArrayNotHasKey('valid_until', $visaDoc['ocr_data']); + $this->assertEquals('some sponsor name', $visaDoc['ocr_data']['sponsor_name']); + $this->assertEquals('2024-02-15', $visaDoc['ocr_data']['expiry_date']); + } } -- 2.43.0 From dcf8481c5f41c70792230d70352a8704805ef4bc Mon Sep 17 00:00:00 2001 From: mohanmd Date: Tue, 23 Jun 2026 19:00:29 +0530 Subject: [PATCH 2/4] charity events for employer --- .../Api/EmployerAnnouncementController.php | 81 ++++- public/swagger.json | 282 +++++++++++++++++- routes/api.php | 1 + tests/Feature/AnnouncementApiTest.php | 59 +++- 4 files changed, 416 insertions(+), 7 deletions(-) diff --git a/app/Http/Controllers/Api/EmployerAnnouncementController.php b/app/Http/Controllers/Api/EmployerAnnouncementController.php index daa98a7..751ec5b 100644 --- a/app/Http/Controllers/Api/EmployerAnnouncementController.php +++ b/app/Http/Controllers/Api/EmployerAnnouncementController.php @@ -11,12 +11,89 @@ class EmployerAnnouncementController extends Controller { /** - * Get all announcements posted by this employer. + * Get all approved announcements for employers. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\JsonResponse */ public function getAnnouncements(Request $request) + { + try { + $page = (int)$request->input('page', 1); + $perPage = (int)$request->input('per_page', 15); + + $query = Announcement::with(['employer.employerProfile', 'sponsor'])->where('status', 'approved')->latest(); + $total = $query->count(); + $offset = ($page - 1) * $perPage; + + $announcements = $query->skip($offset)->take($perPage)->get() + ->map(function ($announcement) { + $postedBy = 'System'; + $organization = 'Migrant Support'; + + if ($announcement->sponsor_id) { + $postedBy = $announcement->sponsor->full_name; + $organization = $announcement->sponsor->organization_name; + } elseif ($announcement->employer_id) { + $postedBy = $announcement->employer->name; + $organization = $announcement->employer->employerProfile->company_name ?? 'Employer'; + } + + // Decode charity details if they exist in json + $charityDetails = null; + $content = $announcement->body; + if (strpos($announcement->body, '{"type":"Charity"') === 0) { + $decoded = json_decode($announcement->body, true); + if ($decoded) { + $charityDetails = $decoded; + $content = $decoded['content'] ?? $announcement->body; + } + } + + return [ + 'id' => $announcement->id, + 'title' => $announcement->title, + 'body' => $content, + 'type' => $announcement->type, + 'employer_name' => $postedBy, + 'company_name' => $organization, + 'created_at' => $announcement->created_at->toISOString(), + 'time_ago' => $announcement->created_at->diffForHumans(), + 'charity_details' => $charityDetails, + ]; + }); + + return response()->json([ + 'success' => true, + 'data' => [ + 'announcements' => $announcements, + 'pagination' => [ + 'total' => $total, + 'per_page' => $perPage, + 'current_page' => $page, + 'last_page' => max(1, (int)ceil($total / $perPage)), + ] + ] + ], 200); + + } catch (\Exception $e) { + logger()->error('Mobile Employer Get All Announcements Failure: ' . $e->getMessage()); + + return response()->json([ + 'success' => false, + 'message' => 'An error occurred while fetching announcements.', + 'error' => app()->environment('local') ? $e->getMessage() : 'Internal Server Error' + ], 500); + } + } + + /** + * Get all announcements posted by this employer. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\JsonResponse + */ + public function getMyAnnouncements(Request $request) { /** @var User $employer */ $employer = $request->attributes->get('employer'); @@ -57,7 +134,7 @@ public function getAnnouncements(Request $request) ], 200); } catch (\Exception $e) { - logger()->error('Mobile Employer Get Announcements Failure: ' . $e->getMessage()); + logger()->error('Mobile Employer Get My Announcements Failure: ' . $e->getMessage()); return response()->json([ 'success' => false, diff --git a/public/swagger.json b/public/swagger.json index adc1825..eb90e2f 100644 --- a/public/swagger.json +++ b/public/swagger.json @@ -3527,7 +3527,91 @@ ], "responses": { "200": { - "description": "List of posted charity events retrieved successfully." + "description": "List of posted charity events retrieved successfully.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": true + }, + "data": { + "type": "object", + "properties": { + "announcements": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "example": 1 + }, + "title": { + "type": "string", + "example": "Holiday Notice" + }, + "body": { + "type": "string", + "example": "Office will be closed tomorrow." + }, + "type": { + "type": "string", + "example": "info" + }, + "employer_name": { + "type": "string", + "example": "Employer One" + }, + "company_name": { + "type": "string", + "example": "Elite Services" + }, + "created_at": { + "type": "string", + "format": "date-time", + "example": "2026-06-23T10:13:17.000000Z" + }, + "time_ago": { + "type": "string", + "example": "2 hours ago" + }, + "charity_details": { + "type": "object", + "nullable": true + } + } + } + }, + "pagination": { + "type": "object", + "properties": { + "total": { + "type": "integer", + "example": 1 + }, + "per_page": { + "type": "integer", + "example": 15 + }, + "current_page": { + "type": "integer", + "example": 1 + }, + "last_page": { + "type": "integer", + "example": 1 + } + } + } + } + } + } + } + } + } } } }, @@ -3628,7 +3712,85 @@ "description": "Retrieves stats including contacted workers count, total hired workers, saved candidates, current plan details, and recent announcements or events.", "responses": { "200": { - "description": "Employer dashboard stats retrieved successfully." + "description": "Employer dashboard stats retrieved successfully.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": true + }, + "data": { + "type": "object", + "properties": { + "stats": { + "type": "object", + "properties": { + "contacted_workers_count": { + "type": "integer", + "example": 0 + }, + "total_hired_workers": { + "type": "integer", + "example": 0 + }, + "saved_candidates": { + "type": "integer", + "example": 0 + }, + "total_workers": { + "type": "integer", + "example": 15 + } + } + }, + "current_plan": { + "type": "object", + "properties": { + "plan_id": { + "type": "string", + "example": "premium" + }, + "name": { + "type": "string", + "example": "Premium Pass" + }, + "status": { + "type": "string", + "example": "active" + }, + "starts_at": { + "type": "string", + "format": "date", + "example": "2026-06-23" + }, + "expires_at": { + "type": "string", + "format": "date", + "example": "2026-07-23" + } + } + }, + "recent_announcements": { + "type": "array", + "items": { + "type": "object" + } + }, + "recent_events": { + "type": "array", + "items": { + "type": "object" + } + } + } + } + } + } + } + } } } } @@ -5515,6 +5677,122 @@ } } } + }, + "/employers/my-announcements": { + "get": { + "tags": [ + "Employer/CharityEvents" + ], + "summary": "Get My Posted Announcements/Charity Events (Employer)", + "description": "Retrieves the list of announcements and charity events posted specifically by the logged-in employer.", + "parameters": [ + { + "name": "page", + "in": "query", + "required": false, + "description": "Page number.", + "schema": { + "type": "integer", + "default": 1 + } + }, + { + "name": "per_page", + "in": "query", + "required": false, + "description": "Number of items per page.", + "schema": { + "type": "integer", + "default": 15 + } + } + ], + "responses": { + "200": { + "description": "List of my posted announcements retrieved successfully.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": true + }, + "data": { + "type": "object", + "properties": { + "announcements": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "example": 1 + }, + "title": { + "type": "string", + "example": "Holiday Notice" + }, + "body": { + "type": "string", + "example": "Office will be closed tomorrow." + }, + "type": { + "type": "string", + "example": "info" + }, + "status": { + "type": "string", + "example": "pending" + }, + "remarks": { + "type": "string", + "nullable": true + }, + "created_at": { + "type": "string", + "format": "date-time", + "example": "2026-06-23T10:13:17.000000Z" + }, + "time_ago": { + "type": "string", + "example": "2 hours ago" + } + } + } + }, + "pagination": { + "type": "object", + "properties": { + "total": { + "type": "integer", + "example": 1 + }, + "per_page": { + "type": "integer", + "example": 15 + }, + "current_page": { + "type": "integer", + "example": 1 + }, + "last_page": { + "type": "integer", + "example": 1 + } + } + } + } + } + } + } + } + } + } + } + } } }, "components": { diff --git a/routes/api.php b/routes/api.php index 1040129..910551d 100644 --- a/routes/api.php +++ b/routes/api.php @@ -119,6 +119,7 @@ // Announcement Management Route::get('/employers/announcements', [EmployerAnnouncementController::class, 'getAnnouncements']); + Route::get('/employers/my-announcements', [EmployerAnnouncementController::class, 'getMyAnnouncements']); Route::post('/employers/announcements', [EmployerAnnouncementController::class, 'createAnnouncement']); Route::delete('/employers/announcements/{id}', [EmployerAnnouncementController::class, 'deleteAnnouncement']); diff --git a/tests/Feature/AnnouncementApiTest.php b/tests/Feature/AnnouncementApiTest.php index bb40e4e..4f18472 100644 --- a/tests/Feature/AnnouncementApiTest.php +++ b/tests/Feature/AnnouncementApiTest.php @@ -103,9 +103,9 @@ public function test_worker_can_get_announcements() } /** - * Test employer can get only their posted announcements. + * Test employer can get only their posted announcements via my-announcements. */ - public function test_employer_can_get_posted_announcements() + public function test_employer_can_get_my_announcements() { // Announcement by this employer Announcement::create([ @@ -131,13 +131,66 @@ public function test_employer_can_get_posted_announcements() $response = $this->withHeaders([ 'Authorization' => 'Bearer ' . $this->employerToken, - ])->getJson('/api/employers/announcements'); + ])->getJson('/api/employers/my-announcements'); $response->assertStatus(200); $this->assertCount(1, $response->json('data.announcements')); $this->assertEquals('Employer Ann', $response->json('data.announcements.0.title')); } + /** + * Test employer can get all approved announcements. + */ + public function test_employer_can_get_all_approved_announcements() + { + // Approved announcement by this employer + Announcement::create([ + 'title' => 'Approved Ann 1', + 'body' => 'Message 1', + 'type' => 'info', + 'employer_id' => $this->employer->id, + 'status' => 'approved', + ]); + + // Pending announcement by this employer + Announcement::create([ + 'title' => 'Pending Ann 1', + 'body' => 'Message 2', + 'type' => 'info', + 'employer_id' => $this->employer->id, + 'status' => 'pending', + ]); + + // Approved announcement by another sponsor + $sponsor = \App\Models\Sponsor::create([ + 'full_name' => 'Sponsor Name', + 'email' => 'sponsor@example.com', + 'mobile' => '+971501112233', + 'organization_name' => 'Charity Org', + 'address' => 'Dubai', + 'role' => 'sponsor', + 'password' => bcrypt('password'), + ]); + Announcement::create([ + 'title' => 'Approved Ann 2', + 'body' => 'Message 3', + 'type' => 'info', + 'sponsor_id' => $sponsor->id, + 'status' => 'approved', + ]); + + $response = $this->withHeaders([ + 'Authorization' => 'Bearer ' . $this->employerToken, + ])->getJson('/api/employers/announcements'); + + $response->assertStatus(200); + $this->assertCount(2, $response->json('data.announcements')); + $titles = collect($response->json('data.announcements'))->pluck('title'); + $this->assertTrue($titles->contains('Approved Ann 1')); + $this->assertTrue($titles->contains('Approved Ann 2')); + $this->assertFalse($titles->contains('Pending Ann 1')); + } + /** * Test employer can create a new announcement. */ -- 2.43.0 From fdffb69b5ee538668145103e89db0d3c54b7f6dd Mon Sep 17 00:00:00 2001 From: mohanmd Date: Tue, 23 Jun 2026 19:06:05 +0530 Subject: [PATCH 3/4] change password api updated --- .../Api/EmployerProfileController.php | 66 ++++ .../Controllers/Api/SponsorController.php | 68 ++++ .../Api/WorkerProfileController.php | 55 ++++ public/swagger.json | 300 ++++++++++++++++++ routes/api.php | 3 + tests/Feature/EmployerProfileApiTest.php | 53 ++++ tests/Feature/SponsorAuthApiTest.php | 54 ++++ tests/Feature/WorkerJourneyApiTest.php | 51 +++ 8 files changed, 650 insertions(+) diff --git a/app/Http/Controllers/Api/EmployerProfileController.php b/app/Http/Controllers/Api/EmployerProfileController.php index 322acd0..62ce416 100644 --- a/app/Http/Controllers/Api/EmployerProfileController.php +++ b/app/Http/Controllers/Api/EmployerProfileController.php @@ -396,4 +396,70 @@ public function getDashboard(Request $request) } } + /** + * Change employer's password. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\JsonResponse + */ + public function changePassword(Request $request) + { + /** @var User $employer */ + $employer = $request->attributes->get('employer'); + + $validator = Validator::make($request->all(), [ + 'current_password' => 'required|string', + 'new_password' => 'required|string|min:8|confirmed', + ]); + + if ($validator->fails()) { + return response()->json([ + 'success' => false, + 'message' => 'Validation error.', + 'errors' => $validator->errors() + ], 422); + } + + if (!Hash::check($request->current_password, $employer->password)) { + return response()->json([ + 'success' => false, + 'message' => 'Validation error.', + 'errors' => [ + 'current_password' => ['The current password is incorrect.'] + ] + ], 422); + } + + try { + $hashedPassword = Hash::make($request->new_password); + + // Update user table + $employer->update([ + 'password' => $hashedPassword + ]); + + // Sync with corresponding sponsor record if found + $matchingSponsor = \App\Models\Sponsor::where('email', $employer->email)->first(); + if ($matchingSponsor) { + $matchingSponsor->update([ + 'password' => $hashedPassword + ]); + } + + return response()->json([ + 'success' => true, + 'message' => 'Password changed successfully.' + ], 200); + + } catch (\Exception $e) { + logger()->error('Mobile Employer Change Password Failure: ' . $e->getMessage()); + + return response()->json([ + 'success' => false, + 'message' => 'An error occurred while changing password.', + 'error' => app()->environment('local') ? $e->getMessage() : 'Internal Server Error' + ], 500); + } + } + } diff --git a/app/Http/Controllers/Api/SponsorController.php b/app/Http/Controllers/Api/SponsorController.php index 79db18b..cea8fd7 100644 --- a/app/Http/Controllers/Api/SponsorController.php +++ b/app/Http/Controllers/Api/SponsorController.php @@ -326,4 +326,72 @@ public function getProfile(Request $request) ] ], 200); } + + /** + * Change sponsor's password. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\JsonResponse + */ + public function changePassword(Request $request) + { + /** @var Sponsor $sponsor */ + $sponsor = $request->attributes->get('sponsor'); + + $validator = \Illuminate\Support\Facades\Validator::make($request->all(), [ + 'current_password' => 'required|string', + 'new_password' => 'required|string|min:8|confirmed', + ]); + + if ($validator->fails()) { + return response()->json([ + 'success' => false, + 'message' => 'Validation error.', + 'errors' => $validator->errors() + ], 422); + } + + if (!\Illuminate\Support\Facades\Hash::check($request->current_password, $sponsor->password)) { + return response()->json([ + 'success' => false, + 'message' => 'Validation error.', + 'errors' => [ + 'current_password' => ['The current password is incorrect.'] + ] + ], 422); + } + + try { + $hashedPassword = \Illuminate\Support\Facades\Hash::make($request->new_password); + + // Update sponsor table + $sponsor->update([ + 'password' => $hashedPassword + ]); + + // Sync with corresponding employer user record if found + $matchingEmployer = \App\Models\User::where('email', $sponsor->email) + ->where('role', 'employer') + ->first(); + if ($matchingEmployer) { + $matchingEmployer->update([ + 'password' => $hashedPassword + ]); + } + + return response()->json([ + 'success' => true, + 'message' => 'Password changed successfully.' + ], 200); + + } catch (\Exception $e) { + logger()->error('Mobile Sponsor Change Password Failure: ' . $e->getMessage()); + + return response()->json([ + 'success' => false, + 'message' => 'An error occurred while changing password.', + 'error' => app()->environment('local') ? $e->getMessage() : 'Internal Server Error' + ], 500); + } + } } diff --git a/app/Http/Controllers/Api/WorkerProfileController.php b/app/Http/Controllers/Api/WorkerProfileController.php index 3af8bc4..bb006b4 100644 --- a/app/Http/Controllers/Api/WorkerProfileController.php +++ b/app/Http/Controllers/Api/WorkerProfileController.php @@ -669,6 +669,61 @@ public function getDashboard(Request $request) } } + /** + * Change worker's password. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\JsonResponse + */ + public function changePassword(Request $request) + { + /** @var Worker $worker */ + $worker = $request->attributes->get('worker'); + + $validator = Validator::make($request->all(), [ + 'current_password' => 'required|string', + 'new_password' => 'required|string|min:8|confirmed', + ]); + + if ($validator->fails()) { + return response()->json([ + 'success' => false, + 'message' => 'Validation error.', + 'errors' => $validator->errors() + ], 422); + } + + if (!\Illuminate\Support\Facades\Hash::check($request->current_password, $worker->password)) { + return response()->json([ + 'success' => false, + 'message' => 'Validation error.', + 'errors' => [ + 'current_password' => ['The current password is incorrect.'] + ] + ], 422); + } + + try { + $worker->update([ + 'password' => \Illuminate\Support\Facades\Hash::make($request->new_password) + ]); + + return response()->json([ + 'success' => true, + 'message' => 'Password changed successfully.' + ], 200); + + } catch (\Exception $e) { + logger()->error('Mobile Worker Change Password Failure: ' . $e->getMessage()); + + return response()->json([ + 'success' => false, + 'message' => 'An error occurred while changing password.', + 'error' => app()->environment('local') ? $e->getMessage() : 'Internal Server Error' + ], 500); + } + } + /** * E.g. "14/05/1990" -> "1990-05-14" */ diff --git a/public/swagger.json b/public/swagger.json index eb90e2f..7005276 100644 --- a/public/swagger.json +++ b/public/swagger.json @@ -5793,6 +5793,306 @@ } } } + }, + "/workers/change-password": { + "post": { + "tags": [ + "Worker/Profile" + ], + "summary": "Change Password", + "description": "Securely changes the user's password by validating the current password and confirming the new password.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "current_password", + "new_password", + "new_password_confirmation" + ], + "properties": { + "current_password": { + "type": "string", + "format": "password", + "description": "The current login password. (REQUIRED)", + "example": "OldPassword@123" + }, + "new_password": { + "type": "string", + "format": "password", + "description": "The new secure password (minimum 8 characters). (REQUIRED)", + "example": "NewPassword@123" + }, + "new_password_confirmation": { + "type": "string", + "format": "password", + "description": "Confirmation of the new password. (REQUIRED)", + "example": "NewPassword@123" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Password changed successfully.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": true + }, + "message": { + "type": "string", + "example": "Password changed successfully." + } + } + } + } + } + }, + "422": { + "description": "Validation error (e.g. invalid current password, password too short, or mismatch).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": false + }, + "message": { + "type": "string", + "example": "Validation error." + }, + "errors": { + "type": "object", + "properties": { + "current_password": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "The current password is incorrect." + ] + } + } + } + } + } + } + } + } + } + } + }, + "/employers/change-password": { + "post": { + "tags": [ + "Employer/Profile" + ], + "summary": "Change Password", + "description": "Securely changes the user's password by validating the current password and confirming the new password.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "current_password", + "new_password", + "new_password_confirmation" + ], + "properties": { + "current_password": { + "type": "string", + "format": "password", + "description": "The current login password. (REQUIRED)", + "example": "OldPassword@123" + }, + "new_password": { + "type": "string", + "format": "password", + "description": "The new secure password (minimum 8 characters). (REQUIRED)", + "example": "NewPassword@123" + }, + "new_password_confirmation": { + "type": "string", + "format": "password", + "description": "Confirmation of the new password. (REQUIRED)", + "example": "NewPassword@123" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Password changed successfully.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": true + }, + "message": { + "type": "string", + "example": "Password changed successfully." + } + } + } + } + } + }, + "422": { + "description": "Validation error (e.g. invalid current password, password too short, or mismatch).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": false + }, + "message": { + "type": "string", + "example": "Validation error." + }, + "errors": { + "type": "object", + "properties": { + "current_password": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "The current password is incorrect." + ] + } + } + } + } + } + } + } + } + } + } + }, + "/sponsors/change-password": { + "post": { + "tags": [ + "Sponsor" + ], + "summary": "Change Password", + "description": "Securely changes the user's password by validating the current password and confirming the new password.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "current_password", + "new_password", + "new_password_confirmation" + ], + "properties": { + "current_password": { + "type": "string", + "format": "password", + "description": "The current login password. (REQUIRED)", + "example": "OldPassword@123" + }, + "new_password": { + "type": "string", + "format": "password", + "description": "The new secure password (minimum 8 characters). (REQUIRED)", + "example": "NewPassword@123" + }, + "new_password_confirmation": { + "type": "string", + "format": "password", + "description": "Confirmation of the new password. (REQUIRED)", + "example": "NewPassword@123" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Password changed successfully.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": true + }, + "message": { + "type": "string", + "example": "Password changed successfully." + } + } + } + } + } + }, + "422": { + "description": "Validation error (e.g. invalid current password, password too short, or mismatch).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": false + }, + "message": { + "type": "string", + "example": "Validation error." + }, + "errors": { + "type": "object", + "properties": { + "current_password": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "The current password is incorrect." + ] + } + } + } + } + } + } + } + } + } + } } }, "components": { diff --git a/routes/api.php b/routes/api.php index 910551d..c38459d 100644 --- a/routes/api.php +++ b/routes/api.php @@ -75,6 +75,7 @@ Route::post('/workers/profile/mark-hired', [WorkerProfileController::class, 'markHired']); Route::get('/workers/dashboard/views', [WorkerProfileController::class, 'getProfileViews']); Route::get('/workers/dashboard', [WorkerProfileController::class, 'getDashboard']); + Route::post('/workers/change-password', [WorkerProfileController::class, 'changePassword']); @@ -110,6 +111,7 @@ Route::get('/employers/profile', [EmployerProfileController::class, 'getProfile']); Route::post('/employers/profile/update', [EmployerProfileController::class, 'updateProfile']); Route::get('/employers/dashboard', [EmployerProfileController::class, 'getDashboard']); + Route::post('/employers/change-password', [EmployerProfileController::class, 'changePassword']); // Messaging Management Route::get('/employers/conversations', [EmployerMessageController::class, 'getConversations']); @@ -160,4 +162,5 @@ Route::get('/sponsors/dashboard', [SponsorController::class, 'getDashboard']); Route::get('/sponsors/charity-events', [SponsorController::class, 'getCharityEvents']); Route::post('/sponsors/charity-events', [SponsorController::class, 'postCharityEvent']); + Route::post('/sponsors/change-password', [SponsorController::class, 'changePassword']); }); diff --git a/tests/Feature/EmployerProfileApiTest.php b/tests/Feature/EmployerProfileApiTest.php index 01540d5..a7adec4 100644 --- a/tests/Feature/EmployerProfileApiTest.php +++ b/tests/Feature/EmployerProfileApiTest.php @@ -288,4 +288,57 @@ public function test_employer_dashboard_returns_total_workers_active() $response->assertStatus(200); $this->assertEquals(3, $response->json('data.stats.total_workers')); } + + /** + * Test employer can change password successfully. + */ + public function test_employer_can_change_password() + { + // 1. Create a matching sponsor + $sponsor = \App\Models\Sponsor::create([ + 'full_name' => 'Employer One', + 'email' => $this->employer->email, + 'mobile' => '+971509990011', + 'organization_name' => 'Elite Services', + 'address' => 'Dubai', + 'role' => 'sponsor', + 'password' => bcrypt('Password@123'), + ]); + + // Verify initial password hash is set + $this->employer->update([ + 'password' => bcrypt('Password@123') + ]); + + // 2. Try with wrong current password + $response = $this->withHeaders([ + 'Authorization' => 'Bearer ' . $this->token, + ])->postJson('/api/employers/change-password', [ + 'current_password' => 'WrongPassword', + 'new_password' => 'NewPassword@123', + 'new_password_confirmation' => 'NewPassword@123', + ]); + + $response->assertStatus(422) + ->assertJsonValidationErrors(['current_password']); + + // 3. Try with correct current password + $response = $this->withHeaders([ + 'Authorization' => 'Bearer ' . $this->token, + ])->postJson('/api/employers/change-password', [ + 'current_password' => 'Password@123', + 'new_password' => 'NewPassword@123', + 'new_password_confirmation' => 'NewPassword@123', + ]); + + $response->assertStatus(200) + ->assertJson([ + 'success' => true, + 'message' => 'Password changed successfully.' + ]); + + // Verify password is updated for both User and Sponsor + $this->assertTrue(\Illuminate\Support\Facades\Hash::check('NewPassword@123', $this->employer->fresh()->password)); + $this->assertTrue(\Illuminate\Support\Facades\Hash::check('NewPassword@123', $sponsor->fresh()->password)); + } } diff --git a/tests/Feature/SponsorAuthApiTest.php b/tests/Feature/SponsorAuthApiTest.php index 2c0ef96..9b28dbb 100644 --- a/tests/Feature/SponsorAuthApiTest.php +++ b/tests/Feature/SponsorAuthApiTest.php @@ -603,4 +603,58 @@ public function test_sponsor_forgot_password_user_not_found() 'message' => 'user not found this email id', ]); } + + /** + * Test sponsor can change password successfully. + */ + public function test_sponsor_can_change_password() + { + $sponsor = Sponsor::create([ + 'full_name' => 'Sponsor Changer', + 'email' => 'changer.sponsor@example.com', + 'mobile' => '+971509997788', + 'password' => Hash::make('Password@123'), + 'api_token' => 'sponsor-changer-token', + 'status' => 'active', + ]); + + // Sync with corresponding employer user record to test sync + $user = User::create([ + 'name' => 'Sponsor Changer', + 'email' => 'changer.sponsor@example.com', + 'password' => Hash::make('Password@123'), + 'role' => 'employer', + ]); + + // 1. Try with wrong current password + $response = $this->withHeaders([ + 'Authorization' => 'Bearer sponsor-changer-token', + ])->postJson('/api/sponsors/change-password', [ + 'current_password' => 'WrongPassword', + 'new_password' => 'NewPassword@123', + 'new_password_confirmation' => 'NewPassword@123', + ]); + + $response->assertStatus(422) + ->assertJsonValidationErrors(['current_password']); + + // 2. Try with correct current password + $response = $this->withHeaders([ + 'Authorization' => 'Bearer sponsor-changer-token', + ])->postJson('/api/sponsors/change-password', [ + 'current_password' => 'Password@123', + 'new_password' => 'NewPassword@123', + 'new_password_confirmation' => 'NewPassword@123', + ]); + + $response->assertStatus(200) + ->assertJson([ + 'success' => true, + 'message' => 'Password changed successfully.' + ]); + + // Verify password is updated for both Sponsor and User + $this->assertTrue(Hash::check('NewPassword@123', $sponsor->fresh()->password)); + $this->assertTrue(Hash::check('NewPassword@123', $user->fresh()->password)); + } } diff --git a/tests/Feature/WorkerJourneyApiTest.php b/tests/Feature/WorkerJourneyApiTest.php index a3c2f5b..dfdf483 100644 --- a/tests/Feature/WorkerJourneyApiTest.php +++ b/tests/Feature/WorkerJourneyApiTest.php @@ -1540,4 +1540,55 @@ public function test_profile_response_does_not_contain_deprecated_fields() $this->assertArrayNotHasKey('city', $json); $this->assertArrayNotHasKey('area', $json); } + + /** + * Test worker can change password successfully. + */ + public function test_worker_can_change_password() + { + $worker = Worker::create([ + 'name' => 'Password Changer', + 'email' => 'changer@example.com', + 'phone' => '+971509990099', + 'language' => 'HI', + 'password' => bcrypt('OldPassword@123'), + 'nationality' => 'Indian', + 'age' => 25, + 'salary' => 1500, + 'availability' => 'Immediate', + 'experience' => 'None', + 'religion' => 'Christian', + 'bio' => 'Some Bio', + 'api_token' => 'changer-token', + ]); + + // 1. Invalid current password + $response = $this->withHeaders([ + 'Authorization' => 'Bearer changer-token', + ])->postJson('/api/workers/change-password', [ + 'current_password' => 'WrongPassword', + 'new_password' => 'NewPassword@123', + 'new_password_confirmation' => 'NewPassword@123', + ]); + + $response->assertStatus(422) + ->assertJsonValidationErrors(['current_password']); + + // 2. Successful change + $response = $this->withHeaders([ + 'Authorization' => 'Bearer changer-token', + ])->postJson('/api/workers/change-password', [ + 'current_password' => 'OldPassword@123', + 'new_password' => 'NewPassword@123', + 'new_password_confirmation' => 'NewPassword@123', + ]); + + $response->assertStatus(200) + ->assertJson([ + 'success' => true, + 'message' => 'Password changed successfully.' + ]); + + $this->assertTrue(\Illuminate\Support\Facades\Hash::check('NewPassword@123', $worker->fresh()->password)); + } } -- 2.43.0 From 9b729e714f9068f053202940adab38ab219c8482 Mon Sep 17 00:00:00 2001 From: mohanmd Date: Tue, 23 Jun 2026 19:12:40 +0530 Subject: [PATCH 4/4] reasons api updated --- app/Http/Controllers/Api/ReportController.php | 22 ++++++++++---- public/swagger.json | 12 +++++--- tests/Feature/ReportApiTest.php | 29 +++++++++++++------ 3 files changed, 44 insertions(+), 19 deletions(-) diff --git a/app/Http/Controllers/Api/ReportController.php b/app/Http/Controllers/Api/ReportController.php index e9b2b16..72159aa 100644 --- a/app/Http/Controllers/Api/ReportController.php +++ b/app/Http/Controllers/Api/ReportController.php @@ -280,12 +280,17 @@ public function reportFromEmployer(Request $request) */ public function getReasonsForWorker(Request $request) { - $type = $request->query('type'); // Chat or Review + $type = $request->query('type'); // Chat or Support $query = DB::table('report_reasons')->where('status', 'Active'); - if ($type && in_array($type, ['Chat', 'Review'])) { - $query->whereIn('type', [$type, 'Both']); + if ($type && in_array(strtolower($type), ['chat', 'support'])) { + $mappedType = ucfirst(strtolower($type)); + if ($mappedType === 'Support') { + $query->where('type', 'Support'); + } else { + $query->whereIn('type', [$mappedType, 'Both']); + } } $reasons = $query->orderBy('id', 'asc')->get(['id', 'reason', 'type']); @@ -302,12 +307,17 @@ public function getReasonsForWorker(Request $request) */ public function getReasonsForEmployer(Request $request) { - $type = $request->query('type'); // Chat or Review + $type = $request->query('type'); // Chat or Support $query = DB::table('report_reasons')->where('status', 'Active'); - if ($type && in_array($type, ['Chat', 'Review'])) { - $query->whereIn('type', [$type, 'Both']); + if ($type && in_array(strtolower($type), ['chat', 'support'])) { + $mappedType = ucfirst(strtolower($type)); + if ($mappedType === 'Support') { + $query->where('type', 'Support'); + } else { + $query->whereIn('type', [$mappedType, 'Both']); + } } $reasons = $query->orderBy('id', 'asc')->get(['id', 'reason', 'type']); diff --git a/public/swagger.json b/public/swagger.json index 7005276..da7b48d 100644 --- a/public/swagger.json +++ b/public/swagger.json @@ -2696,12 +2696,14 @@ "name": "type", "in": "query", "required": false, - "description": "Optional type to filter reasons (Chat or Review). If not provided, returns all active reasons.", + "description": "Optional type to filter reasons (Chat or Support). Case-insensitive.", "schema": { "type": "string", "enum": [ "Chat", - "Review" + "Support", + "chat", + "support" ] } } @@ -5134,12 +5136,14 @@ "name": "type", "in": "query", "required": false, - "description": "Optional type to filter reasons (Chat or Review). If not provided, returns all active reasons.", + "description": "Optional type to filter reasons (Chat or Support). Case-insensitive.", "schema": { "type": "string", "enum": [ "Chat", - "Review" + "Support", + "chat", + "support" ] } } diff --git a/tests/Feature/ReportApiTest.php b/tests/Feature/ReportApiTest.php index 09e02b4..a22b6da 100644 --- a/tests/Feature/ReportApiTest.php +++ b/tests/Feature/ReportApiTest.php @@ -276,6 +276,7 @@ public function test_get_report_reasons_filtering() ['reason' => 'Review Abuse', 'status' => 'Active', 'type' => 'Review'], ['reason' => 'General Impersonation', 'status' => 'Active', 'type' => 'Both'], ['reason' => 'Inactive Reason', 'status' => 'Inactive', 'type' => 'Both'], + ['reason' => 'Billing Issue', 'status' => 'Active', 'type' => 'Support'], ]); // 1. Worker retrieves all Active reasons @@ -284,33 +285,43 @@ public function test_get_report_reasons_filtering() ])->getJson('/api/workers/report-reasons'); $response->assertStatus(200) - ->assertJsonCount(3, 'reasons') + ->assertJsonCount(4, 'reasons') ->assertJsonFragment(['reason' => 'Chat Spam']) ->assertJsonFragment(['reason' => 'Review Abuse']) ->assertJsonFragment(['reason' => 'General Impersonation']) + ->assertJsonFragment(['reason' => 'Billing Issue']) ->assertJsonMissing(['reason' => 'Inactive Reason']); - // 2. Worker retrieves Chat reasons only + // 2. Worker retrieves Chat reasons only (case-insensitive) $responseChat = $this->withHeaders([ 'Authorization' => 'Bearer ' . $this->workerToken, - ])->getJson('/api/workers/report-reasons?type=Chat'); + ])->getJson('/api/workers/report-reasons?type=chat'); $responseChat->assertStatus(200) ->assertJsonCount(2, 'reasons') ->assertJsonFragment(['reason' => 'Chat Spam']) ->assertJsonFragment(['reason' => 'General Impersonation']) - ->assertJsonMissing(['reason' => 'Review Abuse']); + ->assertJsonMissing(['reason' => 'Review Abuse']) + ->assertJsonMissing(['reason' => 'Billing Issue']); - // 3. Worker retrieves Review reasons only + // 3. Worker retrieves Review reasons (unsupported type, ignored) $responseReview = $this->withHeaders([ 'Authorization' => 'Bearer ' . $this->workerToken, ])->getJson('/api/workers/report-reasons?type=Review'); $responseReview->assertStatus(200) - ->assertJsonCount(2, 'reasons') - ->assertJsonFragment(['reason' => 'Review Abuse']) - ->assertJsonFragment(['reason' => 'General Impersonation']) - ->assertJsonMissing(['reason' => 'Chat Spam']); + ->assertJsonCount(4, 'reasons'); + + // 4. Worker retrieves Support reasons only (case-insensitive) + $responseSupport = $this->withHeaders([ + 'Authorization' => 'Bearer ' . $this->workerToken, + ])->getJson('/api/workers/report-reasons?type=support'); + + $responseSupport->assertStatus(200) + ->assertJsonCount(1, 'reasons') + ->assertJsonFragment(['reason' => 'Billing Issue']) + ->assertJsonMissing(['reason' => 'Chat Spam']) + ->assertJsonMissing(['reason' => 'General Impersonation']); } } -- 2.43.0