listForBranch(Auth::user()->cawangan); $cawangan_info = ['details_caw' => Auth::user()->cawangan, 'kodcaw' => Auth::user()->cawangan]; return view('shoraka.index', compact('records', 'cawangan_info')); } public function operasiIndex(Request $request) { abort_unless(in_array(Auth::user()->roles, ['operasi', 'pc', 'ppc'], true), 403); $isOperasi = Auth::user()->roles === 'operasi'; $filters = $request->validate([ 'kodcaw' => 'nullable|string|max:30', 'norujukan' => 'nullable|string|max:100', 'state' => 'nullable|in:WAITING,PROCESSING,REVIEW,DONE', 'date_from' => 'nullable|date_format:Y-m-d', 'date_to' => 'nullable|date_format:Y-m-d' . ($request->filled('date_from') ? '|after_or_equal:date_from' : ''), ]); [$records, $branches] = app(ShorakaFinancingService::class)->search( $filters, $isOperasi ? null : Auth::user()->cawangan ); $cawangan_info = ['details_caw' => Auth::user()->cawangan, 'kodcaw' => Auth::user()->cawangan]; return view('shoraka.operasi', compact('records', 'branches', 'filters', 'isOperasi', 'cawangan_info')); } public function pcIndex(Request $request) { abort_unless(in_array(Auth::user()->roles, ['pc', 'ppc'], true), 403); return $this->operasiIndex($request); } public function show(string $norujukan, ShorakaFinancingService $service) { $record = $service->local($norujukan); $data = null; $error = null; try { $data = $service->api()->get($service->url($norujukan))->throw()->json()['data']; } catch (Throwable $exception) { $error = 'Maklumat Shoraka tidak dapat diambil. Bayaran dan SAG masih ditahan.'; } $cawangan_info = ['details_caw' => Auth::user()->cawangan, 'kodcaw' => Auth::user()->cawangan]; return view('shoraka.show', compact('norujukan', 'record', 'data', 'error', 'cawangan_info')); } public function poll(Request $request, string $norujukan, ShorakaFinancingService $service) { $record = $service->local($norujukan); abort_unless($record && ($record['teller'] ?? null) === Auth::user()->name && ($record['action'] ?? null) === 'gadai', 403); if (($record['state'] ?? null) === 'DONE') { return response()->json(['state' => 'done', 'redirect' => route('customer_info')]); } abort_unless(!empty($record['context']['temporary_gadai']), 409); if (($record['state'] ?? null) === 'REVIEW') { return response()->json(['state' => 'failed', 'message' => 'Penyelesaian memerlukan semakan operasi. Jangan ulang bayaran.']); } if (($record['state'] ?? null) === 'PROCESSING') { return response()->json(['state' => 'pending']); } try { $data = $service->api()->get($service->url($norujukan))->throw()->json()['data']; $order = $data['order'] ?? null; if ($order && !empty($order['order_id']) && ($order['status'] ?? '') !== 'Cancelled') { $service->api()->post($service->url($norujukan, '/refresh'))->throw(); $data = $service->api()->get($service->url($norujukan))->throw()->json()['data']; $order = $data['order'] ?? null; } $status = $order['status'] ?? null; if (in_array($status, ['Cancelled', 'FAILED', 'UNKNOWN'], true) || !$order) { return response()->json(['state' => 'failed', 'message' => !empty($data['simulation']) ? 'Simulasi gagal berjaya diuji. Tiada gadaian atau marhun disimpan dan tiada bayaran diproses.' : 'Transaksi gagal atau hasil penghantaran belum dapat dipastikan. Hubungi operasi; jangan hantar semula.']); } if (!empty($data['can_complete'])) { if ($request->input('progress') === 'check') { return response()->json(['state' => 'ready']); } $result = $service->finish($request, $norujukan); return response()->json(['state' => 'done', 'redirect' => $result->getTargetUrl()]); } if ($status === 'Completed') { return response()->json(['state' => 'failed', 'message' => 'Komoditi selesai tetapi maklumat gadaian belum dapat disahkan. Hubungi operasi.']); } return response()->json(['state' => 'pending']); } catch (Throwable $exception) { $fresh = $service->local($norujukan); if ($fresh && ($fresh['state'] ?? null) === 'REVIEW') { return response()->json(['state' => 'failed', 'message' => 'Penyelesaian memerlukan semakan operasi. Jangan ulang bayaran.']); } if ($exception instanceof \Symfony\Component\HttpKernel\Exception\HttpExceptionInterface && $exception->getStatusCode() >= 400 && $exception->getStatusCode() < 500) { return response()->json(['state' => 'failed', 'message' => $exception->getMessage()]); } if ($exception instanceof \Illuminate\Http\Client\RequestException && in_array($exception->response->status(), [404, 409], true)) { return response()->json(['state' => 'failed', 'message' => 'Maklumat transaksi tidak dapat disahkan. Hubungi operasi; jangan hantar semula.']); } // Connection loss is not proof of a failed commodity trade. return response()->json(['state' => 'pending']); } } public function submit(string $norujukan, ShorakaFinancingService $service) { $record = $service->local($norujukan); abort_unless($record && ($record['teller'] ?? null) === Auth::user()->name, 403); $response = $service->api()->post($service->url($norujukan, '/submit')); return redirect()->route('shoraka.show', compact('norujukan')) ->with('shoraka_message', $response->successful() ? 'Order dihantar. Semak status sebelum menyelesaikan pembiayaan.' : (($response->json()['message'] ?? null) ?: 'Order belum berjaya. Semak status sebelum cuba semula.')); } public function refresh(string $norujukan, ShorakaFinancingService $service) { try { $response = $service->api()->post($service->url($norujukan, '/refresh')); $message = $response->successful() ? 'Status dikemas kini.' : (($response->json()['message'] ?? null) ?: 'Status tidak dapat dikemas kini.'); } catch (Throwable $exception) { $message = 'Sambungan terganggu. Jangan hantar order semula.'; } return redirect()->route('shoraka.show', compact('norujukan'))->with('shoraka_message', $message); } public function finish(Request $request, string $norujukan, ShorakaFinancingService $service) { return $service->finish($request, $norujukan); } public function certificate(string $norujukan, ShorakaFinancingService $service) { $response = $service->api()->get($service->url($norujukan, '/certificate'))->throw(); abort_unless(substr($response->body(), 0, 5) === '%PDF-', 502); return response($response->body(), 200, [ 'Content-Type' => 'application/pdf', 'Content-Disposition' => 'attachment; filename="sijil-shoraka.pdf"', 'Cache-Control' => 'private, no-store', ]); } }