middleware('auth'); } protected function api() { return Http::withOptions(['verify' => config('app.api_verify')]); } protected function kodcaw() { return Auth::user()->cawangan; } protected function cawanganInfo() { return $this->api() ->get(config('api.url') . '/api/cawangan/' . $this->kodcaw()) ->json(); } protected function isPembiayaanOnlineEnabled($cawangan_info = null) { if (!is_array($cawangan_info)) { $cawangan_info = $this->cawanganInfo(); } $features = $cawangan_info['features'] ?? []; return !empty($features['pembiayaan_online']); } protected function abortUnlessPembiayaanOnline($cawangan_info = null) { if (!$this->isPembiayaanOnlineEnabled($cawangan_info)) { abort(403, 'Pembiayaan online tidak diaktifkan untuk cawangan ini.'); } } /** * PC senarai queue + batch history. */ public function index(Request $request) { $auth_user = Auth::user(); $kodcaw = $this->kodcaw(); $api_url = config('api.url'); $cawangan_info = $this->cawanganInfo(); $this->abortUnlessPembiayaanOnline($cawangan_info); $queue = $this->api() ->get($api_url . '/api/pembiayaan-online/' . $kodcaw . '/queue') ->json(); $batches = $this->api() ->get($api_url . '/api/pembiayaan-online/' . $kodcaw . '/batches') ->json(); $banks = $this->api()->get($api_url . '/api/bank')->json(); if (!is_array($banks)) { $banks = []; } $modal = $this->api() ->get($api_url . '/api/modal-online/' . $kodcaw) ->json(); $lines = $queue['data'] ?? []; $slot = $queue['slot_cadangan'] ?? null; if (!is_array($batches)) { $batches = []; } return view('pembiayaan_online.pc.senarai', compact( 'auth_user', 'kodcaw', 'api_url', 'cawangan_info', 'lines', 'batches', 'banks', 'modal', 'slot', 'queue' )); } /** * Lightweight payload for the PC layout cutoff banner / browser notification. */ public function reminderStatus() { $cawangan_info = $this->cawanganInfo(); if (!$this->isPembiayaanOnlineEnabled($cawangan_info)) { return response()->json(['enabled' => false]); } $kodcaw = $this->kodcaw(); $api_url = config('api.url'); $queue = $this->api() ->get($api_url . '/api/pembiayaan-online/' . $kodcaw . '/queue') ->json(); $modal = $this->api() ->get($api_url . '/api/modal-online/' . $kodcaw) ->json(); if (!is_array($queue)) { $queue = []; } if (!is_array($modal)) { $modal = []; } $slot = (is_array($queue) && isset($queue['slot_cadangan']) && is_array($queue['slot_cadangan'])) ? $queue['slot_cadangan'] : []; return response()->json([ 'enabled' => true, 'ditutup' => (int) ($modal['ditutup'] ?? 0) === 1, 'bilangan' => (int) ($queue['bilangan'] ?? 0), 'slot' => $slot['slot'] ?? 'BATCH1', 'cutoff' => $slot['cutoff'] ?? '11:00', 'cutoff_at' => $slot['cutoff_at'] ?? null, 'server_now' => $slot['server_now'] ?? null, 'lewat' => !empty($slot['lewat']), 'baki_minit' => (int) ($slot['baki_minit'] ?? 0), 'url' => route('pembiayaan.online.pc'), 'remind_minutes' => 30, 'last_minute' => 10, ]); } /** * Update bank/docs for a queue line. */ public function updateLine(Request $request, $id) { $this->abortUnlessPembiayaanOnline(); $kodcaw = $this->kodcaw(); $api_url = config('api.url'); $payload = [ 'kodbank' => $request->input('kodbank'), 'noakaun' => $request->input('noakaun'), 'nama_bank' => $request->input('nama_bank'), 'nota' => $request->input('nota'), 'update_customer_bank' => true, ]; if ($request->hasFile('bukti_akaun')) { $file = $request->file('bukti_akaun'); if (!$file->isValid()) { return redirect()->back()->with('error', 'Fail slip bank tidak sah.'); } if ($file->getSize() > 4 * 1024 * 1024) { return redirect()->back()->with('error', 'Saiz fail maksimum 4MB.'); } $payload['bukti_akaun_data'] = base64_encode(file_get_contents($file->getRealPath())); $payload['bukti_akaun_filename'] = $file->getClientOriginalName(); $payload['bukti_akaun_type'] = $file->getMimeType() ?: 'application/octet-stream'; } if ($request->hasFile('borang')) { $file = $request->file('borang'); if (!$file->isValid()) { return redirect()->back()->with('error', 'Fail borang pengesahan tidak sah.'); } if ($file->getSize() > 4 * 1024 * 1024) { return redirect()->back()->with('error', 'Saiz fail maksimum 4MB.'); } $payload['borang_data'] = base64_encode(file_get_contents($file->getRealPath())); $payload['borang_filename'] = $file->getClientOriginalName(); $payload['borang_type'] = $file->getMimeType() ?: 'application/octet-stream'; } $res = $this->api() ->asForm() ->put($api_url . '/api/pembiayaan-online/' . $kodcaw . '/lines/' . $id, $payload); if (!$res->successful()) { $body = $res->json(); $msg = is_array($body) && isset($body['message']) ? $body['message'] : 'Gagal kemaskini rekod.'; return redirect()->back()->with('error', $msg); } return redirect()->route('pembiayaan.online.pc') ->with('message', 'Rekod dikemaskini. Sila semak semula sebelum hantar ke Operasi.'); } /** * PC verifies one queue line before it can be submitted to Operasi. */ public function verifyLine(Request $request, $id) { $this->abortUnlessPembiayaanOnline(); $kodcaw = $this->kodcaw(); $api_url = config('api.url'); $auth_user = Auth::user(); $res = $this->api() ->asForm() ->post($api_url . '/api/pembiayaan-online/' . $kodcaw . '/lines/' . $id . '/verify', [ 'pc_verified_by' => $auth_user['name'], ]); if (!$res->successful()) { $body = $res->json(); $msg = is_array($body) && isset($body['message']) ? $body['message'] : 'Gagal semak rekod.'; return redirect()->back()->with('error', $msg); } return redirect()->route('pembiayaan.online.pc')->with('message', 'Rekod disahkan. Boleh dipilih untuk hantar ke Operasi.'); } /** * Undo PC verification on a queue line. */ public function unverifyLine(Request $request, $id) { $this->abortUnlessPembiayaanOnline(); $kodcaw = $this->kodcaw(); $api_url = config('api.url'); $res = $this->api() ->asForm() ->post($api_url . '/api/pembiayaan-online/' . $kodcaw . '/lines/' . $id . '/unverify'); if (!$res->successful()) { $body = $res->json(); $msg = is_array($body) && isset($body['message']) ? $body['message'] : 'Gagal batal semakan.'; return redirect()->back()->with('error', $msg); } return redirect()->route('pembiayaan.online.pc')->with('message', 'Semakan dibatalkan. Sila semak semula sebelum hantar.'); } /** * Submit selected lines as a batch to Operasi. */ public function submitBatch(Request $request) { $this->abortUnlessPembiayaanOnline(); $kodcaw = $this->kodcaw(); $api_url = config('api.url'); $auth_user = Auth::user(); $ids = $request->input('ids', []); if (!is_array($ids)) { $ids = array_filter(explode(',', (string) $ids)); } $res = $this->api() ->asForm() ->post($api_url . '/api/pembiayaan-online/' . $kodcaw . '/batches/submit', [ 'ids' => $ids, 'pc_submitted_by' => $auth_user['name'], 'slot' => $request->input('slot'), ]); if (!$res->successful()) { $body = $res->json(); $msg = is_array($body) && isset($body['message']) ? $body['message'] : 'Gagal hantar batch.'; return redirect()->back()->with('error', $msg); } $payload = $res->json(); $batchNo = is_array($payload) ? ($payload['batch']['batch_no'] ?? '') : ''; $appended = is_array($payload) && !empty($payload['appended']); $message = $appended ? ('Rekod ditambah ke batch' . ($batchNo ? ' ' . $batchNo : '') . '.') : ('Batch dihantar ke Operasi' . ($batchNo ? ': ' . $batchNo : '.')); return redirect()->route('pembiayaan.online.pc')->with('message', $message); } /** * Batch detail. */ public function showBatch($batchId) { $kodcaw = $this->kodcaw(); $api_url = config('api.url'); $cawangan_info = $this->cawanganInfo(); $this->abortUnlessPembiayaanOnline($cawangan_info); $detail = $this->api() ->get($api_url . '/api/pembiayaan-online/' . $kodcaw . '/batches/' . $batchId) ->json(); if (!is_array($detail) || empty($detail['batch'])) { return redirect()->route('pembiayaan.online.pc')->with('error', 'Batch tidak dijumpai.'); } return view('pembiayaan_online.pc.batch', [ 'batch' => $detail['batch'], 'lines' => $detail['lines'] ?? [], 'kodcaw' => $kodcaw, 'api_url' => $api_url, 'cawangan_info' => $cawangan_info, ]); } /** * PC: senarai PAID untuk tally + blast. */ public function paidIndex(Request $request) { $auth_user = Auth::user(); $kodcaw = $this->kodcaw(); $api_url = config('api.url'); $blastFilter = $request->input('blast_sent', '0'); $cawangan_info = $this->cawanganInfo(); $this->abortUnlessPembiayaanOnline($cawangan_info); $query = []; if ($blastFilter !== 'all' && $blastFilter !== '') { $query['blast_sent'] = $blastFilter; } $paid = $this->api() ->get($api_url . '/api/pembiayaan-online/' . $kodcaw . '/paid', $query) ->json(); $lines = $paid['data'] ?? []; if (!is_array($lines)) { $lines = []; } $bilangan = $paid['bilangan'] ?? count($lines); $jumlah = $paid['jumlah'] ?? 0; return view('pembiayaan_online.pc.paid', compact( 'auth_user', 'kodcaw', 'api_url', 'cawangan_info', 'lines', 'bilangan', 'jumlah', 'blastFilter' )); } /** * PC: view/download bukti bayaran inline. */ public function viewBukti($id) { $this->abortUnlessPembiayaanOnline(); $kodcaw = $this->kodcaw(); $res = $this->api() ->get(config('api.url') . '/api/pembiayaan-online/' . $kodcaw . '/lines/' . $id . '/bukti'); if (!$res->successful()) { $body = $res->json(); $msg = is_array($body) && isset($body['message']) ? $body['message'] : 'Bukti tidak dijumpai.'; return redirect()->back()->with('error', $msg); } $payload = $res->json(); $bukti = $payload['bukti'] ?? null; if (!$bukti || empty($bukti['data'])) { return redirect()->back()->with('error', 'Fail bukti kosong.'); } return $this->streamBase64File($bukti, 'bukti-' . $id); } /** * PC: view uploaded customer bank slip. */ public function viewBuktiAkaun($id) { $this->abortUnlessPembiayaanOnline(); $kodcaw = $this->kodcaw(); $res = $this->api() ->get(config('api.url') . '/api/pembiayaan-online/' . $kodcaw . '/lines/' . $id . '/bukti-akaun'); if (!$res->successful()) { $body = $res->json(); $msg = is_array($body) && isset($body['message']) ? $body['message'] : 'Slip bank tidak dijumpai.'; return redirect()->back()->with('error', $msg); } $payload = $res->json(); $file = $payload['file'] ?? null; if (!$file || empty($file['data'])) { return redirect()->back()->with('error', 'Fail slip bank kosong.'); } return $this->streamBase64File($file, 'slip-bank-' . $id); } /** * PC: view uploaded Borang Pengesahan Penerimaan Pembiayaan Secara Atas Talian. */ public function viewBorang($id) { $this->abortUnlessPembiayaanOnline(); return $this->streamLineFile( $this->kodcaw(), $id, '/borang', 'Borang pengesahan tidak dijumpai.', 'Fail borang pengesahan kosong.', 'borang-pengesahan-' . $id ); } /** * PC: view SAG for a queue line (does not mark SAG as printed). */ public function viewSag($id) { $this->abortUnlessPembiayaanOnline(); return $this->renderLineSag($this->kodcaw(), $id); } /** * Operasi: view uploaded customer bank slip. */ public function operasiViewBuktiAkaun($kodcaw, $id) { $res = $this->api() ->get(config('api.url') . '/api/pembiayaan-online/' . $kodcaw . '/lines/' . $id . '/bukti-akaun'); if (!$res->successful()) { $body = $res->json(); $msg = is_array($body) && isset($body['message']) ? $body['message'] : 'Slip bank tidak dijumpai.'; return redirect()->back()->with('error', $msg); } $payload = $res->json(); $file = $payload['file'] ?? null; if (!$file || empty($file['data'])) { return redirect()->back()->with('error', 'Fail slip bank kosong.'); } return $this->streamBase64File($file, 'slip-bank-' . $id); } /** * Operasi: view uploaded Borang Pengesahan Penerimaan Pembiayaan Secara Atas Talian. */ public function operasiViewBorang($kodcaw, $id) { return $this->streamLineFile( $kodcaw, $id, '/borang', 'Borang pengesahan tidak dijumpai.', 'Fail borang pengesahan kosong.', 'borang-pengesahan-' . $id ); } /** * Operasi: view SAG for a batch line. */ public function operasiViewSag($kodcaw, $id) { return $this->renderLineSag($kodcaw, $id); } protected function streamLineFile($kodcaw, $id, $path, $missingMessage, $emptyMessage, $fallbackName) { $res = $this->api() ->get(config('api.url') . '/api/pembiayaan-online/' . $kodcaw . '/lines/' . $id . $path); if (!$res->successful()) { $body = $res->json(); $msg = is_array($body) && isset($body['message']) ? $body['message'] : $missingMessage; return redirect()->back()->with('error', $msg); } $payload = $res->json(); $file = $payload['file'] ?? null; if (!$file || empty($file['data'])) { return redirect()->back()->with('error', $emptyMessage); } return $this->streamBase64File($file, $fallbackName); } protected function streamBase64File(array $file, $fallbackName) { $raw = $file['data']; if (strpos($raw, 'base64,') !== false) { $raw = substr($raw, strpos($raw, 'base64,') + 7); } $binary = base64_decode($raw, true); if ($binary === false) { $binary = base64_decode($file['data']); } $type = $file['type'] ?? 'application/octet-stream'; $filename = $file['filename'] ?? $fallbackName; return response($binary, 200, [ 'Content-Type' => $type, 'Content-Disposition' => 'inline; filename="' . $filename . '"', ]); } protected function renderLineSag($kodcaw, $id) { $api_url = config('api.url'); $res = $this->api()->get($api_url . '/api/pembiayaan-online/' . $kodcaw . '/lines/' . $id); if (!$res->successful()) { $body = $res->json(); $msg = is_array($body) && isset($body['message']) ? $body['message'] : 'Rekod tidak dijumpai.'; return redirect()->back()->with('error', $msg); } $line = $res->json(); $norujukan = is_array($line) ? trim((string) ($line['norujukan'] ?? '')) : ''; if ($norujukan === '') { return redirect()->back()->with('error', 'No. SAG tidak dijumpai.'); } return $this->renderSagByNorujukan($kodcaw, $norujukan); } protected function renderSagByNorujukan($kodcaw, $norujukan) { $api_url = config('api.url'); $auth_user = Auth::user(); $auth_username = $auth_user['name'] ?? ''; $gadai = $this->api() ->get($api_url . '/api/gadai/norujukan/' . $kodcaw . '/' . $norujukan) ->json(); $gadai_detail = is_array($gadai) ? ($gadai[0] ?? null) : null; if (!is_array($gadai_detail) || empty($gadai_detail['norujukan'])) { return redirect()->back()->with('error', 'SAG tidak dijumpai.'); } $kakitangan = \App\User::where([['role_harian', 'khazanah'], ['cawangan', $kodcaw]])->first(); $customer = $this->api() ->get($api_url . '/api/customers/' . ($gadai_detail['nokp'] ?? '')) ->json(); $customer_detail = is_array($customer) ? $customer : []; $marhun = $this->api() ->get($api_url . '/api/marhun/' . $norujukan) ->json(); $marhun_detail = is_array($marhun) ? ($marhun[0] ?? []) : []; if (!is_array($marhun_detail)) { $marhun_detail = []; } $mutu = $this->api() ->get($api_url . '/api/marhun/mutu/sag', [ 'kodcaw' => $kodcaw, 'norujukan' => $norujukan, ]) ->json(); $cawangan = $this->api() ->get($api_url . '/api/cawangan/detail/' . $kodcaw) ->json(); $cawangan_detail = is_array($cawangan) ? ($cawangan[0] ?? []) : []; $kadar_ujrah = $this->api() ->get($api_url . '/api/kadarujrah/by_group_id/', [ 'kodcaw' => $cawangan_detail['kodcaw'] ?? $kodcaw, ]) ->json(); $pembayaran_segera = 'https://pay.arrahn.com.my'; // Prefer per-Terima BSAS order; fall back to legacy daily KomuditiPurchase. $bsasSag = $this->api() ->get($api_url . '/api/komuditi/bsas/order/norujukan/' . urlencode($norujukan)); if ($bsasSag->successful() && is_array($bsasSag->json())) { $bsasPayload = $bsasSag->json(); $komoditi = $bsasPayload['komoditi'] ?? []; $product = $bsasPayload['product'] ?? []; $kuantiti = (float) ($bsasPayload['kuantiti'] ?? 0); } else { $komoditi = $this->api()->get($api_url . '/api/komuditi/check')->json(); $product = $this->api() ->get($api_url . '/api/komuditi/description', [ 'kodproduk' => is_array($komoditi) ? ($komoditi['kodproduct'] ?? '') : '', ]) ->json(); $nilai = is_array($komoditi) ? (float) ($komoditi['nilai'] ?? 0) : 0; $kuantiti = $nilai > 0 ? ((float) ($gadai_detail['pinjaman'] ?? 0) / $nilai) : 0; } if (!is_array($komoditi)) { $komoditi = []; } if (!is_array($product)) { $product = [ 'kodproduk' => $komoditi['kodproduct'] ?? '', 'description' => '', 'unit' => 'TAN', ]; } if (($gadai_detail['tagbaru'] ?? 0) == 1) { $cawangan_info = $this->api() ->get($api_url . '/api/cawangan/' . $kodcaw)->json(); $pembiayaanOnlineEnabled = !empty(($cawangan_info['features'] ?? [])['pembiayaan_online']); return view('print.sag_ujrah_3', compact( 'komoditi', 'product', 'kuantiti', 'gadai_detail', 'customer_detail', 'marhun_detail', 'cawangan_detail', 'api_url', 'pembayaran_segera', 'auth_username', 'mutu', 'kadar_ujrah', 'kakitangan' ))->with('pembiayaanOnlineEnabled', $pembiayaanOnlineEnabled); } return view('print.sag', compact( 'gadai_detail', 'customer_detail', 'marhun_detail', 'cawangan_detail', 'api_url', 'pembayaran_segera', 'auth_username', 'mutu', 'kakitangan' )); } /** * PC: tanda rekod PAID sebagai selesai semak / blast. */ public function markBlast(Request $request) { $this->abortUnlessPembiayaanOnline(); $kodcaw = $this->kodcaw(); $ids = $request->input('ids', []); if (!is_array($ids)) { $ids = array_filter(explode(',', (string) $ids)); } if (count($ids) === 0) { return redirect()->back()->with('error', 'Sila pilih sekurang-kurangnya satu rekod.'); } $res = $this->api() ->asForm() ->post(config('api.url') . '/api/pembiayaan-online/' . $kodcaw . '/paid/blast', [ 'ids' => $ids, 'blast_by' => Auth::user()['name'], ]); if (!$res->successful()) { $body = $res->json(); $msg = is_array($body) && isset($body['message']) ? $body['message'] : 'Gagal tanda selesai.'; return redirect()->back()->with('error', $msg); } $n = $res->json()['dikemaskini'] ?? count($ids); return redirect() ->route('pembiayaan.online.pc.paid') ->with('message', $n . ' rekod ditanda selesai. WhatsApp notis dihantar (nombor ujian jika bukan production).'); } /** * Operasi: senarai batch semua cawangan. */ public function operasiIndex(Request $request) { $auth_user = Auth::user(); $api_url = config('api.url'); $status = $request->input('status', 'SUBMITTED'); $tarikh = $request->input('tarikh'); $query = ['status' => $status]; if ($tarikh) { $query['tarikh'] = $tarikh; } $res = $this->api() ->get($api_url . '/api/pembiayaan-online/operasi/batches', $query) ->json(); $batches = $res['data'] ?? []; if (!is_array($batches)) { $batches = []; } return view('pembiayaan_online.operasi.senarai', compact( 'auth_user', 'api_url', 'batches', 'status', 'tarikh' )); } /** * Operasi: detail batch + lines. */ public function operasiShowBatch($kodcaw, $batchId) { $auth_user = Auth::user(); $api_url = config('api.url'); $cawangan_info = $this->api() ->get($api_url . '/api/cawangan/' . $kodcaw) ->json(); $detail = $this->api() ->get($api_url . '/api/pembiayaan-online/operasi/' . $kodcaw . '/batches/' . $batchId) ->json(); if (!is_array($detail) || empty($detail['batch'])) { return redirect() ->route('pembiayaan.online.operasi') ->with('error', 'Batch tidak dijumpai.'); } return view('pembiayaan_online.operasi.batch', [ 'auth_user' => $auth_user, 'batch' => $detail['batch'], 'lines' => $detail['lines'] ?? [], 'kodcaw' => $kodcaw, 'api_url' => $api_url, 'cawangan_info' => $cawangan_info, ]); } /** * Operasi: printable batch kelulusan form. */ public function operasiPrintBatch($kodcaw, $batchId) { $api_url = config('api.url'); $detail = $this->api() ->get($api_url . '/api/pembiayaan-online/operasi/' . $kodcaw . '/batches/' . $batchId) ->json(); if (!is_array($detail) || empty($detail['batch'])) { return redirect() ->route('pembiayaan.online.operasi') ->with('error', 'Batch tidak dijumpai.'); } $batch = $detail['batch']; $lines = $detail['lines'] ?? []; if (!is_array($lines)) { $lines = []; } $cawanganResp = $this->api() ->get($api_url . '/api/cawangan/detail/' . $kodcaw) ->json(); $cawangan_detail = (is_array($cawanganResp) && isset($cawanganResp[0])) ? $cawanganResp[0] : (is_array($cawanganResp) ? $cawanganResp : []); $cawangan = $cawangan_detail['namacaw'] ?? $cawangan_detail['details_caw'] ?? $cawangan_detail['nama'] ?? $kodcaw; $alamat1 = $cawangan_detail['alamat1'] ?? ''; $alamat2 = $cawangan_detail['alamat2'] ?? ''; $notel = $cawangan_detail['notel'] ?? ''; $batchNo = $batch['batch_no'] ?? '-'; $slot = $batch['slot'] ?? '-'; $status = $batch['status'] ?? '-'; $dihantarOleh = $batch['pc_submitted_by'] ?? '-'; $jumlah = (float) ($batch['jumlah_keseluruhan'] ?? 0); if ($jumlah <= 0 && count($lines) > 0) { $jumlah = (float) collect($lines)->sum(function ($line) { return (float) ($line['pinjaman'] ?? 0); }); } $opsBy = Auth::user()['name'] ?? ''; foreach ($lines as $line) { if (!empty($line['ops_by'])) { $opsBy = $line['ops_by']; break; } } $tarikh = $this->formatPrintDate($batch['tarikh'] ?? null); $dihantarPada = $this->formatPrintDate($batch['submitted_at'] ?? null, true); return view('print.borang_batch_pembiayaan_online', compact( 'cawangan', 'kodcaw', 'alamat1', 'alamat2', 'notel', 'tarikh', 'batchNo', 'slot', 'status', 'dihantarOleh', 'dihantarPada', 'opsBy', 'lines', 'jumlah' )); } protected function formatPrintDate($value, $withTime = false) { if ($value === null || $value === '' || $value === '-') { return '-'; } try { return Carbon::parse($value)->format($withTime ? 'd/m/Y H:i' : 'd/m/Y'); } catch (\Exception $e) { return (string) $value; } } public function operasiApproveLine(Request $request, $kodcaw, $id) { $res = $this->api() ->asForm() ->post(config('api.url') . '/api/pembiayaan-online/operasi/' . $kodcaw . '/lines/' . $id . '/approve', [ 'ops_by' => Auth::user()['name'], ]); return $this->opsRedirect($res, $kodcaw, $request->input('batch_id'), 'Rekod diluluskan.'); } public function operasiRejectLine(Request $request, $kodcaw, $id) { $reason = trim((string) $request->input('reject_reason', '')); if ($reason === '') { return redirect()->back()->with('error', 'Sebab tolak diperlukan.'); } $res = $this->api() ->asForm() ->post(config('api.url') . '/api/pembiayaan-online/operasi/' . $kodcaw . '/lines/' . $id . '/reject', [ 'ops_by' => Auth::user()['name'], 'reject_reason' => $reason, ]); return $this->opsRedirect($res, $kodcaw, $request->input('batch_id'), 'Rekod ditolak. Dikembalikan ke PC.'); } public function operasiApproveBatch(Request $request, $kodcaw, $batchId) { $res = $this->api() ->asForm() ->post(config('api.url') . '/api/pembiayaan-online/operasi/' . $kodcaw . '/batches/' . $batchId . '/approve', [ 'ops_by' => Auth::user()['name'], ]); return $this->opsRedirect($res, $kodcaw, $batchId, 'Semua rekod menunggu dalam batch diluluskan.'); } public function operasiMarkPaid(Request $request, $kodcaw, $id) { $payload = $this->buildPaidPayload($request); if (isset($payload['error'])) { return redirect()->back()->with('error', $payload['error']); } $res = $this->api() ->asForm() ->post(config('api.url') . '/api/pembiayaan-online/operasi/' . $kodcaw . '/lines/' . $id . '/paid', $payload); return $this->opsRedirect($res, $kodcaw, $request->input('batch_id'), 'Rekod ditanda PAID.'); } public function operasiMarkPaidBatch(Request $request, $kodcaw, $batchId) { $payload = $this->buildPaidPayload($request); if (isset($payload['error'])) { return redirect()->back()->with('error', $payload['error']); } $res = $this->api() ->asForm() ->post(config('api.url') . '/api/pembiayaan-online/operasi/' . $kodcaw . '/batches/' . $batchId . '/paid', $payload); return $this->opsRedirect($res, $kodcaw, $batchId, 'Rekod APPROVED dalam batch ditanda PAID.'); } /** * @return array */ protected function buildPaidPayload(Request $request) { $noRujukan = trim((string) $request->input('no_rujukan_bayaran', '')); if ($noRujukan === '') { return ['error' => 'No. rujukan bayaran diperlukan.']; } if (!$request->hasFile('bukti')) { return ['error' => 'Fail bukti bayaran diperlukan.']; } $file = $request->file('bukti'); if (!$file->isValid()) { return ['error' => 'Fail bukti tidak sah.']; } $raw = base64_encode(file_get_contents($file->getRealPath())); $mime = $file->getMimeType() ?: 'application/octet-stream'; return [ 'ops_by' => Auth::user()['name'], 'no_rujukan_bayaran' => $noRujukan, 'tarikh_bayaran' => $request->input('tarikh_bayaran') ?: date('Y-m-d'), 'bukti_data' => $raw, 'bukti_filename' => $file->getClientOriginalName(), 'bukti_type' => $mime, ]; } protected function opsRedirect($res, $kodcaw, $batchId, $successMsg) { $batchId = $batchId ?: null; if (!$res->successful()) { $body = $res->json(); $msg = is_array($body) && isset($body['message']) ? $body['message'] : 'Tindakan gagal.'; return redirect()->back()->with('error', $msg); } if ($batchId) { return redirect() ->route('pembiayaan.online.operasi.batch', ['kodcaw' => $kodcaw, 'batchId' => $batchId]) ->with('message', $successMsg); } return redirect()->route('pembiayaan.online.operasi')->with('message', $successMsg); } /** * PC: modal online float + top-up request form/history. */ public function topupIndex(Request $request) { $auth_user = Auth::user(); $kodcaw = $this->kodcaw(); $api_url = config('api.url'); $status = $request->input('status', ''); $cawangan_info = $this->cawanganInfo(); $this->abortUnlessPembiayaanOnline($cawangan_info); $modal = $this->api() ->get($api_url . '/api/modal-online/' . $kodcaw) ->json(); $query = []; if ($status !== '' && $status !== 'all') { $query['status'] = $status; } $reqRes = $this->api() ->get($api_url . '/api/modal-online/' . $kodcaw . '/topup/requests', $query) ->json(); $requests = $reqRes['data'] ?? []; if (!is_array($requests)) { $requests = []; } $pendingRes = $this->api() ->get($api_url . '/api/modal-online/' . $kodcaw . '/topup/requests', ['status' => 'PENDING']) ->json(); $pendingList = $pendingRes['data'] ?? []; $hasPending = is_array($pendingList) && count($pendingList) > 0; $isMissing = !empty($modal['missing']); $pendingRequest = $modal['pending_request'] ?? null; if (!$pendingRequest && $hasPending) { $pendingRequest = $pendingList[0]; } return view('pembiayaan_online.pc.topup', compact( 'auth_user', 'kodcaw', 'api_url', 'cawangan_info', 'modal', 'requests', 'status', 'hasPending', 'isMissing', 'pendingRequest' )); } /** * PC: submit top-up request. */ public function topupRequest(Request $request) { $this->abortUnlessPembiayaanOnline(); $kodcaw = $this->kodcaw(); $amaun = (float) $request->input('amaun', 0); if ($amaun <= 0) { return redirect()->back()->with('error', 'Amaun mesti lebih daripada 0.'); } $res = $this->api() ->asForm() ->post(config('api.url') . '/api/modal-online/' . $kodcaw . '/topup/request', [ 'amaun' => $amaun, 'dimohon_oleh' => Auth::user()['name'], 'nota' => $request->input('nota'), ]); if (!$res->successful()) { $body = $res->json(); $msg = is_array($body) && isset($body['message']) ? $body['message'] : 'Gagal hantar permohonan.'; return redirect()->back()->with('error', $msg)->withInput(); } return redirect() ->route('pembiayaan.online.pc.topup') ->with('message', 'Permohonan modal online dihantar. Menunggu kelulusan Operasi.'); } /** * Operasi: senarai permohonan tambah modal online. */ public function operasiTopupIndex(Request $request) { $auth_user = Auth::user(); $api_url = config('api.url'); $status = $request->input('status', 'PENDING'); $tarikh = $request->input('tarikh'); $query = []; if ($status === 'all') { $query['status'] = 'all'; } elseif ($status !== '' && $status !== null) { $query['status'] = $status; } else { $query['status'] = 'PENDING'; } if ($tarikh) { $query['tarikh'] = $tarikh; } $res = $this->api() ->get($api_url . '/api/modal-online/operasi/topup/requests', $query) ->json(); $requests = $res['data'] ?? []; if (!is_array($requests)) { $requests = []; } return view('pembiayaan_online.operasi.topup', compact( 'auth_user', 'api_url', 'requests', 'status', 'tarikh' )); } public function operasiTopupApprove(Request $request, $id) { $amaun = $request->input('amaun'); if ($amaun !== null && $amaun !== '' && (float) $amaun <= 0) { return redirect()->back()->with('error', 'Amaun diluluskan mesti lebih daripada 0.'); } $res = $this->api() ->asForm() ->post(config('api.url') . '/api/modal-online/operasi/topup/' . $id . '/approve', [ 'diluluskan_oleh' => Auth::user()['name'], 'nota' => $request->input('nota'), 'amaun' => $amaun, ]); if (!$res->successful()) { $body = $res->json(); $msg = is_array($body) && isset($body['message']) ? $body['message'] : 'Gagal lulus permohonan.'; return redirect()->back()->with('error', $msg); } $amaunLulus = $res->json()['request']['amaun'] ?? $amaun; return redirect() ->route('pembiayaan.online.operasi.topup') ->with('message', 'Permohonan diluluskan RM ' . number_format((float) $amaunLulus, 2) . '. Modal online cawangan dikemas kini.'); } public function operasiTopupReject(Request $request, $id) { $res = $this->api() ->asForm() ->post(config('api.url') . '/api/modal-online/operasi/topup/' . $id . '/reject', [ 'diluluskan_oleh' => Auth::user()['name'], 'nota' => $request->input('nota'), ]); if (!$res->successful()) { $body = $res->json(); $msg = is_array($body) && isset($body['message']) ? $body['message'] : 'Gagal tolak permohonan.'; return redirect()->back()->with('error', $msg); } return redirect() ->route('pembiayaan.online.operasi.topup') ->with('message', 'Permohonan ditolak.'); } /** * PC: EOD close branch modal online. */ public function closeModal(Request $request) { $this->abortUnlessPembiayaanOnline(); $kodcaw = $this->kodcaw(); $res = $this->api() ->asForm() ->post(config('api.url') . '/api/modal-online/' . $kodcaw . '/close', [ 'ditutup_oleh' => Auth::user()['name'], ]); if (!$res->successful()) { $body = $res->json(); $msg = is_array($body) && isset($body['message']) ? $body['message'] : 'Gagal tutup modal online.'; return redirect()->back()->with('error', $msg); } $dikembalikan = $res->json()['dikembalikan'] ?? 0; return redirect() ->route('pembiayaan.online.pc.topup') ->with('message', 'Modal online ditutup. Baki dikembalikan RM ' . number_format((float) $dikembalikan, 2) . '.'); } /** * Operasi: EOD dashboard semua cawangan. */ public function operasiEodIndex(Request $request) { $auth_user = Auth::user(); $api_url = config('api.url'); $tarikh = $request->input('tarikh') ?: date('Y-m-d'); $res = $this->api() ->get($api_url . '/api/modal-online/operasi/eod', [ 'zone' => 'all', 'tarikh' => $tarikh, ]) ->json(); $rows = $res['data'] ?? []; if (!is_array($rows)) { $rows = []; } $summary = [ 'open' => $res['open'] ?? 0, 'closed' => $res['closed'] ?? 0, 'missing' => $res['missing'] ?? 0, 'bilangan' => $res['bilangan'] ?? count($rows), ]; return view('pembiayaan_online.operasi.eod', compact( 'auth_user', 'api_url', 'rows', 'tarikh', 'summary' )); } public function operasiCloseModal(Request $request, $kodcaw) { $res = $this->api() ->asForm() ->post(config('api.url') . '/api/modal-online/' . $kodcaw . '/close', [ 'ditutup_oleh' => Auth::user()['name'], ]); if (!$res->successful()) { $body = $res->json(); $msg = is_array($body) && isset($body['message']) ? $body['message'] : 'Gagal tutup modal online.'; return redirect()->back()->with('error', $msg); } return redirect() ->route('pembiayaan.online.operasi.eod') ->with('message', 'Modal online ' . $kodcaw . ' ditutup.'); } public function operasiCloseAllModal(Request $request) { $res = $this->api() ->asForm() ->post(config('api.url') . '/api/modal-online/operasi/eod/close-all', [ 'ditutup_oleh' => Auth::user()['name'], 'zone' => 'all', ]); if (!$res->successful()) { $body = $res->json(); $msg = is_array($body) && isset($body['message']) ? $body['message'] : 'Gagal tutup semua modal online.'; return redirect()->back()->with('error', $msg); } $closed = $res->json()['closed'] ?? 0; $skipped = $res->json()['skipped'] ?? 0; return redirect() ->route('pembiayaan.online.operasi.eod') ->with('message', 'EOD selesai: ' . $closed . ' ditutup, ' . $skipped . ' sudah ditutup.'); } }