mohan #5
@ -4,6 +4,7 @@
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Announcement;
|
||||
use App\Models\AnnouncementView;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class WorkerAnnouncementController extends Controller
|
||||
@ -84,4 +85,114 @@ public function getAnnouncements(Request $request)
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get up to 3 unviewed announcements for the worker, and mark them as viewed.
|
||||
*/
|
||||
public function getNewAnnouncements(Request $request)
|
||||
{
|
||||
/** @var \App\Models\Worker $worker */
|
||||
$worker = $request->attributes->get('worker');
|
||||
|
||||
try {
|
||||
$viewedIds = AnnouncementView::where('worker_id', $worker->id)->pluck('announcement_id');
|
||||
|
||||
$dbAnnouncements = Announcement::with(['employer.employerProfile', 'sponsor'])
|
||||
->where('status', 'approved')
|
||||
->whereNotIn('id', $viewedIds)
|
||||
->latest('id')
|
||||
->limit(3)
|
||||
->get();
|
||||
|
||||
// Mark them as viewed
|
||||
foreach ($dbAnnouncements as $announcement) {
|
||||
AnnouncementView::firstOrCreate([
|
||||
'worker_id' => $worker->id,
|
||||
'announcement_id' => $announcement->id,
|
||||
]);
|
||||
}
|
||||
|
||||
$announcements = $dbAnnouncements->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,
|
||||
]
|
||||
], 200);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
logger()->error('Mobile Worker Get New Announcements Failure: ' . $e->getMessage());
|
||||
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'An error occurred while fetching new announcements.',
|
||||
'error' => app()->environment('local') ? $e->getMessage() : 'Internal Server Error'
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Explicitly mark an announcement as viewed by the worker.
|
||||
*/
|
||||
public function markAnnouncementViewed(Request $request, $id)
|
||||
{
|
||||
/** @var \App\Models\Worker $worker */
|
||||
$worker = $request->attributes->get('worker');
|
||||
|
||||
try {
|
||||
AnnouncementView::firstOrCreate([
|
||||
'worker_id' => $worker->id,
|
||||
'announcement_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Announcement marked as viewed.'
|
||||
], 200);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
logger()->error('Mobile Worker Mark Announcement Viewed Failure: ' . $e->getMessage());
|
||||
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'An error occurred.',
|
||||
'error' => app()->environment('local') ? $e->getMessage() : 'Internal Server Error'
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
26
app/Models/AnnouncementView.php
Normal file
26
app/Models/AnnouncementView.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class AnnouncementView extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'worker_id',
|
||||
'announcement_id',
|
||||
];
|
||||
|
||||
public function worker()
|
||||
{
|
||||
return $this->belongsTo(Worker::class, 'worker_id');
|
||||
}
|
||||
|
||||
public function announcement()
|
||||
{
|
||||
return $this->belongsTo(Announcement::class, 'announcement_id');
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('announcement_views', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('worker_id')->constrained('workers')->onDelete('cascade');
|
||||
$table->foreignId('announcement_id')->constrained('announcements')->onDelete('cascade');
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['worker_id', 'announcement_id']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('announcement_views');
|
||||
}
|
||||
};
|
||||
@ -2361,6 +2361,125 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/workers/announcements/new": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Worker/CharityEvents"
|
||||
],
|
||||
"summary": "Get New/Unviewed Announcements/Charity Events",
|
||||
"description": "Retrieves up to 3 active approved charity events/announcements that the worker has not yet viewed, and automatically marks them as viewed.",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successfully retrieved new announcements.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"success": {
|
||||
"type": "boolean",
|
||||
"example": true
|
||||
},
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"example": 4
|
||||
},
|
||||
"title": {
|
||||
"type": "string",
|
||||
"example": "Free Dental Checkup Camp"
|
||||
},
|
||||
"body": {
|
||||
"type": "string",
|
||||
"example": "Emirates Charity is providing free screening."
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"example": "charity"
|
||||
},
|
||||
"employer_name": {
|
||||
"type": "string",
|
||||
"example": "Emirates Charity"
|
||||
},
|
||||
"company_name": {
|
||||
"type": "string",
|
||||
"example": "Emirates Charity Foundation"
|
||||
},
|
||||
"created_at": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"example": "2026-06-01T10:00:00.000000Z"
|
||||
},
|
||||
"time_ago": {
|
||||
"type": "string",
|
||||
"example": "5 hours ago"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthenticated."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/workers/announcements/{id}/view": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"Worker/CharityEvents"
|
||||
],
|
||||
"summary": "Mark Specific Announcement as Viewed",
|
||||
"description": "Explicitly marks a specific announcement/charity event as viewed by the authenticated worker.",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"description": "Announcement ID",
|
||||
"schema": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Announcement marked as viewed successfully.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"success": {
|
||||
"type": "boolean",
|
||||
"example": true
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"example": "Announcement marked as viewed."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthenticated."
|
||||
},
|
||||
"404": {
|
||||
"description": "Announcement not found."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/employers/announcements": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@ -3259,6 +3378,16 @@
|
||||
"type": "integer",
|
||||
"example": 5
|
||||
},
|
||||
"employer_contacted": {
|
||||
"type": "integer",
|
||||
"example": 2,
|
||||
"description": "Unique count of employers who contacted this worker."
|
||||
},
|
||||
"profile_viewed": {
|
||||
"type": "integer",
|
||||
"example": 3,
|
||||
"description": "Unique count of employers who viewed this worker's profile."
|
||||
},
|
||||
"currently_working_sponsor": {
|
||||
"type": "object",
|
||||
"nullable": true,
|
||||
|
||||
@ -72,6 +72,8 @@
|
||||
|
||||
// Announcement Management
|
||||
Route::get('/workers/announcements', [WorkerAnnouncementController::class, 'getAnnouncements']);
|
||||
Route::get('/workers/announcements/new', [WorkerAnnouncementController::class, 'getNewAnnouncements']);
|
||||
Route::post('/workers/announcements/{id}/view', [WorkerAnnouncementController::class, 'markAnnouncementViewed']);
|
||||
|
||||
// Report Management
|
||||
Route::post('/workers/report', [\App\Http\Controllers\Api\ReportController::class, 'reportFromWorker']);
|
||||
|
||||
@ -679,4 +679,124 @@ public function test_worker_dashboard_stats()
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test getting new unviewed announcements and marking them viewed.
|
||||
*/
|
||||
public function test_worker_new_announcements_flow()
|
||||
{
|
||||
// 1. Create a worker
|
||||
$worker = Worker::create([
|
||||
'name' => 'Rahul Sharma',
|
||||
'email' => 'rahul@example.com',
|
||||
'phone' => '+971501234567',
|
||||
'language' => 'HI',
|
||||
'password' => bcrypt('password'),
|
||||
'nationality' => 'India',
|
||||
'age' => 25,
|
||||
'salary' => 1500,
|
||||
'availability' => 'Immediate',
|
||||
'experience' => 'Not Specified',
|
||||
'religion' => 'Not Specified',
|
||||
'bio' => 'Test',
|
||||
'category_id' => $this->category->id,
|
||||
'verified' => true,
|
||||
'status' => 'active',
|
||||
'api_token' => 'worker-test-token-announcements',
|
||||
]);
|
||||
|
||||
// Create passport document to prevent 404/any checks
|
||||
WorkerDocument::create([
|
||||
'worker_id' => $worker->id,
|
||||
'type' => 'passport',
|
||||
'number' => '123456',
|
||||
'file_path' => 'passports/test.pdf',
|
||||
]);
|
||||
|
||||
// 2. Create 4 approved announcements
|
||||
$ann1 = \App\Models\Announcement::create([
|
||||
'title' => 'Event 1',
|
||||
'body' => 'Details 1',
|
||||
'type' => 'charity',
|
||||
'status' => 'approved',
|
||||
]);
|
||||
|
||||
$ann2 = \App\Models\Announcement::create([
|
||||
'title' => 'Event 2',
|
||||
'body' => 'Details 2',
|
||||
'type' => 'charity',
|
||||
'status' => 'approved',
|
||||
]);
|
||||
|
||||
$ann3 = \App\Models\Announcement::create([
|
||||
'title' => 'Event 3',
|
||||
'body' => 'Details 3',
|
||||
'type' => 'charity',
|
||||
'status' => 'approved',
|
||||
]);
|
||||
|
||||
$ann4 = \App\Models\Announcement::create([
|
||||
'title' => 'Event 4',
|
||||
'body' => 'Details 4',
|
||||
'type' => 'charity',
|
||||
'status' => 'approved',
|
||||
]);
|
||||
|
||||
// 3. Get new announcements (should return max 3 events, latest first: Event 4, Event 3, Event 2)
|
||||
$response = $this->withHeaders([
|
||||
'Authorization' => 'Bearer worker-test-token-announcements',
|
||||
])->getJson('/api/workers/announcements/new');
|
||||
|
||||
$response->assertStatus(200);
|
||||
$data = $response->json('data.announcements');
|
||||
$this->assertCount(3, $data);
|
||||
$this->assertEquals('Event 4', $data[0]['title']);
|
||||
$this->assertEquals('Event 3', $data[1]['title']);
|
||||
$this->assertEquals('Event 2', $data[2]['title']);
|
||||
|
||||
// Assert that they are now marked as viewed in the DB
|
||||
$this->assertDatabaseHas('announcement_views', ['worker_id' => $worker->id, 'announcement_id' => $ann4->id]);
|
||||
$this->assertDatabaseHas('announcement_views', ['worker_id' => $worker->id, 'announcement_id' => $ann3->id]);
|
||||
$this->assertDatabaseHas('announcement_views', ['worker_id' => $worker->id, 'announcement_id' => $ann2->id]);
|
||||
|
||||
// 4. Get new announcements again (should return the remaining 1: Event 1)
|
||||
$response = $this->withHeaders([
|
||||
'Authorization' => 'Bearer worker-test-token-announcements',
|
||||
])->getJson('/api/workers/announcements/new');
|
||||
|
||||
$response->assertStatus(200);
|
||||
$data = $response->json('data.announcements');
|
||||
$this->assertCount(1, $data);
|
||||
$this->assertEquals('Event 1', $data[0]['title']);
|
||||
|
||||
// 5. Get new announcements again (should return 0)
|
||||
$response = $this->withHeaders([
|
||||
'Authorization' => 'Bearer worker-test-token-announcements',
|
||||
])->getJson('/api/workers/announcements/new');
|
||||
|
||||
$response->assertStatus(200);
|
||||
$this->assertCount(0, $response->json('data.announcements'));
|
||||
|
||||
// 6. Create a 5th approved announcement
|
||||
$ann5 = \App\Models\Announcement::create([
|
||||
'title' => 'Event 5',
|
||||
'body' => 'Details 5',
|
||||
'type' => 'charity',
|
||||
'status' => 'approved',
|
||||
]);
|
||||
|
||||
// 7. Manually mark Event 5 as viewed
|
||||
$response = $this->withHeaders([
|
||||
'Authorization' => 'Bearer worker-test-token-announcements',
|
||||
])->postJson("/api/workers/announcements/{$ann5->id}/view");
|
||||
$response->assertStatus(200);
|
||||
|
||||
// 8. Get new announcements (should return 0 because Event 5 is already viewed)
|
||||
$response = $this->withHeaders([
|
||||
'Authorization' => 'Bearer worker-test-token-announcements',
|
||||
])->getJson('/api/workers/announcements/new');
|
||||
|
||||
$response->assertStatus(200);
|
||||
$this->assertCount(0, $response->json('data.announcements'));
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user