middleware('auth'); } protected function api() { return Http::withOptions(['verify' => config('app.api_verify')]); } protected function kodcaw() { return Auth::user()->cawangan; } /** * 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->api() ->get($api_url . '/api/cawangan/' . $kodcaw) ->json(); $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' )); } /** * Update bank/docs for a queue line. */ public function updateLine(Request $request, $id) { $kodcaw = $this->kodcaw(); $api_url = config('api.url'); $payload = [ 'kodbank' => $request->input('kodbank'), 'noakaun' => $request->input('noakaun'), 'nama_bank' => $request->input('nama_bank'), 'nama_akaun' => $request->input('nama_akaun'), 'nota' => $request->input('nota'), 'update_customer_bank' => true, ]; $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.'); } /** * Submit selected lines as a batch to Operasi. */ public function submitBatch(Request $request) { $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); } $batchNo = $res->json()['batch']['batch_no'] ?? ''; return redirect()->route('pembiayaan.online.pc') ->with('message', 'Batch dihantar ke Operasi' . ($batchNo ? ': ' . $batchNo : '.')); } /** * Batch detail. */ public function showBatch($batchId) { $kodcaw = $this->kodcaw(); $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/' . $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->api() ->get($api_url . '/api/cawangan/' . $kodcaw) ->json(); $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) { $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.'); } $raw = $bukti['data']; if (strpos($raw, 'base64,') !== false) { $raw = substr($raw, strpos($raw, 'base64,') + 7); } $binary = base64_decode($raw, true); if ($binary === false) { $binary = base64_decode($bukti['data']); } $type = $bukti['type'] ?? 'application/octet-stream'; $filename = $bukti['filename'] ?? ('bukti-' . $id); return response($binary, 200, [ 'Content-Type' => $type, 'Content-Disposition' => 'inline; filename="' . $filename . '"', ]); } /** * PC: tanda rekod PAID sebagai selesai semak / blast. */ public function markBlast(Request $request) { $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 semak/blast.'); } /** * 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, ]); } 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->api() ->get($api_url . '/api/cawangan/' . $kodcaw) ->json(); $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) { $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) { $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.'); } }