admin panel, worker, employer field changes

This commit is contained in:
mohanmd 2026-06-23 17:45:01 +05:30
parent 8444347fb1
commit 8fb9b9dc26
12 changed files with 622 additions and 228 deletions

View File

@ -330,7 +330,34 @@ public function register(Request $request)
return $query->where('type', 'passport');
}),
],
'passport.document_type' => 'nullable|string|max:10',
'passport.issuing_country' => 'nullable|string|max:10',
'passport.surname' => 'nullable|string|max:255',
'passport.given_names' => 'nullable|string|max:255',
'passport.nationality' => 'nullable|string|max:100',
'passport.date_of_birth' => 'nullable|string',
'passport.date_of_expiry' => 'nullable|string',
'passport.place_of_birth' => 'nullable|string|max:255',
'passport.authority' => 'nullable|string|max:255',
'passport.sex' => 'nullable|string|max:10',
'passport.date_of_issue' => 'nullable|string',
'passport.ocr_accuracy' => 'nullable|numeric',
'visa' => 'nullable|array',
'visa.document_type' => 'nullable|string|max:100',
'visa.country' => 'nullable|string|max:100',
'visa.visa_type' => 'nullable|string|max:100',
'visa.entry_permit_no' => 'nullable|string|max:100',
'visa.issue_date' => 'nullable|string',
'visa.valid_until' => 'nullable|string',
'visa.uid_no' => 'nullable|string|max:100',
'visa.full_name' => 'nullable|string|max:255',
'visa.nationality' => 'nullable|string|max:100',
'visa.place_of_birth' => 'nullable|string|max:255',
'visa.date_of_birth' => 'nullable|string',
'visa.passport_no' => 'nullable|string|max:100',
'visa.profession' => 'nullable|string|max:255',
'visa.sponsor_name' => 'nullable|string|max:255',
'visa.ocr_accuracy' => 'nullable|numeric',
], [
'phone.unique' => 'This mobile number is already registered.',
'passport.passport_number.unique' => 'This passport number is already registered.',
@ -462,7 +489,7 @@ public function register(Request $request)
'number' => $passportNum,
'issue_date' => $issueDateFormatted,
'expiry_date' => $expiryDateFormatted,
'ocr_accuracy' => 99.0,
'ocr_accuracy' => isset($passportDataInput['ocr_accuracy']) ? (float)$passportDataInput['ocr_accuracy'] : 99.0,
'file_path' => null,
'ocr_data' => $passportDataInput,
]);
@ -470,8 +497,8 @@ public function register(Request $request)
// Create visa document if provided
if ($visaDataInput) {
$visaNum = $visaDataInput['file_number'] ?? $visaDataInput['id_number'] ?? ('V' . rand(1000000, 9999999));
$expiryDate = $visaDataInput['expiry_date'] ?? null;
$visaNum = $visaDataInput['entry_permit_no'] ?? $visaDataInput['file_number'] ?? $visaDataInput['id_number'] ?? ('V' . rand(1000000, 9999999));
$expiryDate = $visaDataInput['valid_until'] ?? $visaDataInput['expiry_date'] ?? null;
$issueDate = $visaDataInput['issue_date'] ?? null;
$expiryDateFormatted = $expiryDate ? $this->normaliseDateForController($expiryDate) : now()->addYears(2)->toDateString();
@ -482,7 +509,7 @@ public function register(Request $request)
'number' => $visaNum,
'issue_date' => $issueDateFormatted,
'expiry_date' => $expiryDateFormatted,
'ocr_accuracy' => 99.0,
'ocr_accuracy' => isset($visaDataInput['ocr_accuracy']) ? (float)$visaDataInput['ocr_accuracy'] : 99.0,
'file_path' => null,
'ocr_data' => $visaDataInput,
]);

View File

@ -290,17 +290,35 @@ public function uploadEmiratesId(Request $request)
$extractedIdNumber = null;
$extractedExpiry = null;
$extractedName = null;
$extractedDob = null;
$extractedNationality = null;
$extractedIssueDate = null;
$extractedEmployer = null;
$extractedIssuePlace = null;
$extractedOccupation = null;
$extractedCardNumber = null;
$extractedGender = null;
if ($request->hasFile('emirates_id_front')) {
$frontFile = $request->file('emirates_id_front');
$frontOcr = \App\Services\OcrDocumentService::extractEmiratesIdFrontData($frontFile);
$extractedIdNumber = $frontOcr['emirates_id_number'];
$extractedName = $frontOcr['name'] ?? null;
$extractedDob = $frontOcr['date_of_birth'] ?? null;
$extractedNationality = $frontOcr['nationality'] ?? null;
$extractedCardNumber = $frontOcr['card_number'] ?? null;
$extractedGender = $frontOcr['gender'] ?? null;
$extractedOccupation = $frontOcr['occupation'] ?? null;
$extractedEmployer = $frontOcr['employer'] ?? null;
$extractedIssuePlace = $frontOcr['issue_place'] ?? null;
}
if ($request->hasFile('emirates_id_back')) {
$backFile = $request->file('emirates_id_back');
$backOcr = \App\Services\OcrDocumentService::extractEmiratesIdBackData($backFile);
$extractedExpiry = $backOcr['expiry_date'];
$extractedIssueDate = $backOcr['issue_date'] ?? null;
}
if ($request->hasFile('emirates_id_file')) {
@ -309,12 +327,30 @@ public function uploadEmiratesId(Request $request)
$backOcr = \App\Services\OcrDocumentService::extractEmiratesIdBackData($file);
$extractedIdNumber = $frontOcr['emirates_id_number'];
$extractedExpiry = $backOcr['expiry_date'];
$extractedName = $frontOcr['name'] ?? null;
$extractedDob = $frontOcr['date_of_birth'] ?? null;
$extractedNationality = $frontOcr['nationality'] ?? null;
$extractedCardNumber = $frontOcr['card_number'] ?? null;
$extractedGender = $frontOcr['gender'] ?? null;
$extractedOccupation = $frontOcr['occupation'] ?? null;
$extractedEmployer = $frontOcr['employer'] ?? null;
$extractedIssuePlace = $frontOcr['issue_place'] ?? null;
$extractedIssueDate = $backOcr['issue_date'] ?? null;
}
$pending = session('pending_employer_registration');
$pending['emirates_id_file'] = null;
$pending['emirates_id_number'] = $extractedIdNumber;
$pending['emirates_id_expiry'] = $extractedExpiry;
$pending['emirates_id_name'] = $extractedName;
$pending['emirates_id_dob'] = $extractedDob;
$pending['emirates_id_nationality'] = $extractedNationality;
$pending['emirates_id_card_number'] = $extractedCardNumber;
$pending['emirates_id_gender'] = $extractedGender;
$pending['emirates_id_occupation'] = $extractedOccupation;
$pending['emirates_id_employer'] = $extractedEmployer;
$pending['emirates_id_issue_place'] = $extractedIssuePlace;
$pending['emirates_id_issue_date'] = $extractedIssueDate;
session(['pending_employer_registration' => $pending]);
session(['employer_emirates_id_uploaded' => true]);
@ -438,6 +474,13 @@ public function createPassword(Request $request)
'emirates_id_front' => null, // We did not store the file
'emirates_id_number' => $pending['emirates_id_number'] ?? null,
'emirates_id_expiry' => $pending['emirates_id_expiry'] ?? null,
'emirates_id_name' => $pending['emirates_id_name'] ?? null,
'emirates_id_dob' => $pending['emirates_id_dob'] ?? null,
'emirates_id_issue_date' => $pending['emirates_id_issue_date'] ?? null,
'emirates_id_employer' => $pending['emirates_id_employer'] ?? null,
'emirates_id_issue_place' => $pending['emirates_id_issue_place'] ?? null,
'emirates_id_occupation' => $pending['emirates_id_occupation'] ?? null,
'nationality' => $pending['emirates_id_nationality'] ?? null,
'address' => $pending['address'] ?? null,
]);
@ -459,6 +502,15 @@ public function createPassword(Request $request)
'last_login_at' => now(),
'emirates_id_file' => null, // We did not store the file
'address' => $pending['address'] ?? null,
'emirates_id' => $pending['emirates_id_number'] ?? null,
'emirates_id_name' => $pending['emirates_id_name'] ?? null,
'emirates_id_dob' => $pending['emirates_id_dob'] ?? null,
'emirates_id_issue_date' => $pending['emirates_id_issue_date'] ?? null,
'emirates_id_expiry' => $pending['emirates_id_expiry'] ?? null,
'emirates_id_employer' => $pending['emirates_id_employer'] ?? null,
'emirates_id_issue_place' => $pending['emirates_id_issue_place'] ?? null,
'emirates_id_occupation' => $pending['emirates_id_occupation'] ?? null,
'nationality' => $pending['emirates_id_nationality'] ?? null,
]);
// Create active subscription in database

View File

@ -42,15 +42,15 @@ private function buildProfileData($user, $profile)
'email' => $user->email,
'phone' => $profile->phone ?? '+971509990001',
'emirates_id' => [
'emirates_id_number' => $profile->emirates_id_number ?? '784-1988-5310327-2',
'emirates_id_number' => $profile->emirates_id_number ?? '784-1987-5493842-5',
'name' => $profile->emirates_id_name ?? $user->name,
'date_of_birth' => $profile->emirates_id_dob ?? '1990-01-01',
'nationality' => $profile->nationality ?? 'Emirati',
'issue_date' => $profile->emirates_id_issue_date ?? '2023-04-11',
'expiry_date' => $profile->emirates_id_expiry ?? '2028-04-11',
'employer' => $profile->emirates_id_employer ?? 'Federal Authority',
'issue_place' => $profile->emirates_id_issue_place ?? 'Abu Dhabi',
'occupation' => $profile->emirates_id_occupation ?? 'Manager',
'date_of_birth' => $profile->emirates_id_dob ?? '1987-04-14',
'nationality' => $profile->nationality ?? 'Bangladesh',
'issue_date' => $profile->emirates_id_issue_date ?? '2025-12-12',
'expiry_date' => $profile->emirates_id_expiry ?? '2027-12-11',
'employer' => $profile->emirates_id_employer ?? 'Msj International Technical Services L.L.C UAE',
'issue_place' => $profile->emirates_id_issue_place ?? 'Dubai',
'occupation' => $profile->emirates_id_occupation ?? 'Electrician',
],
'fcm_token' => $user->fcm_token ?? 'fcm_token_example',
'address' => $profile->address ?? 'Villa 45, Street 12',
@ -74,15 +74,15 @@ public function index(Request $request)
$profile = EmployerProfile::create([
'user_id' => $user->id,
'phone' => '+971509990001',
'emirates_id_number' => '784-1988-5310327-2',
'emirates_id_name' => 'Ahmad Bin Ahmed',
'emirates_id_dob' => '1990-01-01',
'nationality' => 'Emirati',
'emirates_id_issue_date' => '2023-04-11',
'emirates_id_expiry' => '2028-04-11',
'emirates_id_employer' => 'Federal Authority',
'emirates_id_issue_place' => 'Abu Dhabi',
'emirates_id_occupation' => 'Manager',
'emirates_id_number' => '784-1987-5493842-5',
'emirates_id_name' => 'Mohammad Jobaier Mohammad Abul Kalam',
'emirates_id_dob' => '1987-04-14',
'nationality' => 'Bangladesh',
'emirates_id_issue_date' => '2025-12-12',
'emirates_id_expiry' => '2027-12-11',
'emirates_id_employer' => 'Msj International Technical Services L.L.C UAE',
'emirates_id_issue_place' => 'Dubai',
'emirates_id_occupation' => 'Electrician',
'address' => 'Villa 45, Street 12',
]);
}
@ -106,15 +106,15 @@ public function edit(Request $request)
$profile = EmployerProfile::create([
'user_id' => $user->id,
'phone' => '+971509990001',
'emirates_id_number' => '784-1988-5310327-2',
'emirates_id_name' => 'Ahmad Bin Ahmed',
'emirates_id_dob' => '1990-01-01',
'nationality' => 'Emirati',
'emirates_id_issue_date' => '2023-04-11',
'emirates_id_expiry' => '2028-04-11',
'emirates_id_employer' => 'Federal Authority',
'emirates_id_issue_place' => 'Abu Dhabi',
'emirates_id_occupation' => 'Manager',
'emirates_id_number' => '784-1987-5493842-5',
'emirates_id_name' => 'Mohammad Jobaier Mohammad Abul Kalam',
'emirates_id_dob' => '1987-04-14',
'nationality' => 'Bangladesh',
'emirates_id_issue_date' => '2025-12-12',
'emirates_id_expiry' => '2027-12-11',
'emirates_id_employer' => 'Msj International Technical Services L.L.C UAE',
'emirates_id_issue_place' => 'Dubai',
'emirates_id_occupation' => 'Electrician',
'address' => 'Villa 45, Street 12',
]);
}

View File

@ -316,6 +316,11 @@ public static function extractEmiratesIdFrontData(UploadedFile $file): array
'name' => null,
'nationality' => null,
'date_of_birth' => null,
'card_number' => null,
'gender' => null,
'occupation' => null,
'employer' => null,
'issue_place' => null,
'ocr_accuracy' => 0.0,
'success' => false,
];
@ -376,6 +381,12 @@ public static function extractEmiratesIdFrontData(UploadedFile $file): array
if (!empty($data['date_of_birth'])) {
$extracted['date_of_birth'] = self::normaliseDate($data['date_of_birth']);
}
$extracted['card_number'] = $data['card_number'] ?? null;
$extracted['gender'] = $data['gender'] ?? $data['sex'] ?? null;
$extracted['occupation'] = $data['occupation'] ?? null;
$extracted['employer'] = $data['employer'] ?? $data['company_name'] ?? null;
$extracted['issue_place'] = $data['issue_place'] ?? $data['issuing_place'] ?? $data['place_of_issue'] ?? null;
} else {
Log::warning('Emirates ID Front OCR API success=false', ['response' => $json]);
}
@ -392,13 +403,31 @@ public static function extractEmiratesIdFrontData(UploadedFile $file): array
// Fallbacks
if (empty($extracted['emirates_id_number'])) {
$extracted['emirates_id_number'] = '784-' . rand(1975, 2005) . '-' . rand(1000000, 9999999) . '-' . rand(1, 9);
$extracted['emirates_id_number'] = '784-1987-5493842-5';
}
if (empty($extracted['name'])) {
$extracted['name'] = 'KRISHNA PRASAD';
$extracted['name'] = 'Mohammad Jobaier Mohammad Abul Kalam';
}
if (empty($extracted['nationality'])) {
$extracted['nationality'] = 'NPL';
$extracted['nationality'] = 'Bangladesh';
}
if (empty($extracted['date_of_birth'])) {
$extracted['date_of_birth'] = '1987-04-14';
}
if (empty($extracted['card_number'])) {
$extracted['card_number'] = '151023946';
}
if (empty($extracted['gender'])) {
$extracted['gender'] = 'M';
}
if (empty($extracted['occupation'])) {
$extracted['occupation'] = 'Electrician';
}
if (empty($extracted['employer'])) {
$extracted['employer'] = 'Msj International Technical Services L.L.C UAE';
}
if (empty($extracted['issue_place'])) {
$extracted['issue_place'] = 'Dubai';
}
return $extracted;
@ -517,10 +546,15 @@ private static function emiratesIdFrontTestFallback(): array
{
return [
'success' => true,
'emirates_id_number' => '784-1988-5310327-2',
'name' => 'KRISHNA PRASAD',
'nationality' => 'NPL',
'date_of_birth' => '1988-03-22',
'emirates_id_number' => '784-1987-5493842-5',
'name' => 'Mohammad Jobaier Mohammad Abul Kalam',
'nationality' => 'Bangladesh',
'date_of_birth' => '1987-04-14',
'card_number' => '151023946',
'gender' => 'M',
'occupation' => 'Electrician',
'employer' => 'Msj International Technical Services L.L.C UAE',
'issue_place' => 'Dubai',
'ocr_accuracy' => 98.5,
];
}
@ -529,8 +563,8 @@ private static function emiratesIdBackTestFallback(): array
{
return [
'success' => true,
'expiry_date' => '2028-04-11',
'issue_date' => '2023-04-11',
'expiry_date' => '2027-12-11',
'issue_date' => '2025-12-12',
'ocr_accuracy' => 98.5,
];
}

View File

@ -743,6 +743,11 @@
"nationality": {
"type": "string"
},
"ocr_accuracy": {
"type": "number",
"format": "float",
"example": 99.0
},
"passport_number": {
"type": "string"
},
@ -767,6 +772,22 @@
"accompanied_by": {
"type": "string"
},
"country": {
"type": "string",
"example": "United Arab Emirates"
},
"date_of_birth": {
"type": "string",
"example": "25-JUN-1999"
},
"document_type": {
"type": "string",
"example": "uae_visa"
},
"entry_permit_no": {
"type": "string",
"example": "77003098 / 2019 / 204"
},
"expiry_date": {
"type": "string",
"example": "2024-02-15"
@ -774,6 +795,10 @@
"file_number": {
"type": "string"
},
"full_name": {
"type": "string",
"example": "Mr.MUHAMMAD NADEEM RASHEED"
},
"id_number": {
"type": "string"
},
@ -784,17 +809,50 @@
"name": {
"type": "string"
},
"nationality": {
"type": "string",
"example": "PAKISTAN"
},
"ocr_accuracy": {
"type": "number",
"format": "float",
"example": 99.0
},
"passport_no": {
"type": "string",
"example": "EN9458281"
},
"passport_number": {
"type": "string"
},
"place_of_birth": {
"type": "string",
"example": "SIALKOT PAK"
},
"place_of_issue": {
"type": "string"
},
"profession": {
"type": "string"
"type": "string",
"example": "SALES REPRESENTATIVE"
},
"sponsor": {
"type": "string"
},
"sponsor_name": {
"type": "string"
},
"uid_no": {
"type": "string",
"example": "207404887"
},
"valid_until": {
"type": "string",
"example": "04-MAR-2019"
},
"visa_type": {
"type": "string",
"example": "ENTRY PERMIT"
}
}
}

View File

@ -40,6 +40,7 @@ export default function AdminLayout({ children, title = 'Dashboard' }) {
{ name: 'Support Tickets', href: '/admin/tickets', icon: LifeBuoy },
{ name: 'Frequently Asked Questions', href: '/admin/faqs', icon: HelpCircle },
{ name: 'Payments', href: '/admin/payments', icon: BadgeDollarSign },
{ name: 'Plan Configuration', href: '/admin/subscriptions', icon: CreditCard },
{ name: 'Reports & Analytics', href: '/admin/analytics', icon: BarChart3 },
{ name: 'System Audit Logs', href: '/admin/audit-logs', icon: History },
{ name: 'Charity Events', href: '/admin/events', icon: Heart },

View File

@ -43,19 +43,78 @@ export default function SubscriptionsIndex({ plans: initialPlans }) {
const [isDialogOpen, setIsDialogOpen] = useState(false);
const [editingPlan, setEditingPlan] = useState(null);
const [searchQuery, setSearchQuery] = useState('');
// Form states
const [name, setName] = useState('');
const [price, setPrice] = useState('');
const [duration, setDuration] = useState('Monthly');
const [features, setFeatures] = useState('');
const handleCreateClick = () => {
setEditingPlan(null);
setName('');
setPrice('');
setDuration('Monthly');
setFeatures('');
setIsDialogOpen(true);
};
const handleEdit = (plan) => {
setEditingPlan(plan);
setName(plan.name);
setPrice(plan.price);
setDuration(plan.duration);
setFeatures(plan.features.join('\n'));
setIsDialogOpen(true);
};
const handleDelete = (id) => {
if (confirm('Are you sure you want to delete this plan?')) {
// router.delete(`/admin/subscriptions/plans/${id}`);
setPlans(plans.filter(p => p.id !== id));
}
};
const handleSave = (e) => {
e.preventDefault();
if (!name.trim() || !price) {
alert('Please fill out Name and Price.');
return;
}
const featuresArray = features
.split('\n')
.map(f => f.trim())
.filter(Boolean);
if (editingPlan) {
setPlans(plans.map(p => p.id === editingPlan.id ? {
...p,
name,
price: Number(price),
duration,
features: featuresArray
} : p));
} else {
const newId = name.toLowerCase().replace(/\s+/g, '-');
setPlans([...plans, {
id: newId || `plan-${Date.now()}`,
name,
price: Number(price),
duration,
features: featuresArray,
status: 'Active'
}]);
}
setIsDialogOpen(false);
};
const filteredPlans = plans.filter(plan =>
plan.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
plan.duration.toLowerCase().includes(searchQuery.toLowerCase()) ||
plan.features.some(f => f.toLowerCase().includes(searchQuery.toLowerCase()))
);
return (
<AdminLayout title="Subscription Management">
<Head title="Plans Management" />
@ -68,11 +127,13 @@ export default function SubscriptionsIndex({ plans: initialPlans }) {
<input
type="text"
placeholder="Search plans..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="w-full pl-10 pr-4 py-2 bg-white border border-slate-200 rounded-lg text-sm focus:ring-2 focus:ring-teal-500/10 focus:border-teal-500 outline-none transition-all"
/>
</div>
<button
onClick={() => { setEditingPlan(null); setIsDialogOpen(true); }}
onClick={handleCreateClick}
className="bg-[#0F6E56] text-white px-4 py-2 rounded-lg text-sm font-semibold flex items-center justify-center space-x-2 hover:bg-[#085041] transition-colors shadow-sm"
>
<Plus className="w-4 h-4" />
@ -94,7 +155,14 @@ export default function SubscriptionsIndex({ plans: initialPlans }) {
</TableRow>
</TableHeader>
<TableBody>
{plans.map((plan) => (
{filteredPlans.length === 0 ? (
<TableRow>
<TableCell colSpan={6} className="text-center py-8 text-slate-400 text-sm">
No plans found
</TableCell>
</TableRow>
) : (
filteredPlans.map((plan) => (
<TableRow key={plan.id} className="hover:bg-slate-50/50 transition-colors">
<TableCell className="py-4">
<div className="flex items-center space-x-3">
@ -145,13 +213,14 @@ export default function SubscriptionsIndex({ plans: initialPlans }) {
</div>
</TableCell>
</TableRow>
))}
))
)}
</TableBody>
</Table>
</div>
</div>
{/* Plan Modal (Placeholder UI) */}
{/* Plan Modal */}
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
<DialogContent className="sm:max-w-[500px] rounded-[24px]">
<DialogHeader>
@ -163,44 +232,70 @@ export default function SubscriptionsIndex({ plans: initialPlans }) {
</DialogDescription>
</DialogHeader>
<form onSubmit={handleSave}>
<div className="grid gap-6 py-4">
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<label className="text-[10px] font-black text-slate-400 uppercase tracking-widest ml-1">Plan Name</label>
<input className="w-full px-4 py-2.5 bg-slate-50 border border-slate-100 rounded-xl text-sm font-bold focus:ring-4 focus:ring-teal-500/10 outline-none" placeholder="e.g. Pro Search" />
<input
required
value={name}
onChange={(e) => setName(e.target.value)}
className="w-full px-4 py-2.5 bg-slate-50 border border-slate-100 rounded-xl text-sm font-bold focus:ring-4 focus:ring-teal-500/10 outline-none"
placeholder="e.g. Pro Search"
/>
</div>
<div className="space-y-2">
<label className="text-[10px] font-black text-slate-400 uppercase tracking-widest ml-1">Price (AED)</label>
<input type="number" className="w-full px-4 py-2.5 bg-slate-50 border border-slate-100 rounded-xl text-sm font-bold focus:ring-4 focus:ring-teal-500/10 outline-none" placeholder="299" />
<input
required
type="number"
value={price}
onChange={(e) => setPrice(e.target.value)}
className="w-full px-4 py-2.5 bg-slate-50 border border-slate-100 rounded-xl text-sm font-bold focus:ring-4 focus:ring-teal-500/10 outline-none"
placeholder="299"
/>
</div>
</div>
<div className="space-y-2">
<label className="text-[10px] font-black text-slate-400 uppercase tracking-widest ml-1">Duration</label>
<select className="w-full px-4 py-2.5 bg-slate-50 border border-slate-100 rounded-xl text-sm font-bold focus:ring-4 focus:ring-teal-500/10 outline-none">
<option>Monthly</option>
<option>Quarterly</option>
<option>Yearly</option>
<select
value={duration}
onChange={(e) => setDuration(e.target.value)}
className="w-full px-4 py-2.5 bg-slate-50 border border-slate-100 rounded-xl text-sm font-bold focus:ring-4 focus:ring-teal-500/10 outline-none"
>
<option value="Monthly">Monthly</option>
<option value="Quarterly">Quarterly</option>
<option value="Yearly">Yearly</option>
</select>
</div>
<div className="space-y-2">
<label className="text-[10px] font-black text-slate-400 uppercase tracking-widest ml-1">Features (one per line)</label>
<textarea className="w-full px-4 py-2.5 bg-slate-50 border border-slate-100 rounded-xl text-sm font-bold focus:ring-4 focus:ring-teal-500/10 outline-none min-h-[100px]" placeholder="Unlimited search&#10;Verified badges" />
<textarea
value={features}
onChange={(e) => setFeatures(e.target.value)}
className="w-full px-4 py-2.5 bg-slate-50 border border-slate-100 rounded-xl text-sm font-bold focus:ring-4 focus:ring-teal-500/10 outline-none min-h-[100px]"
placeholder="Unlimited search&#10;Verified badges"
/>
</div>
</div>
<DialogFooter className="sm:justify-end gap-3">
<DialogFooter className="sm:justify-end gap-3 pt-4 border-t border-slate-100">
<button
type="button"
onClick={() => setIsDialogOpen(false)}
className="px-6 py-2.5 rounded-xl text-sm font-bold text-slate-400 uppercase tracking-widest hover:bg-slate-50 transition-colors"
>
Cancel
</button>
<button
type="submit"
className="bg-[#0F6E56] text-white px-8 py-2.5 rounded-xl text-sm font-bold uppercase tracking-widest shadow-lg shadow-teal-500/20 active:scale-95 transition-all"
>
Save Changes
</button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
</AdminLayout>

View File

@ -208,7 +208,7 @@ export default function WorkerManagement({ workers }) {
})).filter(worker => {
const matchesSearch = worker.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
worker.email.toLowerCase().includes(searchTerm.toLowerCase());
const matchesStatus = statusFilter === 'all' || worker.status === statusFilter;
const matchesStatus = statusFilter === 'all' || worker.status?.toLowerCase() === statusFilter.toLowerCase();
return matchesSearch && matchesStatus;
});
@ -258,7 +258,7 @@ export default function WorkerManagement({ workers }) {
<div className="flex items-center gap-2 w-full md:w-auto">
<span className="text-[10px] font-black text-gray-400 uppercase tracking-[0.2em] mr-2">Lifecycle Filter:</span>
<div className="flex bg-slate-100 p-1 rounded-xl">
{['all', 'active', 'inactive', 'suspended'].map((f) => (
{['all', 'active', 'hired', 'suspended'].map((f) => (
<button
key={f}
onClick={() => setStatusFilter(f)}
@ -377,9 +377,13 @@ export default function WorkerManagement({ workers }) {
<Badge
variant="outline"
className={`px-2 py-0.5 rounded-full font-black text-[9px] uppercase tracking-wider block w-fit ${
worker.status === 'active'
worker.status?.toLowerCase() === 'active'
? 'bg-emerald-50 text-emerald-700 border-emerald-200'
: worker.status === 'suspended' ? 'bg-red-100 text-red-800 border-none' : 'bg-slate-50 text-slate-700 border-slate-200'
: worker.status?.toLowerCase() === 'hired'
? 'bg-blue-50 text-blue-700 border-blue-200'
: worker.status?.toLowerCase() === 'suspended'
? 'bg-red-100 text-red-800 border-none'
: 'bg-slate-50 text-slate-700 border-slate-200'
}`}
>
{worker.status}
@ -526,15 +530,7 @@ export default function WorkerManagement({ workers }) {
className="w-full bg-white border border-slate-200 rounded-xl p-2.5 text-xs font-bold text-slate-700 outline-none focus:ring-2 focus:ring-[#0F6E56]/20"
/>
</div>
<div>
<label className="text-[10px] font-bold text-slate-500">Religion</label>
<input
type="text"
value={editForm.religion}
onChange={e => setEditForm({ ...editForm, religion: e.target.value })}
className="w-full bg-white border border-slate-200 rounded-xl p-2.5 text-xs font-bold text-slate-700 outline-none focus:ring-2 focus:ring-[#0F6E56]/20"
/>
</div>
</div>
</div>
@ -716,10 +712,7 @@ export default function WorkerManagement({ workers }) {
<label className="text-[9px] font-black text-slate-400 uppercase tracking-widest block mb-0.5">Visa Status</label>
<span className="text-slate-800 font-extrabold">{selectedWorker?.visa_status || 'N/A'}</span>
</div>
<div>
<label className="text-[9px] font-black text-slate-400 uppercase tracking-widest block mb-0.5">Religion</label>
<span className="text-slate-800 font-extrabold">{selectedWorker?.religion || 'N/A'}</span>
</div>
<div>
<label className="text-[9px] font-black text-slate-400 uppercase tracking-widest block mb-0.5">Age</label>
<span className="text-slate-800 font-extrabold">{selectedWorker?.age || 'N/A'}</span>
@ -760,14 +753,26 @@ export default function WorkerManagement({ workers }) {
<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">{passportDoc.ocr_data?.passport_number || passportDoc.number || 'N/A'}</span>
</div>
<div>
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Surname</span>
<span className="font-bold text-slate-800">{passportDoc.ocr_data?.surname || 'N/A'}</span>
</div>
<div>
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Given Names</span>
<span className="font-bold text-slate-800">{passportDoc.ocr_data?.given_names || 'N/A'}</span>
</div>
<div>
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Sex</span>
<span className="font-bold text-slate-800">{passportDoc.ocr_data?.sex || 'N/A'}</span>
</div>
<div>
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Date of Birth</span>
<span className="font-bold text-slate-800">{passportDoc.ocr_data?.date_of_birth || 'N/A'}</span>
</div>
<div>
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Place of Birth</span>
<span className="font-bold text-slate-800">{passportDoc.ocr_data?.place_of_birth || 'N/A'}</span>
</div>
<div>
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Nationality</span>
<span className="font-bold text-slate-800">{passportDoc.ocr_data?.nationality || 'N/A'}</span>
@ -776,10 +781,6 @@ export default function WorkerManagement({ workers }) {
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Issuing Country</span>
<span className="font-bold text-slate-800">{passportDoc.ocr_data?.issuing_country || 'N/A'}</span>
</div>
<div>
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Place of Birth</span>
<span className="font-bold text-slate-800">{passportDoc.ocr_data?.place_of_birth || 'N/A'}</span>
</div>
<div>
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Date of Issue</span>
<span className="font-bold text-slate-800">{passportDoc.ocr_data?.date_of_issue || passportDoc.issue_date || 'N/A'}</span>
@ -788,6 +789,18 @@ export default function WorkerManagement({ workers }) {
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Date of Expiry</span>
<span className="font-bold text-slate-800">{passportDoc.ocr_data?.date_of_expiry || passportDoc.expiry_date || 'N/A'}</span>
</div>
<div>
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Authority</span>
<span className="font-bold text-slate-800">{passportDoc.ocr_data?.authority || 'N/A'}</span>
</div>
<div>
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Document Type</span>
<span className="font-bold text-slate-800">{passportDoc.ocr_data?.document_type || 'N/A'}</span>
</div>
<div>
<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">{passportDoc.ocr_data?.personal_number || 'N/A'}</span>
</div>
</div>
{passportDoc.file_path && (
<div className="pt-2 border-t border-slate-200/60 flex justify-end">
@ -822,6 +835,14 @@ export default function WorkerManagement({ workers }) {
return (
<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>
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Entry Permit No</span>
<span className="font-mono font-bold text-slate-800">{visaDoc.ocr_data?.entry_permit_no || 'N/A'}</span>
</div>
<div>
<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">{visaDoc.ocr_data?.uid_no || 'N/A'}</span>
</div>
<div>
<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">{visaDoc.ocr_data?.file_number || visaDoc.number || 'N/A'}</span>
@ -830,6 +851,55 @@ export default function WorkerManagement({ workers }) {
<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">{visaDoc.ocr_data?.id_number || 'N/A'}</span>
</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">{visaDoc.ocr_data?.full_name || 'N/A'}</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">{visaDoc.ocr_data?.name || 'N/A'}</span>
</div>
<div>
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Visa Type</span>
<span className="font-bold text-slate-800">{visaDoc.ocr_data?.visa_type || 'N/A'}</span>
</div>
<div>
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Profession</span>
<span className="font-bold text-slate-800">{visaDoc.ocr_data?.profession || 'N/A'}</span>
</div>
<div>
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Sponsor Name</span>
<span className="font-bold text-slate-800">{visaDoc.ocr_data?.sponsor_name || 'N/A'}</span>
</div>
<div>
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Nationality</span>
<span className="font-bold text-slate-800">{visaDoc.ocr_data?.nationality || 'N/A'}</span>
</div>
<div>
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Country</span>
<span className="font-bold text-slate-800">{visaDoc.ocr_data?.country || 'N/A'}</span>
</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">{visaDoc.ocr_data?.passport_no || 'N/A'}</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">{visaDoc.ocr_data?.passport_number || 'N/A'}</span>
</div>
<div>
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Date of Birth</span>
<span className="font-bold text-slate-800">{visaDoc.ocr_data?.date_of_birth || 'N/A'}</span>
</div>
<div>
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Place of Birth</span>
<span className="font-bold text-slate-800">{visaDoc.ocr_data?.place_of_birth || 'N/A'}</span>
</div>
<div>
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Place of Issue</span>
<span className="font-bold text-slate-800">{visaDoc.ocr_data?.place_of_issue || 'N/A'}</span>
</div>
<div>
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Issue Date</span>
<span className="font-bold text-slate-800">{visaDoc.ocr_data?.issue_date || visaDoc.issue_date || 'N/A'}</span>
@ -838,17 +908,18 @@ export default function WorkerManagement({ workers }) {
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Expiry Date</span>
<span className="font-bold text-slate-800">{visaDoc.ocr_data?.expiry_date || visaDoc.expiry_date || 'N/A'}</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">{visaDoc.ocr_data?.passport_number || 'N/A'}</span>
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Accompanied By</span>
<span className="font-bold text-slate-800">{visaDoc.ocr_data?.accompanied_by || 'N/A'}</span>
</div>
<div>
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Place of Issue</span>
<span className="font-bold text-slate-800">{visaDoc.ocr_data?.place_of_issue || 'N/A'}</span>
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Document Type</span>
<span className="font-bold text-slate-800">{visaDoc.ocr_data?.document_type || 'N/A'}</span>
</div>
<div className="col-span-2">
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Sponsor</span>
<span className="font-bold text-slate-800">{visaDoc.ocr_data?.sponsor || 'N/A'}</span>
<div>
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">OCR Accuracy</span>
<span className="font-bold text-slate-800">{visaDoc.ocr_data?.ocr_accuracy || 'N/A'}</span>
</div>
</div>
{visaDoc.file_path && (
@ -869,50 +940,18 @@ export default function WorkerManagement({ workers }) {
})()}
</div>
{/* Compliance Notes */}
<div className="space-y-2 bg-yellow-50/60 p-4 border border-yellow-200 rounded-2xl">
<label className="text-[10px] font-black text-yellow-800 uppercase tracking-widest flex items-center gap-1">
<FileText className="w-3.5 h-3.5" />
<span>Internal Admin Compliance Notes Logs</span>
</label>
<textarea
rows="3"
placeholder="Write admin logs regarding candidate verification reviews or user complaints..."
className="w-full bg-white border border-yellow-200 rounded-xl p-3 text-xs font-bold text-slate-700 focus:ring-2 focus:ring-yellow-500/20 outline-none"
value={adminNotes}
onChange={e => setAdminNotes(e.target.value)}
/>
</div>
</div>
{/* Right Column (1/3) */}
<div className="space-y-6">
{/* Contact & Location */}
{/* Contact Detail */}
<div className="bg-white p-5 rounded-2xl border border-slate-200 shadow-sm space-y-4">
<h3 className="text-xs font-black text-slate-400 uppercase tracking-wider">Contact & Location</h3>
<h3 className="text-xs font-black text-slate-400 uppercase tracking-wider">Contact Detail</h3>
<div className="space-y-3.5 text-xs font-bold text-slate-700">
<div className="flex items-center space-x-2 bg-slate-50 p-2.5 rounded-xl border border-slate-100">
<Phone className="w-4 h-4 text-teal-600 flex-shrink-0" />
<span className="text-slate-800 font-extrabold">{selectedWorker?.phone || '+971 52 489 1209'}</span>
</div>
<div className="grid grid-cols-2 gap-3 pt-1">
<div>
<label className="text-[9px] font-black text-slate-400 uppercase tracking-widest block mb-0.5">Country</label>
<span className="text-slate-800 font-extrabold block">{selectedWorker?.country || 'N/A'}</span>
</div>
<div>
<label className="text-[9px] font-black text-slate-400 uppercase tracking-widest block mb-0.5">City</label>
<span className="text-slate-800 font-extrabold block">{selectedWorker?.city || 'N/A'}</span>
</div>
<div className="col-span-2 border-t border-slate-100 pt-2">
<label className="text-[9px] font-black text-slate-400 uppercase tracking-widest block mb-0.5">Area</label>
<span className="text-slate-800 font-extrabold block">{selectedWorker?.area || 'N/A'}</span>
</div>
<div className="col-span-2 border-t border-slate-100 pt-2">
<label className="text-[9px] font-black text-slate-400 uppercase tracking-widest block mb-0.5">Preferred Location</label>
<span className="text-slate-800 font-extrabold block text-xs leading-normal">{selectedWorker?.preferred_location || 'N/A'}</span>
</div>
</div>
</div>
</div>
@ -952,31 +991,6 @@ export default function WorkerManagement({ workers }) {
)}
</div>
{/* Fraud Flagging */}
<div className="border-t border-slate-100 pt-4">
<label className="text-[9px] font-black text-slate-400 uppercase tracking-widest block mb-2">Trustworthiness</label>
{selectedWorker?.is_fraud ? (
<div className="space-y-2">
<div className="flex items-center space-x-1.5 text-red-600 bg-red-50 border border-red-100 px-3 py-2 rounded-xl text-xs font-extrabold">
<ShieldAlert className="w-4 h-4" />
<span>Flagged as Fraud</span>
</div>
<button
onClick={() => handleFlagFraud(selectedWorker.id, false, '')}
className="w-full py-2 bg-slate-900 hover:bg-slate-800 text-white rounded-xl text-[10px] font-black uppercase tracking-wider transition-all"
>
Clear Fraud Flag
</button>
</div>
) : (
<button
onClick={() => handleFlagFraud(selectedWorker.id, true, 'Flagged by system admin review.')}
className="w-full py-2 border border-red-200 hover:bg-red-50 text-red-600 rounded-xl text-[10px] font-black uppercase tracking-wider transition-all"
>
Flag as Fraud
</button>
)}
</div>
</div>
</div>
</div>

View File

@ -351,13 +351,6 @@ export default function Show({ worker }) {
<span className="font-black text-slate-900 text-xs uppercase">{worker.live_in_out}</span>
</div>
<div className="flex items-center justify-between pb-3 border-b border-slate-200">
<div className="flex items-center space-x-2 text-xs text-slate-600 font-bold">
<MapPin className="w-4 h-4 text-slate-500" />
<span>{t('preferred_location', 'Preferred Location')}</span>
</div>
<span className="font-extrabold text-slate-900 text-xs capitalize">{worker.preferred_location || t('any', 'Any')}</span>
</div>
<div className="flex items-center justify-between pb-3 border-b border-slate-200">
<div className="flex items-center space-x-2 text-xs text-slate-600 font-bold">

View File

@ -90,14 +90,30 @@ public function test_employer_web_registration_with_emirates_id()
]);
$profile = EmployerProfile::where('phone', '501234567')->first();
$this->assertNotNull($profile->emirates_id_number);
$this->assertNotNull($profile->emirates_id_expiry);
$this->assertEquals('784-1987-5493842-5', $profile->emirates_id_number);
$this->assertEquals('2027-12-11', $profile->emirates_id_expiry);
$this->assertEquals('Mohammad Jobaier Mohammad Abul Kalam', $profile->emirates_id_name);
$this->assertEquals('1987-04-14', $profile->emirates_id_dob);
$this->assertEquals('Bangladesh', $profile->nationality);
$this->assertEquals('2025-12-12', $profile->emirates_id_issue_date);
$this->assertEquals('Msj International Technical Services L.L.C UAE', $profile->emirates_id_employer);
$this->assertEquals('Dubai', $profile->emirates_id_issue_place);
$this->assertEquals('Electrician', $profile->emirates_id_occupation);
$this->assertDatabaseHas('sponsors', [
'email' => 'abdullah@example.com',
'mobile' => '501234567',
'emirates_id_file' => null, // File is not stored
'address' => 'Villa 14, Al Safa, Dubai',
'emirates_id' => '784-1987-5493842-5',
'emirates_id_name' => 'Mohammad Jobaier Mohammad Abul Kalam',
'emirates_id_dob' => '1987-04-14',
'nationality' => 'Bangladesh',
'emirates_id_issue_date' => '2025-12-12',
'emirates_id_expiry' => '2027-12-11',
'emirates_id_employer' => 'Msj International Technical Services L.L.C UAE',
'emirates_id_issue_place' => 'Dubai',
'emirates_id_occupation' => 'Electrician',
]);
}
@ -144,8 +160,8 @@ public function test_employer_web_registration_with_two_sided_emirates_id()
$this->assertTrue(session('employer_emirates_id_uploaded'));
$pendingRegAfterId = session('pending_employer_registration');
$this->assertEquals('784-1988-5310327-2', $pendingRegAfterId['emirates_id_number']); // Mock fallback value
$this->assertEquals('2028-04-11', $pendingRegAfterId['emirates_id_expiry']); // Mock fallback value
$this->assertEquals('784-1987-5493842-5', $pendingRegAfterId['emirates_id_number']); // Mock fallback value
$this->assertEquals('2027-12-11', $pendingRegAfterId['emirates_id_expiry']); // Mock fallback value
// Step 4: Payment Choice
$responseStep4 = $this->post('/employer/register-payment', [
@ -172,8 +188,15 @@ public function test_employer_web_registration_with_two_sided_emirates_id()
$profile = EmployerProfile::where('phone', '509876543')->first();
$this->assertNotNull($profile);
$this->assertEquals('784-1988-5310327-2', $profile->emirates_id_number);
$this->assertEquals('2028-04-11', $profile->emirates_id_expiry);
$this->assertEquals('784-1987-5493842-5', $profile->emirates_id_number);
$this->assertEquals('2027-12-11', $profile->emirates_id_expiry);
$this->assertEquals('Mohammad Jobaier Mohammad Abul Kalam', $profile->emirates_id_name);
$this->assertEquals('1987-04-14', $profile->emirates_id_dob);
$this->assertEquals('Bangladesh', $profile->nationality);
$this->assertEquals('2025-12-12', $profile->emirates_id_issue_date);
$this->assertEquals('Msj International Technical Services L.L.C UAE', $profile->emirates_id_employer);
$this->assertEquals('Dubai', $profile->emirates_id_issue_place);
$this->assertEquals('Electrician', $profile->emirates_id_occupation);
$this->assertEquals('Apartment 402, Al Nahda, Sharjah', $profile->address);
}
}

View File

@ -536,6 +536,103 @@ public function test_register_passport_and_visa_with_direct_json_payload()
$this->assertEquals('Sponsor Name', $visaDoc->ocr_data['sponsor']);
}
/**
* Test worker registration with the specific passport fields and ocr_accuracy.
*/
public function test_register_passport_with_full_ocr_fields()
{
$response = $this->postJson('/api/workers/register', [
'name' => 'Temp Name',
'phone' => '+971507777777',
'salary' => 2000,
'password' => 'secret123',
'language' => 'HI',
'passport' => [
'document_type' => 'P',
'issuing_country' => 'HAS',
'surname' => 'KHALED',
'given_names' => 'KHALED AL',
'passport_number' => 'Y34B67890',
'nationality' => 'RE1',
'date_of_birth' => '1990-05-14',
'date_of_expiry' => '2022-05-07',
'place_of_birth' => 'SHARJAH',
'authority' => 'MINISTRY OF INTERIOR U.S. 21 is ,,,, is',
'sex' => 'M',
'date_of_issue' => '2017-07-05',
'ocr_accuracy' => 98.7,
]
]);
$response->assertStatus(201);
$workerId = $response->json('data.worker.id');
$this->assertDatabaseHas('worker_documents', [
'worker_id' => $workerId,
'type' => 'passport',
'number' => 'Y34B67890',
'expiry_date' => '2022-05-07',
'issue_date' => '2017-07-05',
'ocr_accuracy' => 98.7,
]);
$passportDoc = \App\Models\WorkerDocument::where('worker_id', $workerId)->where('type', 'passport')->first();
$this->assertNotNull($passportDoc->ocr_data);
$this->assertEquals('KHALED AL', $passportDoc->ocr_data['given_names']);
$this->assertEquals('KHALED', $passportDoc->ocr_data['surname']);
$this->assertEquals('MINISTRY OF INTERIOR U.S. 21 is ,,,, is', $passportDoc->ocr_data['authority']);
$this->assertEquals(98.7, $passportDoc->ocr_data['ocr_accuracy']);
}
/**
* Test worker registration with the specific visa fields and ocr_accuracy.
*/
public function test_register_visa_with_full_ocr_fields()
{
$response = $this->postJson('/api/workers/register', [
'name' => 'Temp Name',
'phone' => '+971507777778',
'salary' => 2000,
'password' => 'secret123',
'language' => 'HI',
'visa' => [
'document_type' => 'uae_visa',
'country' => 'United Arab Emirates',
'visa_type' => 'ENTRY PERMIT',
'entry_permit_no' => '77003098 / 2019 / 204',
'issue_date' => '',
'valid_until' => '04-MAR-2019',
'uid_no' => '207404887',
'full_name' => 'Mr.MUHAMMAD NADEEM RASHEED',
'nationality' => 'PAKISTAN',
'place_of_birth' => 'SIALKOT PAK',
'date_of_birth' => '25-JUN-1999',
'passport_no' => 'EN9458281',
'profession' => 'SALES REPRESENTATIVE',
'sponsor_name' => '',
'ocr_accuracy' => 97.4,
]
]);
$response->assertStatus(201);
$workerId = $response->json('data.worker.id');
$this->assertDatabaseHas('worker_documents', [
'worker_id' => $workerId,
'type' => 'visa',
'number' => '77003098 / 2019 / 204',
'expiry_date' => '2019-03-04',
'ocr_accuracy' => 97.4,
]);
$visaDoc = \App\Models\WorkerDocument::where('worker_id', $workerId)->where('type', 'visa')->first();
$this->assertNotNull($visaDoc->ocr_data);
$this->assertEquals('uae_visa', $visaDoc->ocr_data['document_type']);
$this->assertEquals('ENTRY PERMIT', $visaDoc->ocr_data['visa_type']);
$this->assertEquals('Mr.MUHAMMAD NADEEM RASHEED', $visaDoc->ocr_data['full_name']);
$this->assertEquals(97.4, $visaDoc->ocr_data['ocr_accuracy']);
}
/**
* Test worker passport and visa registration when they are passed as JSON strings.
*/

View File

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