feat(shoraka): defer gadai persistence until commodity confirmation and add simulation support

This commit is contained in:
2026-10-19 13:07:10 +08:00
parent 63d8c23785
commit 011fbfe8b2
18 changed files with 437 additions and 59 deletions
+62 -32
View File
@@ -184,13 +184,16 @@ class GadaianController extends Controller
'norujukan' => $norujukan,
'nopelanggan' => $customer_info['nopelanggan'],
'nokp' => $noic,
'sirig' => $new_sirig
]);
'sirig' => $new_sirig,
'shoraka_new' => true,
])->throw()->json();
$norujukan = $gadai['norujukan'];
Session::put('norujukan', $norujukan);
} else {
$norujukan = Session::get('norujukan');
$nokp = Session::get('nokp');
$nopelanggan = Session::get('nopelanggan');
$gadai_session = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url') . '/api/check/sag', ['kodcaw' => $auth_user['cawangan'], 'norujukan' => $norujukan, 'nokp' => $nokp])->json();
$gadai_session = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url') . '/api/check/sag', ['kodcaw' => $auth_user['cawangan'], 'norujukan' => $norujukan, 'nokp' => $nokp])->throw()->json();
if ($gadai_session == null) {
$gadai_transaction = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url') . '/api/gadai/' . $auth_user['cawangan'] . '/' . $current->toDateString())->json();
if ($gadai_transaction) {
@@ -209,9 +212,11 @@ class GadaianController extends Controller
'norujukan' => $norujukan_baru,
'nopelanggan' => $nopelanggan,
'nokp' => $nokp,
'sirig' => $new_sirig
]);
$norujukan = $norujukan_baru;
'sirig' => $new_sirig,
'shoraka_new' => true,
])->throw()->json();
$norujukan = $gadai_baru['norujukan'];
Session::put('norujukan', $norujukan);
}
}
@@ -353,6 +358,7 @@ class GadaianController extends Controller
return $shoraka->hold($request, $request->norujukan_gadai, 'gadai', [
'cara_terima' => $caraTerima,
'pinjaman' => $pinjamanAmt,
'temporary_gadai' => true,
]);
}
@@ -544,31 +550,55 @@ class GadaianController extends Controller
$keuntungan6bulan = $keuntungan_sebln * 6;
$keuntungan12bulan = $keuntungan_sebln * 12;
$hargajualan = $request->pinjaman + $keuntungan12bulan;
$gadai = Http::withOptions(['verify' => config('app.api_verify')])->put(config('api.url') . '/api/gadai/norujukan/' . $auth_user['cawangan'] . '/' . $request->norujukan_gadai, [
'kodcaw' => $auth_user['cawangan'],
'nilaimarhun' => round($total_nilai_marhun, 2),
'berat' => round($request->total_berat, 2),
'pinjaman' => round($request->pinjaman, 2),
'kadarupah' => round($kadarupah, 2),
'marhun' => $request->marhun_detail,
'percentpinj' => $margin_pinjaman,
'teller' => $auth_user['name'],
'upah6' => $keuntungan6bulan,
'upah12' => $keuntungan12bulan,
'hargajualan' => $hargajualan,
'kuantiti_marhun' => $request->kuantiti_marhun,
'transaction' => 'Ubah Gadai',
'kadarujrah' => round($kadarupah, 2),
'ujrah' => $ujrah,
'onepercent' => $onepercent,
'margin_semasa' => $margin_pinjaman,
'kadar_rebate' => $this->parseMoney($request->kadar_rebate),
'rebate' => $this->parseMoney($request->rebate),
])->json();
$update_komuditi = Http::withOptions(['verify' => config('app.api_verify')])->put(config('api.url') . '/api/komuditi/update', [
'unit_komuditi' => round($request->pinjaman, 2),
'norujukan' => $request->norujukan_gadai
])->json();
try {
$gadai = Http::withOptions(['verify' => config('app.api_verify')])->put(config('api.url') . '/api/gadai/norujukan/' . $auth_user['cawangan'] . '/' . $request->norujukan_gadai, [
'kodcaw' => $auth_user['cawangan'],
'nilaimarhun' => round($total_nilai_marhun, 2),
'berat' => round($request->total_berat, 2),
'pinjaman' => round($request->pinjaman, 2),
'kadarupah' => round($kadarupah, 2),
'marhun' => $request->marhun_detail,
'percentpinj' => $margin_pinjaman,
'teller' => $auth_user['name'],
'upah6' => $keuntungan6bulan,
'upah12' => $keuntungan12bulan,
'hargajualan' => $hargajualan,
'kuantiti_marhun' => $request->kuantiti_marhun,
'transaction' => 'Ubah Gadai',
'kadarujrah' => round($kadarupah, 2),
'ujrah' => $ujrah,
'onepercent' => $onepercent,
'margin_semasa' => $margin_pinjaman,
'kadar_rebate' => $this->parseMoney($request->kadar_rebate),
'rebate' => $this->parseMoney($request->rebate),
]);
} catch (\Illuminate\Http\Client\ConnectionException $exception) {
return back()->withInput()->with('gadai_edit_error',
'Sambungan terganggu. Semak rekod gadai sebelum mencuba semula.');
}
if (!$gadai->successful()) {
$message = 'Ubah Gadai tidak berjaya. Sila semak dengan operasi.';
if ($gadai->status() === 409 || $gadai->status() === 422) {
$apiMessage = $gadai->json()['message'] ?? null;
if (is_string($apiMessage) && $apiMessage !== '') {
$message = $apiMessage;
}
}
return back()->withInput()->with('gadai_edit_error', $message);
}
try {
$update_komuditi = Http::withOptions(['verify' => config('app.api_verify')])->put(config('api.url') . '/api/komuditi/update', [
'unit_komuditi' => round($request->pinjaman, 2),
'norujukan' => $request->norujukan_gadai
]);
} catch (\Illuminate\Http\Client\ConnectionException $exception) {
return back()->with('gadai_edit_error',
'Rekod gadai telah dikemas kini tetapi kemas kini komoditi tidak dapat disahkan. Sila semak dengan operasi.');
}
if (!$update_komuditi->successful()) {
return back()->with('gadai_edit_error',
'Rekod gadai telah dikemas kini tetapi kemas kini komoditi gagal. Sila semak dengan operasi.');
}
event(new GadaiNotify('lulus', $auth_user['cawangan']));
@@ -582,7 +612,7 @@ class GadaianController extends Controller
$norujukan = $request->norujukan;
$auth_user = Auth::user();
Http::withOptions(['verify' => config('app.api_verify')])->delete(config('api.url') . '/api/gadai/batal/' . $auth_user['cawangan'] . '/' . $norujukan);
Http::withOptions(['verify' => config('app.api_verify')])->delete(config('api.url') . '/api/gadai/batal/' . $auth_user['cawangan'] . '/' . $norujukan)->throw();
$request->session()->forget('page_reload');
$request->session()->forget('norujukan');
$request->session()->forget('borang_online_printed');
@@ -18,6 +18,49 @@ class ShorakaController extends Controller
return view('shoraka.index', compact('records', 'cawangan_info'));
}
public function operasiIndex(Request $request)
{
abort_unless(in_array(Auth::user()->roles, ['operasi', 'pc', 'ppc'], true), 403);
$isOperasi = Auth::user()->roles === 'operasi';
$filters = $request->validate([
'kodcaw' => 'nullable|string|max:30',
'norujukan' => 'nullable|string|max:100',
'state' => 'nullable|in:WAITING,PROCESSING,REVIEW,DONE',
'date_from' => 'nullable|date_format:Y-m-d',
'date_to' => 'nullable|date_format:Y-m-d' . ($request->filled('date_from') ? '|after_or_equal:date_from' : ''),
]);
$branchQuery = ShorakaFinancing::select('kodcaw');
if (!$isOperasi) {
$branchQuery->where('kodcaw', Auth::user()->cawangan);
}
$branches = $branchQuery->distinct()->orderBy('kodcaw')->pluck('kodcaw');
$query = ShorakaFinancing::select('id', 'kodcaw', 'norujukan', 'teller', 'action', 'state', 'created_at', 'updated_at');
if (!$isOperasi) {
$query->where('kodcaw', Auth::user()->cawangan);
}
foreach (['kodcaw', 'norujukan', 'state'] as $field) {
if (!empty($filters[$field])) {
$query->where($field, $filters[$field]);
}
}
if (!empty($filters['date_from'])) {
$query->where('created_at', '>=', $filters['date_from'] . ' 00:00:00');
}
if (!empty($filters['date_to'])) {
$query->where('created_at', '<', \Carbon\Carbon::parse($filters['date_to'])->addDay()->format('Y-m-d') . ' 00:00:00');
}
$records = $query->orderByDesc('created_at')->orderByDesc('id')
->paginate(25)->appends($filters);
$cawangan_info = ['details_caw' => Auth::user()->cawangan, 'kodcaw' => Auth::user()->cawangan];
return view('shoraka.operasi', compact('records', 'branches', 'filters', 'isOperasi', 'cawangan_info'));
}
public function pcIndex(Request $request)
{
abort_unless(in_array(Auth::user()->roles, ['pc', 'ppc'], true), 403);
return $this->operasiIndex($request);
}
public function show(string $norujukan, ShorakaFinancingService $service)
{
$record = $service->local($norujukan);
@@ -32,6 +75,62 @@ class ShorakaController extends Controller
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 === Auth::user()->name && $record->action === 'gadai', 403);
if ($record->state === 'DONE') {
return response()->json(['state' => 'done', 'redirect' => route('customer_info')]);
}
abort_unless(!empty($record->context['temporary_gadai']), 409);
if ($record->state === 'REVIEW') {
return response()->json(['state' => 'failed', 'message' => 'Penyelesaian memerlukan semakan operasi. Jangan ulang bayaran.']);
}
if ($record->state === '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) {
if ($record->fresh()->state === '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);