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. */