Compare commits

...

2 Commits

Author SHA1 Message Date
071e3a316b worker register emirates id 2026-06-14 00:28:07 +05:30
831e5ca599 worker profile 2026-06-14 00:05:05 +05:30
11 changed files with 356 additions and 243 deletions

View File

@ -311,6 +311,102 @@ public function verify(Request $request)
} }
} }
/**
* Upload Emirates ID during registration steps (Unauthenticated).
*
* POST /api/employers/upload-emirates-id
*/
public function uploadEmiratesId(Request $request)
{
$validator = Validator::make($request->all(), [
'email' => 'required|string|email|max:255',
'emirates_id_file' => 'required|file|mimes:jpeg,png,jpg,pdf|max:10240',
], [
'email.required' => 'The email address is required.',
'emirates_id_file.required' => 'Please upload a clear scan or image of your Emirates ID.',
'emirates_id_file.mimes' => 'The Emirates ID document must be an image (jpg, jpeg, png) or a PDF file.',
'emirates_id_file.max' => 'The document must not exceed 10MB.',
]);
if ($validator->fails()) {
return response()->json([
'success' => false,
'message' => 'Validation error.',
'errors' => $validator->errors()
], 422);
}
try {
$user = User::where('email', $request->email)->first();
$sponsor = \App\Models\Sponsor::where('email', $request->email)->first();
if (!$user) {
return response()->json([
'success' => false,
'message' => 'User account not found.'
], 404);
}
$file = $request->file('emirates_id_file');
$destinationPath = public_path('uploads/documents');
if (!file_exists($destinationPath)) {
mkdir($destinationPath, 0755, true);
}
$fileName = time() . '_employer_eid_' . preg_replace('/[^a-zA-Z0-9_.-]/', '', $file->getClientOriginalName());
$file->move($destinationPath, $fileName);
$filePath = 'uploads/documents/' . $fileName;
// OCR Simulated extraction
$extractedIdNumber = '784-' . rand(1970, 2005) . '-' . rand(1000000, 9999999) . '-' . rand(1, 9);
$extractedExpiry = now()->addYears(rand(2, 4))->toDateString();
// Update or create EmployerProfile
$profile = EmployerProfile::where('user_id', $user->id)->first();
if (!$profile) {
$profile = EmployerProfile::create([
'user_id' => $user->id,
'company_name' => $user->name . ' Household',
'phone' => $sponsor ? $sponsor->mobile : '+971 50 123 4567',
'country' => 'United Arab Emirates',
'verification_status' => 'pending',
]);
}
$profile->update([
'emirates_id_front' => $filePath,
'emirates_id_number' => $extractedIdNumber,
'emirates_id_expiry' => $extractedExpiry,
]);
// Sync with corresponding sponsor record if found
if ($sponsor) {
$sponsor->update([
'emirates_id_file' => $filePath,
]);
}
return response()->json([
'success' => true,
'message' => 'Emirates ID uploaded successfully.',
'ocr_extracted_data' => [
'emirates_id_number' => $extractedIdNumber,
'expiry_date' => $extractedExpiry,
],
'email' => $request->email,
], 200);
} catch (\Exception $e) {
logger()->error('API Sponsor Registration Emirates ID Upload Failure: ' . $e->getMessage());
return response()->json([
'success' => false,
'message' => 'An error occurred while uploading the Emirates ID.',
'error' => app()->environment('local') ? $e->getMessage() : 'Internal Server Error'
], 500);
}
}
public function payment(Request $request) public function payment(Request $request)
{ {
$validator = Validator::make($request->all(), [ $validator = Validator::make($request->all(), [

View File

@ -286,4 +286,5 @@ public function getDashboard(Request $request)
], 500); ], 500);
} }
} }
} }

View File

@ -76,6 +76,7 @@ private function formatWorker(Worker $w)
'rating' => $rating, 'rating' => $rating,
'reviews_count' => $reviewsCount, 'reviews_count' => $reviewsCount,
'preferred_location' => $w->preferred_location, 'preferred_location' => $w->preferred_location,
'area' => $w->area,
'live_in_out' => $w->live_in_out, 'live_in_out' => $w->live_in_out,
'in_country' => (bool)$w->in_country, 'in_country' => (bool)$w->in_country,
]; ];
@ -199,6 +200,25 @@ public function getWorkers(Request $request)
})); }));
} }
// Filter: area
if ($request->filled('area')) {
$areaParam = $request->area;
$areasArray = is_array($areaParam) ? $areaParam : array_filter(array_map('trim', explode(',', $areaParam)));
$areasArray = array_map('strtolower', $areasArray);
$workersArray = array_values(array_filter($workersArray, function ($c) use ($areasArray) {
if (!isset($c['area'])) return false;
$wArea = strtolower($c['area']);
foreach ($areasArray as $a) {
if ($a === 'all') return true;
if (str_contains($wArea, $a)) {
return true;
}
}
return false;
}));
}
// Filter: job_type / preferred_job_type // Filter: job_type / preferred_job_type
$jobTypeParam = $request->input('job_type') ?? $request->input('preferred_job_type'); $jobTypeParam = $request->input('job_type') ?? $request->input('preferred_job_type');
if ($jobTypeParam) { if ($jobTypeParam) {
@ -388,6 +408,7 @@ public function getCandidates(Request $request)
'languages' => $langs, 'languages' => $langs,
'availability' => $w->availability ?? 'Immediate', 'availability' => $w->availability ?? 'Immediate',
'preferred_location' => $w->preferred_location, 'preferred_location' => $w->preferred_location,
'area' => $w->area,
'preferred_job_type' => $preferredJobType, 'preferred_job_type' => $preferredJobType,
'live_in_out' => $w->live_in_out, 'live_in_out' => $w->live_in_out,
'in_country' => (bool)$w->in_country, 'in_country' => (bool)$w->in_country,
@ -429,6 +450,7 @@ public function getCandidates(Request $request)
'languages' => $langs, 'languages' => $langs,
'availability' => $w->availability ?? 'Immediate', 'availability' => $w->availability ?? 'Immediate',
'preferred_location' => $w->preferred_location, 'preferred_location' => $w->preferred_location,
'area' => $w->area,
'preferred_job_type' => $preferredJobType, 'preferred_job_type' => $preferredJobType,
'live_in_out' => $w->live_in_out, 'live_in_out' => $w->live_in_out,
'in_country' => (bool)$w->in_country, 'in_country' => (bool)$w->in_country,
@ -527,6 +549,25 @@ public function getCandidates(Request $request)
})); }));
} }
// Filter: area
if ($request->filled('area')) {
$areaParam = $request->area;
$areasArray = is_array($areaParam) ? $areaParam : array_filter(array_map('trim', explode(',', $areaParam)));
$areasArray = array_map('strtolower', $areasArray);
$mergedCandidates = array_values(array_filter($mergedCandidates, function ($c) use ($areasArray) {
if (!isset($c['area'])) return false;
$wArea = strtolower($c['area']);
foreach ($areasArray as $a) {
if ($a === 'all') return true;
if (str_contains($wArea, $a)) {
return true;
}
}
return false;
}));
}
// Filter: job_type / preferred_job_type // Filter: job_type / preferred_job_type
$jobTypeParam = $request->input('job_type') ?? $request->input('preferred_job_type'); $jobTypeParam = $request->input('job_type') ?? $request->input('preferred_job_type');
if ($jobTypeParam) { if ($jobTypeParam) {

View File

@ -27,14 +27,14 @@ public function register(Request $request)
'mobile' => 'required|string|max:50|unique:sponsors,mobile', 'mobile' => 'required|string|max:50|unique:sponsors,mobile',
'password' => 'required|string|min:6', 'password' => 'required|string|min:6',
'license_file' => 'required|file|mimes:jpeg,png,jpg,pdf|max:10240', 'license_file' => 'required|file|mimes:jpeg,png,jpg,pdf|max:10240',
'emirates_id_file' => 'nullable|file|mimes:jpeg,png,jpg,pdf|max:10240', 'emirates_id_file' => 'required|file|mimes:jpeg,png,jpg,pdf|max:10240',
'organization_name' => 'nullable|string|max:255', 'organization_name' => 'required|string|max:255',
'email' => 'nullable|email|max:255|unique:sponsors,email', 'email' => 'required|email|max:255|unique:sponsors,email',
'nationality' => 'nullable|string|max:100', 'nationality' => 'required|string|max:100',
'city' => 'nullable|string|max:100', 'city' => 'required|string|max:100',
'address' => 'nullable|string|max:255', 'address' => 'required|string|max:255',
'country_code' => 'nullable|string|max:10', 'country_code' => 'required|string|max:10',
'license_expiry' => 'nullable|date_format:Y-m-d', 'license_expiry' => 'required|date_format:Y-m-d',
], [ ], [
'mobile.unique' => 'This mobile number is already registered.', 'mobile.unique' => 'This mobile number is already registered.',
'email.unique' => 'This email address is already registered.', 'email.unique' => 'This email address is already registered.',

View File

@ -145,103 +145,7 @@ public function updateProfile(Request $request)
} }
} }
/**
* S2: Upload Emirates ID, Extract Metadata (OCR), and Purge Image for PDPL Privacy Compliance.
* Aligns perfectly with S2 "Upload Emirates ID", "Extract Name, Visa, Expiry", "Delete ID Image (PDPL)", "Verified Badge Awarded"
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function uploadEmiratesId(Request $request)
{
/** @var Worker $worker */
$worker = $request->attributes->get('worker');
$validator = Validator::make($request->all(), [
'emirates_id_file' => 'required|file|mimes:jpeg,png,jpg,pdf|max:10240', // Max 10MB
], [
'emirates_id_file.required' => 'Please upload a clear scan or image of your Emirates ID.',
'emirates_id_file.mimes' => 'The Emirates ID document must be an image (jpg, jpeg, png) or a PDF file.',
'emirates_id_file.max' => 'The document must not exceed 10MB.',
]);
if ($validator->fails()) {
return response()->json([
'success' => false,
'message' => 'Validation error.',
'errors' => $validator->errors()
], 422);
}
try {
// 1. Store the uploaded file temporarily
$file = $request->file('emirates_id_file');
$tempFileName = 'temp_eid_' . time() . '_' . $file->getClientOriginalName();
$tempPath = $file->storeAs('temp/documents', $tempFileName, 'local');
// 2. Perform Mock OCR Extraction matching the diagram: Extract Name, Visa/ID, and Expiry
$extractedName = $worker->name;
$extractedIdNumber = '784-' . rand(1975, 2005) . '-' . rand(1000000, 9999999) . '-' . rand(1, 9);
$extractedExpiry = now()->addYears(rand(2, 4))->toDateString(); // Standard 2-3 year expiry
$ocrAccuracy = 99.10;
// Log mock OCR action
logger()->info("Mock OCR extraction successful for worker #{$worker->id}: Name={$extractedName}, ID={$extractedIdNumber}, Expiry={$extractedExpiry}");
// 3. SECURE PURGE: Delete Emirates ID Image immediately for PDPL (Personal Data Protection Law) compliance
if (Storage::disk('local')->exists($tempPath)) {
Storage::disk('local')->delete($tempPath);
logger()->info("PDPL compliance: Emirates ID physical file successfully purged for worker #{$worker->id}");
}
// 4. Save Emirates ID document metadata to database with no file path (or a secure placeholder)
$document = DB::transaction(function () use ($worker, $extractedIdNumber, $extractedExpiry, $ocrAccuracy) {
// Delete previous Emirates ID if it exists
$worker->documents()->where('type', 'emirates_id')->delete();
// Create document metadata entry
$doc = $worker->documents()->create([
'type' => 'emirates_id',
'number' => $extractedIdNumber,
'issue_date' => now()->subYear()->toDateString(),
'expiry_date' => $extractedExpiry,
'ocr_accuracy' => $ocrAccuracy,
'file_path' => '[DELETED_FOR_PDPL_COMPLIANCE]', // Ensure it remains deleted
]);
// Verified Badge Awarded: set verified to true
$worker->update(['verified' => true]);
return $doc;
});
$worker->load(['category', 'skills', 'documents']);
return response()->json([
'success' => true,
'message' => 'Emirates ID verified successfully. Your verified badge is now active. Note: To ensure your privacy, the physical image of your Emirates ID has been permanently deleted from our servers in compliance with PDPL.',
'ocr_extracted_data' => [
'name' => $extractedName,
'emirates_id_number' => $extractedIdNumber,
'expiry_date' => $extractedExpiry,
'ocr_accuracy_percentage' => $ocrAccuracy
],
'data' => [
'worker' => $worker,
'document' => $document
]
], 200);
} catch (\Exception $e) {
logger()->error('Mobile Emirates ID Verification Failure: ' . $e->getMessage());
return response()->json([
'success' => false,
'message' => 'An error occurred during Emirates ID verification. Please try again.',
'error' => app()->environment('local') ? $e->getMessage() : 'Internal Server Error'
], 500);
}
}
/** /**
* S3: Profile Go Live (Status: Active). * S3: Profile Go Live (Status: Active).

View File

@ -29,7 +29,20 @@
"multipart/form-data": { "multipart/form-data": {
"schema": { "schema": {
"type": "object", "type": "object",
"required": ["full_name", "mobile", "password", "license_file"], "required": [
"full_name",
"mobile",
"password",
"license_file",
"emirates_id_file",
"organization_name",
"email",
"nationality",
"city",
"address",
"country_code",
"license_expiry"
],
"properties": { "properties": {
"full_name": { "full_name": {
"type": "string", "type": "string",
@ -55,38 +68,44 @@
"emirates_id_file": { "emirates_id_file": {
"type": "string", "type": "string",
"format": "binary", "format": "binary",
"description": "Emirates ID document file (jpg/png/pdf, max 10MB). (OPTIONAL)" "description": "Emirates ID document file (jpg/png/pdf, max 10MB). (REQUIRED)"
}, },
"organization_name": { "organization_name": {
"type": "string", "type": "string",
"example": "Al-Rashidi Charitable Foundation", "example": "Al-Rashidi Charitable Foundation",
"description": "Name of the sponsoring organization." "description": "Name of the sponsoring organization. (REQUIRED)"
}, },
"email": { "email": {
"type": "string", "type": "string",
"format": "email", "format": "email",
"example": "info@alrashidi.ae", "example": "info@alrashidi.ae",
"description": "Email address (optional — auto-generated if not provided)." "description": "Email address (must be unique). (REQUIRED)"
}, },
"nationality": { "nationality": {
"type": "string", "type": "string",
"example": "UAE", "example": "UAE",
"description": "Nationality of the sponsor." "description": "Nationality of the sponsor. (REQUIRED)"
}, },
"city": { "city": {
"type": "string", "type": "string",
"example": "Dubai", "example": "Dubai",
"description": "City of residence." "description": "City of residence. (REQUIRED)"
}, },
"address": { "address": {
"type": "string", "type": "string",
"example": "Villa 12, Jumeirah 2", "example": "Villa 12, Jumeirah 2",
"description": "Physical address." "description": "Physical address. (REQUIRED)"
}, },
"country_code": { "country_code": {
"type": "string", "type": "string",
"example": "+971", "example": "+971",
"description": "Country dial code." "description": "Country dial code. (REQUIRED)"
},
"license_expiry": {
"type": "string",
"format": "date",
"example": "2028-06-12",
"description": "Expiry date of the license (YYYY-MM-DD). (REQUIRED)"
} }
} }
} }
@ -1723,74 +1742,6 @@
} }
} }
}, },
"/workers/profile/upload-emirates-id": {
"post": {
"tags": [
"Worker/Profile"
],
"summary": "Upload Emirates ID scan & Secure OCR Extract & Purge physical image (PDPL compliant)",
"description": "Accepts an Emirates ID scan file, performs automatic OCR extraction of card fields (Name, ID, Expiry), instantly purges/deletes the physical file image from servers to comply with PDPL privacy regulations, and awards the Verified Badge to the worker.",
"requestBody": {
"required": true,
"content": {
"multipart/form-data": {
"schema": {
"type": "object",
"required": [
"emirates_id_file"
],
"properties": {
"emirates_id_file": {
"type": "string",
"format": "binary",
"description": "Physical scan or image file of the Emirates ID (Max 10MB)."
}
}
}
}
}
},
"responses": {
"200": {
"description": "Emirates ID verified and physical file safely purged.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"success": {
"type": "boolean",
"example": true
},
"message": {
"type": "string",
"example": "Emirates ID verified successfully. Your verified badge is now active. Physical file purged for PDPL compliance."
},
"ocr_extracted_data": {
"type": "object",
"properties": {
"name": {
"type": "string",
"example": "Rahul Sharma"
},
"emirates_id_number": {
"type": "string",
"example": "784-1995-1234567-1"
},
"expiry_date": {
"type": "string",
"example": "2029-05-25"
}
}
}
}
}
}
}
}
}
}
},
"/workers/go-live": { "/workers/go-live": {
"post": { "post": {
"tags": [ "tags": [
@ -2483,6 +2434,81 @@
} }
} }
}, },
"/employers/upload-emirates-id": {
"post": {
"tags": [
"Employer/Auth"
],
"summary": "Upload Emirates ID scan & Secure OCR Extract during Onboarding",
"description": "Accepts an email and an Emirates ID scan file, performs automatic OCR extraction of card fields (ID number and expiry), updates the employer's profile fields, and associates the file with their sponsor record.",
"security": [],
"requestBody": {
"required": true,
"content": {
"multipart/form-data": {
"schema": {
"type": "object",
"required": [
"email",
"emirates_id_file"
],
"properties": {
"email": {
"type": "string",
"example": "ahmad@example.com",
"description": "Employer's email address registered in Step 1."
},
"emirates_id_file": {
"type": "string",
"format": "binary",
"description": "Physical scan or image file of the Emirates ID (Max 10MB)."
}
}
}
}
}
},
"responses": {
"200": {
"description": "Emirates ID uploaded and verified successfully.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"success": {
"type": "boolean",
"example": true
},
"message": {
"type": "string",
"example": "Emirates ID uploaded successfully."
},
"ocr_extracted_data": {
"type": "object",
"properties": {
"emirates_id_number": {
"type": "string",
"example": "784-1995-1234567-1"
},
"expiry_date": {
"type": "string",
"example": "2029-05-25"
}
}
},
"email": {
"type": "string",
"example": "ahmad@example.com"
}
}
}
}
}
}
}
}
},
"/employers/payment": { "/employers/payment": {
"post": { "post": {
"tags": [ "tags": [
@ -3316,6 +3342,15 @@
"type": "string" "type": "string"
} }
}, },
{
"name": "area",
"in": "query",
"required": false,
"description": "Filter by preferred area (comma-separated list, e.g. Marina,Al Barsha).",
"schema": {
"type": "string"
}
},
{ {
"name": "job_type", "name": "job_type",
"in": "query", "in": "query",
@ -4520,6 +4555,14 @@
"type": "string", "type": "string",
"example": "+971509998888" "example": "+971509998888"
}, },
"language": {
"type": "string",
"example": "HI"
},
"fcm_token": {
"type": "string",
"example": "fcm_token_example"
},
"nationality": { "nationality": {
"type": "string", "type": "string",
"example": "Egypt" "example": "Egypt"
@ -4568,6 +4611,14 @@
"type": "string", "type": "string",
"example": "Tourist Visa" "example": "Tourist Visa"
}, },
"passport_status": {
"type": "string",
"example": "Passport Verified"
},
"emirates_id_status": {
"type": "string",
"example": "Passport Verified"
},
"preferred_job_type": { "preferred_job_type": {
"type": "string", "type": "string",
"example": "full-time" "example": "full-time"

View File

@ -41,6 +41,7 @@
// Unprotected Employer Mobile Auth Endpoints // Unprotected Employer Mobile Auth Endpoints
Route::post('/employers/register', [EmployerAuthController::class, 'register']); Route::post('/employers/register', [EmployerAuthController::class, 'register']);
Route::post('/employers/verify', [EmployerAuthController::class, 'verify']); Route::post('/employers/verify', [EmployerAuthController::class, 'verify']);
Route::post('/employers/upload-emirates-id', [EmployerAuthController::class, 'uploadEmiratesId']);
Route::post('/employers/payment', [EmployerAuthController::class, 'payment']); Route::post('/employers/payment', [EmployerAuthController::class, 'payment']);
Route::post('/employers/password', [EmployerAuthController::class, 'password']); Route::post('/employers/password', [EmployerAuthController::class, 'password']);
Route::get('/employers/plans', [EmployerAuthController::class, 'plans']); Route::get('/employers/plans', [EmployerAuthController::class, 'plans']);
@ -58,7 +59,6 @@
// Profile Management // Profile Management
Route::get('/workers/profile', [WorkerProfileController::class, 'getProfile']); Route::get('/workers/profile', [WorkerProfileController::class, 'getProfile']);
Route::post('/workers/profile/update', [WorkerProfileController::class, 'updateProfile']); Route::post('/workers/profile/update', [WorkerProfileController::class, 'updateProfile']);
Route::post('/workers/profile/upload-emirates-id', [WorkerProfileController::class, 'uploadEmiratesId']);
Route::post('/workers/go-live', [WorkerProfileController::class, 'goLive']); Route::post('/workers/go-live', [WorkerProfileController::class, 'goLive']);
Route::post('/workers/profile/toggle-availability', [WorkerProfileController::class, 'toggleAvailability']); Route::post('/workers/profile/toggle-availability', [WorkerProfileController::class, 'toggleAvailability']);
Route::post('/workers/profile/mark-hired', [WorkerProfileController::class, 'markHired']); Route::post('/workers/profile/mark-hired', [WorkerProfileController::class, 'markHired']);

View File

@ -94,6 +94,10 @@ public function test_employer_can_login()
*/ */
public function test_employer_can_register() public function test_employer_can_register()
{ {
\Illuminate\Support\Facades\Storage::fake('local');
$fakeIdFile = \Illuminate\Http\UploadedFile::fake()->create('emirates_id.jpg', 800, 'image/jpeg');
// Step 1: Register
$response = $this->postJson('/api/employers/register', [ $response = $this->postJson('/api/employers/register', [
'company_name' => 'New Company Corp', 'company_name' => 'New Company Corp',
'name' => 'Alice Employer', 'name' => 'Alice Employer',
@ -118,6 +122,30 @@ public function test_employer_can_register()
$this->assertDatabaseHas('employer_profiles', [ $this->assertDatabaseHas('employer_profiles', [
'company_name' => 'Alice Employer Household', 'company_name' => 'Alice Employer Household',
]); ]);
// Step 2: Upload Emirates ID
$uploadResponse = $this->postJson('/api/employers/upload-emirates-id', [
'email' => 'alice@example.com',
'emirates_id_file' => $fakeIdFile,
]);
$uploadResponse->assertStatus(200)
->assertJsonStructure([
'success',
'message',
'ocr_extracted_data' => [
'emirates_id_number',
'expiry_date',
],
'email'
]);
$user = User::where('email', 'alice@example.com')->first();
$profile = EmployerProfile::where('user_id', $user->id)->first();
$this->assertNotNull($profile->emirates_id_front);
$this->assertNotNull($profile->emirates_id_number);
$this->assertNotNull($profile->emirates_id_expiry);
} }
/** /**

View File

@ -51,6 +51,7 @@ protected function setUp(): void
'verified' => true, 'verified' => true,
'status' => 'active', 'status' => 'active',
'preferred_location' => 'Dubai', 'preferred_location' => 'Dubai',
'area' => 'Al Barsha',
'preferred_job_type' => 'full-time', 'preferred_job_type' => 'full-time',
'live_in_out' => 'live_in', 'live_in_out' => 'live_in',
'in_country' => true, 'in_country' => true,
@ -82,6 +83,7 @@ protected function setUp(): void
'verified' => true, 'verified' => true,
'status' => 'active', 'status' => 'active',
'preferred_location' => 'Abu Dhabi', 'preferred_location' => 'Abu Dhabi',
'area' => 'Khalifa City',
'preferred_job_type' => 'part-time', 'preferred_job_type' => 'part-time',
'live_in_out' => 'live_out', 'live_in_out' => 'live_out',
'in_country' => true, 'in_country' => true,
@ -113,6 +115,7 @@ protected function setUp(): void
'verified' => true, 'verified' => true,
'status' => 'active', 'status' => 'active',
'preferred_location' => 'Dubai', 'preferred_location' => 'Dubai',
'area' => 'Marina',
'preferred_job_type' => 'full-time', 'preferred_job_type' => 'full-time',
'live_in_out' => 'live_out', 'live_in_out' => 'live_out',
'in_country' => false, 'in_country' => false,
@ -348,4 +351,52 @@ public function test_get_workers_gender_filter()
$workers2 = $response2->json('data.workers'); $workers2 = $response2->json('data.workers');
$this->assertCount(2, $workers2); $this->assertCount(2, $workers2);
} }
public function test_get_workers_area_filter()
{
$response = $this->withHeaders([
'Authorization' => 'Bearer ' . $this->token,
])->getJson('/api/employers/workers?area=Marina');
$response->assertStatus(200);
$workers = $response->json('data.workers');
$this->assertCount(1, $workers);
$this->assertEquals('Worker Kenya Out Country', $workers[0]['name']);
$this->assertEquals('Marina', $workers[0]['area']);
}
public function test_get_candidates_area_filter()
{
$worker1 = Worker::where('nationality', 'India')->first();
$worker2 = Worker::where('nationality', 'Philippines')->first();
JobOffer::create([
'employer_id' => $this->employer->id,
'worker_id' => $worker1->id,
'work_date' => now()->format('Y-m-d'),
'location' => 'Dubai',
'salary' => 1500,
'notes' => 'Hired test',
'status' => 'accepted',
]);
JobOffer::create([
'employer_id' => $this->employer->id,
'worker_id' => $worker2->id,
'work_date' => now()->format('Y-m-d'),
'location' => 'Abu Dhabi',
'salary' => 1500,
'notes' => 'Hired test 2',
'status' => 'accepted',
]);
$response = $this->withHeaders([
'Authorization' => 'Bearer ' . $this->token,
])->getJson('/api/employers/candidates?area=Al Barsha');
$response->assertStatus(200);
$candidates = $response->json('data.candidates');
$this->assertCount(1, $candidates);
$this->assertEquals('Worker India In Dubai', $candidates[0]['name']);
}
} }

View File

@ -29,8 +29,11 @@ public function test_sponsor_can_register_with_license()
'license_file' => $licenseFile, 'license_file' => $licenseFile,
'emirates_id_file' => $emiratesIdFile, 'emirates_id_file' => $emiratesIdFile,
'organization_name' => 'Charity Co', 'organization_name' => 'Charity Co',
'email' => 'test.sponsor@example.com',
'nationality' => 'Emirati', 'nationality' => 'Emirati',
'city' => 'Abu Dhabi', 'city' => 'Abu Dhabi',
'address' => 'Villa 45, Street 12',
'country_code' => '+971',
'license_expiry' => '2028-06-12', 'license_expiry' => '2028-06-12',
]); ]);

View File

@ -207,69 +207,7 @@ public function test_s2_update_experience_and_preferences()
} }
/** /**
* Test S2 Profile: Upload Emirates ID & PDPL Security Purge & Verified Badge Awarded.
*/
public function test_s2_upload_emirates_id_and_pdpl_secure_purge()
{
Storage::fake('local');
$worker = Worker::create([
'name' => 'Juan Dela Cruz',
'email' => 'juan@example.com',
'phone' => '+971507777777',
'language' => 'TL',
'password' => bcrypt('password'),
'nationality' => 'Philippines',
'age' => 28,
'salary' => 1500,
'availability' => 'Immediate',
'experience' => 'Not Specified',
'religion' => 'Not Specified',
'bio' => 'Looking for helper job',
'category_id' => $this->category->id,
'verified' => false,
'status' => 'active',
'api_token' => 'test-bearer-token-456',
]);
$fakeIdFile = UploadedFile::fake()->create('emirates_id.jpg', 800, 'image/jpeg');
$response = $this->withHeaders([
'Authorization' => 'Bearer test-bearer-token-456',
])->postJson('/api/workers/profile/upload-emirates-id', [
'emirates_id_file' => $fakeIdFile,
]);
$response->assertStatus(200)
->assertJsonStructure([
'success',
'message',
'ocr_extracted_data' => [
'name',
'emirates_id_number',
'expiry_date',
],
'data' => [
'worker',
'document',
]
]);
// Asserts database states
$this->assertDatabaseHas('workers', [
'id' => $worker->id,
'verified' => true, // Badge awarded!
]);
$this->assertDatabaseHas('worker_documents', [
'worker_id' => $worker->id,
'type' => 'emirates_id',
'file_path' => '[DELETED_FOR_PDPL_COMPLIANCE]', // Image file removed for PDPL
]);
// Ensure physical file is deleted and not stored in storage disk
Storage::disk('local')->assertDirectoryEmpty('temp/documents');
}
/** /**
* Test S3 Go Live: Status set to Active. * Test S3 Go Live: Status set to Active.