mohan #14

Merged
mohanmd merged 5 commits from mohan into master 2026-06-23 19:12:29 +00:00
6 changed files with 226 additions and 80 deletions
Showing only changes of commit 2e89215c9f - Show all commits

View File

@ -54,6 +54,17 @@ public function getProfile(Request $request)
'occupation' => $profile->emirates_id_occupation, 'occupation' => $profile->emirates_id_occupation,
'employer' => $profile->emirates_id_employer, 'employer' => $profile->emirates_id_employer,
'issuing_place' => $profile->emirates_id_issue_place, 'issuing_place' => $profile->emirates_id_issue_place,
'emirates_id' => [
'emirates_id_number' => $profile->emirates_id_number,
'name' => $profile->emirates_id_name,
'date_of_birth' => $profile->emirates_id_dob,
'nationality' => $profile->nationality,
'issue_date' => $profile->emirates_id_issue_date,
'expiry_date' => $profile->emirates_id_expiry,
'employer' => $profile->emirates_id_employer,
'issue_place' => $profile->emirates_id_issue_place,
'occupation' => $profile->emirates_id_occupation,
],
]; ];
return response()->json([ return response()->json([
@ -270,6 +281,17 @@ public function updateProfile(Request $request)
'occupation' => $profile->emirates_id_occupation, 'occupation' => $profile->emirates_id_occupation,
'employer' => $profile->emirates_id_employer, 'employer' => $profile->emirates_id_employer,
'issuing_place' => $profile->emirates_id_issue_place, 'issuing_place' => $profile->emirates_id_issue_place,
'emirates_id' => [
'emirates_id_number' => $profile->emirates_id_number,
'name' => $profile->emirates_id_name,
'date_of_birth' => $profile->emirates_id_dob,
'nationality' => $profile->nationality,
'issue_date' => $profile->emirates_id_issue_date,
'expiry_date' => $profile->emirates_id_expiry,
'employer' => $profile->emirates_id_employer,
'issue_place' => $profile->emirates_id_issue_place,
'occupation' => $profile->emirates_id_occupation,
],
]; ];
return response()->json([ return response()->json([
@ -309,7 +331,14 @@ public function getDashboard(Request $request)
$savedCandidates = \App\Models\Shortlist::where('employer_id', $employer->id)->count(); $savedCandidates = \App\Models\Shortlist::where('employer_id', $employer->id)->count();
$totalWorkers = \App\Models\Worker::where('status', 'active')->count(); $totalWorkers = \App\Models\Worker::with('documents')
->where('status', '!=', 'Hired')
->where('status', '!=', 'hidden')
->get()
->filter(function ($w) {
return !str_contains(strtolower($w->passport_status), 'pending');
})
->count();
// Resolve plan // Resolve plan
$sub = \Illuminate\Support\Facades\DB::table('subscriptions') $sub = \Illuminate\Support\Facades\DB::table('subscriptions')

View File

@ -3897,6 +3897,48 @@
"issuing_place": { "issuing_place": {
"type": "string", "type": "string",
"example": "Dubai" "example": "Dubai"
},
"emirates_id": {
"type": "object",
"description": "Consolidated Emirates ID details.",
"properties": {
"emirates_id_number": {
"type": "string",
"example": "784-1988-5310327-2"
},
"name": {
"type": "string",
"example": "Ahmad Bin Ahmed"
},
"date_of_birth": {
"type": "string",
"example": "1990-01-01"
},
"nationality": {
"type": "string",
"example": "Emirati"
},
"issue_date": {
"type": "string",
"example": "2023-04-11"
},
"expiry_date": {
"type": "string",
"example": "2028-04-11"
},
"employer": {
"type": "string",
"example": "Federal Authority"
},
"issue_place": {
"type": "string",
"example": "Abu Dhabi"
},
"occupation": {
"type": "string",
"example": "Manager"
}
}
} }
} }
} }
@ -4114,6 +4156,48 @@
"issuing_place": { "issuing_place": {
"type": "string", "type": "string",
"example": "Dubai" "example": "Dubai"
},
"emirates_id": {
"type": "object",
"description": "Consolidated Emirates ID details.",
"properties": {
"emirates_id_number": {
"type": "string",
"example": "784-1988-5310327-2"
},
"name": {
"type": "string",
"example": "Ahmad Bin Ahmed"
},
"date_of_birth": {
"type": "string",
"example": "1990-01-01"
},
"nationality": {
"type": "string",
"example": "Emirati"
},
"issue_date": {
"type": "string",
"example": "2023-04-11"
},
"expiry_date": {
"type": "string",
"example": "2028-04-11"
},
"employer": {
"type": "string",
"example": "Federal Authority"
},
"issue_place": {
"type": "string",
"example": "Abu Dhabi"
},
"occupation": {
"type": "string",
"example": "Manager"
}
}
} }
} }
} }

View File

@ -805,10 +805,14 @@ export default function WorkerManagement({ workers }) {
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Document Type</span> <span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Document Type</span>
<span className="font-bold text-slate-800">{safeRender(passportDoc.ocr_data?.document_type)}</span> <span className="font-bold text-slate-800">{safeRender(passportDoc.ocr_data?.document_type)}</span>
</div> </div>
{passportDoc.ocr_data?.personal_number &&
passportDoc.ocr_data?.personal_number !== 'N/A' &&
passportDoc.ocr_data?.personal_number !== (passportDoc.ocr_data?.passport_number || passportDoc.number) && (
<div> <div>
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Personal Number</span> <span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Personal Number</span>
<span className="font-mono font-bold text-slate-800">{safeRender(passportDoc.ocr_data?.personal_number)}</span> <span className="font-mono font-bold text-slate-800">{safeRender(passportDoc.ocr_data?.personal_number)}</span>
</div> </div>
)}
</div> </div>
{passportDoc.file_path && ( {passportDoc.file_path && (
<div className="pt-2 border-t border-slate-200/60 flex justify-end"> <div className="pt-2 border-t border-slate-200/60 flex justify-end">
@ -840,6 +844,16 @@ export default function WorkerManagement({ workers }) {
</div> </div>
); );
} }
const sponsorObj = visaDoc.ocr_data?.sponsor;
const isSponsorObj = sponsorObj && typeof sponsorObj === 'object' && !Array.isArray(sponsorObj);
const sponsorName = isSponsorObj
? (sponsorObj.name || sponsorObj.sponsor_name || sponsorObj.full_name)
: (visaDoc.ocr_data?.sponsor_name || (typeof sponsorObj === 'string' ? sponsorObj : null));
const sponsorMobile = isSponsorObj
? (sponsorObj.mobile || sponsorObj.mobile_number || sponsorObj.phone)
: null;
return ( return (
<div className="p-4 bg-slate-50 rounded-xl border border-slate-100 space-y-3"> <div className="p-4 bg-slate-50 rounded-xl border border-slate-100 space-y-3">
<div className="grid grid-cols-2 gap-3 text-xs"> <div className="grid grid-cols-2 gap-3 text-xs">
@ -851,22 +865,12 @@ export default function WorkerManagement({ workers }) {
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">UID No</span> <span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">UID No</span>
<span className="font-mono font-bold text-slate-800">{safeRender(visaDoc.ocr_data?.uid_no)}</span> <span className="font-mono font-bold text-slate-800">{safeRender(visaDoc.ocr_data?.uid_no)}</span>
</div> </div>
<div> {visaDoc.ocr_data?.id_number && visaDoc.ocr_data?.id_number !== 'N/A' && (
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">File Number</span>
<span className="font-mono font-bold text-slate-800">{safeRender(visaDoc.ocr_data?.file_number || visaDoc.number)}</span>
</div>
<div> <div>
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">ID Number</span> <span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">ID Number</span>
<span className="font-mono font-bold text-slate-800">{safeRender(visaDoc.ocr_data?.id_number)}</span> <span className="font-mono font-bold text-slate-800">{safeRender(visaDoc.ocr_data?.id_number)}</span>
</div> </div>
<div> )}
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Full Name</span>
<span className="font-bold text-slate-800">{safeRender(visaDoc.ocr_data?.full_name)}</span>
</div>
<div>
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Name</span>
<span className="font-bold text-slate-800">{safeRender(visaDoc.ocr_data?.name)}</span>
</div>
<div> <div>
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Visa Type</span> <span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Visa Type</span>
<span className="font-bold text-slate-800">{safeRender(visaDoc.ocr_data?.visa_type)}</span> <span className="font-bold text-slate-800">{safeRender(visaDoc.ocr_data?.visa_type)}</span>
@ -875,10 +879,18 @@ export default function WorkerManagement({ workers }) {
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Profession</span> <span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Profession</span>
<span className="font-bold text-slate-800">{safeRender(visaDoc.ocr_data?.profession)}</span> <span className="font-bold text-slate-800">{safeRender(visaDoc.ocr_data?.profession)}</span>
</div> </div>
{sponsorName && (
<div> <div>
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Sponsor Name</span> <span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Sponsor Name</span>
<span className="font-bold text-slate-800">{safeRender(visaDoc.ocr_data?.sponsor_name)}</span> <span className="font-bold text-slate-800">{safeRender(sponsorName)}</span>
</div> </div>
)}
{sponsorMobile && (
<div>
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Sponsor Mobile</span>
<span className="font-mono font-bold text-slate-800">{safeRender(sponsorMobile)}</span>
</div>
)}
<div> <div>
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Nationality</span> <span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Nationality</span>
@ -888,14 +900,6 @@ export default function WorkerManagement({ workers }) {
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Country</span> <span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Country</span>
<span className="font-bold text-slate-800">{safeRender(visaDoc.ocr_data?.country)}</span> <span className="font-bold text-slate-800">{safeRender(visaDoc.ocr_data?.country)}</span>
</div> </div>
<div>
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Passport No</span>
<span className="font-mono font-bold text-slate-800">{safeRender(visaDoc.ocr_data?.passport_no)}</span>
</div>
<div>
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Passport Number</span>
<span className="font-mono font-bold text-slate-800">{safeRender(visaDoc.ocr_data?.passport_number)}</span>
</div>
<div> <div>
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Date of Birth</span> <span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Date of Birth</span>
<span className="font-bold text-slate-800">{safeRender(visaDoc.ocr_data?.date_of_birth)}</span> <span className="font-bold text-slate-800">{safeRender(visaDoc.ocr_data?.date_of_birth)}</span>
@ -917,10 +921,6 @@ export default function WorkerManagement({ workers }) {
<span className="font-bold text-slate-800">{safeRender(visaDoc.ocr_data?.expiry_date || visaDoc.expiry_date)}</span> <span className="font-bold text-slate-800">{safeRender(visaDoc.ocr_data?.expiry_date || visaDoc.expiry_date)}</span>
</div> </div>
<div>
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Accompanied By</span>
<span className="font-bold text-slate-800">{safeRender(visaDoc.ocr_data?.accompanied_by)}</span>
</div>
<div> <div>
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Document Type</span> <span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Document Type</span>
<span className="font-bold text-slate-800">{safeRender(visaDoc.ocr_data?.document_type)}</span> <span className="font-bold text-slate-800">{safeRender(visaDoc.ocr_data?.document_type)}</span>

View File

@ -564,28 +564,34 @@ export default function Show({ worker }) {
</div> </div>
<div className="p-6"> <div className="p-6">
{visaDoc.ocr_data ? ( {visaDoc.ocr_data ? (() => {
const sponsorObj = visaDoc.ocr_data?.sponsor;
const isSponsorObj = sponsorObj && typeof sponsorObj === 'object' && !Array.isArray(sponsorObj);
const sponsorName = isSponsorObj
? (sponsorObj.name || sponsorObj.sponsor_name || sponsorObj.full_name)
: (visaDoc.ocr_data?.sponsor_name || (typeof sponsorObj === 'string' ? sponsorObj : null));
const sponsorMobile = isSponsorObj
? (sponsorObj.mobile || sponsorObj.mobile_number || sponsorObj.phone)
: null;
return (
<div className="grid grid-cols-2 md:grid-cols-3 gap-y-4 gap-x-6"> <div className="grid grid-cols-2 md:grid-cols-3 gap-y-4 gap-x-6">
<div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('full_name', 'Full Name')}</div>
<div className="text-xs font-bold text-slate-800">{safeRender(visaDoc.ocr_data.name)}</div>
</div>
<div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('file_number', 'File Number')}</div>
<div className="text-xs font-bold text-slate-800">{safeRender(visaDoc.ocr_data.file_number || visaDoc.number)}</div>
</div>
<div> <div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('profession', 'Profession')}</div> <div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('profession', 'Profession')}</div>
<div className="text-xs font-bold text-slate-800">{safeRender(visaDoc.ocr_data.profession || worker.visa_status)}</div> <div className="text-xs font-bold text-slate-800">{safeRender(visaDoc.ocr_data.profession || worker.visa_status)}</div>
</div> </div>
{sponsorName && (
<div> <div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('sponsor', 'Sponsor')}</div> <div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('sponsor', 'Sponsor')}</div>
<div className="text-xs font-bold text-slate-800">{safeRender(visaDoc.ocr_data.sponsor || visaDoc.ocr_data.sponsor_name)}</div> <div className="text-xs font-bold text-slate-800">{safeRender(sponsorName)}</div>
</div> </div>
)}
{sponsorMobile && (
<div> <div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('passport_number', 'Passport Number')}</div> <div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('sponsor_mobile', 'Sponsor Mobile')}</div>
<div className="text-xs font-bold text-slate-800">{safeRender(visaDoc.ocr_data.passport_number)}</div> <div className="text-xs font-mono font-bold text-slate-800">{safeRender(sponsorMobile)}</div>
</div> </div>
)}
<div> <div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('place_of_issue', 'Place of Issue')}</div> <div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('place_of_issue', 'Place of Issue')}</div>
<div className="text-xs font-bold text-slate-800">{safeRender(visaDoc.ocr_data.place_of_issue)}</div> <div className="text-xs font-bold text-slate-800">{safeRender(visaDoc.ocr_data.place_of_issue)}</div>
@ -598,12 +604,9 @@ export default function Show({ worker }) {
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('expiry_date', 'Date of Expiry')}</div> <div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('expiry_date', 'Date of Expiry')}</div>
<div className="text-xs font-bold text-emerald-600">{safeRender(visaDoc.ocr_data.expiry_date || visaDoc.expiry_date)}</div> <div className="text-xs font-bold text-emerald-600">{safeRender(visaDoc.ocr_data.expiry_date || visaDoc.expiry_date)}</div>
</div> </div>
<div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('accompanied_by', 'Accompanied By')}</div>
<div className="text-xs font-bold text-slate-800">{safeRender(visaDoc.ocr_data.accompanied_by)}</div>
</div> </div>
</div> );
) : ( })() : (
<div className="grid grid-cols-2 md:grid-cols-3 gap-y-4 gap-x-6"> <div className="grid grid-cols-2 md:grid-cols-3 gap-y-4 gap-x-6">
<div> <div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('document_id', 'Document ID')}</div> <div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('document_id', 'Document ID')}</div>

View File

@ -57,6 +57,7 @@ public function test_employer_can_get_profile()
'address', 'address',
'notifications', 'notifications',
'verification_status', 'verification_status',
'emirates_id',
] ]
] ]
]); ]);
@ -91,6 +92,7 @@ public function test_employer_can_update_profile()
'phone', 'phone',
'address', 'address',
'notifications', 'notifications',
'emirates_id',
] ]
] ]
]); ]);
@ -147,6 +149,17 @@ public function test_employer_can_update_profile_with_emirates_id_fields()
'card_number' => '151023946', 'card_number' => '151023946',
'full_name' => 'Mohammad Jobaier Mohammad Abul Kalam', 'full_name' => 'Mohammad Jobaier Mohammad Abul Kalam',
'gender' => 'M', 'gender' => 'M',
'emirates_id' => [
'emirates_id_number' => '784-1987-5493842-5',
'name' => 'Mohammad Jobaier Mohammad Abul Kalam',
'date_of_birth' => '14/04/1987',
'nationality' => 'Bangladesh',
'issue_date' => '12/12/2025',
'expiry_date' => '11/12/2027',
'employer' => 'Msj International Technical Services L.L.C UAE',
'issue_place' => 'Dubai',
'occupation' => 'Electrician',
]
] ]
] ]
]); ]);
@ -248,23 +261,40 @@ public function test_employer_dashboard_returns_total_workers_active()
\App\Models\Worker::query()->delete(); \App\Models\Worker::query()->delete();
// Create 3 active workers // Create 3 active workers
\App\Models\Worker::create([ $w1 = \App\Models\Worker::create([
'name' => 'Active 1', 'email' => 'a1@test.com', 'phone' => '+971501110001', 'name' => 'Active 1', 'email' => 'a1@test.com', 'phone' => '+971501110001',
'password' => bcrypt('password'), 'status' => 'active', 'nationality' => 'Indian', 'password' => bcrypt('password'), 'status' => 'active', 'nationality' => 'Indian',
'preferred_location' => 'Dubai', 'age' => 25, 'salary' => 1500, 'experience' => '3 Years', 'preferred_location' => 'Dubai', 'age' => 25, 'salary' => 1500, 'experience' => '3 Years',
'availability' => 'Immediate', 'religion' => 'Christian', 'bio' => 'experienced worker', 'availability' => 'Immediate', 'religion' => 'Christian', 'bio' => 'experienced worker',
'verified' => true,
]); ]);
\App\Models\Worker::create([ \App\Models\WorkerDocument::create([
'worker_id' => $w1->id, 'type' => 'passport', 'number' => 'P111',
'issue_date' => '2020-01-01', 'expiry_date' => '2030-01-01',
]);
$w2 = \App\Models\Worker::create([
'name' => 'Active 2', 'email' => 'a2@test.com', 'phone' => '+971501110002', 'name' => 'Active 2', 'email' => 'a2@test.com', 'phone' => '+971501110002',
'password' => bcrypt('password'), 'status' => 'active', 'nationality' => 'Indian', 'password' => bcrypt('password'), 'status' => 'active', 'nationality' => 'Indian',
'preferred_location' => 'Dubai', 'age' => 26, 'salary' => 1500, 'experience' => '3 Years', 'preferred_location' => 'Dubai', 'age' => 26, 'salary' => 1500, 'experience' => '3 Years',
'availability' => 'Immediate', 'religion' => 'Christian', 'bio' => 'experienced worker', 'availability' => 'Immediate', 'religion' => 'Christian', 'bio' => 'experienced worker',
'verified' => true,
]); ]);
\App\Models\Worker::create([ \App\Models\WorkerDocument::create([
'worker_id' => $w2->id, 'type' => 'passport', 'number' => 'P222',
'issue_date' => '2020-01-01', 'expiry_date' => '2030-01-01',
]);
$w3 = \App\Models\Worker::create([
'name' => 'Active 3', 'email' => 'a3@test.com', 'phone' => '+971501110003', 'name' => 'Active 3', 'email' => 'a3@test.com', 'phone' => '+971501110003',
'password' => bcrypt('password'), 'status' => 'active', 'nationality' => 'Indian', 'password' => bcrypt('password'), 'status' => 'active', 'nationality' => 'Indian',
'preferred_location' => 'Dubai', 'age' => 27, 'salary' => 1500, 'experience' => '3 Years', 'preferred_location' => 'Dubai', 'age' => 27, 'salary' => 1500, 'experience' => '3 Years',
'availability' => 'Immediate', 'religion' => 'Christian', 'bio' => 'experienced worker', 'availability' => 'Immediate', 'religion' => 'Christian', 'bio' => 'experienced worker',
'verified' => true,
]);
\App\Models\WorkerDocument::create([
'worker_id' => $w3->id, 'type' => 'passport', 'number' => 'P333',
'issue_date' => '2020-01-01', 'expiry_date' => '2030-01-01',
]); ]);
// Create 2 inactive/hidden workers // Create 2 inactive/hidden workers

View File

@ -23,7 +23,7 @@ export default defineConfig({
port: 5173, port: 5173,
cors: true, cors: true,
hmr: { hmr: {
host: '192.168.0.166', host: '192.168.29.131',
}, },
watch: { watch: {
ignored: ['**/storage/framework/views/**'], ignored: ['**/storage/framework/views/**'],