Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8720b92f79 | |||
| 1debb30751 |
+2
-1
@@ -63,7 +63,8 @@ class Customer extends Model
|
||||
'nota_pekerjaan',
|
||||
'tagpelanggan',
|
||||
'tag_anggota',
|
||||
'gambar_mykad'
|
||||
'gambar_mykad',
|
||||
'scan_ic'
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -53,7 +53,6 @@ class StokAuditController extends Controller
|
||||
$hinggatempoh = $request->hinggatempoh;
|
||||
$cawangan = Cawangan::on('mysql7')->where('kodcaw', '=', $request->kodcaw)->first();
|
||||
|
||||
// Query asal gadai
|
||||
$audit = Gadai::on($cawangan['mysql'])
|
||||
->where([
|
||||
['tagpalsu', '=', 0],
|
||||
@@ -66,47 +65,7 @@ class StokAuditController extends Controller
|
||||
->orderBy('sirig', 'ASC')
|
||||
->get();
|
||||
|
||||
// Dapatkan semua norujukan awal
|
||||
$arraynorujukan = $audit->pluck('norujukan')->toArray();
|
||||
|
||||
// Dapatkan tarikh terkini untuk setiap norujukan ikut trans_type T
|
||||
$tarikhTerkiniT = Transaction::on('mysql7')
|
||||
->select('norujukan', DB::raw('MAX(created_at) as tarikh_max'))
|
||||
->where('trans_type', 'T')
|
||||
->whereIn('norujukan', $arraynorujukan)
|
||||
->groupBy('norujukan')
|
||||
->pluck('tarikh_max', 'norujukan')
|
||||
->toArray();
|
||||
|
||||
// Dapatkan tarikh terkini untuk setiap norujukan ikut trans_type A
|
||||
$tarikhTerkiniA = Transaction::on('mysql7')
|
||||
->select('norujukan', DB::raw('MAX(created_at) as tarikh_max'))
|
||||
->where('trans_type', 'A')
|
||||
->whereIn('norujukan', $arraynorujukan)
|
||||
->groupBy('norujukan')
|
||||
->pluck('tarikh_max', 'norujukan')
|
||||
->toArray();
|
||||
|
||||
// Filter audit ikut tarikh masing-masing
|
||||
$rekodaudit = $audit->filter(function ($item) use ($tarikhTerkiniT, $tarikhTerkiniA) {
|
||||
$norujukan = $item->norujukan;
|
||||
|
||||
$adaTebusTerkini = isset($tarikhTerkiniT[$norujukan]) &&
|
||||
Transaction::on('mysql7')
|
||||
->where('norujukan', $norujukan)
|
||||
->where('trans_type', 'T')
|
||||
->whereDate('created_at', '>=', date('Y-m-d', strtotime($tarikhTerkiniT[$norujukan])))
|
||||
->exists();
|
||||
|
||||
$adaAnsuranTerkini = isset($tarikhTerkiniA[$norujukan]) &&
|
||||
Transaction::on('mysql7')
|
||||
->where('norujukan', $norujukan)
|
||||
->where('trans_type', 'A')
|
||||
->whereDate('created_at', '>=', date('Y-m-d', strtotime($tarikhTerkiniA[$norujukan])))
|
||||
->exists();
|
||||
|
||||
return !$adaTebusTerkini && !$adaAnsuranTerkini;
|
||||
})->values();
|
||||
$rekodaudit = $this->filterRekodAuditKhas($audit);
|
||||
|
||||
// Gunakan hanya norujukan yang dah ditapis
|
||||
$arraynorujukan = $rekodaudit->pluck('norujukan')->toArray();
|
||||
@@ -179,13 +138,17 @@ class StokAuditController extends Controller
|
||||
$audit = Gadai::on($cawangan['mysql'])
|
||||
->where([['tagpalsu','=',0],['tempoh','>=',$daritempoh],['tempoh','<=',$hinggatempoh],['sttebus','=',''],['taglel','=',0]])
|
||||
->orderBy('t_gadai', 'ASC')
|
||||
->orderBy('sirig', 'ASC');
|
||||
// ->get();
|
||||
// dd($audit);
|
||||
$rekodaudit = $audit->get();
|
||||
$arraynorujukan = $audit->pluck('norujukan')->toArray();
|
||||
->orderBy('sirig', 'ASC')
|
||||
->get();
|
||||
|
||||
$rekodaudit = $this->filterRekodAuditKhas($audit);
|
||||
$arraynorujukan = $rekodaudit->pluck('norujukan')->toArray();
|
||||
$keybynorujukan = $rekodaudit->keyBy('norujukan');
|
||||
|
||||
|
||||
if (empty($arraynorujukan)) {
|
||||
return response()->json('success');
|
||||
}
|
||||
|
||||
$gadainokp = Gadai::on($cawangan['mysql'])->whereIn('norujukan', $arraynorujukan)
|
||||
->select('nokp')
|
||||
->groupBy('nokp')
|
||||
@@ -261,12 +224,17 @@ class StokAuditController extends Controller
|
||||
$audit = Gadai::on($cawangan['mysql'])
|
||||
->where([['tagpalsu','=',0],['tempoh','>=',$daritempoh],['tempoh','<=',$hinggatempoh],['sttebus','=',''],['taglel','=',0]])
|
||||
->orderBy('t_gadai', 'ASC')
|
||||
->orderBy('sirig', 'ASC');
|
||||
->orderBy('sirig', 'ASC')
|
||||
->get();
|
||||
|
||||
$rekodaudit = $audit->get();
|
||||
$arraynorujukan = $audit->pluck('norujukan')->toArray();
|
||||
$rekodaudit = $this->filterRekodAuditKhas($audit);
|
||||
$arraynorujukan = $rekodaudit->pluck('norujukan')->toArray();
|
||||
$keybynorujukan = $rekodaudit->keyBy('norujukan');
|
||||
|
||||
|
||||
if (empty($arraynorujukan)) {
|
||||
return response()->json('success');
|
||||
}
|
||||
|
||||
$gadainokp = Gadai::on($cawangan['mysql'])->whereIn('norujukan', $arraynorujukan)
|
||||
// ->select('nokp')
|
||||
// ->groupBy('nokp')
|
||||
@@ -441,7 +409,6 @@ class StokAuditController extends Controller
|
||||
|
||||
$cawangan = Cawangan::on('mysql7')->where('kodcaw', '=', $request->kodcaw)->first();
|
||||
|
||||
// Ambil semua rekod gadai asas
|
||||
$audit = Gadai::on($cawangan['mysql'])
|
||||
->where([
|
||||
['tagpalsu', '=', 0],
|
||||
@@ -450,52 +417,10 @@ class StokAuditController extends Controller
|
||||
['sttebus', '=', ''],
|
||||
['taglel', '=', 0],
|
||||
])
|
||||
->orderBy('norujukan', 'ASC') // susun ikut norujukan
|
||||
->orderBy('norujukan', 'ASC')
|
||||
->get();
|
||||
|
||||
$arraynorujukan = $audit->pluck('norujukan')->toArray();
|
||||
|
||||
// Tarikh terkini ikut trans_type T
|
||||
$tarikhTerkiniT = Transaction::on('mysql7')
|
||||
->select('norujukan', DB::raw('MAX(created_at) as tarikh_max'))
|
||||
->where('trans_type', 'T')
|
||||
->whereIn('norujukan', $arraynorujukan)
|
||||
->groupBy('norujukan')
|
||||
->pluck('tarikh_max', 'norujukan')
|
||||
->toArray();
|
||||
|
||||
// Tarikh terkini ikut trans_type A
|
||||
$tarikhTerkiniA = Transaction::on('mysql7')
|
||||
->select('norujukan', DB::raw('MAX(created_at) as tarikh_max'))
|
||||
->where('trans_type', 'A')
|
||||
->whereIn('norujukan', $arraynorujukan)
|
||||
->groupBy('norujukan')
|
||||
->pluck('tarikh_max', 'norujukan')
|
||||
->toArray();
|
||||
|
||||
// Filter audit
|
||||
$rekodaudit = $audit->filter(function ($item) use ($tarikhTerkiniT, $tarikhTerkiniA) {
|
||||
if ($item->lelong_tawarruq == 0) {
|
||||
return true;
|
||||
}
|
||||
$norujukan = $item->norujukan;
|
||||
|
||||
$adaTebusTerkini = isset($tarikhTerkiniT[$norujukan]) &&
|
||||
Transaction::on('mysql7')
|
||||
->where('norujukan', $norujukan)
|
||||
->where('trans_type', 'T')
|
||||
->whereDate('created_at', '>=', date('Y-m-d', strtotime($tarikhTerkiniT[$norujukan])))
|
||||
->exists();
|
||||
|
||||
$adaAnsuranTerkini = isset($tarikhTerkiniA[$norujukan]) &&
|
||||
Transaction::on('mysql7')
|
||||
->where('norujukan', $norujukan)
|
||||
->where('trans_type', 'A')
|
||||
->whereDate('created_at', '>=', date('Y-m-d', strtotime($tarikhTerkiniA[$norujukan])))
|
||||
->exists();
|
||||
|
||||
return !$adaTebusTerkini && !$adaAnsuranTerkini;
|
||||
})->values();
|
||||
$rekodaudit = $this->filterRekodAuditKhas($audit);
|
||||
|
||||
// Attach customer info + blast untuk setiap norujukan
|
||||
foreach ($rekodaudit as $index => $rekod) {
|
||||
@@ -563,7 +488,50 @@ class StokAuditController extends Controller
|
||||
return ['ujrah' => $ujrah_semasa, 'onepercent' => $onepercent_semasa];
|
||||
}
|
||||
|
||||
// dalam Controller (private/protected)
|
||||
/**
|
||||
* SAG untuk blast Stok Audit Khas:
|
||||
* - Fasa 1 (belum lanjut): buang jika dah ada tebus (T) atau bayaran ansuran (A).
|
||||
* - Fasa 2 (dah lanjut, lelong_tawarruq=0): buang jika masih sekitar matang pertama (tempoh < 11).
|
||||
* Kekalkan tempoh >= 11 untuk peringatan tamat kontrak.
|
||||
*/
|
||||
private function filterRekodAuditKhas($audit)
|
||||
{
|
||||
$arraynorujukan = $audit->pluck('norujukan')->filter()->unique()->values()->all();
|
||||
|
||||
$norujukanWithT = [];
|
||||
$norujukanWithA = [];
|
||||
|
||||
if (!empty($arraynorujukan)) {
|
||||
$norujukanWithT = array_flip(
|
||||
Transaction::on('mysql7')
|
||||
->where('trans_type', 'T')
|
||||
->whereIn('norujukan', $arraynorujukan)
|
||||
->distinct()
|
||||
->pluck('norujukan')
|
||||
->all()
|
||||
);
|
||||
|
||||
$norujukanWithA = array_flip(
|
||||
Transaction::on('mysql7')
|
||||
->where('trans_type', 'A')
|
||||
->whereIn('norujukan', $arraynorujukan)
|
||||
->distinct()
|
||||
->pluck('norujukan')
|
||||
->all()
|
||||
);
|
||||
}
|
||||
|
||||
return $audit->filter(function ($item) use ($norujukanWithT, $norujukanWithA) {
|
||||
if ((int) $item->lelong_tawarruq === 0) {
|
||||
return (float) $item->tempoh >= 11;
|
||||
}
|
||||
|
||||
$norujukan = $item->norujukan;
|
||||
|
||||
return !isset($norujukanWithT[$norujukan]) && !isset($norujukanWithA[$norujukan]);
|
||||
})->values();
|
||||
}
|
||||
|
||||
private function floorTo5SenFrom2dp($amount)
|
||||
{
|
||||
// pastikan numeric
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddScanIcToCustomerTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Master DB (mysql7) — stores arrahn_files.id for the scanned IC document.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
if (
|
||||
Schema::connection('mysql7')->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');
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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']);
|
||||
|
||||
Reference in New Issue
Block a user