Compare commits

..

No commits in common. "6071c6144006cf522cfcfdf772b4faa59a7d9576" and "2733d281aaf2afa10b42ef195e88074c1aa41a98" have entirely different histories.

4 changed files with 45 additions and 230 deletions

View File

@ -497,10 +497,9 @@ public function register(Request $request)
// Create visa document if provided // Create visa document if provided
if ($visaDataInput) { if ($visaDataInput) {
$cleanedVisaOcr = $this->cleanVisaData($visaDataInput); $visaNum = $visaDataInput['entry_permit_no'] ?? $visaDataInput['file_number'] ?? $visaDataInput['id_number'] ?? ('V' . rand(1000000, 9999999));
$visaNum = $cleanedVisaOcr['entry_permit_no'] ?: ('V' . rand(1000000, 9999999)); $expiryDate = $visaDataInput['valid_until'] ?? $visaDataInput['expiry_date'] ?? null;
$expiryDate = $cleanedVisaOcr['valid_until'] ?? null; $issueDate = $visaDataInput['issue_date'] ?? null;
$issueDate = $cleanedVisaOcr['issue_date'] ?? null;
$expiryDateFormatted = $expiryDate ? $this->normaliseDateForController($expiryDate) : now()->addYears(2)->toDateString(); $expiryDateFormatted = $expiryDate ? $this->normaliseDateForController($expiryDate) : now()->addYears(2)->toDateString();
$issueDateFormatted = $issueDate ? $this->normaliseDateForController($issueDate) : now()->subYears(2)->toDateString(); $issueDateFormatted = $issueDate ? $this->normaliseDateForController($issueDate) : now()->subYears(2)->toDateString();
@ -512,7 +511,7 @@ public function register(Request $request)
'expiry_date' => $expiryDateFormatted, 'expiry_date' => $expiryDateFormatted,
'ocr_accuracy' => isset($visaDataInput['ocr_accuracy']) ? (float)$visaDataInput['ocr_accuracy'] : 99.0, 'ocr_accuracy' => isset($visaDataInput['ocr_accuracy']) ? (float)$visaDataInput['ocr_accuracy'] : 99.0,
'file_path' => null, 'file_path' => null,
'ocr_data' => $cleanedVisaOcr, 'ocr_data' => $visaDataInput,
]); ]);
} }
@ -1237,62 +1236,6 @@ private function normaliseDateForController(?string $raw): ?string
} }
} }
/**
* Standardize and clean Visa OCR data structure to exactly the 15 required fields.
*/
private function cleanVisaData(array $visaDataInput): array
{
$rawVisaType = $visaDataInput['visa_type'] ?? null;
$rawEntryPermitNo = $visaDataInput['entry_permit_no'] ?? $visaDataInput['file_number'] ?? $visaDataInput['id_number'] ?? $visaDataInput['number'] ?? null;
$rawIssueDate = $visaDataInput['issue_date'] ?? null;
$rawValidUntil = $visaDataInput['valid_until'] ?? $visaDataInput['expiry_date'] ?? null;
$rawUidNo = $visaDataInput['uid_no'] ?? null;
$rawFullName = $visaDataInput['full_name'] ?? $visaDataInput['name'] ?? null;
$rawNationality = $visaDataInput['nationality'] ?? null;
$rawPlaceOfBirth = $visaDataInput['place_of_birth'] ?? null;
$rawDateOfBirth = $visaDataInput['date_of_birth'] ?? $visaDataInput['dob'] ?? null;
$rawPassportNo = $visaDataInput['passport_no'] ?? $visaDataInput['passport_number'] ?? null;
$rawProfession = $visaDataInput['profession'] ?? null;
$rawSponsorName = $visaDataInput['sponsor_name'] ?? null;
// Clean sponsor
$sponsorData = $visaDataInput['sponsor'] ?? [];
if (is_string($sponsorData)) {
$sponsorNameFromObj = $sponsorData;
$sponsorAddress = '';
$sponsorPhone = '';
} else {
$sponsorNameFromObj = $sponsorData['name'] ?? $sponsorData['sponsor_name'] ?? $sponsorData['full_name'] ?? '';
$sponsorAddress = $sponsorData['address'] ?? '';
$sponsorPhone = $sponsorData['phone'] ?? $sponsorData['mobile'] ?? $sponsorData['mobile_number'] ?? $sponsorData['phone_number'] ?? '';
}
if (empty($rawSponsorName)) {
$rawSponsorName = $sponsorNameFromObj;
}
return [
'document_type' => 'uae_visa',
'country' => 'United Arab Emirates',
'visa_type' => $rawVisaType ?: 'ENTRY PERMIT',
'entry_permit_no' => $rawEntryPermitNo ?: '',
'issue_date' => $rawIssueDate ?: '',
'valid_until' => $rawValidUntil ?: '',
'uid_no' => $rawUidNo ?: '',
'full_name' => $rawFullName ?: '',
'nationality' => $rawNationality ?: '',
'place_of_birth' => $rawPlaceOfBirth ?: '',
'date_of_birth' => $rawDateOfBirth ?: '',
'passport_no' => $rawPassportNo ?: '',
'profession' => $rawProfession ?: '',
'sponsor_name' => $rawSponsorName ?: '',
'sponsor' => [
'name' => $sponsorNameFromObj ?: '',
'address' => $sponsorAddress ?: '',
'phone' => $sponsorPhone ?: '',
]
];
}
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// Forgot / Reset Password (via Mobile Number) // Forgot / Reset Password (via Mobile Number)
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------

View File

@ -201,15 +201,14 @@ public function updateProfile(Request $request)
$visaDataInput = $request->input('visa'); $visaDataInput = $request->input('visa');
if ($visaDataInput) { if ($visaDataInput) {
$existingVisa = $worker->documents()->where('type', 'visa')->first(); $existingVisa = $worker->documents()->where('type', 'visa')->first();
$cleanedVisaOcr = $this->cleanVisaData($visaDataInput); $expiryDate = $visaDataInput['expiry_date'] ?? null;
$expiryDate = $cleanedVisaOcr['valid_until'] ?? null; $issueDate = $visaDataInput['issue_date'] ?? null;
$issueDate = $cleanedVisaOcr['issue_date'] ?? null;
$expiryDateFormatted = $expiryDate ? $this->normaliseDateForController($expiryDate) : null; $expiryDateFormatted = $expiryDate ? $this->normaliseDateForController($expiryDate) : null;
$issueDateFormatted = $issueDate ? $this->normaliseDateForController($issueDate) : null; $issueDateFormatted = $issueDate ? $this->normaliseDateForController($issueDate) : null;
if ($existingVisa) { if ($existingVisa) {
$updateFields = [ $updateFields = [
'ocr_data' => $cleanedVisaOcr, 'ocr_data' => $visaDataInput,
]; ];
if ($expiryDateFormatted) { if ($expiryDateFormatted) {
$updateFields['expiry_date'] = $expiryDateFormatted; $updateFields['expiry_date'] = $expiryDateFormatted;
@ -217,12 +216,9 @@ public function updateProfile(Request $request)
if ($issueDateFormatted) { if ($issueDateFormatted) {
$updateFields['issue_date'] = $issueDateFormatted; $updateFields['issue_date'] = $issueDateFormatted;
} }
if (!empty($cleanedVisaOcr['entry_permit_no'])) {
$updateFields['number'] = $cleanedVisaOcr['entry_permit_no'];
}
$existingVisa->update($updateFields); $existingVisa->update($updateFields);
} else { } else {
$visaNum = $cleanedVisaOcr['entry_permit_no'] ?: ('V' . rand(1000000, 9999999)); $visaNum = $visaDataInput['file_number'] ?? $visaDataInput['id_number'] ?? ('V' . rand(1000000, 9999999));
$expiryDateFormatted = $expiryDateFormatted ?: now()->addYears(2)->toDateString(); $expiryDateFormatted = $expiryDateFormatted ?: now()->addYears(2)->toDateString();
$issueDateFormatted = $issueDateFormatted ?: now()->subYears(2)->toDateString(); $issueDateFormatted = $issueDateFormatted ?: now()->subYears(2)->toDateString();
@ -231,9 +227,9 @@ public function updateProfile(Request $request)
'number' => $visaNum, 'number' => $visaNum,
'issue_date' => $issueDateFormatted, 'issue_date' => $issueDateFormatted,
'expiry_date' => $expiryDateFormatted, 'expiry_date' => $expiryDateFormatted,
'ocr_accuracy' => isset($visaDataInput['ocr_accuracy']) ? (float)$visaDataInput['ocr_accuracy'] : 99.0, 'ocr_accuracy' => 99.0,
'file_path' => null, 'file_path' => null,
'ocr_data' => $cleanedVisaOcr, 'ocr_data' => $visaDataInput,
]); ]);
} }
} }
@ -758,60 +754,4 @@ private function normaliseDateForController(?string $raw): ?string
return $raw; return $raw;
} }
} }
/**
* Standardize and clean Visa OCR data structure to exactly the 15 required fields.
*/
private function cleanVisaData(array $visaDataInput): array
{
$rawVisaType = $visaDataInput['visa_type'] ?? null;
$rawEntryPermitNo = $visaDataInput['entry_permit_no'] ?? $visaDataInput['file_number'] ?? $visaDataInput['id_number'] ?? $visaDataInput['number'] ?? null;
$rawIssueDate = $visaDataInput['issue_date'] ?? null;
$rawValidUntil = $visaDataInput['valid_until'] ?? $visaDataInput['expiry_date'] ?? null;
$rawUidNo = $visaDataInput['uid_no'] ?? null;
$rawFullName = $visaDataInput['full_name'] ?? $visaDataInput['name'] ?? null;
$rawNationality = $visaDataInput['nationality'] ?? null;
$rawPlaceOfBirth = $visaDataInput['place_of_birth'] ?? null;
$rawDateOfBirth = $visaDataInput['date_of_birth'] ?? $visaDataInput['dob'] ?? null;
$rawPassportNo = $visaDataInput['passport_no'] ?? $visaDataInput['passport_number'] ?? null;
$rawProfession = $visaDataInput['profession'] ?? null;
$rawSponsorName = $visaDataInput['sponsor_name'] ?? null;
// Clean sponsor
$sponsorData = $visaDataInput['sponsor'] ?? [];
if (is_string($sponsorData)) {
$sponsorNameFromObj = $sponsorData;
$sponsorAddress = '';
$sponsorPhone = '';
} else {
$sponsorNameFromObj = $sponsorData['name'] ?? $sponsorData['sponsor_name'] ?? $sponsorData['full_name'] ?? '';
$sponsorAddress = $sponsorData['address'] ?? '';
$sponsorPhone = $sponsorData['phone'] ?? $sponsorData['mobile'] ?? $sponsorData['mobile_number'] ?? $sponsorData['phone_number'] ?? '';
}
if (empty($rawSponsorName)) {
$rawSponsorName = $sponsorNameFromObj;
}
return [
'document_type' => 'uae_visa',
'country' => 'United Arab Emirates',
'visa_type' => $rawVisaType ?: 'ENTRY PERMIT',
'entry_permit_no' => $rawEntryPermitNo ?: '',
'issue_date' => $rawIssueDate ?: '',
'valid_until' => $rawValidUntil ?: '',
'uid_no' => $rawUidNo ?: '',
'full_name' => $rawFullName ?: '',
'nationality' => $rawNationality ?: '',
'place_of_birth' => $rawPlaceOfBirth ?: '',
'date_of_birth' => $rawDateOfBirth ?: '',
'passport_no' => $rawPassportNo ?: '',
'profession' => $rawProfession ?: '',
'sponsor_name' => $rawSponsorName ?: '',
'sponsor' => [
'name' => $sponsorNameFromObj ?: '',
'address' => $sponsorAddress ?: '',
'phone' => $sponsorPhone ?: '',
]
];
}
} }

View File

@ -2564,80 +2564,14 @@
}, },
"visa": { "visa": {
"type": "object", "type": "object",
"description": "Standardized UAE Visa metadata.",
"properties": { "properties": {
"document_type": { "file_number": {
"type": "string", "type": "string",
"example": "uae_visa" "example": "202/2022/2840234"
}, },
"country": { "expiry_date": {
"type": "string", "type": "string",
"example": "United Arab Emirates" "example": "2028-12-31"
},
"visa_type": {
"type": "string",
"example": "ENTRY PERMIT"
},
"entry_permit_no": {
"type": "string",
"example": "87654091 / 2016 / 210"
},
"issue_date": {
"type": "string",
"example": "2016-11-18"
},
"valid_until": {
"type": "string",
"example": "2017-01-18"
},
"uid_no": {
"type": "string",
"example": "181825009"
},
"full_name": {
"type": "string",
"example": "GILBERT DSOUZA W/O TIMOTHY"
},
"nationality": {
"type": "string",
"example": "INDIAN"
},
"place_of_birth": {
"type": "string",
"example": "MANGALORE"
},
"date_of_birth": {
"type": "string",
"example": "1980-02-02"
},
"passport_no": {
"type": "string",
"example": "U4690639"
},
"profession": {
"type": "string",
"example": "BUSINESS PERSON"
},
"sponsor_name": {
"type": "string",
"example": "Sponsor LLC"
},
"sponsor": {
"type": "object",
"properties": {
"name": {
"type": "string",
"example": "Sponsor LLC"
},
"address": {
"type": "string",
"example": "Dubai Marina, Dubai"
},
"phone": {
"type": "string",
"example": "2977725"
}
}
} }
} }
} }

View File

@ -676,57 +676,51 @@ export default function Show({ worker }) {
)} )}
</div> </div>
<div className="p-6">
{visaDoc.ocr_data ? (() => { {visaDoc.ocr_data ? (() => {
const sponsorObj = visaDoc.ocr_data?.sponsor; const sponsorObj = visaDoc.ocr_data?.sponsor;
const isSponsorObj = sponsorObj && typeof sponsorObj === 'object' && !Array.isArray(sponsorObj); const isSponsorObj = sponsorObj && typeof sponsorObj === 'object' && !Array.isArray(sponsorObj);
const sponsorName = isSponsorObj const sponsorName = isSponsorObj
? (sponsorObj.name || sponsorObj.sponsor_name) ? (sponsorObj.name || sponsorObj.sponsor_name || sponsorObj.full_name)
: (visaDoc.ocr_data?.sponsor_name || (typeof sponsorObj === 'string' ? sponsorObj : '')); : (visaDoc.ocr_data?.sponsor_name || (typeof sponsorObj === 'string' ? sponsorObj : null));
const sponsorPhone = isSponsorObj const sponsorMobile = isSponsorObj
? (sponsorObj.phone || sponsorObj.mobile || sponsorObj.mobile_number) ? (sponsorObj.mobile || sponsorObj.mobile_number || sponsorObj.phone)
: ''; : null;
const sponsorAddress = isSponsorObj
? (sponsorObj.address || '')
: '';
return ( 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.full_name || worker.name)}</div>
</div>
<div> <div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('entry_permit_no', 'Entry Permit No')}</div> <div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('entry_permit_no', 'Entry Permit No')}</div>
<div className="text-xs font-mono font-bold text-slate-800">{safeRender(visaDoc.ocr_data.entry_permit_no || visaDoc.number)}</div> <div className="text-xs font-mono font-bold text-slate-800">{safeRender(visaDoc.ocr_data.entry_permit_no)}</div>
</div> </div>
<div> <div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('uid_no', 'UID No')}</div> <div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('uid_no', 'UID No')}</div>
<div className="text-xs font-mono font-bold text-slate-800">{safeRender(visaDoc.ocr_data.uid_no)}</div> <div className="text-xs font-mono font-bold text-slate-800">{safeRender(visaDoc.ocr_data.uid_no)}</div>
</div> </div>
<div> <div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('visa_type', 'Visa Type')}</div> <div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('id_number', 'ID Number')}</div>
<div className="text-xs font-bold text-slate-800 uppercase">{safeRender(visaDoc.ocr_data.visa_type || 'ENTRY PERMIT')}</div> <div className="text-xs font-mono font-bold text-slate-800">{safeRender(visaDoc.ocr_data.id_number || visaDoc.ocr_data.emirates_id_number)}</div>
</div> </div>
<div> <div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('passport_no', 'Passport No')}</div> <div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('visa_type', 'Visa Type')}</div>
<div className="text-xs font-bold text-slate-800">{safeRender(visaDoc.ocr_data.passport_no || visaDoc.ocr_data.passport_number)}</div> <div className="text-xs font-bold text-slate-800 uppercase">{safeRender(visaDoc.ocr_data.visa_type || 'ENTRY PERMIT')}</div>
</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_name', 'Sponsor Name')}</div> <div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('sponsor_name', 'Sponsor Name')}</div>
<div className="text-xs font-bold text-slate-800">{safeRender(sponsorName || 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('sponsor_phone', 'Sponsor Phone')}</div> <div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('sponsor_mobile', 'Sponsor Mobile')}</div>
<div className="text-xs font-mono font-bold text-slate-800">{safeRender(sponsorPhone)}</div> <div className="text-xs font-mono font-bold text-slate-800">{safeRender(sponsorMobile)}</div>
</div>
<div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('sponsor_address', 'Sponsor Address')}</div>
<div className="text-xs font-bold text-slate-800">{safeRender(sponsorAddress)}</div>
</div> </div>
)}
<div> <div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('nationality', 'Nationality')}</div> <div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('nationality', 'Nationality')}</div>
<div className="text-xs font-bold text-slate-800">{safeRender(visaDoc.ocr_data.nationality || worker.nationality)}</div> <div className="text-xs font-bold text-slate-800">{safeRender(visaDoc.ocr_data.nationality || worker.nationality)}</div>
@ -743,13 +737,17 @@ export default function Show({ worker }) {
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('place_of_birth', 'Place of Birth')}</div> <div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('place_of_birth', 'Place of Birth')}</div>
<div className="text-xs font-bold text-slate-800">{safeRender(visaDoc.ocr_data.place_of_birth)}</div> <div className="text-xs font-bold text-slate-800">{safeRender(visaDoc.ocr_data.place_of_birth)}</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-xs font-bold text-slate-800">{safeRender(visaDoc.ocr_data.place_of_issue)}</div>
</div>
<div> <div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('issue_date', 'Issue Date')}</div> <div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('issue_date', 'Issue Date')}</div>
<div className="text-xs font-bold text-slate-800">{safeRender(visaDoc.ocr_data.issue_date || visaDoc.issue_date)}</div> <div className="text-xs font-bold text-slate-800">{safeRender(visaDoc.ocr_data.issue_date || visaDoc.issue_date)}</div>
</div> </div>
<div> <div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('valid_until', 'Valid Until')}</div> <div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('expiry_date', 'Expiry Date')}</div>
<div className="text-xs font-bold text-emerald-600">{safeRender(visaDoc.ocr_data.valid_until || 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>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('document_type', 'Document Type')}</div> <div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('document_type', 'Document Type')}</div>