DONE: drop table shoraka financings, and improve UI checking on shoraka order status
This commit is contained in:
@@ -286,8 +286,12 @@ class GadaianController extends Controller
|
||||
{
|
||||
$auth_user = Auth::user();
|
||||
$shoraka = app(\App\Services\ShorakaFinancingService::class);
|
||||
if ($shoraka->local($request->norujukan_gadai)) {
|
||||
return $shoraka->pendingResponse($request, $request->norujukan_gadai);
|
||||
if ($shoraka->hasOrder($request->norujukan_gadai)) {
|
||||
$message = 'Order Shoraka telah dihantar. Jangan hantar semula.';
|
||||
if ($request->ajax() || $request->expectsJson()) {
|
||||
abort(409, $message);
|
||||
}
|
||||
return redirect()->back()->with('error', $message);
|
||||
}
|
||||
$shoraka->ensureReady();
|
||||
$caraTerima = strtoupper((string) $request->input('cara_terima', 'TUNAI'));
|
||||
@@ -398,9 +402,9 @@ class GadaianController extends Controller
|
||||
$request->session()->forget('norujukan');
|
||||
$request->session()->forget('borang_online_printed');
|
||||
$request->session()->flash('activate_tab', 'SAG_baru');
|
||||
if ($caraTerima === 'ONLINE') {
|
||||
$request->session()->flash('message', 'Gadai berjaya. Pembiayaan online menunggu semakan PC.');
|
||||
}
|
||||
$request->session()->flash('message', $caraTerima === 'ONLINE'
|
||||
? 'Gadai berjaya. Pembiayaan online menunggu semakan PC.'
|
||||
: 'Pembiayaan berjaya dihantar.');
|
||||
|
||||
return redirect()->route('customer_info');
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ class ShorakaController extends Controller
|
||||
$filters = $request->validate([
|
||||
'kodcaw' => 'nullable|string|max:30',
|
||||
'norujukan' => 'nullable|string|max:100',
|
||||
'state' => 'nullable|in:WAITING,PROCESSING,REVIEW,DONE',
|
||||
'status' => 'nullable|in:PENDING,Active,Completed,Cancelled,UNKNOWN,FAILED',
|
||||
'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' : ''),
|
||||
]);
|
||||
@@ -43,101 +43,15 @@ class ShorakaController extends Controller
|
||||
|
||||
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'];
|
||||
$data = $service->snapshot($norujukan);
|
||||
} 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);
|
||||
return view('shoraka.show', compact('norujukan', 'data', 'error', 'cawangan_info'));
|
||||
}
|
||||
|
||||
public function certificate(string $norujukan, ShorakaFinancingService $service)
|
||||
|
||||
@@ -10,7 +10,6 @@ use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Throwable;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class ShorakaFinancingService
|
||||
{
|
||||
@@ -32,34 +31,32 @@ class ShorakaFinancingService
|
||||
. rawurlencode(Auth::user()->cawangan) . '/' . rawurlencode($reference) . $suffix;
|
||||
}
|
||||
|
||||
public function financingUrl(string $reference): string
|
||||
public function hasOrder(string $reference): bool
|
||||
{
|
||||
return rtrim(config('api.url'), '/') . '/api/shoraka/financings/'
|
||||
. rawurlencode(Auth::user()->cawangan) . '/' . rawurlencode($reference);
|
||||
$snapshot = $this->snapshot($reference);
|
||||
return !empty($snapshot['order']);
|
||||
}
|
||||
|
||||
public function local(string $reference): ?array
|
||||
public function snapshot(string $reference): array
|
||||
{
|
||||
$response = $this->api()->get($this->financingUrl($reference));
|
||||
$response = $this->api()->get($this->url($reference));
|
||||
if ($response->status() === 404) {
|
||||
return null;
|
||||
return [];
|
||||
}
|
||||
if ($response->serverError()) {
|
||||
abort(503, 'Shoraka belum dikonfigurasi. Pembiayaan baharu ditahan.');
|
||||
}
|
||||
$response->throw();
|
||||
return $this->record($response->json()['data'] ?? []);
|
||||
return $response->json()['data'] ?? [];
|
||||
}
|
||||
|
||||
public function listForBranch(string $branch)
|
||||
{
|
||||
$response = $this->api()->get(rtrim(config('api.url'), '/') . '/api/shoraka/financings', [
|
||||
$response = $this->api()->get(rtrim(config('api.url'), '/') . '/api/shoraka/transactions', [
|
||||
'kodcaw' => $branch,
|
||||
'per_page' => 100,
|
||||
])->throw()->json();
|
||||
return collect($response['data'] ?? [])->map(function ($row) {
|
||||
return $this->record($row);
|
||||
});
|
||||
return collect($response['data'] ?? []);
|
||||
}
|
||||
|
||||
public function search(array $filters, ?string $onlyBranch): array
|
||||
@@ -75,15 +72,13 @@ class ShorakaFinancingService
|
||||
} elseif (!empty($filters['kodcaw'])) {
|
||||
$query['kodcaw'] = $filters['kodcaw'];
|
||||
}
|
||||
foreach (['norujukan', 'state', 'date_from', 'date_to'] as $field) {
|
||||
foreach (['norujukan', 'status', 'date_from', 'date_to'] as $field) {
|
||||
if (!empty($filters[$field])) {
|
||||
$query[$field] = $filters[$field];
|
||||
}
|
||||
}
|
||||
$response = $this->api()->get(rtrim(config('api.url'), '/') . '/api/shoraka/financings', $query)->throw()->json();
|
||||
$items = collect($response['data'] ?? [])->map(function ($row) {
|
||||
return $this->record($row);
|
||||
});
|
||||
$response = $this->api()->get(rtrim(config('api.url'), '/') . '/api/shoraka/transactions', $query)->throw()->json();
|
||||
$items = collect($response['data'] ?? []);
|
||||
$records = new LengthAwarePaginator(
|
||||
$items,
|
||||
(int) ($response['total'] ?? 0),
|
||||
@@ -96,113 +91,91 @@ class ShorakaFinancingService
|
||||
|
||||
public function hold(Request $request, string $reference, string $action, array $extra = [])
|
||||
{
|
||||
$user = Auth::user();
|
||||
$context = array_merge($request->except(['_token', 'password']), $extra);
|
||||
if (!empty($extra['temporary_gadai'])) {
|
||||
$key = 'shoraka:payout:' . hash('sha256', $user->cawangan . ':' . $reference);
|
||||
$temporaryContext = $context;
|
||||
$context = ['temporary_gadai' => true, 'pinjaman' => $extra['pinjaman']];
|
||||
if ($this->hasOrder($reference)) {
|
||||
return $this->failed($request, 'Order Shoraka telah dihantar. Jangan hantar semula.');
|
||||
}
|
||||
$response = $this->api()->post($this->financingUrl($reference), [
|
||||
'teller' => $user->name,
|
||||
'action' => $action,
|
||||
'context' => $context,
|
||||
])->throw()->json();
|
||||
if (!empty($response['created'])) {
|
||||
if (isset($temporaryContext) && !Cache::store('file')->put($key, $temporaryContext, 604800)) {
|
||||
$this->api()->put($this->financingUrl($reference), ['state' => 'REVIEW'])->throw();
|
||||
abort(503, 'Maklumat pembiayaan sementara tidak dapat disimpan.');
|
||||
|
||||
$context = array_merge($request->except(['_token', 'password']), $extra, ['action' => $action]);
|
||||
try {
|
||||
$response = $this->api()->post($this->url($reference, '/submit'), [
|
||||
'teller' => Auth::user()->name,
|
||||
]);
|
||||
} catch (Throwable $exception) {
|
||||
if ($this->hasOrder($reference)) {
|
||||
return $this->failed($request, 'Order Shoraka telah dihantar. Jangan hantar semula.');
|
||||
}
|
||||
return $this->failed($request, 'Order Shoraka tidak berjaya dihantar. Gadaian tidak dihantar.');
|
||||
}
|
||||
|
||||
if (!$response->successful()) {
|
||||
$message = $response->json()['message'] ?? ($this->hasOrder($reference)
|
||||
? 'Order Shoraka telah dihantar. Jangan hantar semula.'
|
||||
: 'Order Shoraka tidak berjaya dihantar. Gadaian tidak dihantar.');
|
||||
return $this->failed($request, $message);
|
||||
}
|
||||
|
||||
$snapshot = $this->waitUntilCompleted($reference);
|
||||
$status = $snapshot['order']['status'] ?? null;
|
||||
if ($status !== 'Completed') {
|
||||
return $this->failed($request, 'Pengesahan Shoraka belum Completed. Jangan hantar semula.');
|
||||
}
|
||||
if (empty($snapshot['can_complete'])
|
||||
|| (array_key_exists('pinjaman', $context)
|
||||
&& number_format((float) $context['pinjaman'], 2, '.', '')
|
||||
!== number_format((float) ($snapshot['order_amount'] ?? 0), 2, '.', ''))) {
|
||||
return $this->failed($request, 'Amaun pembiayaan berubah. Bayaran tidak diproses.');
|
||||
}
|
||||
|
||||
return $this->complete($request, $reference, $context);
|
||||
}
|
||||
|
||||
private function waitUntilCompleted(string $reference): array
|
||||
{
|
||||
set_time_limit(150);
|
||||
$deadline = microtime(true) + 120;
|
||||
$snapshot = [];
|
||||
do {
|
||||
try {
|
||||
$this->api()->post($this->url($reference, '/submit'));
|
||||
$this->api()->post($this->url($reference, '/refresh'))->throw();
|
||||
$snapshot = $this->snapshot($reference);
|
||||
} catch (Throwable $exception) {
|
||||
// The persisted workflow remains available after timeouts.
|
||||
$snapshot = [];
|
||||
}
|
||||
}
|
||||
return $this->pendingResponse($request, $reference);
|
||||
$status = $snapshot['order']['status'] ?? null;
|
||||
if ($status === 'Completed' || in_array($status, ['Cancelled', 'FAILED', 'UNKNOWN'], true)) {
|
||||
return $snapshot;
|
||||
}
|
||||
if (microtime(true) >= $deadline) {
|
||||
return $snapshot;
|
||||
}
|
||||
sleep(2);
|
||||
} while (true);
|
||||
}
|
||||
|
||||
public function pendingResponse(Request $request, string $reference)
|
||||
private function complete(Request $request, string $reference, array $context)
|
||||
{
|
||||
$url = route('shoraka.show', ['norujukan' => $reference]);
|
||||
if ($request->ajax() || $request->expectsJson()) {
|
||||
return response()->json(['shoraka_pending' => true, 'redirect' => $url], 202);
|
||||
}
|
||||
return redirect($url);
|
||||
}
|
||||
|
||||
public function finish(Request $request, string $reference)
|
||||
{
|
||||
$record = $this->local($reference);
|
||||
abort_unless($record && ($record['teller'] ?? null) === Auth::user()->name, 403,
|
||||
'Pembiayaan perlu diselesaikan oleh teller asal.');
|
||||
if (($record['state'] ?? null) === 'DONE') {
|
||||
return redirect()->route('shoraka.show', ['norujukan' => $reference]);
|
||||
}
|
||||
abort_unless(($record['state'] ?? null) === 'WAITING', 409,
|
||||
'Penyelesaian telah diproses atau memerlukan semakan operasi. Jangan ulang bayaran.');
|
||||
|
||||
$this->api()->post($this->url($reference, '/refresh'))->throw();
|
||||
$snapshot = $this->api()->get($this->url($reference))->throw()->json()['data'];
|
||||
abort_unless(!empty($snapshot['can_complete']), 409,
|
||||
'Bayaran ditahan sehingga Shoraka Completed dan amaun sepadan.');
|
||||
|
||||
abort_unless(
|
||||
number_format((float) ($record['context']['pinjaman'] ?? 0), 2, '.', '')
|
||||
=== number_format((float) ($snapshot['order_amount'] ?? 0), 2, '.', ''),
|
||||
409, 'Amaun pembiayaan berubah. Semakan operasi diperlukan sebelum bayaran.'
|
||||
);
|
||||
|
||||
$context = $record['context'] ?? [];
|
||||
if (!empty($context['temporary_gadai'])) {
|
||||
$context = Cache::store('file')->get('shoraka:payout:' . hash('sha256', $record['kodcaw'] . ':' . $reference));
|
||||
abort_unless(is_array($context), 409, 'Maklumat sementara tamat. Semakan operasi diperlukan sebelum bayaran.');
|
||||
}
|
||||
|
||||
$claim = $this->api()->put($this->financingUrl($reference), [
|
||||
'state' => 'PROCESSING',
|
||||
'expected_state' => 'WAITING',
|
||||
])->throw()->json();
|
||||
abort_unless(!empty($claim['claimed']), 409, 'Penyelesaian sedang diproses.');
|
||||
|
||||
$branch = Auth::user()->cawangan;
|
||||
$saved = new Request($context);
|
||||
$saved->setLaravelSession($request->session());
|
||||
try {
|
||||
if (($record['action'] ?? null) === 'gadai') {
|
||||
$result = app(GadaianController::class)->completeShorakaFinancing($saved);
|
||||
} else {
|
||||
if (!empty($record['context']['update_history'])) {
|
||||
$this->api()->put(rtrim(config('api.url'), '/') . '/api/gadai/updatehistory/'
|
||||
. rawurlencode($record['kodcaw']) . '/' . rawurlencode($reference), [
|
||||
'historygadaian' => $saved->norujukan,
|
||||
'jenistebus' => $saved->jenistebus,
|
||||
])->throw();
|
||||
}
|
||||
$cash = app(AliranTunaiController::class);
|
||||
if (($record['action'] ?? null) === 'autotawarruq') {
|
||||
$cash->alirantunaiTebusAutoTawarruq($saved);
|
||||
} else {
|
||||
$cash->alirantunaiTebus($saved);
|
||||
}
|
||||
event(new \App\Events\Dashboard\TebusNotify('lulus', $record['kodcaw']));
|
||||
event(new GadaiNotify('lulus', $record['kodcaw']));
|
||||
$result = redirect()->route('shoraka.show', ['norujukan' => $reference]);
|
||||
}
|
||||
$this->api()->put($this->financingUrl($reference), [
|
||||
'state' => 'DONE',
|
||||
'context' => [],
|
||||
])->throw();
|
||||
if (!empty($context['temporary_gadai'])) {
|
||||
Cache::store('file')->forget('shoraka:payout:' . hash('sha256', $record['kodcaw'] . ':' . $reference));
|
||||
}
|
||||
return $result;
|
||||
} catch (Throwable $exception) {
|
||||
try {
|
||||
$this->api()->put($this->financingUrl($reference), ['state' => 'REVIEW'])->throw();
|
||||
} catch (Throwable $updateError) {
|
||||
}
|
||||
throw $exception;
|
||||
if (($context['action'] ?? null) === 'gadai') {
|
||||
return app(GadaianController::class)->completeShorakaFinancing($saved);
|
||||
}
|
||||
if (!empty($context['update_history'])) {
|
||||
$this->api()->put(rtrim(config('api.url'), '/') . '/api/gadai/updatehistory/'
|
||||
. rawurlencode($branch) . '/' . rawurlencode($reference), [
|
||||
'historygadaian' => $saved->norujukan,
|
||||
'jenistebus' => $saved->jenistebus,
|
||||
])->throw();
|
||||
}
|
||||
$cash = app(AliranTunaiController::class);
|
||||
if (($context['action'] ?? null) === 'autotawarruq') {
|
||||
$cash->alirantunaiTebusAutoTawarruq($saved);
|
||||
} else {
|
||||
$cash->alirantunaiTebus($saved);
|
||||
}
|
||||
event(new \App\Events\Dashboard\TebusNotify('lulus', $branch));
|
||||
event(new GadaiNotify('lulus', $branch));
|
||||
return redirect()->route('customer_info');
|
||||
}
|
||||
|
||||
public function printData(string $reference): ?array
|
||||
@@ -210,12 +183,10 @@ class ShorakaFinancingService
|
||||
$response = $this->api()->get($this->url($reference))->throw()->json()['data'];
|
||||
$order = $response['order'] ?? null;
|
||||
if (!$order) {
|
||||
abort_if($this->local($reference), 409, 'SAG ditahan: transaksi Shoraka belum tersedia.');
|
||||
return null; // Historical financing keeps its historical print path.
|
||||
return null;
|
||||
}
|
||||
abort_unless(!empty($response['can_complete']), 409, 'SAG ditahan: Shoraka belum Completed.');
|
||||
$details = $response['details'] ?? [];
|
||||
// Shoraka may return a list of pipe-delimited certificate details.
|
||||
$certificateDetails = $details['certificateDetails1'] ?? '';
|
||||
if (is_array($certificateDetails)) {
|
||||
$certificateDetails = $certificateDetails[0] ?? '';
|
||||
@@ -234,9 +205,11 @@ class ShorakaFinancingService
|
||||
];
|
||||
}
|
||||
|
||||
private function record(array $data): array
|
||||
private function failed(Request $request, string $message)
|
||||
{
|
||||
$data['context'] = $data['context'] ?? [];
|
||||
return $data;
|
||||
if ($request->ajax() || $request->expectsJson()) {
|
||||
abort(502, $message);
|
||||
}
|
||||
return redirect()->back()->with('error', $message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -949,7 +949,7 @@
|
||||
text: `Komen : ${data.komen}`
|
||||
}).then(function(){
|
||||
$(func).attr("onSubmit","return true;");
|
||||
window.shorakaProgress(1, 'Sedang menghantar pembiayaan kepada Shoraka...');
|
||||
window.shorakaTrack(api_url + '/api/shoraka/gadai/' + encodeURIComponent($('#kodcaw').val()) + '/' + encodeURIComponent($('#norujukan').val()));
|
||||
$(func).submit();
|
||||
return true;
|
||||
});
|
||||
@@ -1062,7 +1062,7 @@
|
||||
}).then(function(){
|
||||
|
||||
$(func).attr("onSubmit","return true;");
|
||||
window.shorakaProgress(1, 'Sedang menghantar pembiayaan kepada Shoraka...');
|
||||
window.shorakaTrack(api_url + '/api/shoraka/gadai/' + encodeURIComponent($('#kodcaw').val()) + '/' + encodeURIComponent($('#norujukan').val()));
|
||||
$(func).submit();
|
||||
return true;
|
||||
|
||||
@@ -1097,7 +1097,7 @@
|
||||
return false;
|
||||
}
|
||||
else if(pinjaman < 10000 && pinjaman <= Number(pembiayaan_max_teller)){
|
||||
window.shorakaProgress(1, 'Sedang menghantar pembiayaan kepada Shoraka...');
|
||||
window.shorakaTrack(api_url + '/api/shoraka/gadai/' + encodeURIComponent($('#kodcaw').val()) + '/' + encodeURIComponent($('#norujukan').val()));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1615,7 +1615,7 @@
|
||||
text: `Komen : ${data.komen}`
|
||||
}).then(function(){
|
||||
$(func).attr("onSubmit","return true;");
|
||||
window.shorakaProgress(1, 'Sedang menghantar pembiayaan kepada Shoraka...');
|
||||
window.shorakaTrack(api_url + '/api/shoraka/gadai/' + encodeURIComponent($('#kodcaw').val()) + '/' + encodeURIComponent($('#norujukan').val()));
|
||||
$(func).submit();
|
||||
return true;
|
||||
});
|
||||
@@ -1728,7 +1728,7 @@
|
||||
}).then(function(){
|
||||
|
||||
$(func).attr("onSubmit","return true;");
|
||||
window.shorakaProgress(1, 'Sedang menghantar pembiayaan kepada Shoraka...');
|
||||
window.shorakaTrack(api_url + '/api/shoraka/gadai/' + encodeURIComponent($('#kodcaw').val()) + '/' + encodeURIComponent($('#norujukan').val()));
|
||||
$(func).submit();
|
||||
return true;
|
||||
|
||||
@@ -1763,7 +1763,7 @@
|
||||
return false;
|
||||
}
|
||||
else if(pinjaman < 10000 && pinjaman <= Number(pembiayaan_max_teller)){
|
||||
window.shorakaProgress(1, 'Sedang menghantar pembiayaan kepada Shoraka...');
|
||||
window.shorakaTrack(api_url + '/api/shoraka/gadai/' + encodeURIComponent($('#kodcaw').val()) + '/' + encodeURIComponent($('#norujukan').val()));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -435,7 +435,6 @@
|
||||
text: @json(session('error'))
|
||||
});
|
||||
@endif
|
||||
|
||||
$.ajax({
|
||||
method:'GET',
|
||||
url: api_url + '/api/jemas',
|
||||
@@ -1787,6 +1786,7 @@
|
||||
}).then(function(){
|
||||
|
||||
$(func).attr("onSubmit","return true;");
|
||||
window.shorakaTrack(api_url + '/api/shoraka/gadai/' + encodeURIComponent($('#kodcaw').val()) + '/' + encodeURIComponent($('#norujukan_gadai').val()));
|
||||
$(func).submit();
|
||||
return true;
|
||||
});
|
||||
@@ -1936,6 +1936,7 @@
|
||||
|
||||
|
||||
$(func).attr("onSubmit","return true;");
|
||||
window.shorakaTrack(api_url + '/api/shoraka/gadai/' + encodeURIComponent($('#kodcaw').val()) + '/' + encodeURIComponent($('#norujukan_gadai').val()));
|
||||
$(func).submit();
|
||||
return true;
|
||||
|
||||
@@ -1999,12 +2000,7 @@
|
||||
return false;
|
||||
} else {
|
||||
//insert code asal
|
||||
Swal.fire({
|
||||
icon: 'info',
|
||||
title: 'Sedang diproses',
|
||||
text: 'Sedang menghantar pembiayaan untuk pengesahan Shoraka.'
|
||||
});
|
||||
|
||||
window.shorakaTrack(api_url + '/api/shoraka/gadai/' + encodeURIComponent($('#kodcaw').val()) + '/' + encodeURIComponent($('#norujukan_gadai').val()));
|
||||
$(func).attr("onSubmit","return true;");
|
||||
$(func).submit();
|
||||
return true;
|
||||
|
||||
@@ -32,28 +32,24 @@
|
||||
</a>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="text-muted mb-4">Semak transaksi komoditi dan selesaikan pembiayaan selepas status Completed.</p>
|
||||
<p class="text-muted mb-4">Semak transaksi komoditi Shoraka cawangan ini.</p>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-bordered table-hover mb-0">
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<th scope="col">No. rujukan</th>
|
||||
<th scope="col">Teller</th>
|
||||
<th scope="col">Penyelesaian pembiayaan</th>
|
||||
<th scope="col">Status komoditi</th>
|
||||
<th scope="col" class="text-center">Tindakan</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse($records as $record)
|
||||
@php
|
||||
$statusLabels = ['WAITING' => 'Menunggu Shoraka / penyelesaian', 'PROCESSING' => 'Sedang diproses', 'REVIEW' => 'Perlu semakan operasi', 'DONE' => 'Selesai'];
|
||||
$statusColors = ['WAITING' => 'warning', 'PROCESSING' => 'info', 'REVIEW' => 'danger', 'DONE' => 'success'];
|
||||
@endphp
|
||||
<tr>
|
||||
<td class="text-nowrap">{{ $record['norujukan'] }}</td>
|
||||
<td>{{ $record['teller'] }}</td>
|
||||
<td>{{ $record['teller'] ?: '—' }}</td>
|
||||
<td>
|
||||
<span class="badge badge-{{ $statusColors[$record['state']] ?? 'secondary' }} status-badge">{{ $statusLabels[$record['state']] ?? $record['state'] }}</span>
|
||||
<span class="badge badge-info status-badge">{{ $record['status'] ?: '—' }}</span>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<a class="btn btn-info btn-sm" href="{{ route('shoraka.show', $record['norujukan']) }}" aria-label="Buka pembiayaan {{ $record['norujukan'] }}">Buka</a>
|
||||
|
||||
@@ -3,32 +3,31 @@
|
||||
<h4 id="shoraka-modal-title">Pembelian Komoditi Shoraka</h4>
|
||||
<p id="shoraka-modal-message" role="status" aria-live="polite">Sedang memproses pembelian komoditi...</p>
|
||||
<ol style="text-align:left;padding-left:28px">
|
||||
<li data-shoraka-step="1">Menghantar order ke Shoraka</li>
|
||||
<li data-shoraka-step="1">Menyimpan gadaian dan menghantar order</li>
|
||||
<li data-shoraka-step="2">Menunggu pengesahan Shoraka</li>
|
||||
<li data-shoraka-step="3">Menyelesaikan pembiayaan dan menyimpan gadaian</li>
|
||||
<li data-shoraka-step="3">Menyelesaikan pembiayaan</li>
|
||||
</ol>
|
||||
<p class="text-muted small">Sila tunggu sehingga proses selesai. Jangan hantar transaksi semula.</p>
|
||||
<div id="shoraka-modal-spinner" class="spinner-border text-primary" role="status"><span class="sr-only">Sedang memproses</span></div>
|
||||
<button id="shoraka-modal-close" type="button" class="btn btn-secondary" hidden>Tutup</button>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
window.shorakaProgress = (function () {
|
||||
var modal = document.getElementById('shoraka-modal');
|
||||
var close = document.getElementById('shoraka-modal-close');
|
||||
var previousFocus;
|
||||
close.addEventListener('click', function () {
|
||||
modal.hidden = true;
|
||||
modal.style.display = 'none';
|
||||
if (previousFocus) { previousFocus.focus(); }
|
||||
});
|
||||
modal.addEventListener('keydown', function (event) {
|
||||
if (event.key === 'Tab') {
|
||||
event.preventDefault();
|
||||
(close.hidden ? modal.querySelector('[role="dialog"]') : close).focus();
|
||||
modal.querySelector('[role="dialog"]').focus();
|
||||
}
|
||||
});
|
||||
return function (step, message, stopped) {
|
||||
if (stopped) {
|
||||
modal.hidden = true;
|
||||
modal.style.display = 'none';
|
||||
if (previousFocus) { previousFocus.focus(); }
|
||||
return;
|
||||
}
|
||||
if (modal.hidden) { previousFocus = document.activeElement; }
|
||||
modal.hidden = false;
|
||||
modal.style.display = 'flex';
|
||||
@@ -39,29 +38,26 @@ window.shorakaProgress = (function () {
|
||||
if (number === step) { item.setAttribute('aria-current', 'step'); }
|
||||
else { item.removeAttribute('aria-current'); }
|
||||
});
|
||||
document.getElementById('shoraka-modal-spinner').style.display = stopped ? 'none' : 'inline-block';
|
||||
close.hidden = !stopped;
|
||||
(stopped ? close : modal.querySelector('[role="dialog"]')).focus();
|
||||
modal.querySelector('[role="dialog"]').focus();
|
||||
};
|
||||
}());
|
||||
</script>
|
||||
<script>
|
||||
// Existing refinancing AJAX handlers must not print receipts or announce payment
|
||||
// success when the server has deferred the financial completion.
|
||||
$.ajaxPrefilter(function (options) {
|
||||
var success = options.success;
|
||||
options.success = function (data) {
|
||||
if (data && data.shoraka_pending && data.redirect) {
|
||||
window.location.assign(data.redirect);
|
||||
return;
|
||||
}
|
||||
if (typeof success === 'function') {
|
||||
return success.apply(this, arguments);
|
||||
}
|
||||
if (Array.isArray(success)) {
|
||||
var args = arguments, context = this;
|
||||
success.forEach(function (callback) { callback.apply(context, args); });
|
||||
}
|
||||
};
|
||||
});
|
||||
window.shorakaTrack = function (statusUrl) {
|
||||
if (window.shorakaTrackTimer) { clearInterval(window.shorakaTrackTimer); }
|
||||
window.shorakaProgress(1, 'Menyimpan gadaian dan menghantar order...');
|
||||
window.shorakaTrackTimer = setInterval(function () {
|
||||
$.get(statusUrl).done(function (body) {
|
||||
var order = body && body.data && body.data.order;
|
||||
var status = order && order.status;
|
||||
if (status === 'Completed') {
|
||||
clearInterval(window.shorakaTrackTimer);
|
||||
window.shorakaProgress(3, 'Menyelesaikan pembiayaan...');
|
||||
} else if (status === 'Cancelled' || status === 'FAILED' || status === 'UNKNOWN') {
|
||||
clearInterval(window.shorakaTrackTimer);
|
||||
window.shorakaProgress(2, 'Transaksi Shoraka gagal. Sila tunggu...');
|
||||
} else if (status) {
|
||||
window.shorakaProgress(2, 'Menunggu pengesahan Shoraka...');
|
||||
}
|
||||
});
|
||||
}, 1500);
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
@section('content')
|
||||
@php
|
||||
$listRoute = $isOperasi ? 'operasi.shoraka.index' : 'pc.shoraka.index';
|
||||
$statusLabels = ['WAITING' => 'Menunggu Shoraka / penyelesaian', 'PROCESSING' => 'Sedang diproses', 'REVIEW' => 'Perlu semakan operasi', 'DONE' => 'Selesai'];
|
||||
$statusColors = ['WAITING' => 'warning', 'PROCESSING' => 'info', 'REVIEW' => 'danger', 'DONE' => 'success'];
|
||||
$statusLabels = ['PENDING' => 'PENDING', 'Active' => 'Active', 'Completed' => 'Completed', 'Cancelled' => 'Cancelled', 'UNKNOWN' => 'UNKNOWN', 'FAILED' => 'FAILED'];
|
||||
@endphp
|
||||
<div class="container-fluid">
|
||||
<nav aria-label="breadcrumb">
|
||||
@@ -19,7 +18,7 @@
|
||||
<a href="{{ route('home') }}" class="btn btn-info btn-sm">Kembali ke Paparan Utama</a>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="text-muted">Paparan pemantauan rekod pembiayaan {{ $isOperasi ? 'semua cawangan' : 'cawangan sendiri' }}. Status di bawah ialah status penyelesaian dalam sistem kaunter, bukan status langsung daripada Shoraka.</p>
|
||||
<p class="text-muted">Paparan transaksi komoditi Shoraka {{ $isOperasi ? 'semua cawangan' : 'cawangan sendiri' }}.</p>
|
||||
@if($errors->any())
|
||||
<div class="alert alert-danger" role="alert">{{ $errors->first() }}</div>
|
||||
@endif
|
||||
@@ -41,11 +40,11 @@
|
||||
<input id="shoraka-reference" name="norujukan" class="form-control" maxlength="100" value="{{ $filters['norujukan'] ?? '' }}">
|
||||
</div>
|
||||
<div class="form-group col-md-3">
|
||||
<label for="shoraka-state">Penyelesaian pembiayaan</label>
|
||||
<select id="shoraka-state" name="state" class="form-control">
|
||||
<label for="shoraka-status">Status komoditi</label>
|
||||
<select id="shoraka-status" name="status" class="form-control">
|
||||
<option value="">Semua status</option>
|
||||
@foreach($statusLabels as $state => $label)
|
||||
<option value="{{ $state }}" {{ ($filters['state'] ?? '') === $state ? 'selected' : '' }}>{{ $label }}</option>
|
||||
@foreach($statusLabels as $status => $label)
|
||||
<option value="{{ $status }}" {{ ($filters['status'] ?? '') === $status ? 'selected' : '' }}>{{ $label }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
@@ -70,7 +69,7 @@
|
||||
<th scope="col">Cawangan</th>
|
||||
<th scope="col">No. rujukan</th>
|
||||
<th scope="col">Teller</th>
|
||||
<th scope="col">Penyelesaian pembiayaan</th>
|
||||
<th scope="col">Status komoditi</th>
|
||||
<th scope="col">Didaftarkan</th>
|
||||
<th scope="col">Dikemas kini</th>
|
||||
</tr></thead>
|
||||
@@ -79,8 +78,8 @@
|
||||
<tr>
|
||||
<td>{{ $record['kodcaw'] }}</td>
|
||||
<td class="text-nowrap">{{ $record['norujukan'] }}</td>
|
||||
<td>{{ $record['teller'] }}</td>
|
||||
<td><span class="badge badge-{{ $statusColors[$record['state']] ?? 'secondary' }} p-2">{{ $statusLabels[$record['state']] ?? $record['state'] }}</span></td>
|
||||
<td>{{ $record['teller'] ?: '—' }}</td>
|
||||
<td><span class="badge badge-info p-2">{{ $record['status'] ?: '—' }}</span></td>
|
||||
<td class="text-nowrap">{{ !empty($record['created_at']) ? \Carbon\Carbon::parse($record['created_at'])->format('d/m/Y H:i') : '—' }}</td>
|
||||
<td class="text-nowrap">{{ !empty($record['updated_at']) ? \Carbon\Carbon::parse($record['updated_at'])->format('d/m/Y H:i') : '—' }}</td>
|
||||
</tr>
|
||||
|
||||
@@ -23,82 +23,12 @@
|
||||
@if(!empty($data['simulation']))
|
||||
<div class="alert alert-warning">SIMULASI LOCAL — tiada transaksi sebenar dihantar ke Shoraka.</div>
|
||||
@endif
|
||||
@if($record && !empty($record['context']['temporary_gadai']) && $record['teller'] === Auth::user()->name && $record['state'] === 'WAITING')
|
||||
<div id="shoraka-progress" class="alert alert-info" role="status">Sedang memproses pembiayaan dan menyemak komoditi Shoraka secara automatik. Sila tunggu.</div>
|
||||
<script>
|
||||
(function () {
|
||||
var started = Date.now();
|
||||
var failures = 0;
|
||||
var completing = false;
|
||||
window.shorakaProgress(2, 'Order telah dihantar. Sedang menyemak pengesahan Shoraka...');
|
||||
var panel = document.getElementById('shoraka-progress');
|
||||
function stop(message) {
|
||||
panel.className = 'alert alert-warning';
|
||||
panel.textContent = message;
|
||||
window.shorakaProgress(completing ? 3 : 2, message, true);
|
||||
}
|
||||
function poll() {
|
||||
if (Date.now() - started > 300000) {
|
||||
stop('Masih menunggu pengesahan Shoraka. Jangan hantar transaksi semula. Buka semula halaman ini untuk menyambung semakan.');
|
||||
return;
|
||||
}
|
||||
fetch(@json(route('shoraka.poll', $norujukan)), {
|
||||
method: 'POST', credentials: 'same-origin',
|
||||
headers: {'X-CSRF-TOKEN': @json(csrf_token()), 'Accept': 'application/json', 'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({progress: completing ? 'complete' : 'check'})
|
||||
}).then(function (response) {
|
||||
if (!response.ok) { throw new Error('Status unavailable'); }
|
||||
return response.json();
|
||||
}).then(function (data) {
|
||||
failures = 0;
|
||||
if (data.state === 'ready') {
|
||||
completing = true;
|
||||
window.shorakaProgress(3, 'Shoraka telah disahkan. Sedang menyelesaikan pembiayaan dan menyimpan gadaian...');
|
||||
window.setTimeout(poll, 0);
|
||||
return;
|
||||
}
|
||||
if (data.state === 'done') {
|
||||
window.shorakaProgress(4, 'Pembiayaan berjaya diselesaikan.', true);
|
||||
panel.className = 'alert alert-success';
|
||||
panel.textContent = 'Pembiayaan berjaya diselesaikan.';
|
||||
if (window.Swal && typeof window.Swal.fire === 'function') {
|
||||
document.getElementById('shoraka-modal-close').click();
|
||||
window.Swal.fire({
|
||||
icon: 'success',
|
||||
title: 'Pembiayaan berjaya',
|
||||
text: 'Transaksi komoditi Shoraka dan proses pembiayaan telah selesai.',
|
||||
confirmButtonText: 'OK',
|
||||
allowOutsideClick: false,
|
||||
allowEscapeKey: false
|
||||
}).then(function () { window.location.assign(data.redirect); });
|
||||
} else {
|
||||
window.alert('Pembiayaan berjaya diselesaikan.');
|
||||
window.location.assign(data.redirect);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (data.state === 'failed') { stop(data.message); return; }
|
||||
window.setTimeout(poll, 5000);
|
||||
}).catch(function () {
|
||||
if (++failures >= 6) {
|
||||
stop('Sambungan semakan terganggu. Jangan hantar transaksi semula. Muat semula halaman untuk menyambung semakan.');
|
||||
return;
|
||||
}
|
||||
window.setTimeout(poll, 5000);
|
||||
});
|
||||
}
|
||||
poll();
|
||||
}());
|
||||
</script>
|
||||
@else
|
||||
|
||||
@if($error)<div class="alert alert-danger">{{ $error }}</div>@endif
|
||||
@if(session('shoraka_message'))<div class="alert alert-info">{{ session('shoraka_message') }}</div>@endif
|
||||
@php
|
||||
$order = $data['order'] ?? null;
|
||||
$status = $order['status'] ?? 'Belum dihantar';
|
||||
$ready = !empty($data['can_complete']);
|
||||
@endphp
|
||||
@if($error)<div class="alert alert-danger">{{ $error }}</div>@endif
|
||||
<p class="text-muted mb-3">No. rujukan: <strong class="text-dark">{{ $norujukan }}</strong></p>
|
||||
<div class="table-responsive mb-4">
|
||||
<table class="table table-striped table-bordered mb-0">
|
||||
@@ -128,29 +58,9 @@
|
||||
@if(in_array($status, ['UNKNOWN', 'FAILED', 'PENDING']))
|
||||
<p>Jangan hantar order sekali lagi. Hubungi operasi untuk semakan dengan Shoraka.</p>
|
||||
@endif
|
||||
@if($record && in_array($record['state'], ['REVIEW', 'PROCESSING']))
|
||||
<div class="alert alert-warning">Penyelesaian sedang diproses atau memerlukan semakan operasi. Jangan ulang bayaran.</div>
|
||||
@elseif($record && $record['state'] === 'DONE')
|
||||
<div class="alert alert-success">Pembiayaan telah diselesaikan.</div>
|
||||
@endif
|
||||
@if($ready)
|
||||
<div class="d-flex flex-wrap pt-3 mt-4 border-top">
|
||||
@if($order && !empty($order['order_id']))
|
||||
<form class="mr-2 mb-2" method="post" action="{{ route('shoraka.refresh', $norujukan) }}">@csrf
|
||||
<button class="btn btn-info" type="submit">Semak Status</button>
|
||||
</form>
|
||||
@elseif($data && !$order && $record && $record['teller'] === Auth::user()->name)
|
||||
<form class="mr-2 mb-2" method="post" action="{{ route('shoraka.submit', $norujukan) }}">@csrf
|
||||
<button class="btn btn-primary" type="submit">Hantar Order</button>
|
||||
</form>
|
||||
@endif
|
||||
@if($ready && $record && $record['state'] === 'WAITING' && $record['teller'] === Auth::user()->name)
|
||||
<form class="mr-2 mb-2" method="post" action="{{ route('shoraka.finish', $norujukan) }}">@csrf
|
||||
<button class="btn btn-success" type="submit">Selesaikan Pembiayaan</button>
|
||||
</form>
|
||||
@endif
|
||||
@if($ready)
|
||||
<a class="btn btn-secondary mb-2" href="{{ route('shoraka.certificate', $norujukan) }}">Muat Turun Sijil</a>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@@ -158,11 +68,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
document.querySelectorAll('form').forEach(function (form) {
|
||||
form.addEventListener('submit', function () {
|
||||
form.querySelectorAll('button[type="submit"]').forEach(function (button) { button.disabled = true; });
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endsection
|
||||
|
||||
@@ -613,9 +613,5 @@ Route::get('/pampasan/surat/{norujukan}', ['as' => 'pampasan.surat', 'uses' => '
|
||||
Route::group(['prefix' => 'shoraka', 'middleware' => ['auth', 'role:teller,penilai,khazanah,admin,masteradmin,operasi']], function () {
|
||||
Route::get('/', 'ShorakaController@index')->name('shoraka.index');
|
||||
Route::get('/{norujukan}', 'ShorakaController@show')->name('shoraka.show');
|
||||
Route::post('/{norujukan}/submit', 'ShorakaController@submit')->name('shoraka.submit');
|
||||
Route::post('/{norujukan}/poll', 'ShorakaController@poll')->name('shoraka.poll');
|
||||
Route::post('/{norujukan}/refresh', 'ShorakaController@refresh')->name('shoraka.refresh');
|
||||
Route::post('/{norujukan}/finish', 'ShorakaController@finish')->name('shoraka.finish');
|
||||
Route::get('/{norujukan}/certificate', 'ShorakaController@certificate')->name('shoraka.certificate');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user