feat(shoraka): integrate transactions with Shoraka API
This commit is contained in:
@@ -125,10 +125,10 @@ class AliranTunaiController extends Controller
|
||||
|
||||
$auth_user = Auth::user();
|
||||
|
||||
$gadai = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url') . '/api/gadai/norujukan/'. $auth_user['cawangan'] .'/' .$request->norujukan_gadai)->json();
|
||||
$gadai = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url') . '/api/gadai/norujukan/'. $auth_user['cawangan'] .'/' .$request->norujukan_gadai)->throw()->json();
|
||||
$gadai_detail = $gadai[0];
|
||||
|
||||
$getTunai2=Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url').'/api/GetCurrentTunai/'.$auth_user['cawangan'],['kodlaluan' => $teller])->json();
|
||||
$getTunai2=Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url').'/api/GetCurrentTunai/'.$auth_user['cawangan'],['kodlaluan' => $teller])->throw()->json();
|
||||
|
||||
$keluar = $getTunai2['keluar'];
|
||||
$bakiakhir = $getTunai2['baki'];
|
||||
@@ -143,7 +143,7 @@ class AliranTunaiController extends Controller
|
||||
'keluar' => $keluar,
|
||||
'masuk' => $masuk,
|
||||
'bakiakhir' => $bakiakhir
|
||||
]);
|
||||
])->throw();
|
||||
|
||||
return response()->json($updateTunai,201);
|
||||
|
||||
@@ -153,13 +153,13 @@ class AliranTunaiController extends Controller
|
||||
|
||||
$auth_user = Auth::user();
|
||||
|
||||
$gadai = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url') . '/api/gadai/norujukan/'. $auth_user['cawangan'] .'/' .$request->norujukan)->json();
|
||||
$gadai = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url') . '/api/gadai/norujukan/'. $auth_user['cawangan'] .'/' .$request->norujukan)->throw()->json();
|
||||
$gadai_detail = $gadai[0];
|
||||
|
||||
$tebus = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url') . '/api/listpenebusanhistory/'. $auth_user['cawangan'],['norujukan' => $request->norujukan])->json();
|
||||
$tebus = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url') . '/api/listpenebusanhistory/'. $auth_user['cawangan'],['norujukan' => $request->norujukan])->throw()->json();
|
||||
$tebus_detail = $tebus[0];
|
||||
|
||||
$getTunai2=Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url').'/api/GetCurrentTunai/'.$auth_user['cawangan'],['kodlaluan' => $tebus_detail['teller']])->json();
|
||||
$getTunai2=Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url').'/api/GetCurrentTunai/'.$auth_user['cawangan'],['kodlaluan' => $tebus_detail['teller']])->throw()->json();
|
||||
|
||||
$keluar=$getTunai2['keluar'];
|
||||
$baki=$getTunai2['baki'];
|
||||
@@ -181,7 +181,7 @@ class AliranTunaiController extends Controller
|
||||
'keluar'=>$keluar,
|
||||
'masuk'=>$masuk,
|
||||
'bakiakhir'=>$bakiakhir
|
||||
]);
|
||||
])->throw();
|
||||
|
||||
}else{
|
||||
|
||||
@@ -199,7 +199,7 @@ class AliranTunaiController extends Controller
|
||||
'keluar'=>$keluar,
|
||||
'masuk'=>$masuk,
|
||||
'bakiakhir'=>$bakiakhir
|
||||
]);
|
||||
])->throw();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -208,13 +208,13 @@ class AliranTunaiController extends Controller
|
||||
|
||||
$auth_user = Auth::user();
|
||||
|
||||
$gadai = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url') . '/api/gadai/norujukan/'. $auth_user['cawangan'] .'/' .$request->norujukan)->json();
|
||||
$gadai = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url') . '/api/gadai/norujukan/'. $auth_user['cawangan'] .'/' .$request->norujukan)->throw()->json();
|
||||
$gadai_detail = $gadai[0];
|
||||
|
||||
$tebus = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url') . '/api/listpenebusanhistory/'. $auth_user['cawangan'],['norujukan' => $request->norujukan])->json();
|
||||
$tebus = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url') . '/api/listpenebusanhistory/'. $auth_user['cawangan'],['norujukan' => $request->norujukan])->throw()->json();
|
||||
$tebus_detail = $tebus[0];
|
||||
|
||||
$getTunai2=Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url').'/api/GetCurrentTunai/'.$auth_user['cawangan'],['kodlaluan' => $tebus_detail['teller']])->json();
|
||||
$getTunai2=Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url').'/api/GetCurrentTunai/'.$auth_user['cawangan'],['kodlaluan' => $tebus_detail['teller']])->throw()->json();
|
||||
|
||||
$keluar=$getTunai2['keluar'];
|
||||
$baki=$getTunai2['baki'];
|
||||
@@ -228,7 +228,7 @@ class AliranTunaiController extends Controller
|
||||
'keluar'=>$keluar,
|
||||
'masuk'=>$masuk,
|
||||
'bakiakhir'=>$bakiakhir
|
||||
]);
|
||||
])->throw();
|
||||
}
|
||||
|
||||
public function alirantunaiUpahBknTawarruq(Request $request){
|
||||
|
||||
@@ -285,6 +285,11 @@ class GadaianController extends Controller
|
||||
public function store(Request $request)
|
||||
{
|
||||
$auth_user = Auth::user();
|
||||
$shoraka = app(\App\Services\ShorakaFinancingService::class);
|
||||
if ($shoraka->local($request->norujukan_gadai)) {
|
||||
return $shoraka->pendingResponse($request, $request->norujukan_gadai);
|
||||
}
|
||||
$shoraka->ensureReady();
|
||||
$caraTerima = strtoupper((string) $request->input('cara_terima', 'TUNAI'));
|
||||
$pinjamanAmt = round((float) $request->pinjaman, 2);
|
||||
|
||||
@@ -342,16 +347,20 @@ class GadaianController extends Controller
|
||||
'nokp' => $request->nokp,
|
||||
'kadar_rebate' => $this->parseMoney($request->kadar_rebate),
|
||||
'rebate' => $this->parseMoney($request->rebate),
|
||||
])->json();
|
||||
])->throw()->json();
|
||||
|
||||
|
||||
$komuditi = Http::withOptions(['verify' => config('app.api_verify')])->put(config('api.url') . '/api/komuditi/transaksi', [
|
||||
'unit_komuditi' => round($request->pinjaman, 2),
|
||||
'norujukan' => $request->norujukan_gadai,
|
||||
'kodcaw' => $auth_user['cawangan'],
|
||||
'teller' => $auth_user['name']
|
||||
return $shoraka->hold($request, $request->norujukan_gadai, 'gadai', [
|
||||
'cara_terima' => $caraTerima,
|
||||
'pinjaman' => $pinjamanAmt,
|
||||
]);
|
||||
}
|
||||
|
||||
public function completeShorakaFinancing(Request $request)
|
||||
{
|
||||
$auth_user = Auth::user();
|
||||
$caraTerima = $request->cara_terima;
|
||||
$pinjamanAmt = round((float) $request->pinjaman, 2);
|
||||
if ($caraTerima === 'ONLINE') {
|
||||
$online = Http::withOptions(['verify' => config('app.api_verify')])
|
||||
->asForm()
|
||||
@@ -374,7 +383,7 @@ class GadaianController extends Controller
|
||||
$msg = is_array($body) && isset($body['message'])
|
||||
? $body['message']
|
||||
: 'Gagal daftar pembiayaan online. Sila cuba lagi.';
|
||||
return redirect()->back()->with('error', $msg);
|
||||
throw new \RuntimeException($msg);
|
||||
}
|
||||
|
||||
$this->notifyPcPembiayaanOnlinePending($auth_user, $request, $pinjamanAmt);
|
||||
@@ -594,6 +603,7 @@ class GadaianController extends Controller
|
||||
|
||||
public function cetakSAG(Request $request)
|
||||
{
|
||||
app(\App\Services\ShorakaFinancingService::class)->printData($request->norujukan_modal);
|
||||
$auth_user = Auth::user();
|
||||
|
||||
$gadai = Http::withOptions(['verify' => config('app.api_verify')])->put(config('api.url') . '/api/gadai/norujukan/cetak/' . $auth_user['cawangan'] . '/' . $request->norujukan_modal);
|
||||
@@ -614,6 +624,7 @@ class GadaianController extends Controller
|
||||
}
|
||||
|
||||
foreach ($arraynorujukan as $index => $norujukan) {
|
||||
app(\App\Services\ShorakaFinancingService::class)->printData($norujukan);
|
||||
$gadai = Http::withOptions(['verify' => config('app.api_verify')])->put(config('api.url') . '/api/gadai/norujukan/serah/' . $auth_user['cawangan'] . '/' . $norujukan);
|
||||
|
||||
if ($gadai->failed()) {
|
||||
@@ -671,6 +682,12 @@ class GadaianController extends Controller
|
||||
// $pembayaran_segera = "https://spare.arrahn.com.my/pembayaransegera";
|
||||
$pembayaran_segera = "https://pay.arrahn.com.my";
|
||||
|
||||
$shorakaPrint = app(\App\Services\ShorakaFinancingService::class)->printData($gadai_detail['norujukan']);
|
||||
if ($shorakaPrint) {
|
||||
$komoditi = $shorakaPrint['komoditi'];
|
||||
$product = $shorakaPrint['product'];
|
||||
$kuantiti = $shorakaPrint['kuantiti'];
|
||||
} else {
|
||||
$komoditi = Http::withOptions(['verify' => config('app.api_verify')])
|
||||
->get($api_url . '/api/komuditi/check')
|
||||
->json();
|
||||
@@ -678,6 +695,7 @@ class GadaianController extends Controller
|
||||
->get($api_url . '/api/komuditi/description', ['kodproduk' => $komoditi['kodproduct']])
|
||||
->json();
|
||||
$kuantiti = $gadai_detail['pinjaman'] / $komoditi['nilai'];
|
||||
}
|
||||
|
||||
if ($gadai_detail['tagbaru'] == 1) {
|
||||
$cawangan_info = Http::withOptions(['verify' => config('app.api_verify')])
|
||||
|
||||
@@ -522,6 +522,12 @@ class PembiayaanOnlineController extends Controller
|
||||
|
||||
$pembayaran_segera = 'https://pay.arrahn.com.my';
|
||||
|
||||
$shorakaPrint = app(\App\Services\ShorakaFinancingService::class)->printData($gadai_detail['norujukan']);
|
||||
if ($shorakaPrint) {
|
||||
$komoditi = $shorakaPrint['komoditi'];
|
||||
$product = $shorakaPrint['product'];
|
||||
$kuantiti = $shorakaPrint['kuantiti'];
|
||||
} else {
|
||||
$komoditi = $this->api()->get($api_url . '/api/komuditi/check')->json();
|
||||
$product = $this->api()
|
||||
->get($api_url . '/api/komuditi/description', [
|
||||
@@ -531,6 +537,7 @@ class PembiayaanOnlineController extends Controller
|
||||
|
||||
$nilai = is_array($komoditi) ? (float) ($komoditi['nilai'] ?? 0) : 0;
|
||||
$kuantiti = $nilai > 0 ? ((float) ($gadai_detail['pinjaman'] ?? 0) / $nilai) : 0;
|
||||
}
|
||||
|
||||
if (($gadai_detail['tagbaru'] ?? 0) == 1) {
|
||||
$cawangan_info = $this->api()
|
||||
|
||||
@@ -271,6 +271,7 @@ class PenebusanController extends Controller
|
||||
|
||||
public function tambahpinjaman(Request $request)
|
||||
{
|
||||
app(\App\Services\ShorakaFinancingService::class)->ensureReady();
|
||||
$api_url = config('api.url');
|
||||
$current = Carbon::now();
|
||||
$current = new Carbon();
|
||||
@@ -405,33 +406,16 @@ class PenebusanController extends Controller
|
||||
'upah12' => $keuntungan12bulan,
|
||||
'hargajualan' => $hargajualan,
|
||||
'kuantiti_marhun' => $request->kuantiti_marhun
|
||||
])->json();
|
||||
|
||||
$komuditi = Http::withOptions(['verify' => config('app.api_verify')])->put(config('api.url').'/api/komuditi/transaksi',[
|
||||
'unit_komuditi' => round($request->pinjaman, 2),
|
||||
'norujukan' => $norujukanbaru,
|
||||
'kodcaw' => $auth_user['cawangan'],
|
||||
'teller' => $auth_user['name']
|
||||
]);
|
||||
|
||||
////////////////////////////
|
||||
////Update History Gadaian///
|
||||
////////////////////////////
|
||||
|
||||
$updatehistorygadaian = Http::withOptions(['verify' => config('app.api_verify')])->put(config('api.url').'/api/gadai/updatehistory/'. $auth_user['cawangan'] .'/' . $norujukanbaru,[
|
||||
'historygadaian' => $request->norujukan,
|
||||
'jenistebus' => $request->jenistebus,
|
||||
])->json();
|
||||
|
||||
|
||||
event(new TebusNotify('lulus',Auth::user()->cawangan));
|
||||
event(new GadaiNotify('lulus',Auth::user()->cawangan));
|
||||
$this->AliranTunaiController->alirantunaiTebus($request);
|
||||
])->throw()->json();
|
||||
|
||||
return app(\App\Services\ShorakaFinancingService::class)->hold(
|
||||
$request, $norujukanbaru, 'refinance', ['update_history' => true]
|
||||
);
|
||||
}
|
||||
|
||||
public function tambahpinjaman_latest(Request $request)
|
||||
{
|
||||
app(\App\Services\ShorakaFinancingService::class)->ensureReady();
|
||||
$api_url = config('api.url');
|
||||
$current = Carbon::now();
|
||||
$current = new Carbon();
|
||||
@@ -475,17 +459,18 @@ class PenebusanController extends Controller
|
||||
])->json();
|
||||
|
||||
|
||||
if($updatetebus['result'] == 'success'){
|
||||
event(new TebusNotify('lulus',Auth::user()->cawangan));
|
||||
event(new GadaiNotify('lulus',Auth::user()->cawangan));
|
||||
$this->AliranTunaiController->alirantunaiTebus($request);
|
||||
if (($updatetebus['result'] ?? null) === 'success') {
|
||||
abort_unless(!empty($updatetebus['norujukan_baru']), 502, 'Rujukan gadaian baharu tidak diterima.');
|
||||
return app(\App\Services\ShorakaFinancingService::class)->hold(
|
||||
$request, $updatetebus['norujukan_baru'], 'refinance'
|
||||
);
|
||||
}
|
||||
return response()->json($updatetebus['result'],200);
|
||||
|
||||
return response()->json($updatetebus['result'] ?? 'error', 200);
|
||||
}
|
||||
|
||||
public function tebusautotawarruq(Request $request)
|
||||
{
|
||||
app(\App\Services\ShorakaFinancingService::class)->ensureReady();
|
||||
$auth_user = Auth::user();
|
||||
|
||||
////////////////////////
|
||||
@@ -515,17 +500,18 @@ class PenebusanController extends Controller
|
||||
"noic" => $request->noic,
|
||||
"jenisAT" => $request->jenisAT,
|
||||
])->json();
|
||||
if($updatetebus['result'] == 'success'){
|
||||
$alirantunai = new AliranTunaiController();
|
||||
event(new TebusNotify('lulus',Auth::user()->cawangan));
|
||||
event(new GadaiNotify('lulus',Auth::user()->cawangan));
|
||||
$alirantunai->alirantunaiTebusAutoTawarruq($request);
|
||||
if (($updatetebus['result'] ?? null) === 'success') {
|
||||
abort_unless(!empty($updatetebus['norujukan_baru']), 502, 'Rujukan gadaian baharu tidak diterima.');
|
||||
return app(\App\Services\ShorakaFinancingService::class)->hold(
|
||||
$request, $updatetebus['norujukan_baru'], 'autotawarruq'
|
||||
);
|
||||
}
|
||||
return response()->json($updatetebus['result'],200);
|
||||
return response()->json($updatetebus['result'] ?? 'error', 200);
|
||||
}
|
||||
|
||||
public function tebusseparuh(Request $request)
|
||||
{
|
||||
app(\App\Services\ShorakaFinancingService::class)->ensureReady();
|
||||
$api_url = config('api.url');
|
||||
$current = Carbon::now();
|
||||
$current = new Carbon();
|
||||
@@ -666,39 +652,13 @@ class PenebusanController extends Controller
|
||||
'upah12' => $keuntungan12bulan,
|
||||
'hargajualan' => $hargajualan,
|
||||
'kuantiti_marhun' => $request->kuantiti_marhun
|
||||
])->json();
|
||||
])->throw()->json();
|
||||
|
||||
$komuditi = Http::withOptions(['verify' => config('app.api_verify')])->put(config('api.url').'/api/komuditi/transaksi',[
|
||||
'unit_komuditi' => round($request->pinjaman, 2),
|
||||
'norujukan' => $norujukanbaru,
|
||||
'kodcaw' => $auth_user['cawangan'],
|
||||
'teller' => $auth_user['name']
|
||||
]);
|
||||
|
||||
////////////////////////////
|
||||
////Update History Gadaian///
|
||||
////////////////////////////
|
||||
|
||||
$updatehistorygadaian = Http::withOptions(['verify' => config('app.api_verify')])->put(config('api.url').'/api/gadai/updatehistory/'. $auth_user['cawangan'] .'/' . $norujukanbaru,[
|
||||
'historygadaian' => $request->norujukan,
|
||||
'jenistebus' => $request->jenistebus,
|
||||
])->json();
|
||||
|
||||
|
||||
$alirantunai = new AliranTunaiController();
|
||||
event(new TebusNotify('lulus',Auth::user()->cawangan));
|
||||
event(new GadaiNotify('lulus',Auth::user()->cawangan));
|
||||
|
||||
$alirantunai->alirantunaiTebus($request);
|
||||
|
||||
|
||||
return response()->json($updatemarhun,200);
|
||||
|
||||
return app(\App\Services\ShorakaFinancingService::class)->hold(
|
||||
$request, $norujukanbaru, 'refinance', ['update_history' => true]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
//Wakil Function
|
||||
|
||||
public function getfirstWakil()
|
||||
{
|
||||
$auth_user = Auth::user();
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\ShorakaFinancing;
|
||||
use App\Services\ShorakaFinancingService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Throwable;
|
||||
|
||||
class ShorakaController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$records = ShorakaFinancing::where('kodcaw', Auth::user()->cawangan)
|
||||
->orderByDesc('id')->limit(100)->get();
|
||||
$cawangan_info = ['details_caw' => Auth::user()->cawangan, 'kodcaw' => Auth::user()->cawangan];
|
||||
return view('shoraka.index', compact('records', 'cawangan_info'));
|
||||
}
|
||||
|
||||
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 submit(string $norujukan, ShorakaFinancingService $service)
|
||||
{
|
||||
$record = $service->local($norujukan);
|
||||
abort_unless($record && $record->teller === 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',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\ShorakaFinancing;
|
||||
use App\Http\Controllers\GadaianController;
|
||||
use App\Http\Controllers\AliranTunaiController;
|
||||
use App\Events\Dashboard\GadaiNotify;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Throwable;
|
||||
|
||||
class ShorakaFinancingService
|
||||
{
|
||||
public function ensureReady(): void
|
||||
{
|
||||
// Check local migration before any registration side effect.
|
||||
ShorakaFinancing::where('kodcaw', Auth::user()->cawangan)->exists();
|
||||
$response = $this->api()->get(rtrim(config('api.url'), '/') . '/api/shoraka/readiness')->throw()->json();
|
||||
abort_unless(!empty($response['ready']), 503, 'Shoraka belum dikonfigurasi. Pembiayaan baharu ditahan.');
|
||||
}
|
||||
|
||||
public function api()
|
||||
{
|
||||
return Http::withOptions(['verify' => config('app.api_verify')])
|
||||
->acceptJson()->timeout(75);
|
||||
}
|
||||
|
||||
public function url(string $reference, string $suffix = ''): string
|
||||
{
|
||||
return rtrim(config('api.url'), '/') . '/api/shoraka/gadai/'
|
||||
. rawurlencode(Auth::user()->cawangan) . '/' . rawurlencode($reference) . $suffix;
|
||||
}
|
||||
|
||||
public function local(string $reference)
|
||||
{
|
||||
return ShorakaFinancing::where('kodcaw', Auth::user()->cawangan)
|
||||
->where('norujukan', $reference)->first();
|
||||
}
|
||||
|
||||
public function hold(Request $request, string $reference, string $action, array $extra = [])
|
||||
{
|
||||
$user = Auth::user();
|
||||
$record = ShorakaFinancing::firstOrCreate(
|
||||
['kodcaw' => $user->cawangan, 'norujukan' => $reference],
|
||||
[
|
||||
'teller' => $user->name, 'action' => $action, 'state' => 'WAITING',
|
||||
'context' => array_merge($request->except(['_token', 'password']), $extra),
|
||||
]
|
||||
);
|
||||
// A duplicate request only opens the saved workflow; it never replays registration.
|
||||
if ($record->wasRecentlyCreated) {
|
||||
try {
|
||||
$this->api()->post($this->url($reference, '/submit'));
|
||||
} catch (Throwable $exception) {
|
||||
// The persisted workflow remains available after timeouts.
|
||||
}
|
||||
}
|
||||
return $this->pendingResponse($request, $reference);
|
||||
}
|
||||
|
||||
public function pendingResponse(Request $request, string $reference)
|
||||
{
|
||||
$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 === Auth::user()->name, 403,
|
||||
'Pembiayaan perlu diselesaikan oleh teller asal.');
|
||||
if ($record->state === 'DONE') {
|
||||
return redirect()->route('shoraka.show', ['norujukan' => $reference]);
|
||||
}
|
||||
abort_unless($record->state === '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.'
|
||||
);
|
||||
|
||||
$claimed = ShorakaFinancing::where('id', $record->id)->where('state', 'WAITING')
|
||||
->update(['state' => 'PROCESSING', 'updated_at' => now()]);
|
||||
abort_unless($claimed === 1, 409, 'Penyelesaian sedang diproses.');
|
||||
|
||||
$saved = new Request($record->context);
|
||||
$saved->setLaravelSession($request->session());
|
||||
try {
|
||||
if ($record->action === '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 === '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]);
|
||||
}
|
||||
$record->update(['state' => 'DONE', 'context' => []]);
|
||||
return $result;
|
||||
} catch (Throwable $exception) {
|
||||
// A failed remote payment response can be ambiguous. Never retry automatically.
|
||||
$record->update(['state' => 'REVIEW']);
|
||||
throw $exception;
|
||||
}
|
||||
}
|
||||
|
||||
public function printData(string $reference): ?array
|
||||
{
|
||||
$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.
|
||||
}
|
||||
abort_unless(!empty($response['can_complete']), 409, 'SAG ditahan: Shoraka belum Completed.');
|
||||
$details = $response['details'] ?? [];
|
||||
$certificate = explode('|', $details['certificateDetails1'] ?? '')[0];
|
||||
abort_unless($certificate !== '' && isset($details['unit'], $details['volume'], $details['commodityType']),
|
||||
409, 'Maklumat sijil belum lengkap. Semak status Shoraka dahulu.');
|
||||
return [
|
||||
'komoditi' => ['no_sijil' => $certificate],
|
||||
'product' => [
|
||||
'kodproduk' => $details['commodityType'],
|
||||
'description' => $details['commodityType'] === '000-COPPER' ? 'Copper' : 'Plastic resin',
|
||||
'unit' => $details['unit'],
|
||||
],
|
||||
'kuantiti' => $details['volume'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ShorakaFinancing extends Model
|
||||
{
|
||||
protected $guarded = [];
|
||||
protected $casts = ['context' => 'array'];
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateShorakaFinancingsTable extends Migration
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
Schema::create('shoraka_financings', function (Blueprint $table) {
|
||||
$table->bigIncrements('id');
|
||||
$table->string('kodcaw', 20);
|
||||
$table->string('norujukan');
|
||||
$table->string('teller');
|
||||
$table->string('action', 30);
|
||||
$table->string('state', 30)->default('WAITING');
|
||||
$table->json('context');
|
||||
$table->timestamps();
|
||||
$table->unique(['kodcaw', 'norujukan']);
|
||||
});
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('shoraka_financings');
|
||||
}
|
||||
}
|
||||
@@ -2814,25 +2814,25 @@
|
||||
|
||||
$.ajax({
|
||||
method:'get',
|
||||
url: api + '/api/komuditi/check',
|
||||
url: api + '/api/shoraka/readiness',
|
||||
success:function(komoditi){
|
||||
var check_komuditi = Object.keys(komoditi).length;
|
||||
if(check_komuditi == 0){
|
||||
if (!komoditi.ready){
|
||||
$("#terima_transaksi").prop('disabled', false);
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Unit Komuditi Berbaki Minumum',
|
||||
text: 'Sila Hubungi Operasi Untuk Membuat Pembelian Komuditi'
|
||||
title: 'Shoraka Belum Tersedia',
|
||||
text: 'Sila hubungi operasi untuk semakan sambungan Shoraka.'
|
||||
});
|
||||
event.preventDefault();
|
||||
return false;
|
||||
}else{
|
||||
if(Number(komoditi['unit_komuditi']) < pinjaman_baru){
|
||||
if (!komoditi.ready){
|
||||
$("#terima_transaksi").prop('disabled', false);
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Baki Unit Komuditi Tidak Mencukupi',
|
||||
text: 'Sila Hubungi Operasi Untuk Membuat Pembelian Komuditi'
|
||||
title: 'Shoraka Belum Tersedia',
|
||||
text: 'Sila hubungi operasi untuk semakan sambungan Shoraka.'
|
||||
});
|
||||
event.preventDefault();
|
||||
return false;
|
||||
|
||||
@@ -461,25 +461,25 @@
|
||||
}else{
|
||||
$.ajax({
|
||||
method:'get',
|
||||
url: api_url + '/api/komuditi/check',
|
||||
url: api_url + '/api/shoraka/readiness',
|
||||
success:function(data){
|
||||
var check_komuditi = Object.keys(data).length;
|
||||
if(check_komuditi == 0){
|
||||
if (!data.ready){
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Unit Komoditi Berbaki Minima',
|
||||
text: 'Sila Hubungi Operasi Untuk Membuat Pembelian Komoditi'
|
||||
title: 'Shoraka Belum Tersedia',
|
||||
text: 'Sila hubungi operasi untuk semakan sambungan Shoraka.'
|
||||
});
|
||||
$("#daftar_gadai").prop('disabled', false);
|
||||
event.preventDefault();
|
||||
return false;
|
||||
}else{
|
||||
if(Number(data['unit_komuditi']) <= min_komoditi){
|
||||
if (!data.ready){
|
||||
$("#daftar_gadai").prop('disabled', false);
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Unit Komoditi Berbaki Minima',
|
||||
text: 'Sila Hubungi Operasi Untuk Membuat Pembelian Komoditi'
|
||||
title: 'Shoraka Belum Tersedia',
|
||||
text: 'Sila hubungi operasi untuk semakan sambungan Shoraka.'
|
||||
});
|
||||
event.preventDefault();
|
||||
return false;
|
||||
|
||||
@@ -758,25 +758,25 @@
|
||||
} else {
|
||||
$.ajax({
|
||||
method: 'get',
|
||||
url: api_url + '/api/komuditi/check',
|
||||
url: api_url + '/api/shoraka/readiness',
|
||||
success: function(data) {
|
||||
var check_komuditi = Object.keys(data).length;
|
||||
if (check_komuditi == 0) {
|
||||
if (!data.ready) {
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Unit Komoditi Berbaki Minima',
|
||||
text: 'Sila Hubungi Operasi Untuk Membuat Pembelian Komoditi'
|
||||
title: 'Shoraka Belum Tersedia',
|
||||
text: 'Sila hubungi operasi untuk semakan sambungan Shoraka.'
|
||||
});
|
||||
$("#daftar_gadai").prop('disabled', false);
|
||||
event.preventDefault();
|
||||
return false;
|
||||
} else {
|
||||
if (Number(data['unit_komuditi']) <= min_komoditi) {
|
||||
if (!data.ready) {
|
||||
$("#daftar_gadai").prop('disabled', false);
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Unit Komoditi Berbaki Minima',
|
||||
text: 'Sila Hubungi Operasi Untuk Membuat Pembelian Komoditi'
|
||||
title: 'Shoraka Belum Tersedia',
|
||||
text: 'Sila hubungi operasi untuk semakan sambungan Shoraka.'
|
||||
});
|
||||
event.preventDefault();
|
||||
return false;
|
||||
|
||||
@@ -819,25 +819,25 @@
|
||||
}else{
|
||||
$.ajax({
|
||||
method:'get',
|
||||
url: api_url + '/api/komuditi/check',
|
||||
url: api_url + '/api/shoraka/readiness',
|
||||
success:function(data){
|
||||
var check_komuditi = Object.keys(data).length;
|
||||
if(check_komuditi == 0){
|
||||
if (!data.ready){
|
||||
$("#terima_gadai").prop('disabled', false);
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Unit Komuditi Berbaki Minumum',
|
||||
text: 'Sila Hubungi Operasi Untuk Membuat Pembelian Komuditi'
|
||||
title: 'Shoraka Belum Tersedia',
|
||||
text: 'Sila hubungi operasi untuk semakan sambungan Shoraka.'
|
||||
});
|
||||
event.preventDefault();
|
||||
return false;
|
||||
}else{
|
||||
if(Number(data['unit_komuditi']) < pinjaman){
|
||||
if (!data.ready){
|
||||
$("#terima_gadai").prop('disabled', false);
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Baki Unit Komuditi Tidak Mencukupi',
|
||||
text: 'Sila Hubungi Operasi Untuk Membuat Pembelian Komuditi'
|
||||
title: 'Shoraka Belum Tersedia',
|
||||
text: 'Sila hubungi operasi untuk semakan sambungan Shoraka.'
|
||||
});
|
||||
event.preventDefault();
|
||||
return false;
|
||||
|
||||
@@ -1655,25 +1655,25 @@
|
||||
}else{
|
||||
$.ajax({
|
||||
method:'get',
|
||||
url: api_url + '/api/komuditi/check',
|
||||
url: api_url + '/api/shoraka/readiness',
|
||||
success:function(data){
|
||||
var check_komuditi = Object.keys(data).length;
|
||||
if(check_komuditi == 0){
|
||||
if (!data.ready){
|
||||
$("#terima_gadai").prop('disabled', false);
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Unit Komuditi Berbaki Minumum',
|
||||
text: 'Sila Hubungi Operasi Untuk Membuat Pembelian Komuditi'
|
||||
title: 'Shoraka Belum Tersedia',
|
||||
text: 'Sila hubungi operasi untuk semakan sambungan Shoraka.'
|
||||
});
|
||||
event.preventDefault();
|
||||
return false;
|
||||
}else{
|
||||
if(Number(data['unit_komuditi']) < pinjaman){
|
||||
if (!data.ready){
|
||||
$("#terima_gadai").prop('disabled', false);
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Baki Unit Komuditi Tidak Mencukupi',
|
||||
text: 'Sila Hubungi Operasi Untuk Membuat Pembelian Komuditi'
|
||||
title: 'Shoraka Belum Tersedia',
|
||||
text: 'Sila hubungi operasi untuk semakan sambungan Shoraka.'
|
||||
});
|
||||
event.preventDefault();
|
||||
return false;
|
||||
|
||||
@@ -405,6 +405,7 @@
|
||||
@endif
|
||||
|
||||
<main class="py-4">
|
||||
@include('shoraka.navigation')
|
||||
@yield('content')
|
||||
</main>
|
||||
</div>
|
||||
|
||||
@@ -2022,25 +2022,25 @@
|
||||
|
||||
$.ajax({
|
||||
method:'get',
|
||||
url: api + '/api/komuditi/check',
|
||||
url: api + '/api/shoraka/readiness',
|
||||
success:function(komoditi){
|
||||
var check_komuditi = Object.keys(komoditi).length;
|
||||
if(check_komuditi == 0){
|
||||
if (!komoditi.ready){
|
||||
$("#terima_transaksi").prop('disabled', false);
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Unit Komuditi Berbaki Minumum',
|
||||
text: 'Sila Hubungi Operasi Untuk Membuat Pembelian Komuditi'
|
||||
title: 'Shoraka Belum Tersedia',
|
||||
text: 'Sila hubungi operasi untuk semakan sambungan Shoraka.'
|
||||
});
|
||||
event.preventDefault();
|
||||
return false;
|
||||
}else{
|
||||
if(Number(komoditi['unit_komuditi']) < pinjaman_baru){
|
||||
if (!komoditi.ready){
|
||||
$("#terima_transaksi").prop('disabled', false);
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Baki Unit Komuditi Tidak Mencukupi',
|
||||
text: 'Sila Hubungi Operasi Untuk Membuat Pembelian Komuditi'
|
||||
title: 'Shoraka Belum Tersedia',
|
||||
text: 'Sila hubungi operasi untuk semakan sambungan Shoraka.'
|
||||
});
|
||||
event.preventDefault();
|
||||
return false;
|
||||
|
||||
@@ -2258,25 +2258,25 @@
|
||||
|
||||
$.ajax({
|
||||
method:'get',
|
||||
url: api + '/api/komuditi/check',
|
||||
url: api + '/api/shoraka/readiness',
|
||||
success:function(komoditi){
|
||||
var check_komuditi = Object.keys(komoditi).length;
|
||||
if(check_komuditi == 0){
|
||||
if (!komoditi.ready){
|
||||
$("#terima_transaksi").prop('disabled', false);
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Unit Komuditi Berbaki Minumum',
|
||||
text: 'Sila Hubungi Operasi Untuk Membuat Pembelian Komuditi'
|
||||
title: 'Shoraka Belum Tersedia',
|
||||
text: 'Sila hubungi operasi untuk semakan sambungan Shoraka.'
|
||||
});
|
||||
event.preventDefault();
|
||||
return false;
|
||||
}else{
|
||||
if(Number(komoditi['unit_komuditi']) < pinjaman_baru){
|
||||
if (!komoditi.ready){
|
||||
$("#terima_transaksi").prop('disabled', false);
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Baki Unit Komuditi Tidak Mencukupi',
|
||||
text: 'Sila Hubungi Operasi Untuk Membuat Pembelian Komuditi'
|
||||
title: 'Shoraka Belum Tersedia',
|
||||
text: 'Sila hubungi operasi untuk semakan sambungan Shoraka.'
|
||||
});
|
||||
event.preventDefault();
|
||||
return false;
|
||||
|
||||
@@ -2264,27 +2264,27 @@
|
||||
let komuditiblock = false;
|
||||
$.ajax({
|
||||
method:'get',
|
||||
url: api + '/api/komuditi/check',
|
||||
url: api + '/api/shoraka/readiness',
|
||||
async:false,
|
||||
success:function(komoditi){
|
||||
var check_komuditi = Object.keys(komoditi).length;
|
||||
if(check_komuditi == 0){
|
||||
if (!komoditi.ready){
|
||||
$("#terima_transaksi").prop('disabled', false);
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Unit Komuditi Berbaki Minumum',
|
||||
text: 'Sila Hubungi Operasi Untuk Membuat Pembelian Komuditi'
|
||||
title: 'Shoraka Belum Tersedia',
|
||||
text: 'Sila hubungi operasi untuk semakan sambungan Shoraka.'
|
||||
});
|
||||
komuditiblock = true;
|
||||
event.preventDefault();
|
||||
return false;
|
||||
}else{
|
||||
if(Number(komoditi['unit_komuditi']) < pinjaman_baru){
|
||||
if (!komoditi.ready){
|
||||
$("#terima_transaksi").prop('disabled', false);
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Baki Unit Komuditi Tidak Mencukupi',
|
||||
text: 'Sila Hubungi Operasi Untuk Membuat Pembelian Komuditi'
|
||||
title: 'Shoraka Belum Tersedia',
|
||||
text: 'Sila hubungi operasi untuk semakan sambungan Shoraka.'
|
||||
});
|
||||
komuditiblock = true;
|
||||
event.preventDefault();
|
||||
@@ -2646,29 +2646,29 @@
|
||||
let komuditiblock = false;
|
||||
$.ajax({
|
||||
method:'get',
|
||||
url: api + '/api/komuditi/check',
|
||||
url: api + '/api/shoraka/readiness',
|
||||
async:false,
|
||||
success:function(komoditi){
|
||||
var check_komuditi = Object.keys(komoditi).length;
|
||||
if(check_komuditi == 0){
|
||||
if (!komoditi.ready){
|
||||
$("#terima_transaksi").prop('disabled', false);
|
||||
Swal.close();
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Unit Komuditi Berbaki Minumum',
|
||||
text: 'Sila Hubungi Operasi Untuk Membuat Pembelian Komuditi'
|
||||
title: 'Shoraka Belum Tersedia',
|
||||
text: 'Sila hubungi operasi untuk semakan sambungan Shoraka.'
|
||||
});
|
||||
komuditiblock = true;
|
||||
event.preventDefault();
|
||||
return false;
|
||||
}else{
|
||||
if(Number(komoditi['unit_komuditi']) < pinjaman_baru){
|
||||
if (!komoditi.ready){
|
||||
$("#terima_transaksi").prop('disabled', false);
|
||||
Swal.close();
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Baki Unit Komuditi Tidak Mencukupi',
|
||||
text: 'Sila Hubungi Operasi Untuk Membuat Pembelian Komuditi'
|
||||
title: 'Shoraka Belum Tersedia',
|
||||
text: 'Sila hubungi operasi untuk semakan sambungan Shoraka.'
|
||||
});
|
||||
komuditiblock = true;
|
||||
event.preventDefault();
|
||||
@@ -3106,25 +3106,25 @@
|
||||
|
||||
$.ajax({
|
||||
method:'get',
|
||||
url: api + '/api/komuditi/check',
|
||||
url: api + '/api/shoraka/readiness',
|
||||
success:function(komoditi){
|
||||
var check_komuditi = Object.keys(komoditi).length;
|
||||
if(check_komuditi == 0){
|
||||
if (!komoditi.ready){
|
||||
$("#terima_transaksi").prop('disabled', false);
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Unit Komuditi Berbaki Minumum',
|
||||
text: 'Sila Hubungi Operasi Untuk Membuat Pembelian Komuditi'
|
||||
title: 'Shoraka Belum Tersedia',
|
||||
text: 'Sila hubungi operasi untuk semakan sambungan Shoraka.'
|
||||
});
|
||||
event.preventDefault();
|
||||
return false;
|
||||
}else{
|
||||
if(Number(komoditi['unit_komuditi']) < pinjaman_baru){
|
||||
if (!komoditi.ready){
|
||||
$("#terima_transaksi").prop('disabled', false);
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Baki Unit Komuditi Tidak Mencukupi',
|
||||
text: 'Sila Hubungi Operasi Untuk Membuat Pembelian Komuditi'
|
||||
title: 'Shoraka Belum Tersedia',
|
||||
text: 'Sila hubungi operasi untuk semakan sambungan Shoraka.'
|
||||
});
|
||||
event.preventDefault();
|
||||
return false;
|
||||
|
||||
@@ -556,9 +556,16 @@
|
||||
</table>
|
||||
|
||||
@php
|
||||
$komuditi = Http::withOptions(['verify' => config('app.api_verify')])->get($api_url . '/api/komuditi/check')->json();
|
||||
$shorakaPrint = app(\App\Services\ShorakaFinancingService::class)->printData($gadai_detail['norujukan']);
|
||||
if ($shorakaPrint) {
|
||||
$komuditi = $shorakaPrint['komoditi'];
|
||||
$product = $shorakaPrint['product'];
|
||||
$kuantiti = $shorakaPrint['kuantiti'];
|
||||
} else {
|
||||
$komuditi = Http::withOptions(['verify' => config('app.api_verify')])->get($api_url . '/api/komuditi/check')->json();
|
||||
$product = Http::withOptions(['verify' => config('app.api_verify')])->get($api_url . '/api/komuditi/description',['kodproduk'=>$komuditi['kodproduct']])->json();
|
||||
$kuantiti = $gadai_detail['pinjaman']/$komuditi['nilai'];
|
||||
}
|
||||
@endphp
|
||||
|
||||
<table style="margin-bottom:10px;" id="keuntungan">
|
||||
|
||||
@@ -654,13 +654,20 @@
|
||||
|
||||
|
||||
@php
|
||||
$komuditi = Http::withOptions(['verify' => config('app.api_verify')])
|
||||
$shorakaPrint = app(\App\Services\ShorakaFinancingService::class)->printData($gadai_detail['norujukan']);
|
||||
if ($shorakaPrint) {
|
||||
$komuditi = $shorakaPrint['komoditi'];
|
||||
$product = $shorakaPrint['product'];
|
||||
$kuantiti = $shorakaPrint['kuantiti'];
|
||||
} else {
|
||||
$komuditi = Http::withOptions(['verify' => config('app.api_verify')])
|
||||
->get($api_url . '/api/komuditi/check')
|
||||
->json();
|
||||
$product = Http::withOptions(['verify' => config('app.api_verify')])
|
||||
->get($api_url . '/api/komuditi/description', ['kodproduk' => $komuditi['kodproduct']])
|
||||
->json();
|
||||
$kuantiti = $gadai_detail['pinjaman'] / $komuditi['nilai'];
|
||||
}
|
||||
@endphp
|
||||
|
||||
<table style="margin-bottom:10px;" id="keuntungan">
|
||||
|
||||
@@ -713,13 +713,20 @@
|
||||
</tr>
|
||||
</table>
|
||||
@php
|
||||
$komuditi = Http::withOptions(['verify' => config('app.api_verify')])
|
||||
$shorakaPrint = app(\App\Services\ShorakaFinancingService::class)->printData($gadai_detail['norujukan']);
|
||||
if ($shorakaPrint) {
|
||||
$komuditi = $shorakaPrint['komoditi'];
|
||||
$product = $shorakaPrint['product'];
|
||||
$kuantiti = $shorakaPrint['kuantiti'];
|
||||
} else {
|
||||
$komuditi = Http::withOptions(['verify' => config('app.api_verify')])
|
||||
->get($api_url . '/api/komuditi/check')
|
||||
->json();
|
||||
$product = Http::withOptions(['verify' => config('app.api_verify')])
|
||||
->get($api_url . '/api/komuditi/description', ['kodproduk' => $komuditi['kodproduct']])
|
||||
->json();
|
||||
$kuantiti = $gadai_detail['pinjaman'] / $komuditi['nilai'];
|
||||
}
|
||||
@endphp
|
||||
|
||||
<table id="keuntungan" style="margin-bottom:10px;">
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
@extends('layouts.app')
|
||||
@section('content')
|
||||
<div class="container py-3">
|
||||
<h3>Pembiayaan Shoraka</h3>
|
||||
<p>Semak transaksi komoditi dan selesaikan pembiayaan selepas status Completed.</p>
|
||||
<table class="table table-bordered">
|
||||
<thead><tr><th>No. rujukan</th><th>Teller</th><th>Penyelesaian pembiayaan</th><th></th></tr></thead>
|
||||
<tbody>
|
||||
@forelse($records as $record)
|
||||
<tr>
|
||||
<td>{{ $record->norujukan }}</td><td>{{ $record->teller }}</td>
|
||||
<td>{{ ['WAITING' => 'Menunggu Shoraka / penyelesaian', 'PROCESSING' => 'Sedang diproses', 'REVIEW' => 'Perlu semakan operasi', 'DONE' => 'Selesai'][$record->state] ?? $record->state }}</td>
|
||||
<td><a class="btn btn-primary" href="{{ route('shoraka.show', $record->norujukan) }}">Buka</a></td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr><td colspan="4">Tiada transaksi.</td></tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,27 @@
|
||||
@auth
|
||||
@if(in_array(Auth::user()->roles, ['teller', 'penilai', 'pc', 'ppc', 'admin', 'masteradmin', 'operasi']) || Auth::user()->role_harian === 'teller')
|
||||
<div class="container-fluid text-right py-1">
|
||||
<a class="btn btn-sm btn-outline-primary" href="{{ route('shoraka.index') }}">Pembiayaan Shoraka</a>
|
||||
</div>
|
||||
@endif
|
||||
@endauth
|
||||
<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); });
|
||||
}
|
||||
};
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,57 @@
|
||||
@extends('layouts.app')
|
||||
@section('content')
|
||||
<div class="container py-3">
|
||||
<a href="{{ route('shoraka.index') }}">Kembali ke senarai</a>
|
||||
<h3 class="mt-3">Komoditi Shoraka — {{ $norujukan }}</h3>
|
||||
@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
|
||||
<dl class="row">
|
||||
<dt class="col-sm-4">Status komoditi</dt><dd class="col-sm-8">{{ $status }}</dd>
|
||||
<dt class="col-sm-4">Order ID</dt><dd class="col-sm-8">{{ $order['order_id'] ?? '—' }}</dd>
|
||||
<dt class="col-sm-4">Pembiayaan</dt><dd class="col-sm-8">RM {{ number_format($data['order_amount'] ?? 0, 2) }}</dd>
|
||||
<dt class="col-sm-4">Harga jualan</dt><dd class="col-sm-8">RM {{ number_format($data['murabaha_amount'] ?? 0, 2) }}</dd>
|
||||
</dl>
|
||||
@if(!$ready)
|
||||
<div class="alert alert-warning">Bayaran pembiayaan dan SAG ditahan sehingga transaksi komoditi Completed.</div>
|
||||
@endif
|
||||
@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
|
||||
<div class="d-flex flex-wrap">
|
||||
@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>
|
||||
</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
|
||||
+9
-1
@@ -605,4 +605,12 @@ Route::post('/pampasan/terima',['as' => 'pampasan.terima', 'uses' => 'KhazanahCo
|
||||
Route::get(('/pampasan/resit'), ['as' => 'pampasan.resit', 'uses' => 'KhazanahController@resitPampasan'])->middleware('auth', 'khazanah');
|
||||
|
||||
//Surat Pampasan
|
||||
Route::get('/pampasan/surat/{norujukan}', ['as' => 'pampasan.surat', 'uses' => 'KhazanahController@suratPampasan'])->middleware('auth', 'role:teller,khazanah');
|
||||
Route::get('/pampasan/surat/{norujukan}', ['as' => 'pampasan.surat', 'uses' => 'KhazanahController@suratPampasan'])->middleware('auth', 'role:teller,khazanah');
|
||||
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}/refresh', 'ShorakaController@refresh')->name('shoraka.refresh');
|
||||
Route::post('/{norujukan}/finish', 'ShorakaController@finish')->name('shoraka.finish');
|
||||
Route::get('/{norujukan}/certificate', 'ShorakaController@certificate')->name('shoraka.certificate');
|
||||
});
|
||||
|
||||
@@ -0,0 +1,238 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\ShorakaFinancing;
|
||||
use App\User;
|
||||
use App\Services\ShorakaFinancingService;
|
||||
use App\Http\Controllers\GadaianController;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Http\Client\Factory;
|
||||
use Mockery;
|
||||
use Tests\TestCase;
|
||||
|
||||
class ShorakaFinancingTest extends TestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
config([
|
||||
'database.default' => 'shoraka_test',
|
||||
'database.connections.shoraka_test' => ['driver' => 'sqlite', 'database' => ':memory:', 'prefix' => ''],
|
||||
'api.url' => 'https://api.test', 'session.driver' => 'array',
|
||||
]);
|
||||
DB::purge('shoraka_test');
|
||||
require_once database_path('migrations/2026_10_05_120000_create_shoraka_financings_table.php');
|
||||
(new \CreateShorakaFinancingsTable())->up();
|
||||
$this->actingAs(new User(['name' => 'teller1', 'cawangan' => 'KB2', 'roles' => 'teller']));
|
||||
Http::swap(new Factory());
|
||||
}
|
||||
|
||||
protected function record(): ShorakaFinancing
|
||||
{
|
||||
return ShorakaFinancing::create([
|
||||
'kodcaw' => 'KB2', 'norujukan' => 'SAG-001', 'teller' => 'teller1',
|
||||
'action' => 'gadai', 'state' => 'WAITING',
|
||||
'context' => ['norujukan_gadai' => 'SAG-001', 'pinjaman' => 1000, 'cara_terima' => 'TUNAI'],
|
||||
]);
|
||||
}
|
||||
|
||||
protected function request(): Request
|
||||
{
|
||||
$request = new Request(['norujukan_gadai' => 'SAG-001', 'pinjaman' => 1000, '_token' => 'private']);
|
||||
$request->setLaravelSession(app('session.store'));
|
||||
return $request;
|
||||
}
|
||||
|
||||
public function testHoldPersistsAndNeverSubmitsTwice(): void
|
||||
{
|
||||
Http::fake(function ($request) {
|
||||
$this->assertSame('https://api.test/api/shoraka/gadai/KB2/SAG-001/submit', $request->url());
|
||||
return Http::response(['success' => true, 'data' => ['status' => 'Active']]);
|
||||
});
|
||||
$service = new ShorakaFinancingService();
|
||||
$service->hold($this->request(), 'SAG-001', 'gadai');
|
||||
$service->hold($this->request(), 'SAG-001', 'gadai');
|
||||
$this->assertSame(1, ShorakaFinancing::count());
|
||||
$this->assertSame('WAITING', ShorakaFinancing::first()->state);
|
||||
$this->assertArrayNotHasKey('_token', ShorakaFinancing::first()->context);
|
||||
Http::assertSentCount(1);
|
||||
}
|
||||
|
||||
public function testPendingStatusCannotTriggerPayment(): void
|
||||
{
|
||||
$record = $this->record();
|
||||
Http::fake(function () { return Http::response(['data' => ['can_complete' => false]]); });
|
||||
$controller = Mockery::mock(GadaianController::class);
|
||||
$controller->shouldNotReceive('completeShorakaFinancing');
|
||||
$this->app->instance(GadaianController::class, $controller);
|
||||
try {
|
||||
(new ShorakaFinancingService())->finish($this->request(), 'SAG-001');
|
||||
$this->fail('Pending trade must block payment.');
|
||||
} catch (\Symfony\Component\HttpKernel\Exception\HttpException $exception) {
|
||||
$this->assertSame(409, $exception->getStatusCode());
|
||||
$this->assertSame('WAITING', $record->fresh()->state);
|
||||
}
|
||||
}
|
||||
|
||||
public function testCompletedTradePaysOnlyOnce(): void
|
||||
{
|
||||
$record = $this->record();
|
||||
Http::fake(function () { return Http::response(['data' => ['can_complete' => true, 'order_amount' => 1000]]); });
|
||||
$controller = Mockery::mock(GadaianController::class);
|
||||
$controller->shouldReceive('completeShorakaFinancing')->once()->andReturn(redirect('/'));
|
||||
$this->app->instance(GadaianController::class, $controller);
|
||||
$service = new ShorakaFinancingService();
|
||||
$service->finish($this->request(), 'SAG-001');
|
||||
$service->finish($this->request(), 'SAG-001');
|
||||
$this->assertSame('DONE', $record->fresh()->state);
|
||||
$this->assertSame([], $record->fresh()->context);
|
||||
Http::assertSentCount(2);
|
||||
}
|
||||
|
||||
public function testAmbiguousPaymentFailureRequiresReview(): void
|
||||
{
|
||||
$record = $this->record();
|
||||
Http::fake(function () { return Http::response(['data' => ['can_complete' => true, 'order_amount' => 1000]]); });
|
||||
$controller = Mockery::mock(GadaianController::class);
|
||||
$controller->shouldReceive('completeShorakaFinancing')->once()->andThrow(new \RuntimeException('Timeout'));
|
||||
$this->app->instance(GadaianController::class, $controller);
|
||||
try {
|
||||
(new ShorakaFinancingService())->finish($this->request(), 'SAG-001');
|
||||
$this->fail('Expected payment failure.');
|
||||
} catch (\RuntimeException $exception) {
|
||||
$this->assertSame('REVIEW', $record->fresh()->state);
|
||||
}
|
||||
$this->expectException(\Symfony\Component\HttpKernel\Exception\HttpException::class);
|
||||
(new ShorakaFinancingService())->finish($this->request(), 'SAG-001');
|
||||
}
|
||||
|
||||
public function testDifferentTellerCannotCompletePayment(): void
|
||||
{
|
||||
$record = $this->record();
|
||||
$record->update(['teller' => 'someone-else']);
|
||||
Http::fake();
|
||||
try {
|
||||
(new ShorakaFinancingService())->finish($this->request(), 'SAG-001');
|
||||
$this->fail('Expected teller restriction.');
|
||||
} catch (\Symfony\Component\HttpKernel\Exception\HttpException $exception) {
|
||||
$this->assertSame(403, $exception->getStatusCode());
|
||||
}
|
||||
Http::assertNothingSent();
|
||||
}
|
||||
|
||||
public function testPendingTradeCannotPrintSag(): void
|
||||
{
|
||||
$this->record();
|
||||
Http::fake(function () { return Http::response(['data' => [
|
||||
'can_complete' => false, 'order' => ['status' => 'Active'],
|
||||
]]); });
|
||||
$this->expectException(\Symfony\Component\HttpKernel\Exception\HttpException::class);
|
||||
(new ShorakaFinancingService())->printData('SAG-001');
|
||||
}
|
||||
|
||||
public function testPrintUsesOrderCertificateAndVolume(): void
|
||||
{
|
||||
Http::fake(function () { return Http::response(['data' => [
|
||||
'can_complete' => true, 'order' => ['status' => 'Completed'],
|
||||
'details' => ['certificateDetails1' => 'CERT-01|timestamp|seller', 'unit' => 'Tonnes',
|
||||
'volume' => '1.25', 'commodityType' => '000-COPPER'],
|
||||
]]); });
|
||||
$data = (new ShorakaFinancingService())->printData('SAG-001');
|
||||
$this->assertSame('CERT-01', $data['komoditi']['no_sijil']);
|
||||
$this->assertSame('1.25', $data['kuantiti']);
|
||||
}
|
||||
|
||||
public function testBranchComesFromAuthenticatedUser(): void
|
||||
{
|
||||
$service = new ShorakaFinancingService();
|
||||
$this->assertSame('https://api.test/api/shoraka/gadai/KB2/SAG-001', $service->url('SAG-001'));
|
||||
$record = $this->record();
|
||||
$record->update(['kodcaw' => 'KB3']);
|
||||
$this->assertNull($service->local('SAG-001'));
|
||||
}
|
||||
|
||||
public function testRegistrationDefersCashAndOnlinePayment(): void
|
||||
{
|
||||
\Illuminate\Support\Facades\Event::fake();
|
||||
Http::fake(function ($request) {
|
||||
if (strpos($request->url(), '/shoraka/readiness') !== false) {
|
||||
return Http::response(['ready' => true]);
|
||||
}
|
||||
if (strpos($request->url(), '/marhun/gadai/') !== false) {
|
||||
return Http::response('2000.00');
|
||||
}
|
||||
if (strpos($request->url(), '/gadai/norujukan/') !== false) {
|
||||
return Http::response('true');
|
||||
}
|
||||
if (strpos($request->url(), '/shoraka/gadai/') !== false) {
|
||||
return Http::response(['success' => true, 'data' => ['status' => 'Active']]);
|
||||
}
|
||||
$this->fail('Unexpected payment request: ' . $request->url());
|
||||
});
|
||||
$request = $this->request();
|
||||
$request->merge([
|
||||
'cara_terima' => 'TUNAI', 'keuntungan_sebulan' => 10,
|
||||
'kadarkeuntungan' => 1, 'total_berat' => 5, 'nokp' => 'IC-1',
|
||||
]);
|
||||
$result = app(GadaianController::class)->store($request);
|
||||
$this->assertStringContainsString('/shoraka/SAG-001', $result->getTargetUrl());
|
||||
$this->assertSame('WAITING', ShorakaFinancing::first()->state);
|
||||
app(GadaianController::class)->store($request);
|
||||
Http::assertSentCount(4);
|
||||
\Illuminate\Support\Facades\Event::assertNotDispatched(\App\Events\Dashboard\GadaiNotify::class);
|
||||
}
|
||||
|
||||
public function testReadinessFailureBlocksRegistrationBeforeSave(): void
|
||||
{
|
||||
Http::fake(function () { return Http::response(['ready' => false]); });
|
||||
try {
|
||||
app(GadaianController::class)->store($this->request());
|
||||
$this->fail('Registration should be blocked.');
|
||||
} catch (\Symfony\Component\HttpKernel\Exception\HttpException $exception) {
|
||||
$this->assertSame(503, $exception->getStatusCode());
|
||||
}
|
||||
Http::assertSentCount(1);
|
||||
$this->assertSame(0, ShorakaFinancing::count());
|
||||
}
|
||||
|
||||
public function testRefinancingUsesNewReferenceAndDefersCash(): void
|
||||
{
|
||||
\Illuminate\Support\Facades\Event::fake();
|
||||
Http::fake(function ($request) {
|
||||
if (strpos($request->url(), '/shoraka/readiness') !== false) {
|
||||
return Http::response(['ready' => true]);
|
||||
}
|
||||
if (strpos($request->url(), '/updatetambahpinjaman/') !== false) {
|
||||
return Http::response(['result' => 'success', 'norujukan_baru' => 'NEW-SAG']);
|
||||
}
|
||||
if (strpos($request->url(), '/shoraka/gadai/KB2/NEW-SAG/submit') !== false) {
|
||||
return Http::response(['success' => true]);
|
||||
}
|
||||
$this->fail('Unexpected cash/legacy commodity request: ' . $request->url());
|
||||
});
|
||||
$request = $this->request();
|
||||
$request->merge(['norujukan' => 'OLD-SAG']);
|
||||
$request->headers->set('X-Requested-With', 'XMLHttpRequest');
|
||||
$result = app(\App\Http\Controllers\PenebusanController::class)->tambahpinjaman_latest($request);
|
||||
$this->assertSame(202, $result->getStatusCode());
|
||||
$this->assertSame('NEW-SAG', ShorakaFinancing::first()->norujukan);
|
||||
$this->assertSame('refinance', ShorakaFinancing::first()->action);
|
||||
\Illuminate\Support\Facades\Event::assertNotDispatched(\App\Events\Dashboard\GadaiNotify::class);
|
||||
}
|
||||
|
||||
public function testChangedSavedPayoutAmountCannotBePaid(): void
|
||||
{
|
||||
$this->record();
|
||||
Http::fake(function () { return Http::response(['data' => [
|
||||
'can_complete' => true, 'order_amount' => 2000,
|
||||
]]); });
|
||||
$controller = Mockery::mock(GadaianController::class);
|
||||
$controller->shouldNotReceive('completeShorakaFinancing');
|
||||
$this->app->instance(GadaianController::class, $controller);
|
||||
$this->expectException(\Symfony\Component\HttpKernel\Exception\HttpException::class);
|
||||
(new ShorakaFinancingService())->finish($this->request(), 'SAG-001');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user