mohan #15

Merged
mohanmd merged 2 commits from mohan into master 2026-06-24 07:35:11 +00:00
16 changed files with 1349 additions and 61 deletions

View File

@ -198,6 +198,13 @@ public function register(Request $request)
'phone.unique' => 'This mobile number is already registered.', 'phone.unique' => 'This mobile number is already registered.',
]); ]);
$validator->after(function ($validator) use ($request) {
$emiratesIdInput = $request->input('emirates_id');
if (is_array($emiratesIdInput) && empty($emiratesIdInput['name'])) {
$validator->errors()->add('emirates_id.name', 'Failed to extract name from the Emirates ID. Please upload a clearer document.');
}
});
if ($validator->fails()) { if ($validator->fails()) {
return response()->json([ return response()->json([
'success' => false, 'success' => false,
@ -232,6 +239,9 @@ public function register(Request $request)
$emiratesIdNumber = $emiratesIdInput['emirates_id_number'] ?? $emiratesIdInput['id_number'] ?? $emiratesIdInput['id number'] ?? $emiratesIdInput['card_number'] ?? null; $emiratesIdNumber = $emiratesIdInput['emirates_id_number'] ?? $emiratesIdInput['id_number'] ?? $emiratesIdInput['id number'] ?? $emiratesIdInput['card_number'] ?? null;
$emiratesIdExpiry = $emiratesIdInput['expiry_date'] ?? $emiratesIdInput['expiry date'] ?? $emiratesIdInput['emirates_id_expiry'] ?? null; $emiratesIdExpiry = $emiratesIdInput['expiry_date'] ?? $emiratesIdInput['expiry date'] ?? $emiratesIdInput['emirates_id_expiry'] ?? null;
$emiratesIdName = $emiratesIdInput['name'] ?? null; $emiratesIdName = $emiratesIdInput['name'] ?? null;
if ($emiratesIdName) {
$request->merge(['name' => $emiratesIdName]);
}
$emiratesIdDob = $emiratesIdInput['date_of_birth'] ?? $emiratesIdInput['date of birth'] ?? $emiratesIdInput['dob'] ?? null; $emiratesIdDob = $emiratesIdInput['date_of_birth'] ?? $emiratesIdInput['date of birth'] ?? $emiratesIdInput['dob'] ?? null;
$emiratesIdNationality = $emiratesIdInput['nationality'] ?? null; $emiratesIdNationality = $emiratesIdInput['nationality'] ?? null;
$emiratesIdIssueDate = $emiratesIdInput['issue_date'] ?? $emiratesIdInput['issue date'] ?? null; $emiratesIdIssueDate = $emiratesIdInput['issue_date'] ?? $emiratesIdInput['issue date'] ?? null;

View File

@ -133,6 +133,12 @@ public function updateProfile(Request $request)
'issuing_place' => 'nullable|string|max:255', 'issuing_place' => 'nullable|string|max:255',
]); ]);
$validator->after(function ($validator) use ($request) {
if ($request->has('full_name') && !$request->filled('full_name')) {
$validator->errors()->add('full_name', 'Failed to extract name from the Emirates ID. Please upload a clearer document.');
}
});
if ($validator->fails()) { if ($validator->fails()) {
return response()->json([ return response()->json([
'success' => false, 'success' => false,
@ -142,6 +148,10 @@ public function updateProfile(Request $request)
} }
try { try {
if ($request->filled('full_name')) {
$request->merge(['name' => $request->full_name]);
}
// Validate Emirates ID immutability — once set, it cannot be changed // Validate Emirates ID immutability — once set, it cannot be changed
if ($request->filled('id_number')) { if ($request->filled('id_number')) {
$existingProfile = $employer->employerProfile; $existingProfile = $employer->employerProfile;

View File

@ -97,6 +97,7 @@ private function formatWorker(Worker $w)
unset($ocrData['passport_number']); unset($ocrData['passport_number']);
unset($ocrData['accompanied_by']); unset($ocrData['accompanied_by']);
unset($ocrData['valid_until']); unset($ocrData['valid_until']);
unset($ocrData['sponsor']);
} }
return [ return [
'id' => $doc->id, 'id' => $doc->id,
@ -1008,6 +1009,7 @@ public function getWorkerDetail(Request $request, $id)
unset($ocrData['passport_number']); unset($ocrData['passport_number']);
unset($ocrData['accompanied_by']); unset($ocrData['accompanied_by']);
unset($ocrData['valid_until']); unset($ocrData['valid_until']);
unset($ocrData['sponsor']);
} }
return [ return [
'id' => $doc->id, 'id' => $doc->id,

View File

@ -41,6 +41,13 @@ public function register(Request $request)
'email.unique' => 'This email address is already registered.', 'email.unique' => 'This email address is already registered.',
]); ]);
$validator->after(function ($validator) use ($request) {
$emiratesIdInput = $request->input('emirates_id');
if (is_array($emiratesIdInput) && empty($emiratesIdInput['name'])) {
$validator->errors()->add('emirates_id.name', 'Failed to extract name from the Emirates ID. Please upload a clearer document.');
}
});
if ($validator->fails()) { if ($validator->fails()) {
return response()->json([ return response()->json([
'success' => false, 'success' => false,
@ -59,8 +66,48 @@ public function register(Request $request)
$licenseExpiry = $request->license_expiry; $licenseExpiry = $request->license_expiry;
$licenseInput = $request->input('license'); $licenseInput = $request->input('license');
$licenseData = null;
if (is_array($licenseInput)) { if (is_array($licenseInput)) {
$licenseExpiry = $licenseExpiry ?? ($licenseInput['expiry_date'] ?? $licenseInput['license_expiry'] ?? null); $licenseExpiry = $licenseExpiry ?? ($licenseInput['expiry_date'] ?? $licenseInput['license_expiry'] ?? null);
$licenseData = [
'document_type' => $licenseInput['document_type'] ?? null,
'country' => $licenseInput['country'] ?? null,
'authority' => $licenseInput['authority'] ?? null,
'license_no' => $licenseInput['license_no'] ?? $licenseInput['license_number'] ?? null,
'company_name' => $licenseInput['company_name'] ?? $licenseInput['organization_name'] ?? null,
'business_name' => $licenseInput['business_name'] ?? null,
'license_category' => $licenseInput['license_category'] ?? null,
'legal_type' => $licenseInput['legal_type'] ?? null,
'issue_date' => $licenseInput['issue_date'] ?? null,
'expiry_date' => $licenseInput['expiry_date'] ?? ($licenseInput['license_expiry'] ?? null),
'main_license_no' => $licenseInput['main_license_no'] ?? null,
'register_no' => $licenseInput['register_no'] ?? null,
'dcci_no' => $licenseInput['dcci_no'] ?? null,
'members' => $licenseInput['members'] ?? null,
];
} else {
$licenseData = [
'document_type' => $request->document_type,
'country' => $request->country,
'authority' => $request->authority,
'license_no' => $request->license_no,
'company_name' => $request->company_name,
'business_name' => $request->business_name,
'license_category' => $request->license_category,
'legal_type' => $request->legal_type,
'issue_date' => $request->issue_date,
'expiry_date' => $request->expiry_date,
'main_license_no' => $request->main_license_no,
'register_no' => $request->register_no,
'dcci_no' => $request->dcci_no,
'members' => $request->members,
];
}
if (is_array($licenseData)) {
$licenseData = array_filter($licenseData, function ($val) {
return !is_null($val);
});
} }
$emiratesIdInput = $request->input('emirates_id'); $emiratesIdInput = $request->input('emirates_id');
@ -78,6 +125,9 @@ public function register(Request $request)
$emiratesId = $emiratesIdInput['emirates_id_number'] ?? $emiratesIdInput['id_number'] ?? $emiratesIdInput['id number'] ?? $emiratesIdInput['card_number'] ?? null; $emiratesId = $emiratesIdInput['emirates_id_number'] ?? $emiratesIdInput['id_number'] ?? $emiratesIdInput['id number'] ?? $emiratesIdInput['card_number'] ?? null;
$emiratesIdExpiry = $emiratesIdInput['expiry_date'] ?? $emiratesIdInput['expiry date'] ?? $emiratesIdInput['emirates_id_expiry'] ?? null; $emiratesIdExpiry = $emiratesIdInput['expiry_date'] ?? $emiratesIdInput['expiry date'] ?? $emiratesIdInput['emirates_id_expiry'] ?? null;
$emiratesIdName = $emiratesIdInput['name'] ?? null; $emiratesIdName = $emiratesIdInput['name'] ?? null;
if ($emiratesIdName) {
$request->merge(['full_name' => $emiratesIdName]);
}
$emiratesIdDob = $emiratesIdInput['date_of_birth'] ?? $emiratesIdInput['date of birth'] ?? $emiratesIdInput['dob'] ?? null; $emiratesIdDob = $emiratesIdInput['date_of_birth'] ?? $emiratesIdInput['date of birth'] ?? $emiratesIdInput['dob'] ?? null;
$emiratesIdNationality = $emiratesIdInput['nationality'] ?? null; $emiratesIdNationality = $emiratesIdInput['nationality'] ?? null;
$emiratesIdIssueDate = $emiratesIdInput['issue_date'] ?? $emiratesIdInput['issue date'] ?? null; $emiratesIdIssueDate = $emiratesIdInput['issue_date'] ?? $emiratesIdInput['issue date'] ?? null;
@ -94,7 +144,7 @@ public function register(Request $request)
$sponsor = DB::transaction(function () use ( $sponsor = DB::transaction(function () use (
$request, $email, $licensePath, $emiratesIdPath, $emiratesId, $apiToken, $licenseExpiry, $request, $email, $licensePath, $emiratesIdPath, $emiratesId, $apiToken, $licenseExpiry,
$emiratesIdExpiry, $emiratesIdName, $emiratesIdDob, $emiratesIdIssueDate, $emiratesIdEmployer, $emiratesIdExpiry, $emiratesIdName, $emiratesIdDob, $emiratesIdIssueDate, $emiratesIdEmployer,
$emiratesIdIssuePlace, $emiratesIdOccupation $emiratesIdIssuePlace, $emiratesIdOccupation, $licenseData
) { ) {
return Sponsor::create([ return Sponsor::create([
'full_name' => $request->full_name, 'full_name' => $request->full_name,
@ -122,6 +172,7 @@ public function register(Request $request)
'emirates_id_employer' => $emiratesIdEmployer, 'emirates_id_employer' => $emiratesIdEmployer,
'emirates_id_issue_place' => $emiratesIdIssuePlace, 'emirates_id_issue_place' => $emiratesIdIssuePlace,
'emirates_id_occupation' => $emiratesIdOccupation, 'emirates_id_occupation' => $emiratesIdOccupation,
'license_data' => $licenseData,
]); ]);
}); });

View File

@ -305,7 +305,51 @@ public function getProfile(Request $request)
return response()->json([ return response()->json([
'success' => true, 'success' => true,
'data' => [ 'data' => [
'sponsor' => [ 'sponsor' => $this->buildSponsorResponse($sponsor)
]
], 200);
}
/**
* Build the standardized sponsor response array with nested license and emirates_id objects.
*/
private function buildSponsorResponse(Sponsor $sponsor): array
{
$licenseData = [
'license_number' => $sponsor->license_data['license_no'] ?? $sponsor->license_data['license_number'] ?? null,
'organization_name' => $sponsor->organization_name,
'expiry_date' => $sponsor->license_expiry ? $sponsor->license_expiry->toDateString() : ($sponsor->license_data['expiry_date'] ?? null),
'document_type' => $sponsor->license_data['document_type'] ?? null,
'country' => $sponsor->license_data['country'] ?? null,
'authority' => $sponsor->license_data['authority'] ?? null,
'license_no' => $sponsor->license_data['license_no'] ?? null,
'company_name' => $sponsor->license_data['company_name'] ?? null,
'business_name' => $sponsor->license_data['business_name'] ?? null,
'license_category' => $sponsor->license_data['license_category'] ?? null,
'legal_type' => $sponsor->license_data['legal_type'] ?? null,
'issue_date' => $sponsor->license_data['issue_date'] ?? null,
'main_license_no' => $sponsor->license_data['main_license_no'] ?? null,
'register_no' => $sponsor->license_data['register_no'] ?? null,
'dcci_no' => $sponsor->license_data['dcci_no'] ?? null,
'members' => $sponsor->license_data['members'] ?? [],
];
$emiratesIdData = null;
if ($sponsor->emirates_id) {
$emiratesIdData = [
'emirates_id_number' => $sponsor->emirates_id,
'name' => $sponsor->emirates_id_name,
'nationality' => $sponsor->nationality,
'date_of_birth' => $sponsor->emirates_id_dob,
'expiry_date' => $sponsor->emirates_id_expiry,
'issue_date' => $sponsor->emirates_id_issue_date,
'employer' => $sponsor->emirates_id_employer,
'issue_place' => $sponsor->emirates_id_issue_place,
'occupation' => $sponsor->emirates_id_occupation,
];
}
return [
'id' => $sponsor->id, 'id' => $sponsor->id,
'full_name' => $sponsor->full_name, 'full_name' => $sponsor->full_name,
'organization_name' => $sponsor->organization_name, 'organization_name' => $sponsor->organization_name,
@ -322,9 +366,10 @@ public function getProfile(Request $request)
'license_expiry' => $sponsor->license_expiry ? $sponsor->license_expiry->toDateString() : null, 'license_expiry' => $sponsor->license_expiry ? $sponsor->license_expiry->toDateString() : null,
'validity' => $sponsor->license_expiry ? ($sponsor->license_expiry->isFuture() ? 'Valid' : 'Expired') : 'Pending Review', 'validity' => $sponsor->license_expiry ? ($sponsor->license_expiry->isFuture() ? 'Valid' : 'Expired') : 'Pending Review',
'joined_at' => $sponsor->created_at->toIso8601String(), 'joined_at' => $sponsor->created_at->toIso8601String(),
] 'fcm_token' => $sponsor->fcm_token,
] 'license' => $licenseData,
], 200); 'emirates_id' => $emiratesIdData,
];
} }
/** /**
@ -394,4 +439,276 @@ public function changePassword(Request $request)
], 500); ], 500);
} }
} }
/**
* POST /api/sponsors/profile/update
*
* Updates the authenticated sponsor's profile.
*/
public function updateProfile(Request $request)
{
/** @var Sponsor $sponsor */
$sponsor = $request->attributes->get('sponsor');
if (!$sponsor) {
return response()->json([
'success' => false,
'message' => 'Unauthorized.'
], 401);
}
// Find matching employer user using sponsor's original email
$oldEmail = $sponsor->getOriginal('email') ?? $sponsor->email;
$matchingUser = \App\Models\User::where('email', $oldEmail)
->where('role', 'employer')
->first();
$matchingUserId = $matchingUser ? $matchingUser->id : 'NULL';
$validator = \Illuminate\Support\Facades\Validator::make($request->all(), [
'name' => 'required_without:full_name|nullable|string|max:255',
'full_name' => 'required_without:name|nullable|string|max:255',
'email' => 'required|email|max:255|unique:sponsors,email,' . $sponsor->id . '|unique:users,email,' . $matchingUserId,
'mobile' => 'required|string|max:255|unique:sponsors,mobile,' . $sponsor->id,
'organization_name' => 'required|string|max:255',
'city' => 'required|string|max:100',
'address' => 'required|string|max:255',
'nationality' => 'required|string|max:100',
'country_code' => 'required|string|max:10',
'fcm_token' => 'nullable|string|max:255',
// Emirates ID — accepts nested object OR flat fields
'emirates_id' => 'nullable|array',
'emirates_id.emirates_id_number' => 'nullable|string|max:255',
'emirates_id.name' => 'nullable|string|max:255',
'emirates_id.nationality' => 'nullable|string|max:255',
'emirates_id.date_of_birth' => 'nullable|string|max:255',
'emirates_id.expiry_date' => 'nullable|string|max:255',
'emirates_id.issue_date' => 'nullable|string|max:255',
'emirates_id.employer' => 'nullable|string|max:255',
'emirates_id.issue_place' => 'nullable|string|max:255',
'emirates_id.occupation' => 'nullable|string|max:255',
// Flat emirates ID fields (backward compatibility)
'id_number' => 'nullable|string|max:255',
'card_number' => 'nullable|string|max:255',
'date_of_birth' => 'nullable|string|max:255',
'gender' => 'nullable|string|max:255',
'issue_date' => 'nullable|string|max:255',
'expiry_date' => 'nullable|string|max:255',
'occupation' => 'nullable|string|max:255',
'employer' => 'nullable|string|max:255',
'issuing_place' => 'nullable|string|max:255',
// License — accepts nested object OR flat fields
'license' => 'nullable|array',
'license.document_type' => 'nullable|string|max:255',
'license.country' => 'nullable|string|max:255',
'license.authority' => 'nullable|string|max:255',
'license.license_no' => 'nullable|string|max:255',
'license.company_name' => 'nullable|string|max:255',
'license.business_name' => 'nullable|string|max:255',
'license.license_category' => 'nullable|string|max:255',
'license.legal_type' => 'nullable|string|max:255',
'license.main_license_no' => 'nullable|string|max:255',
'license.register_no' => 'nullable|string|max:255',
'license.dcci_no' => 'nullable|string|max:255',
'license.members' => 'nullable|array',
// Flat license fields (backward compatibility)
'document_type' => 'nullable|string|max:255',
'country' => 'nullable|string|max:255',
'authority' => 'nullable|string|max:255',
'license_no' => 'nullable|string|max:255',
'company_name' => 'nullable|string|max:255',
'business_name' => 'nullable|string|max:255',
'license_category' => 'nullable|string|max:255',
'legal_type' => 'nullable|string|max:255',
'main_license_no' => 'nullable|string|max:255',
'register_no' => 'nullable|string|max:255',
'dcci_no' => 'nullable|string|max:255',
'members' => 'nullable|array',
]);
$validator->after(function ($validator) use ($request) {
$nameFromEid = $request->input('emirates_id.name') ?? $request->input('full_name');
if ($request->has('full_name') && !$request->filled('full_name') && !$nameFromEid) {
$validator->errors()->add('full_name', 'Failed to extract name from the Emirates ID. Please upload a clearer document.');
}
});
if ($validator->fails()) {
return response()->json([
'success' => false,
'message' => 'Validation error.',
'errors' => $validator->errors()
], 422);
}
try {
// Resolve nested emirates_id input into flat variables
$eidInput = $request->input('emirates_id');
$eidNumber = null;
$eidName = null;
$eidDob = null;
$eidExpiry = null;
$eidIssueDate = null;
$eidEmployer = null;
$eidIssuePlace = null;
$eidOccupation = null;
$eidCardNumber = null;
$eidGender = null;
if (is_array($eidInput)) {
$eidNumber = $eidInput['emirates_id_number'] ?? $eidInput['id_number'] ?? null;
$eidName = $eidInput['name'] ?? null;
$eidDob = $eidInput['date_of_birth'] ?? $eidInput['dob'] ?? null;
$eidExpiry = $eidInput['expiry_date'] ?? null;
$eidIssueDate = $eidInput['issue_date'] ?? null;
$eidEmployer = $eidInput['employer'] ?? null;
$eidIssuePlace = $eidInput['issue_place'] ?? $eidInput['issuing_place'] ?? null;
$eidOccupation = $eidInput['occupation'] ?? null;
$eidCardNumber = $eidInput['card_number'] ?? null;
$eidGender = $eidInput['gender'] ?? null;
}
// Flat field fallbacks (backward compatibility)
$eidNumber = $eidNumber ?? $request->input('id_number');
$eidName = $eidName ?? $request->input('full_name');
$eidDob = $eidDob ?? $request->input('date_of_birth');
$eidExpiry = $eidExpiry ?? $request->input('expiry_date');
$eidIssueDate = $eidIssueDate ?? $request->input('issue_date');
$eidEmployer = $eidEmployer ?? $request->input('employer');
$eidIssuePlace = $eidIssuePlace ?? $request->input('issuing_place');
$eidOccupation = $eidOccupation ?? $request->input('occupation');
$eidCardNumber = $eidCardNumber ?? $request->input('card_number');
$eidGender = $eidGender ?? $request->input('gender');
$nameToUse = $eidName ?? $request->name ?? $request->full_name;
// Validate Emirates ID immutability — once set, it cannot be changed
if ($eidNumber) {
if ($sponsor->emirates_id && $sponsor->emirates_id !== $eidNumber) {
return response()->json([
'success' => false,
'message' => 'Emirates ID mismatch. You cannot change your registered Emirates ID number.',
'errors' => [
'id_number' => ['Emirates ID mismatch.']
]
], 422);
}
}
// Sync with corresponding employer user and profile records if found
if ($matchingUser) {
$userData = [
'name' => $nameToUse,
'email' => $request->email,
];
if ($request->has('fcm_token')) {
$userData['fcm_token'] = $request->fcm_token;
}
$matchingUser->update($userData);
$profile = $matchingUser->employerProfile;
if (!$profile) {
$profile = new \App\Models\EmployerProfile(['user_id' => $matchingUser->id]);
}
$profile->address = $request->address;
$profile->phone = $request->mobile;
if ($eidNumber) { $profile->emirates_id_number = $eidNumber; }
if ($eidCardNumber) { $profile->emirates_id_card_number = $eidCardNumber; }
if ($eidName) { $profile->emirates_id_name = $eidName; }
if ($eidDob) { $profile->emirates_id_dob = $eidDob; }
if ($request->has('nationality')) { $profile->nationality = $request->nationality; }
if ($eidGender) { $profile->emirates_id_gender = $eidGender; }
if ($eidIssueDate) { $profile->emirates_id_issue_date = $eidIssueDate; }
if ($eidExpiry) { $profile->emirates_id_expiry = $eidExpiry; }
if ($eidOccupation) { $profile->emirates_id_occupation = $eidOccupation; }
if ($eidEmployer) { $profile->emirates_id_employer = $eidEmployer; }
if ($eidIssuePlace) { $profile->emirates_id_issue_place = $eidIssuePlace; }
$profile->save();
}
// Update Sponsor profile
$sponsorData = [
'full_name' => $nameToUse,
'email' => $request->email,
'mobile' => $request->mobile,
'organization_name' => $request->organization_name,
'city' => $request->city,
'address' => $request->address,
'nationality' => $request->nationality,
'country_code' => $request->country_code,
];
if ($request->has('fcm_token')) {
$sponsorData['fcm_token'] = $request->fcm_token;
}
if ($eidNumber) { $sponsorData['emirates_id'] = $eidNumber; }
if ($eidCardNumber) { $sponsorData['emirates_id_card_number'] = $eidCardNumber; }
if ($eidName) { $sponsorData['emirates_id_name'] = $eidName; }
if ($eidDob) { $sponsorData['emirates_id_dob'] = $eidDob; }
if ($eidGender) { $sponsorData['emirates_id_gender'] = $eidGender; }
if ($eidIssueDate) { $sponsorData['emirates_id_issue_date'] = $eidIssueDate; }
if ($eidExpiry) { $sponsorData['emirates_id_expiry'] = $eidExpiry; }
if ($eidOccupation) { $sponsorData['emirates_id_occupation'] = $eidOccupation; }
if ($eidEmployer) { $sponsorData['emirates_id_employer'] = $eidEmployer; }
if ($eidIssuePlace) { $sponsorData['emirates_id_issue_place'] = $eidIssuePlace; }
// Parse and merge license data — from nested license object or flat fields
$currentLicenseData = is_array($sponsor->license_data) ? $sponsor->license_data : [];
$licenseInput = $request->input('license');
$newLicenseData = [];
if (is_array($licenseInput)) {
foreach ([
'document_type', 'country', 'authority', 'license_no', 'company_name',
'business_name', 'license_category', 'legal_type', 'issue_date', 'expiry_date',
'main_license_no', 'register_no', 'dcci_no', 'members'
] as $field) {
if (isset($licenseInput[$field])) {
$newLicenseData[$field] = $licenseInput[$field];
}
}
}
// Flat field fallbacks (backward compatibility)
foreach ([
'document_type', 'country', 'authority', 'license_no', 'company_name',
'business_name', 'license_category', 'legal_type', 'issue_date', 'expiry_date',
'main_license_no', 'register_no', 'dcci_no', 'members'
] as $field) {
if (!isset($newLicenseData[$field]) && $request->has($field)) {
$newLicenseData[$field] = $request->input($field);
}
}
if (!empty($newLicenseData)) {
$sponsorData['license_data'] = array_merge($currentLicenseData, $newLicenseData);
}
$sponsor->update($sponsorData);
return response()->json([
'success' => true,
'message' => 'Profile updated successfully.',
'data' => [
'sponsor' => $this->buildSponsorResponse($sponsor)
]
], 200);
} catch (\Exception $e) {
logger()->error('Mobile Sponsor Update Profile Failure: ' . $e->getMessage());
return response()->json([
'success' => false,
'message' => 'An error occurred while updating profile.',
'error' => app()->environment('local') ? $e->getMessage() : 'Internal Server Error'
], 500);
}
}
} }

View File

@ -338,6 +338,14 @@ public function uploadEmiratesId(Request $request)
$extractedIssueDate = $backOcr['issue_date'] ?? null; $extractedIssueDate = $backOcr['issue_date'] ?? null;
} }
if (($request->hasFile('emirates_id_front') || $request->hasFile('emirates_id_file')) && empty($extractedName)) {
return response()->json([
'errors' => [
'emirates_id_front' => ['Failed to extract name from the Emirates ID. Please upload a clear image.']
]
], 422);
}
$pending = session('pending_employer_registration'); $pending = session('pending_employer_registration');
$pending['emirates_id_file'] = null; $pending['emirates_id_file'] = null;
$pending['emirates_id_number'] = $extractedIdNumber; $pending['emirates_id_number'] = $extractedIdNumber;
@ -351,6 +359,14 @@ public function uploadEmiratesId(Request $request)
$pending['emirates_id_employer'] = $extractedEmployer; $pending['emirates_id_employer'] = $extractedEmployer;
$pending['emirates_id_issue_place'] = $extractedIssuePlace; $pending['emirates_id_issue_place'] = $extractedIssuePlace;
$pending['emirates_id_issue_date'] = $extractedIssueDate; $pending['emirates_id_issue_date'] = $extractedIssueDate;
if (!empty($extractedName)) {
if (isset($pending['company_name']) && (empty($pending['company_name']) || $pending['company_name'] === ($pending['name'] ?? '') . ' Household')) {
$pending['company_name'] = $extractedName . ' Household';
}
$pending['name'] = $extractedName;
}
session(['pending_employer_registration' => $pending]); session(['pending_employer_registration' => $pending]);
session(['employer_emirates_id_uploaded' => true]); session(['employer_emirates_id_uploaded' => true]);

View File

@ -170,6 +170,62 @@ public function update(Request $request)
$oldEmail = $user->getOriginal('email') ?? $user->email; $oldEmail = $user->getOriginal('email') ?? $user->email;
// Update EmployerProfile
$profile = $user->employerProfile;
if (!$profile) {
$profile = new EmployerProfile(['user_id' => $user->id]);
}
// Handle physical uploads/OCR for Emirates ID first to allow name autofill
$uploaded = false;
$extractedIdNumber = null;
$extractedExpiry = null;
$extractedName = null;
$extractedDob = null;
$extractedNationality = null;
$extractedCardNumber = null;
$extractedGender = null;
$extractedOccupation = null;
$extractedEmployer = null;
$extractedIssuePlace = null;
$extractedIssueDate = null;
if ($request->hasFile('emirates_id_front')) {
$file = $request->file('emirates_id_front');
$ocrFront = \App\Services\OcrDocumentService::extractEmiratesIdFrontData($file);
$extractedIdNumber = $ocrFront['emirates_id_number'] ?? null;
$extractedName = $ocrFront['name'] ?? null;
$extractedDob = $ocrFront['date_of_birth'] ?? null;
$extractedNationality = $ocrFront['nationality'] ?? null;
$extractedCardNumber = $ocrFront['card_number'] ?? null;
$extractedGender = $ocrFront['gender'] ?? null;
$extractedOccupation = $ocrFront['occupation'] ?? null;
$extractedEmployer = $ocrFront['employer'] ?? null;
$extractedIssuePlace = $ocrFront['issue_place'] ?? null;
$profile->emirates_id_front = '[DELETED_FOR_PDPL_COMPLIANCE]';
$uploaded = true;
}
if ($request->hasFile('emirates_id_back')) {
$file = $request->file('emirates_id_back');
$ocrBack = \App\Services\OcrDocumentService::extractEmiratesIdBackData($file);
$extractedExpiry = $ocrBack['expiry_date'] ?? null;
$extractedIssueDate = $ocrBack['issue_date'] ?? null;
$profile->emirates_id_back = '[DELETED_FOR_PDPL_COMPLIANCE]';
$uploaded = true;
}
if ($uploaded) {
// Validate that name was extracted
if ($request->hasFile('emirates_id_front') && empty($extractedName)) {
return back()->withErrors(['emirates_id_front' => 'Failed to extract name from the Emirates ID. Please upload a clear image.']);
}
// Autofill user's name
if (!empty($extractedName)) {
$request->merge(['name' => $extractedName]);
}
}
// Update User Model // Update User Model
$user->update([ $user->update([
'name' => $request->name, 'name' => $request->name,
@ -179,18 +235,50 @@ public function update(Request $request)
// Sync with corresponding sponsor record if found // Sync with corresponding sponsor record if found
$matchingSponsor = \App\Models\Sponsor::where('email', $oldEmail)->first(); $matchingSponsor = \App\Models\Sponsor::where('email', $oldEmail)->first();
if ($matchingSponsor) { if ($matchingSponsor) {
$matchingSponsor->update([ $sponsorData = [
'full_name' => $request->name, 'full_name' => $request->name,
'email' => $request->email, 'email' => $request->email,
'mobile' => $request->phone, 'mobile' => $request->phone,
]); ];
if ($uploaded) {
if ($extractedIdNumber) {
$sponsorData['emirates_id'] = $extractedIdNumber;
}
if ($extractedExpiry) {
$sponsorData['emirates_id_expiry'] = $extractedExpiry;
}
if ($extractedName) {
$sponsorData['emirates_id_name'] = $extractedName;
}
if ($extractedDob) {
$sponsorData['emirates_id_dob'] = $extractedDob;
}
if ($extractedNationality) {
$sponsorData['nationality'] = $extractedNationality;
}
if ($extractedGender) {
$sponsorData['emirates_id_gender'] = $extractedGender;
}
if ($extractedOccupation) {
$sponsorData['emirates_id_occupation'] = $extractedOccupation;
}
if ($extractedEmployer) {
$sponsorData['emirates_id_employer'] = $extractedEmployer;
}
if ($extractedIssuePlace) {
$sponsorData['emirates_id_issue_place'] = $extractedIssuePlace;
}
if ($extractedIssueDate) {
$sponsorData['emirates_id_issue_date'] = $extractedIssueDate;
}
if ($extractedCardNumber) {
$sponsorData['emirates_id_card_number'] = $extractedCardNumber;
}
}
$matchingSponsor->update($sponsorData);
} }
// Update EmployerProfile
$profile = $user->employerProfile;
if (!$profile) {
$profile = new EmployerProfile(['user_id' => $user->id]);
}
$profile->phone = $request->phone; $profile->phone = $request->phone;
if ($request->has('company_name')) { if ($request->has('company_name')) {
@ -202,7 +290,7 @@ public function update(Request $request)
if ($request->has('notifications')) { if ($request->has('notifications')) {
$profile->notifications = $request->notifications; $profile->notifications = $request->notifications;
} }
if ($request->has('nationality')) { if ($request->has('nationality') && !$uploaded) {
$profile->nationality = $request->nationality; $profile->nationality = $request->nationality;
} }
if ($request->has('family_size')) { if ($request->has('family_size')) {
@ -227,35 +315,38 @@ public function update(Request $request)
$profile->push_notifications = $request->push_notifications; $profile->push_notifications = $request->push_notifications;
} }
// Handle physical uploads/OCR for Emirates ID
$uploaded = false;
$extractedIdNumber = null;
$extractedExpiry = null;
if ($request->hasFile('emirates_id_front')) {
$file = $request->file('emirates_id_front');
$ocrFront = \App\Services\OcrDocumentService::extractData($file);
$extractedIdNumber = $ocrFront['document_number'];
$extractedExpiry = $ocrFront['expiry_date'];
$profile->emirates_id_front = '[DELETED_FOR_PDPL_COMPLIANCE]';
$uploaded = true;
}
if ($request->hasFile('emirates_id_back')) {
$file = $request->file('emirates_id_back');
$ocrBack = \App\Services\OcrDocumentService::extractData($file);
if (empty($extractedIdNumber)) {
$extractedIdNumber = $ocrBack['document_number'];
}
if (empty($extractedExpiry)) {
$extractedExpiry = $ocrBack['expiry_date'];
}
$profile->emirates_id_back = '[DELETED_FOR_PDPL_COMPLIANCE]';
$uploaded = true;
}
if ($uploaded) { if ($uploaded) {
$profile->emirates_id_number = $extractedIdNumber ?? ('784-' . rand(1975, 2005) . '-' . rand(1000000, 9999999) . '-' . rand(1, 9)); $profile->emirates_id_number = $extractedIdNumber ?? $profile->emirates_id_number ?? ('784-' . rand(1975, 2005) . '-' . rand(1000000, 9999999) . '-' . rand(1, 9));
$profile->emirates_id_expiry = $extractedExpiry ?? now()->addYears(rand(2, 4))->toDateString(); $profile->emirates_id_expiry = $extractedExpiry ?? $profile->emirates_id_expiry ?? now()->addYears(rand(2, 4))->toDateString();
if ($extractedName) {
$profile->emirates_id_name = $extractedName;
}
if ($extractedDob) {
$profile->emirates_id_dob = $extractedDob;
}
if ($extractedNationality) {
$profile->nationality = $extractedNationality;
}
if ($extractedCardNumber) {
$profile->emirates_id_card_number = $extractedCardNumber;
}
if ($extractedGender) {
$profile->emirates_id_gender = $extractedGender;
}
if ($extractedOccupation) {
$profile->emirates_id_occupation = $extractedOccupation;
}
if ($extractedEmployer) {
$profile->emirates_id_employer = $extractedEmployer;
}
if ($extractedIssuePlace) {
$profile->emirates_id_issue_place = $extractedIssuePlace;
}
if ($extractedIssueDate) {
$profile->emirates_id_issue_date = $extractedIssueDate;
}
$profile->verification_status = 'approved'; $profile->verification_status = 'approved';
} }

View File

@ -45,6 +45,7 @@ class Sponsor extends Authenticatable
'emirates_id_occupation', 'emirates_id_occupation',
'emirates_id_card_number', 'emirates_id_card_number',
'emirates_id_gender', 'emirates_id_gender',
'license_data',
]; ];
protected $hidden = [ protected $hidden = [
@ -59,6 +60,7 @@ class Sponsor extends Authenticatable
'subscription_end_date' => 'datetime', 'subscription_end_date' => 'datetime',
'last_login_at' => 'datetime', 'last_login_at' => 'datetime',
'license_expiry' => 'datetime', 'license_expiry' => 'datetime',
'license_data' => 'array',
]; ];
public function hasActiveSubscription(): bool public function hasActiveSubscription(): bool
@ -66,4 +68,58 @@ public function hasActiveSubscription(): bool
return $this->subscription_status === 'active' && return $this->subscription_status === 'active' &&
($this->subscription_end_date === null || $this->subscription_end_date->isFuture()); ($this->subscription_end_date === null || $this->subscription_end_date->isFuture());
} }
/**
* Convert the model instance to an array.
*
* @return array
*/
public function toArray()
{
$array = parent::toArray();
// Remove any flat properties that should be nested
unset($array['license_data']);
$licenseDataObj = [
'license_number' => $this->license_data['license_no'] ?? $this->license_data['license_number'] ?? null,
'organization_name' => $this->organization_name,
'expiry_date' => $this->license_expiry ? $this->license_expiry->toDateString() : ($this->license_data['expiry_date'] ?? null),
'document_type' => $this->license_data['document_type'] ?? null,
'country' => $this->license_data['country'] ?? null,
'authority' => $this->license_data['authority'] ?? null,
'license_no' => $this->license_data['license_no'] ?? null,
'company_name' => $this->license_data['company_name'] ?? null,
'business_name' => $this->license_data['business_name'] ?? null,
'license_category' => $this->license_data['license_category'] ?? null,
'legal_type' => $this->license_data['legal_type'] ?? null,
'issue_date' => $this->license_data['issue_date'] ?? null,
'main_license_no' => $this->license_data['main_license_no'] ?? null,
'register_no' => $this->license_data['register_no'] ?? null,
'dcci_no' => $this->license_data['dcci_no'] ?? null,
'members' => $this->license_data['members'] ?? [],
];
$emiratesIdData = null;
if ($this->emirates_id) {
$emiratesIdData = [
'emirates_id_number' => $this->emirates_id,
'name' => $this->emirates_id_name,
'nationality' => $this->nationality,
'date_of_birth' => $this->emirates_id_dob,
'expiry_date' => $this->emirates_id_expiry,
'issue_date' => $this->emirates_id_issue_date,
'employer' => $this->emirates_id_employer,
'issue_place' => $this->emirates_id_issue_place,
'occupation' => $this->emirates_id_occupation,
'card_number' => $this->emirates_id_card_number,
'gender' => $this->emirates_id_gender,
];
}
return array_merge($array, [
'license' => $licenseDataObj,
'emirates_id' => $emiratesIdData,
]);
}
} }

View File

@ -0,0 +1,32 @@
<?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::table('sponsors', function (Blueprint $table) {
if (!Schema::hasColumn('sponsors', 'license_data')) {
$table->json('license_data')->nullable()->after('license_expiry');
}
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('sponsors', function (Blueprint $table) {
if (Schema::hasColumn('sponsors', 'license_data')) {
$table->dropColumn('license_data');
}
});
}
};

View File

@ -74,6 +74,78 @@
"expiry_date": { "expiry_date": {
"type": "string", "type": "string",
"example": "2028-06-12" "example": "2028-06-12"
},
"document_type": {
"type": "string",
"example": "dubai_commercial_license"
},
"country": {
"type": "string",
"example": "United Arab Emirates"
},
"authority": {
"type": "string",
"example": "Dubai Economy and Tourism"
},
"license_no": {
"type": "string",
"example": "828302"
},
"company_name": {
"type": "string",
"example": "MSJ INTERNATIONAL TECHNICAL SERVICES L.L.C"
},
"business_name": {
"type": "string",
"example": "MSJ INTERNATIONAL TECHNICAL SERVICES L.L.C"
},
"license_category": {
"type": "string",
"example": "Dep. of Economic Development"
},
"legal_type": {
"type": "string",
"example": "Limited Liability Company Single Owner(LLC- - SO)"
},
"issue_date": {
"type": "string",
"example": "06/03/2019"
},
"main_license_no": {
"type": "string",
"example": "828302"
},
"register_no": {
"type": "string",
"example": "1656486"
},
"dcci_no": {
"type": "string",
"example": "317412"
},
"members": {
"type": "array",
"items": {
"type": "object",
"properties": {
"person_no": {
"type": "string",
"example": "752967"
},
"name": {
"type": "string",
"example": "Lo lo wallus issued in"
},
"nationality": {
"type": "string",
"example": "India"
},
"role": {
"type": "string",
"example": "Shares Owner Who"
}
}
}
} }
} }
}, },
@ -586,7 +658,28 @@
"description": "Returns the full profile of the authenticated sponsor.", "description": "Returns the full profile of the authenticated sponsor.",
"responses": { "responses": {
"200": { "200": {
"description": "Profile retrieved successfully." "description": "Profile retrieved successfully.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"success": {
"type": "boolean",
"example": true
},
"data": {
"type": "object",
"properties": {
"sponsor": {
"$ref": "#/components/schemas/Sponsor"
}
}
}
}
}
}
}
}, },
"401": { "401": {
"description": "Unauthenticated." "description": "Unauthenticated."
@ -594,6 +687,248 @@
} }
} }
}, },
"/sponsors/profile/update": {
"post": {
"tags": [
"Sponsor"
],
"summary": "Update Sponsor Profile Details",
"description": "Updates the authenticated sponsor's profile details including optional Emirates ID metadata. Password changes are not supported via this endpoint.",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"email",
"mobile",
"organization_name",
"city",
"address",
"nationality",
"country_code"
],
"properties": {
"name": {
"type": "string",
"example": "Ahmad Bin Ahmed",
"description": "Sponsor's full name (required if full_name is not provided)"
},
"full_name": {
"type": "string",
"example": "Ahmad Bin Ahmed",
"description": "Sponsor's full name (required if name is not provided)"
},
"email": {
"type": "string",
"format": "email",
"example": "test.sponsor@example.com"
},
"mobile": {
"type": "string",
"example": "+971509990001"
},
"organization_name": {
"type": "string",
"example": "Charity Co"
},
"city": {
"type": "string",
"example": "Abu Dhabi"
},
"address": {
"type": "string",
"example": "Villa 45, Street 12"
},
"nationality": {
"type": "string",
"example": "Emirati"
},
"country_code": {
"type": "string",
"example": "+971"
},
"fcm_token": {
"type": "string",
"example": "fcm_token_example",
"description": "Firebase Cloud Messaging token for push notifications."
},
"license": {
"type": "object",
"description": "Optional JSON object containing extracted commercial license details.",
"properties": {
"license_number": {
"type": "string",
"example": "828302"
},
"organization_name": {
"type": "string",
"example": "MSJ INTERNATIONAL TECHNICAL SERVICES L.L.C"
},
"expiry_date": {
"type": "string",
"example": "05/03/2026"
},
"document_type": {
"type": "string",
"example": "dubai_commercial_license"
},
"country": {
"type": "string",
"example": "United Arab Emirates"
},
"authority": {
"type": "string",
"example": "Dubai Economy and Tourism"
},
"license_no": {
"type": "string",
"example": "828302"
},
"company_name": {
"type": "string",
"example": "MSJ INTERNATIONAL TECHNICAL SERVICES L.L.C"
},
"business_name": {
"type": "string",
"example": "MSJ INTERNATIONAL TECHNICAL SERVICES L.L.C"
},
"license_category": {
"type": "string",
"example": "Dep. of Economic Development"
},
"legal_type": {
"type": "string",
"example": "Limited Liability Company Single Owner(LLC- - SO)"
},
"issue_date": {
"type": "string",
"example": "06/03/2019"
},
"main_license_no": {
"type": "string",
"example": "828302"
},
"register_no": {
"type": "string",
"example": "1656486"
},
"dcci_no": {
"type": "string",
"example": "317412"
},
"members": {
"type": "array",
"items": {
"type": "object",
"properties": {
"person_no": {
"type": "string",
"example": "752967"
},
"name": {
"type": "string",
"example": "Lo lo wallus issued in"
},
"nationality": {
"type": "string",
"example": "India"
},
"role": {
"type": "string",
"example": "Shares Owner Who"
}
}
}
}
}
},
"emirates_id": {
"type": "object",
"description": "Optional JSON object containing extracted Emirates ID details.",
"properties": {
"emirates_id_number": {
"type": "string",
"example": "784-1988-5310327-2"
},
"name": {
"type": "string",
"example": "KRISHNA PRASAD"
},
"nationality": {
"type": "string",
"example": "NPL"
},
"date_of_birth": {
"type": "string",
"example": "1988-03-22"
},
"expiry_date": {
"type": "string",
"example": "2028-04-11"
},
"issue_date": {
"type": "string",
"example": "2023-04-11"
},
"employer": {
"type": "string",
"example": "Federal Authority"
},
"issue_place": {
"type": "string",
"example": "Abu Dhabi"
},
"occupation": {
"type": "string",
"example": "Manager"
}
}
}
}
}
}
}
},
"responses": {
"200": {
"description": "Profile updated successfully.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"success": {
"type": "boolean",
"example": true
},
"message": {
"type": "string",
"example": "Profile updated successfully."
},
"data": {
"type": "object",
"properties": {
"sponsor": {
"$ref": "#/components/schemas/Sponsor"
}
}
}
}
}
}
}
},
"401": {
"description": "Unauthenticated."
},
"422": {
"description": "Validation error (duplicate mobile/email, mismatched immutable fields, or name extraction failure)."
}
}
}
},
"/workers/register": { "/workers/register": {
"post": { "post": {
"tags": [ "tags": [
@ -6251,6 +6586,131 @@
"type": "string", "type": "string",
"example": "fcm_token_example" "example": "fcm_token_example"
}, },
"document_type": {
"type": "string",
"example": "dubai_commercial_license"
},
"country": {
"type": "string",
"example": "United Arab Emirates"
},
"authority": {
"type": "string",
"example": "Dubai Economy and Tourism"
},
"license_no": {
"type": "string",
"example": "828302"
},
"company_name": {
"type": "string",
"example": "MSJ INTERNATIONAL TECHNICAL SERVICES L.L.C"
},
"business_name": {
"type": "string",
"example": "MSJ INTERNATIONAL TECHNICAL SERVICES L.L.C"
},
"license_category": {
"type": "string",
"example": "Dep. of Economic Development"
},
"legal_type": {
"type": "string",
"example": "Limited Liability Company Single Owner(LLC- - SO)"
},
"issue_date": {
"type": "string",
"example": "06/03/2019"
},
"expiry_date": {
"type": "string",
"example": "05/03/2026"
},
"main_license_no": {
"type": "string",
"example": "828302"
},
"register_no": {
"type": "string",
"example": "1656486"
},
"dcci_no": {
"type": "string",
"example": "317412"
},
"members": {
"type": "array",
"items": {
"type": "object",
"properties": {
"person_no": {
"type": "string",
"example": "752967"
},
"name": {
"type": "string",
"example": "Lo lo wallus issued in"
},
"nationality": {
"type": "string",
"example": "India"
},
"role": {
"type": "string",
"example": "Shares Owner Who"
}
}
}
},
"emirates_id": {
"type": "object",
"properties": {
"emirates_id_number": {
"type": "string",
"example": "784-1988-5310327-2"
},
"name": {
"type": "string",
"example": "KRISHNA PRASAD"
},
"nationality": {
"type": "string",
"example": "NPL"
},
"date_of_birth": {
"type": "string",
"example": "1988-03-22"
},
"expiry_date": {
"type": "string",
"example": "2028-04-11"
},
"issue_date": {
"type": "string",
"example": "2023-04-11"
},
"employer": {
"type": "string",
"example": "Federal Authority"
},
"issue_place": {
"type": "string",
"example": "Abu Dhabi"
},
"occupation": {
"type": "string",
"example": "Manager"
},
"card_number": {
"type": "string",
"example": "987654321"
},
"gender": {
"type": "string",
"example": "Male"
}
}
},
"created_at": { "created_at": {
"type": "string", "type": "string",
"format": "date-time" "format": "date-time"

View File

@ -72,9 +72,10 @@ export default function UploadEmiratesId({ email }) {
} catch (err) { } catch (err) {
if (err.response && err.response.data && err.response.data.errors) { if (err.response && err.response.data && err.response.data.errors) {
const errors = err.response.data.errors; const errors = err.response.data.errors;
const errorMsg = errors.emirates_id_front?.[0] || errors.emirates_id_back?.[0] || 'Validation failed.'; const firstError = Object.values(errors).flat()[0];
const errorMsg = firstError || 'Validation failed.';
setError(errorMsg); setError(errorMsg);
toast.error('Validation failed. Please verify the document.'); toast.error(errorMsg);
} else { } else {
toast.error('Scan extraction failed. Please try again.'); toast.error('Scan extraction failed. Please try again.');
} }

View File

@ -159,6 +159,7 @@
// Sponsors can ONLY access: dashboard and charity events. No payments, no worker browsing. // Sponsors can ONLY access: dashboard and charity events. No payments, no worker browsing.
Route::middleware(['auth.sponsor'])->group(function () { Route::middleware(['auth.sponsor'])->group(function () {
Route::get('/sponsors/profile', [SponsorController::class, 'getProfile']); Route::get('/sponsors/profile', [SponsorController::class, 'getProfile']);
Route::post('/sponsors/profile/update', [SponsorController::class, 'updateProfile']);
Route::get('/sponsors/dashboard', [SponsorController::class, 'getDashboard']); Route::get('/sponsors/dashboard', [SponsorController::class, 'getDashboard']);
Route::get('/sponsors/charity-events', [SponsorController::class, 'getCharityEvents']); Route::get('/sponsors/charity-events', [SponsorController::class, 'getCharityEvents']);
Route::post('/sponsors/charity-events', [SponsorController::class, 'postCharityEvent']); Route::post('/sponsors/charity-events', [SponsorController::class, 'postCharityEvent']);

View File

@ -141,7 +141,7 @@ public function test_employer_can_update_profile_with_emirates_id_fields()
'success' => true, 'success' => true,
'data' => [ 'data' => [
'profile' => [ 'profile' => [
'name' => 'Ahmad', 'name' => 'Mohammad Jobaier Mohammad Abul Kalam',
'email' => 'ahmad@example.com', 'email' => 'ahmad@example.com',
'phone' => '+971509990001', 'phone' => '+971509990001',
'address' => 'Villa 12, Jumeirah 2', 'address' => 'Villa 12, Jumeirah 2',

View File

@ -54,6 +54,7 @@ public function test_employer_registration_and_login_stores_fcm_token_and_sends_
'fcm_token' => 'employer_register_fcm_token', 'fcm_token' => 'employer_register_fcm_token',
'emirates_id' => [ 'emirates_id' => [
'emirates_id_number' => '784-1988-5310327-2', 'emirates_id_number' => '784-1988-5310327-2',
'name' => 'Employer FCM Test',
'expiry_date' => '2028-04-11', 'expiry_date' => '2028-04-11',
] ]
]); ]);
@ -182,6 +183,7 @@ public function test_sponsor_registration_and_login_stores_fcm_token_and_sends_n
], ],
'emirates_id' => [ 'emirates_id' => [
'emirates_id_number' => '784-1988-5310327-2', 'emirates_id_number' => '784-1988-5310327-2',
'name' => 'Sponsor FCM Org',
], ],
'organization_name' => 'Sponsor FCM Co', 'organization_name' => 'Sponsor FCM Co',
'email' => 'sponsor.fcm@example.com', 'email' => 'sponsor.fcm@example.com',

View File

@ -71,7 +71,7 @@ public function test_sponsor_can_register_with_license()
$this->assertDatabaseHas('sponsors', [ $this->assertDatabaseHas('sponsors', [
'mobile' => '+971509990001', 'mobile' => '+971509990001',
'full_name' => 'Test Sponsor Organization', 'full_name' => 'Ahmad Bin Ahmed',
'license_expiry' => '2028-06-12 00:00:00', 'license_expiry' => '2028-06-12 00:00:00',
'emirates_id_name' => 'Ahmad Bin Ahmed', 'emirates_id_name' => 'Ahmad Bin Ahmed',
'emirates_id_dob' => '1990-01-01', 'emirates_id_dob' => '1990-01-01',
@ -537,6 +537,7 @@ public function test_sponsor_registration_with_emirates_id_and_separate_license(
'country_code' => '+971', 'country_code' => '+971',
'emirates_id' => [ 'emirates_id' => [
'emirates_id_number' => '784-1988-5310327-2', 'emirates_id_number' => '784-1988-5310327-2',
'name' => 'Test Sponsor Sep',
], ],
]); ]);
@ -657,4 +658,227 @@ public function test_sponsor_can_change_password()
$this->assertTrue(Hash::check('NewPassword@123', $sponsor->fresh()->password)); $this->assertTrue(Hash::check('NewPassword@123', $sponsor->fresh()->password));
$this->assertTrue(Hash::check('NewPassword@123', $user->fresh()->password)); $this->assertTrue(Hash::check('NewPassword@123', $user->fresh()->password));
} }
/**
* Test sponsor can update profile and it correctly syncs with matching user/employer profile.
*/
public function test_sponsor_can_update_profile_and_syncs_with_matching_user()
{
$sponsor = Sponsor::create([
'full_name' => 'Original Sponsor Name',
'email' => 'sync-test@example.com',
'mobile' => '+971500000001',
'password' => Hash::make('Password@123'),
'organization_name' => 'Original Org',
'city' => 'Dubai',
'address' => 'Old Address',
'nationality' => 'Emirati',
'country_code' => '+971',
'api_token' => 'sync-test-token',
'status' => 'active',
]);
$user = User::create([
'name' => 'Original Sponsor Name',
'email' => 'sync-test@example.com',
'password' => Hash::make('Password@123'),
'role' => 'employer',
]);
$profile = EmployerProfile::create([
'user_id' => $user->id,
'company_name' => 'Original Org',
'phone' => '+971500000001',
'address' => 'Old Address',
]);
$response = $this->withHeaders([
'Authorization' => 'Bearer sync-test-token',
])->postJson('/api/sponsors/profile/update', [
'name' => 'Updated Sponsor Name',
'email' => 'new-sync-test@example.com',
'mobile' => '+971500000002',
'organization_name' => 'Updated Org',
'city' => 'Abu Dhabi',
'address' => 'New Address',
'nationality' => 'Jordanian',
'country_code' => '+962',
'emirates_id' => [
'emirates_id_number' => '784-2000-1234567-1',
'name' => 'Updated Sponsor Name',
'date_of_birth' => '2000-01-01',
'expiry_date' => '2028-01-01',
'issue_date' => '2023-01-01',
'occupation' => 'Business Owner',
'employer' => 'Self',
'issue_place' => 'Abu Dhabi',
],
'license' => [
'document_type' => 'dubai_commercial_license',
'country' => 'United Arab Emirates',
'authority' => 'Dubai Economy and Tourism',
'license_no' => '828302',
'company_name' => 'MSJ INTERNATIONAL TECHNICAL SERVICES L.L.C',
'business_name' => 'MSJ INTERNATIONAL TECHNICAL SERVICES L.L.C',
'license_category' => 'Dep. of Economic Development',
'legal_type' => 'Limited Liability Company Single Owner(LLC- - SO)',
'main_license_no' => '828302',
'register_no' => '1656486',
'dcci_no' => '317412',
'members' => [
[
'person_no' => '752967',
'name' => 'Lo lo wallus issued in',
'nationality' => 'India',
'role' => 'Shares Owner Who'
]
]
]
]);
$response->assertStatus(200)
->assertJson([
'success' => true,
'message' => 'Profile updated successfully.',
])
->assertJsonPath('data.sponsor.full_name', 'Updated Sponsor Name')
->assertJsonPath('data.sponsor.email', 'new-sync-test@example.com')
->assertJsonPath('data.sponsor.mobile', '+971500000002')
->assertJsonPath('data.sponsor.organization_name', 'Updated Org')
->assertJsonPath('data.sponsor.city', 'Abu Dhabi')
->assertJsonPath('data.sponsor.address', 'New Address')
->assertJsonPath('data.sponsor.nationality', 'Jordanian')
->assertJsonPath('data.sponsor.country_code', '+962')
->assertJsonPath('data.sponsor.license.document_type', 'dubai_commercial_license')
->assertJsonPath('data.sponsor.license.license_no', '828302')
->assertJsonPath('data.sponsor.license.members.0.name', 'Lo lo wallus issued in')
->assertJsonPath('data.sponsor.emirates_id.emirates_id_number', '784-2000-1234567-1')
->assertJsonPath('data.sponsor.emirates_id.name', 'Updated Sponsor Name')
->assertJsonPath('data.sponsor.emirates_id.occupation', 'Business Owner')
->assertJsonPath('data.sponsor.emirates_id.employer', 'Self');
// Check Sponsor table
$this->assertDatabaseHas('sponsors', [
'id' => $sponsor->id,
'full_name' => 'Updated Sponsor Name',
'email' => 'new-sync-test@example.com',
'mobile' => '+971500000002',
'emirates_id' => '784-2000-1234567-1',
'emirates_id_name' => 'Updated Sponsor Name',
'emirates_id_dob' => '2000-01-01',
'emirates_id_issue_date' => '2023-01-01',
'emirates_id_expiry' => '2028-01-01',
'emirates_id_occupation' => 'Business Owner',
'emirates_id_employer' => 'Self',
'emirates_id_issue_place' => 'Abu Dhabi',
]);
$dbSponsor = Sponsor::find($sponsor->id);
$this->assertEquals('dubai_commercial_license', $dbSponsor->license_data['document_type']);
$this->assertEquals('Lo lo wallus issued in', $dbSponsor->license_data['members'][0]['name']);
// Check User table
$this->assertDatabaseHas('users', [
'id' => $user->id,
'name' => 'Updated Sponsor Name',
'email' => 'new-sync-test@example.com',
]);
// Check EmployerProfile table
$this->assertDatabaseHas('employer_profiles', [
'id' => $profile->id,
'address' => 'New Address',
'phone' => '+971500000002',
'emirates_id_number' => '784-2000-1234567-1',
'emirates_id_name' => 'Updated Sponsor Name',
'emirates_id_dob' => '2000-01-01',
'emirates_id_issue_date' => '2023-01-01',
'emirates_id_expiry' => '2028-01-01',
'emirates_id_occupation' => 'Business Owner',
'emirates_id_employer' => 'Self',
'emirates_id_issue_place' => 'Abu Dhabi',
]);
}
/**
* Test sponsor cannot change immutable Emirates ID.
*/
public function test_sponsor_cannot_change_immutable_emirates_id()
{
$sponsor = Sponsor::create([
'full_name' => 'Sponsor Name',
'email' => 'immutable-id-test@example.com',
'mobile' => '+971500000003',
'password' => Hash::make('Password@123'),
'organization_name' => 'Org',
'city' => 'Dubai',
'address' => 'Address',
'nationality' => 'Emirati',
'country_code' => '+971',
'api_token' => 'immutable-id-token',
'status' => 'active',
'emirates_id' => '784-2000-1234567-1',
]);
$response = $this->withHeaders([
'Authorization' => 'Bearer immutable-id-token',
])->postJson('/api/sponsors/profile/update', [
'name' => 'Sponsor Name',
'email' => 'immutable-id-test@example.com',
'mobile' => '+971500000003',
'organization_name' => 'Org',
'city' => 'Dubai',
'address' => 'Address',
'nationality' => 'Emirati',
'country_code' => '+971',
'id_number' => '784-2000-7654321-2', // different EID
]);
$response->assertStatus(422)
->assertJson([
'success' => false,
'message' => 'Emirates ID mismatch. You cannot change your registered Emirates ID number.',
])
->assertJsonValidationErrors(['id_number']);
}
/**
* Test sponsor profile update validates Emirates ID name OCR.
*/
public function test_sponsor_profile_update_validates_emirates_id_name_ocr()
{
$sponsor = Sponsor::create([
'full_name' => 'Sponsor Name',
'email' => 'ocr-test@example.com',
'mobile' => '+971500000004',
'password' => Hash::make('Password@123'),
'organization_name' => 'Org',
'city' => 'Dubai',
'address' => 'Address',
'nationality' => 'Emirati',
'country_code' => '+971',
'api_token' => 'ocr-token',
'status' => 'active',
]);
$response = $this->withHeaders([
'Authorization' => 'Bearer ocr-token',
])->postJson('/api/sponsors/profile/update', [
'name' => 'Sponsor Name',
'email' => 'ocr-test@example.com',
'mobile' => '+971500000004',
'organization_name' => 'Org',
'city' => 'Dubai',
'address' => 'Address',
'nationality' => 'Emirati',
'country_code' => '+971',
'id_number' => '784-2000-1234567-1',
'full_name' => '', // Failed OCR name extraction
]);
$response->assertStatus(422)
->assertJsonValidationErrors(['full_name'])
->assertJsonPath('errors.full_name.0', 'Failed to extract name from the Emirates ID. Please upload a clearer document.');
}
} }

View File

@ -80,11 +80,12 @@ public function test_employer_web_registration_with_emirates_id()
$this->assertDatabaseHas('users', [ $this->assertDatabaseHas('users', [
'email' => 'abdullah@example.com', 'email' => 'abdullah@example.com',
'role' => 'employer', 'role' => 'employer',
'name' => 'Mohammad Jobaier Mohammad Abul Kalam',
]); ]);
$this->assertDatabaseHas('employer_profiles', [ $this->assertDatabaseHas('employer_profiles', [
'phone' => '501234567', 'phone' => '501234567',
'company_name' => 'Abdullah Ahmed Household', 'company_name' => 'Mohammad Jobaier Mohammad Abul Kalam Household',
'emirates_id_front' => null, // File is not stored 'emirates_id_front' => null, // File is not stored
'address' => 'Villa 14, Al Safa, Dubai', 'address' => 'Villa 14, Al Safa, Dubai',
]); ]);
@ -106,6 +107,7 @@ public function test_employer_web_registration_with_emirates_id()
'emirates_id_file' => null, // File is not stored 'emirates_id_file' => null, // File is not stored
'address' => 'Villa 14, Al Safa, Dubai', 'address' => 'Villa 14, Al Safa, Dubai',
'emirates_id' => '784-1987-5493842-5', 'emirates_id' => '784-1987-5493842-5',
'full_name' => 'Mohammad Jobaier Mohammad Abul Kalam',
'emirates_id_name' => 'Mohammad Jobaier Mohammad Abul Kalam', 'emirates_id_name' => 'Mohammad Jobaier Mohammad Abul Kalam',
'emirates_id_dob' => '1987-04-14', 'emirates_id_dob' => '1987-04-14',
'nationality' => 'Bangladesh', 'nationality' => 'Bangladesh',
@ -184,6 +186,19 @@ public function test_employer_web_registration_with_two_sided_emirates_id()
$this->assertDatabaseHas('users', [ $this->assertDatabaseHas('users', [
'email' => 'fatima@example.com', 'email' => 'fatima@example.com',
'role' => 'employer', 'role' => 'employer',
'name' => 'Mohammad Jobaier Mohammad Abul Kalam',
]);
$this->assertDatabaseHas('employer_profiles', [
'phone' => '509876543',
'company_name' => 'Mohammad Jobaier Mohammad Abul Kalam Household',
'address' => 'Apartment 402, Al Nahda, Sharjah',
]);
$this->assertDatabaseHas('sponsors', [
'email' => 'fatima@example.com',
'mobile' => '509876543',
'full_name' => 'Mohammad Jobaier Mohammad Abul Kalam',
]); ]);
$profile = EmployerProfile::where('phone', '509876543')->first(); $profile = EmployerProfile::where('phone', '509876543')->first();