diff --git a/app/Customer.php b/app/Customer.php index b463f8c..c01e94a 100644 --- a/app/Customer.php +++ b/app/Customer.php @@ -63,7 +63,8 @@ class Customer extends Model 'nota_pekerjaan', 'tagpelanggan', 'tag_anggota', - 'gambar_mykad' + 'gambar_mykad', + 'scan_ic' ]; /** diff --git a/app/Http/Controllers/CustomerController.php b/app/Http/Controllers/CustomerController.php index bc45c47..6262034 100644 --- a/app/Http/Controllers/CustomerController.php +++ b/app/Http/Controllers/CustomerController.php @@ -370,6 +370,7 @@ class CustomerController extends Controller ]); $this->attachMykadPhoto($customer, $request); + $this->attachIcScan($customer, $request); $audit = new Audit(); $audit->users = $request->teller; @@ -1160,6 +1161,81 @@ public function AdminsearchCustomerBySAG(Request $request) ], 200); } + /** + * Store or replace the scanned IC / passport document for a customer. + */ + public function storeIcScan($ic, Request $request) + { + $customer = Customer::on('mysql7') + ->where('kplama', '=', $ic) + ->orWhere('kpbaru', '=', $ic) + ->first(); + + if (!$customer) { + return response()->json(['error' => 'Customer not found.', 'stored' => false], 404); + } + + $fileId = $this->storeCustomerDocument( + $this->fileBase64FromRequest($request), + $request->input('file_mime', $request->json('file_mime')), + $request->input('filename', $request->json('filename')), + $customer, + 'scan-ic', + 4 * 1024 * 1024 + ); + + if (!$fileId) { + return response()->json([ + 'error' => 'Fail scan IC tidak sah. Sila muat naik JPG, PNG atau PDF (maks 4MB).', + 'stored' => false, + ], 422); + } + + Customer::on('mysql7') + ->where('kpbaru', $customer->kpbaru) + ->where('kplama', $customer->kplama) + ->update(['scan_ic' => $fileId]); + + $customer->scan_ic = $fileId; + + return response()->json([ + 'message' => 'Scan dokumen IC berjaya dimuat naik.', + 'scan_ic' => $customer->scan_ic, + 'has_scan_ic' => true, + 'stored' => true, + ], 200); + } + + public function showIcScan($ic) + { + $customer = Customer::on('mysql7') + ->where('kplama', '=', $ic) + ->orWhere('kpbaru', '=', $ic) + ->first(); + + if (!$customer) { + return response()->json(['error' => 'Customer not found.'], 404); + } + + if (empty($customer->scan_ic)) { + return response()->json(['error' => 'Scan IC belum dimuat naik.'], 404); + } + + $file = ARFiles::on('mysql7')->where('id', $customer->scan_ic)->first(); + if (!$file || empty($file->data)) { + return response()->json(['error' => 'Fail scan IC tidak dijumpai.'], 404); + } + + return response()->json([ + 'file' => [ + 'data' => $file->data, + 'type' => $file->type ?: 'application/octet-stream', + 'filename' => $file->filename ?: 'scan-ic', + ], + 'has_scan_ic' => true, + ]); + } + /** * Save MyKad photo into arrahn_files and link it on customer.gambar_mykad. */ @@ -1191,6 +1267,41 @@ public function AdminsearchCustomerBySAG(Request $request) } } + protected function attachIcScan($customer, Request $request) + { + if (!$customer) { + return; + } + + $raw = $this->fileBase64FromRequest($request); + if ($raw === '') { + return; + } + + try { + $fileId = $this->storeCustomerDocument( + $raw, + $request->input('file_mime', $request->json('file_mime')), + $request->input('filename', $request->json('filename')), + $customer, + 'scan-ic', + 4 * 1024 * 1024 + ); + if (!$fileId) { + return; + } + + Customer::on('mysql7') + ->where('kpbaru', $customer->kpbaru) + ->where('kplama', $customer->kplama) + ->update(['scan_ic' => $fileId]); + + $customer->scan_ic = $fileId; + } catch (\Throwable $e) { + Log::error('Failed to attach IC scan: ' . $e->getMessage()); + } + } + protected function photoBase64FromRequest(Request $request) { $raw = $request->input('photo_base64'); @@ -1254,20 +1365,102 @@ public function AdminsearchCustomerBySAG(Request $request) $payload = $customer->toArray(); $payload['photo_base64'] = null; $payload['photo_mime'] = null; + $payload['has_scan_ic'] = !empty($customer->scan_ic); + $payload['scan_ic'] = $customer->scan_ic; + $payload['scan_ic_mime'] = null; + $payload['scan_ic_filename'] = null; - if (empty($customer->gambar_mykad)) { - return $payload; + if (!empty($customer->scan_ic)) { + $scanFile = ARFiles::on('mysql7')->select('type', 'filename')->where('id', $customer->scan_ic)->first(); + if ($scanFile) { + $payload['scan_ic_mime'] = $scanFile->type ?: null; + $payload['scan_ic_filename'] = $scanFile->filename ?: null; + } } - $file = ARFiles::on('mysql7')->where('id', $customer->gambar_mykad)->first(); - if ($file && $file->data) { - $payload['photo_base64'] = $file->data; - $payload['photo_mime'] = $file->type ?: 'image/jpeg'; + if (!empty($customer->gambar_mykad)) { + $file = ARFiles::on('mysql7')->where('id', $customer->gambar_mykad)->first(); + if ($file && $file->data) { + $payload['photo_base64'] = $file->data; + $payload['photo_mime'] = $file->type ?: 'image/jpeg'; + } } return $payload; } + protected function fileBase64FromRequest(Request $request) + { + $raw = $request->input('file_base64'); + if ($raw === null || $raw === '') { + $raw = $request->json('file_base64'); + } + if ($raw === null || $raw === '') { + $raw = $this->photoBase64FromRequest($request); + } + + return is_string($raw) ? $raw : ''; + } + + protected function storeCustomerDocument($raw, $mime, $filename, $customer, $fallbackName, $maxBytes) + { + $commaPos = strpos($raw, ','); + if ($commaPos !== false) { + $raw = substr($raw, $commaPos + 1); + } + $raw = preg_replace('/\s+/', '', $raw); + + if ($raw === '') { + return null; + } + + $decoded = base64_decode($raw, true); + if ($decoded === false || $decoded === '') { + Log::warning('Customer document skipped: invalid base64'); + return null; + } + + if (strlen($decoded) > $maxBytes) { + Log::warning('Customer document skipped: payload too large'); + return null; + } + + $allowed = [ + 'image/jpeg' => 'jpg', + 'image/jpg' => 'jpg', + 'image/png' => 'png', + 'application/pdf' => 'pdf', + ]; + $finfo = finfo_open(FILEINFO_MIME_TYPE); + $detected = $finfo ? finfo_buffer($finfo, $decoded) : false; + if ($finfo) { + finfo_close($finfo); + } + $detected = strtolower((string) ($detected ?: '')); + $claimed = strtolower((string) ($mime ?: '')); + if (isset($allowed[$detected])) { + $mime = $detected; + } elseif (isset($allowed[$claimed]) && ($claimed === 'image/jpg' || $claimed === $detected)) { + $mime = $claimed; + } else { + return null; + } + + $nric = preg_replace('/[^0-9A-Za-z]/', '', (string) ($customer->kpbaru ?: $customer->kplama ?: '')); + $ext = $allowed[$mime]; + $safeName = $filename ? pathinfo((string) $filename, PATHINFO_FILENAME) : ''; + $safeName = preg_replace('/[^0-9A-Za-z_-]/', '', $safeName); + + $file = new ARFiles(); + $file->data = $raw; + $file->filename = ($nric !== '' ? $nric . '-' : '') . ($safeName !== '' ? $safeName : $fallbackName) . '.' . $ext; + $file->type = $mime; + $file->tarikhmasa = Carbon::now()->toDateTimeString(); + $file->save(); + + return $file->id; + } + // public function createNotaArcc($kodcaw, Request $request) // { // $current = Carbon::now(); diff --git a/database/migrations/2026_10_27_120000_add_scan_ic_to_customer_table.php b/database/migrations/2026_10_27_120000_add_scan_ic_to_customer_table.php new file mode 100644 index 0000000..3e53f4b --- /dev/null +++ b/database/migrations/2026_10_27_120000_add_scan_ic_to_customer_table.php @@ -0,0 +1,46 @@ +hasTable('customer') + && !Schema::connection('mysql7')->hasColumn('customer', 'scan_ic') + ) { + Schema::connection('mysql7')->table('customer', function (Blueprint $table) { + if (Schema::connection('mysql7')->hasColumn('customer', 'gambar_mykad')) { + $table->unsignedBigInteger('scan_ic')->nullable()->after('gambar_mykad'); + } else { + $table->unsignedBigInteger('scan_ic')->nullable(); + } + }); + } + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + if ( + Schema::connection('mysql7')->hasTable('customer') + && Schema::connection('mysql7')->hasColumn('customer', 'scan_ic') + ) { + Schema::connection('mysql7')->table('customer', function (Blueprint $table) { + $table->dropColumn('scan_ic'); + }); + } + } +} diff --git a/routes/web.php b/routes/web.php index d1878ec..badc5e3 100644 --- a/routes/web.php +++ b/routes/web.php @@ -58,6 +58,8 @@ $router->group(['prefix'=>'api'], function () use ($router){ $router->get('customers/{ic}/gadai/{status}/{kodcaw}',['uses'=>'CustomerController@showOneCustomerGadaiByCawangan']); $router->post('customers/create/{kodcaw}',['uses'=>'CustomerController@createCustomer']); $router->post('customers/{ic}/mykad-photo', ['uses' => 'CustomerController@storeMykadPhotoIfMissing']); + $router->post('customers/{ic}/ic-scan', ['uses' => 'CustomerController@storeIcScan']); + $router->get('customers/{ic}/ic-scan', ['uses' => 'CustomerController@showIcScan']); $router->delete('customers/{kodcaw}/{nokp}', ['uses' => 'CustomerController@delete']); $router->put('customers/update/{nokp}', ['uses' => 'CustomerController@customerUpdate']); $router->put('customers/update/kaunter/{nokp}/{kodcaw}', ['uses' => 'CustomerController@customerUpdateKaunter']);