DONE: add notification for gadaian and pendahuluan

This commit is contained in:
ISMAIL MASSERAN
2026-09-21 14:30:30 +08:00
parent d0f4e04f96
commit 0e8f340b29
10 changed files with 474 additions and 80 deletions
@@ -29,6 +29,7 @@ use Carbon\Carbon;
use PDF; use PDF;
use DateTime; use DateTime;
use App\Helper; use App\Helper;
use App\Services\NotificationService;
use Log; use Log;
class KhazanahController extends Controller class KhazanahController extends Controller
@@ -1084,6 +1085,7 @@ class KhazanahController extends Controller
if($test == 'success'){ if($test == 'success'){
event(new PendahuluanSerahan("Lulus","pendahuluan",$auth_user['cawangan'],$request->teller)); event(new PendahuluanSerahan("Lulus","pendahuluan",$auth_user['cawangan'],$request->teller));
$this->notifyTellerAliranTunai('Lulus', 'pendahuluan', $auth_user['cawangan'], $request->teller, $amount);
return redirect()->route('khazanah.pendahulu'); return redirect()->route('khazanah.pendahulu');
}else{ }else{
return redirect()->route('khazanah.pendahulu')->with('error','Permohonan tidak berjaya! Permohonan Semula perlu melebihi 1 minit.'); return redirect()->route('khazanah.pendahulu')->with('error','Permohonan tidak berjaya! Permohonan Semula perlu melebihi 1 minit.');
@@ -1109,6 +1111,7 @@ class KhazanahController extends Controller
if($test == 'success'){ if($test == 'success'){
event(new PendahuluanSerahan("Lulus","serahan",$auth_user['cawangan'],$request->teller)); event(new PendahuluanSerahan("Lulus","serahan",$auth_user['cawangan'],$request->teller));
$this->notifyTellerAliranTunai('Lulus', 'serahan', $auth_user['cawangan'], $request->teller, $request->amt);
return redirect()->route('khazanah.serahan'); return redirect()->route('khazanah.serahan');
}else{ }else{
return redirect()->route('khazanah.serahan')->with('error','Permohonan tidak berjaya! Permohonan Semula perlu melebihi 1 minit.'); return redirect()->route('khazanah.serahan')->with('error','Permohonan tidak berjaya! Permohonan Semula perlu melebihi 1 minit.');
@@ -2848,10 +2851,43 @@ class KhazanahController extends Controller
]); ]);
event(new PendahuluanSerahan("Batal","all",$auth_user['cawangan'],$kodlaluan)); event(new PendahuluanSerahan("Batal","all",$auth_user['cawangan'],$kodlaluan));
$this->notifyTellerAliranTunai('Batal', 'all', $kodcaw, $kodlaluan);
return response('Berjaya'); return response('Berjaya');
} }
/**
* Inbox for the teller waiting on aliran tunai. Failures must not block approval.
*
* @param string $pengesahan
* @param string $jenis
* @param string $kodcaw
* @param string $teller
* @param mixed $amaun
* @return void
*/
protected function notifyTellerAliranTunai($pengesahan, $jenis, $kodcaw, $teller, $amaun = 0)
{
try {
$raw = str_replace(',', '', (string) $amaun);
$amount = is_numeric($raw) ? (float) $raw : 0;
app(NotificationService::class)->notifyAliranTunaiDecided($kodcaw, $teller, [
'pengesahan' => $pengesahan,
'jenis' => $jenis,
'amaun' => $amount,
'kodlaluan' => $teller,
'khazanah' => Auth::user() ? Auth::user()->name : '',
]);
} catch (\Throwable $e) {
Log::warning('Aliran tunai teller notification failed: '.$e->getMessage(), [
'kodcaw' => $kodcaw,
'teller' => $teller,
'jenis' => $jenis,
]);
}
}
public function SuratAkuanGadaian(Request $request) public function SuratAkuanGadaian(Request $request)
{ {
$auth_user = Auth::user(); $auth_user = Auth::user();
+40 -3
View File
@@ -9,6 +9,7 @@ use App\Events\StatusLiked;
use App\Events\NotifikasiKelulusan; use App\Events\NotifikasiKelulusan;
use App\Events\NotifikasiAnggota; use App\Events\NotifikasiAnggota;
use App\Events\Dashboard\KomoditiNotify; use App\Events\Dashboard\KomoditiNotify;
use App\Services\NotificationService;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@@ -309,9 +310,48 @@ class OperasiController extends Controller
event(new StatusLiked($auth_user['cawangan'], $auth_user['name'])); event(new StatusLiked($auth_user['cawangan'], $auth_user['name']));
$this->notifyGadaiAboveTenThousand($auth_user, $request, $cawangan_info);
return response()->json($siri, 201); return response()->json($siri, 201);
} }
/**
* Inbox for PC/PPC and operasi when teller mohon kelulusan gadai >= RM10,000.
*/
protected function notifyGadaiAboveTenThousand($auth_user, Request $request, $cawangan_info)
{
$raw = str_replace(',', '', (string) $request->input('jumpinjam', 0));
$pinjaman = is_numeric($raw) ? (float) $raw : 0;
if ($pinjaman < NotificationService::GADAI_KELULUSAN_MIN) {
return;
}
$status = strtolower((string) $request->input('status', ''));
if (strpos($status, 'tebus') !== false) {
return;
}
$namaCaw = '';
if (is_array($cawangan_info)) {
$namaCaw = isset($cawangan_info['details_caw']) ? $cawangan_info['details_caw'] : '';
}
try {
app(NotificationService::class)->notifyGadaiKelulusan($auth_user['cawangan'], [
'norujukan' => $request->norujukan,
'pinjaman' => $pinjaman,
'teller' => $request->teller ?: $auth_user['name'],
'cawangan_nama' => $namaCaw,
'status' => $request->status,
]);
} catch (\Throwable $e) {
Log::warning('Gadai kelulusan notification failed: '.$e->getMessage(), [
'norujukan' => $request->norujukan,
'cawangan' => $auth_user['cawangan'],
]);
}
}
public function createkelulusanAnggota(Request $request) public function createkelulusanAnggota(Request $request)
{ {
@@ -320,8 +360,6 @@ class OperasiController extends Controller
$api_url = config('api.url'); $api_url = config('api.url');
$current = Carbon::now(); $current = Carbon::now();
Log::info('createkelulusanAnggota', $request->all());
// Dapatkan maklumat cawangan // Dapatkan maklumat cawangan
$cawangan_info = Http::withOptions(['verify' => config('app.api_verify')]) $cawangan_info = Http::withOptions(['verify' => config('app.api_verify')])
->get($api_url . '/api/cawangan/' . $auth_user['cawangan']) ->get($api_url . '/api/cawangan/' . $auth_user['cawangan'])
@@ -360,7 +398,6 @@ class OperasiController extends Controller
'body' => $response->body(), 'body' => $response->body(),
]); ]);
} catch (\Throwable $th) { } catch (\Throwable $th) {
Log::error('❌ createkelulusanAnggota error: ' . $th->getMessage());
return response()->json([ return response()->json([
'success' => false, 'success' => false,
'message' => $th->getMessage(), 'message' => $th->getMessage(),
+205
View File
@@ -4,6 +4,7 @@ namespace App\Services;
use App\StaffNotification; use App\StaffNotification;
use App\User; use App\User;
use Illuminate\Support\Facades\Log;
class NotificationService class NotificationService
{ {
@@ -12,6 +13,10 @@ class NotificationService
const TYPE_PEMBIAYAAN_BATCH_OPERASI = 'pembiayaan_online.batch_operasi'; const TYPE_PEMBIAYAAN_BATCH_OPERASI = 'pembiayaan_online.batch_operasi';
const TYPE_PEMBIAYAAN_REJECTED_PC = 'pembiayaan_online.rejected_pc'; const TYPE_PEMBIAYAAN_REJECTED_PC = 'pembiayaan_online.rejected_pc';
const TYPE_PEMBIAYAAN_PAID_PC = 'pembiayaan_online.paid_pc'; const TYPE_PEMBIAYAAN_PAID_PC = 'pembiayaan_online.paid_pc';
const TYPE_GADAI_KELULUSAN = 'gadai.kelulusan';
const TYPE_ALIRAN_TUNAI_PENDING = 'aliran_tunai.pending_khazanah';
const TYPE_ALIRAN_TUNAI_DECIDED = 'aliran_tunai.decided_teller';
const GADAI_KELULUSAN_MIN = 10000;
/** /**
* Create one inbox row per staff matching role + branch. * Create one inbox row per staff matching role + branch.
@@ -70,6 +75,10 @@ class NotificationService
$role = isset($audience['role']) ? $audience['role'] : null; $role = isset($audience['role']) ? $audience['role'] : null;
$data = isset($input['data']) && is_array($input['data']) ? $input['data'] : []; $data = isset($input['data']) && is_array($input['data']) ? $input['data'] : [];
if ($type === self::TYPE_ALIRAN_TUNAI_PENDING && $kodcaw) {
return $this->notifyAliranTunaiPending($kodcaw, $data);
}
$payload = $this->payloadForType($type, $kodcaw, $data); $payload = $this->payloadForType($type, $kodcaw, $data);
if (!empty($input['title'])) { if (!empty($input['title'])) {
@@ -93,6 +102,90 @@ class NotificationService
return collect(); return collect();
} }
/**
* Gadai >= RM10,000: inbox for PC/PPC at the branch and all operasi.
*
* @param string $kodcaw
* @param array $data
* @return \Illuminate\Support\Collection
*/
public function notifyGadaiKelulusan($kodcaw, array $data)
{
$pinjaman = isset($data['pinjaman']) ? (float) $data['pinjaman'] : 0;
if ($pinjaman < self::GADAI_KELULUSAN_MIN) {
return collect();
}
$payload = $this->payloadForType(self::TYPE_GADAI_KELULUSAN, $kodcaw, $data);
$created = collect();
foreach (['pc', 'ppc'] as $role) {
$created = $created->merge(
$this->notifyRoleAtCawangan($role, $kodcaw, array_merge($payload, [
'url' => '/kelulusan',
]))
);
}
return $created->merge(
$this->notifyRole('operasi', array_merge($payload, [
'url' => '/operasi/kelulusan',
]))
);
}
/**
* Teller pendahuluan/serahan: inbox for PC/PPC at the branch.
*
* @param string $kodcaw
* @param array $data
* @return \Illuminate\Support\Collection
*/
public function notifyAliranTunaiPending($kodcaw, array $data)
{
$payload = $this->payloadForType(self::TYPE_ALIRAN_TUNAI_PENDING, $kodcaw, $data);
$created = collect();
foreach (['pc', 'ppc'] as $role) {
$created = $created->merge($this->notifyRoleAtCawangan($role, $kodcaw, $payload));
}
return $created;
}
/**
* Khazanah lulus/batal: inbox for the teller who requested cash.
*
* @param string $kodcaw
* @param string $tellerName
* @param array $data
* @return \Illuminate\Support\Collection
*/
public function notifyAliranTunaiDecided($kodcaw, $tellerName, array $data)
{
$tellerName = trim((string) $tellerName);
if ($tellerName === '') {
return collect();
}
$payload = $this->payloadForType(self::TYPE_ALIRAN_TUNAI_DECIDED, $kodcaw, $data);
$user = User::query()
->where('name', $tellerName)
->where('cawangan', $kodcaw)
->first();
if (!$user) {
$user = User::query()->where('name', $tellerName)->first();
}
if (!$user) {
return collect();
}
return collect([$this->notifyUser($user, $kodcaw, $user->roles, $payload)]);
}
/** /**
* @param string $type * @param string $type
* @param string|null $kodcaw * @param string|null $kodcaw
@@ -223,6 +316,78 @@ class NotificationService
]; ];
} }
if ($type === self::TYPE_ALIRAN_TUNAI_PENDING) {
$jenis = $this->aliranTunaiJenis($data);
$amaun = $this->aliranTunaiAmaun($data, $jenis);
$teller = isset($data['kodlaluan']) ? trim((string) $data['kodlaluan']) : '';
$label = $jenis === 'serahan' ? 'serahan' : 'pendahuluan';
return [
'type' => $type,
'cawangan' => $kodcaw,
'title' => $jenis === 'serahan' ? 'Permohonan serahan modal' : 'Permohonan pendahuluan',
'body' => sprintf(
'%s mohon %s RM %s',
$teller !== '' ? $teller : 'Teller',
$label,
number_format($amaun, 2)
),
'url' => $jenis === 'serahan' ? '/serahan' : '/pendahuluan',
'data' => $data,
];
}
if ($type === self::TYPE_ALIRAN_TUNAI_DECIDED) {
$jenis = $this->aliranTunaiJenis($data);
$amaun = $this->aliranTunaiAmaun($data, $jenis);
$lulus = isset($data['pengesahan']) && strcasecmp((string) $data['pengesahan'], 'Lulus') === 0;
$label = 'Pendahuluan';
if ($jenis === 'serahan') {
$label = 'Serahan';
} elseif ($jenis === 'all') {
$label = 'Aliran tunai';
}
if ($lulus) {
$title = $label.' diluluskan';
$body = sprintf('%s RM %s telah diluluskan', $label, number_format($amaun, 2));
} else {
$title = $label.' tidak diluluskan';
$body = sprintf('Permohonan %s tidak diluluskan', strtolower($label));
}
return [
'type' => $type,
'cawangan' => $kodcaw,
'title' => $title,
'body' => $body,
'url' => '/aliran_tunai',
'data' => $data,
];
}
if ($type === self::TYPE_GADAI_KELULUSAN) {
$norujukan = !empty($data['norujukan']) ? $data['norujukan'] : 'gadai';
$amaun = number_format(isset($data['pinjaman']) ? (float) $data['pinjaman'] : 0, 2);
$teller = isset($data['teller']) ? trim((string) $data['teller']) : '';
$namaCaw = !empty($data['cawangan_nama']) ? $data['cawangan_nama'] : $kodcaw;
return [
'type' => $type,
'cawangan' => $kodcaw,
'title' => 'Kelulusan gadai melebihi RM 10,000',
'body' => sprintf(
'%s — %s (RM %s)%s',
$namaCaw,
$norujukan,
$amaun,
$teller !== '' ? ' — '.$teller : ''
),
'url' => '/kelulusan',
'data' => $data,
];
}
return [ return [
'type' => $type, 'type' => $type,
'cawangan' => $kodcaw, 'cawangan' => $kodcaw,
@@ -233,6 +398,46 @@ class NotificationService
]; ];
} }
/**
* @param array $data
* @return string pendahuluan|serahan|all
*/
protected function aliranTunaiJenis(array $data)
{
if (!empty($data['jenis'])) {
$jenis = strtolower(trim((string) $data['jenis']));
if ($jenis === 'serahan' || $jenis === 'pendahuluan' || $jenis === 'all') {
return $jenis;
}
}
$modal = isset($data['modal']) ? (float) $data['modal'] : 0;
$serahan = isset($data['serahan']) ? (float) $data['serahan'] : 0;
if ($serahan > 0 && $modal <= 0) {
return 'serahan';
}
return 'pendahuluan';
}
/**
* @param array $data
* @param string $jenis
* @return float
*/
protected function aliranTunaiAmaun(array $data, $jenis)
{
if (isset($data['amaun']) && is_numeric($data['amaun'])) {
return (float) $data['amaun'];
}
if ($jenis === 'serahan') {
return isset($data['serahan']) ? (float) $data['serahan'] : 0;
}
return isset($data['modal']) ? (float) $data['modal'] : 0;
}
/** /**
* Create one inbox row for a specific user. * Create one inbox row for a specific user.
* *
@@ -204,7 +204,7 @@
/> />
<div class="title"> <div class="title">
<h1>Borang Pengesahan Penerimaan Pembiayaan<br>Secara Online (Akaun Bank)</h1> <h1>Borang Pengesahan Penerimaan Pembiayaan<br>Secara Atas Talian (Online)</h1>
<p>Untuk diisi dan ditandatangani oleh pelanggan semasa transaksi gadai.</p> <p>Untuk diisi dan ditandatangani oleh pelanggan semasa transaksi gadai.</p>
</div> </div>
+1 -2
View File
@@ -246,9 +246,8 @@
</div> </div>
<div class="form-check form-check-inline"> <div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="cara_terima" id="cara_terima_online" value="ONLINE"> <input class="form-check-input" type="radio" name="cara_terima" id="cara_terima_online" value="ONLINE">
<label class="form-check-label" for="cara_terima_online">Online (Akaun Bank)</label> <label class="form-check-label" for="cara_terima_online">Atas Talian (Online)</label>
</div> </div>
<small class="form-text text-muted">Pilihan Online untuk pembiayaan RM5,000 – RM50,000 sahaja.</small>
</div> </div>
</div> </div>
<div id="bank_online_section" style="display:none;"> <div id="bank_online_section" style="display:none;">
+4 -51
View File
@@ -283,7 +283,7 @@
}); });
</script> </script>
@elseif($auth_user['role_harian'] == 'teller') @elseif($auth_user['role_harian'] == 'teller' || $auth_user['roles'] == 'teller')
<li class="nav-item dropdown" style="margin-right:10px;"> <li class="nav-item dropdown" style="margin-right:10px;">
<a id="navbarDropdown" <a id="navbarDropdown"
class="nav-link" class="nav-link"
@@ -295,60 +295,13 @@
v-pre v-pre
style="color: {{ isset($cawangan_info['details_caw']) && $cawangan_info['details_caw'] == 'Putatan' ? 'white' : (isset($cawangan_info['details_caw']) && $cawangan_info['details_caw'] == 'Putatan 2' ? 'black' : '#1abc9c') }};"> style="color: {{ isset($cawangan_info['details_caw']) && $cawangan_info['details_caw'] == 'Putatan' ? 'white' : (isset($cawangan_info['details_caw']) && $cawangan_info['details_caw'] == 'Putatan 2' ? 'black' : '#1abc9c') }};">
<i id="bell" class="fa fa-bell"></i> <i id="bell" class="fa fa-bell"></i>
<span id="staff-notification-badge" class="staff-notification-badge"></span>
</a> </a>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown"> <div class="dropdown-menu dropdown-menu-right staff-notification-menu" aria-labelledby="navbarDropdown">
<div id="divbiasa"> @include('layouts.partials.notification_inbox')
<a class="dropdown-item" id="gadailinkz" href="#">
Tiada Data
</a>
</div>
</div> </div>
</li> </li>
{{-- <script>
// Enable pusher logging - don't include this in production
// Pusher.logToConsole = true;
var pusher = new Pusher("{{ env('PUSHER_APP_KEY') }}", {
cluster: 'ap1',
encrypted: true
});
var channel = pusher.subscribe('notifikasi-pembayaran');
channel.bind('trigger-pembayaran', function(data) {
let message = JSON.stringify(data);
$('#bell').css("color", "red");
$('#gadailinkz').text("Khazanah Serah Marhun Teller");
// $("#gadailinkz").attr("href", "{{ url('kelulusan') }}");
const Toast = Swal.mixin({
toast: true,
position: 'top-end',
showConfirmButton: false,
timer: 5000,
timerProgressBar: true,
didOpen: (toast) => {
toast.addEventListener('mouseenter', Swal.stopTimer)
toast.addEventListener('mouseleave', Swal.resumeTimer)
}
});
Toast.fire({
icon: 'error',
title: 'Notifikasi Pembayaran!'
});
});
$("#navbarDropdown").click(function(){
$('#bell').css("color", "grey");
});
</script> --}}
@endif @endif
@@ -54,8 +54,10 @@
@if($pendingCount > 0) @if($pendingCount > 0)
<form method="post" <form method="post"
action="{{ route('pembiayaan.online.operasi.approve.batch', ['kodcaw' => $kodcaw, 'batchId' => $batch['id']]) }}" action="{{ route('pembiayaan.online.operasi.approve.batch', ['kodcaw' => $kodcaw, 'batchId' => $batch['id']]) }}"
class="d-inline" class="d-inline js-swal-confirm"
onsubmit="return confirm('Luluskan semua {{ $pendingCount }} rekod menunggu dalam batch ini?');"> data-title="Lulus Semua Menunggu?"
data-html="Luluskan semua <b>{{ $pendingCount }}</b> rekod menunggu dalam batch <b>{{ $batch['batch_no'] ?? '-' }}</b>?"
data-confirm="Ya, Lulus Semua">
@csrf @csrf
<button type="submit" class="btn btn-success btn-sm">Lulus Semua Menunggu ({{ $pendingCount }})</button> <button type="submit" class="btn btn-success btn-sm">Lulus Semua Menunggu ({{ $pendingCount }})</button>
</form> </form>
@@ -116,8 +118,10 @@
@if(in_array($st, ['IN_BATCH', 'OPS_REVIEW'], true)) @if(in_array($st, ['IN_BATCH', 'OPS_REVIEW'], true))
<form method="post" <form method="post"
action="{{ route('pembiayaan.online.operasi.approve', ['kodcaw' => $kodcaw, 'id' => $line['id']]) }}" action="{{ route('pembiayaan.online.operasi.approve', ['kodcaw' => $kodcaw, 'id' => $line['id']]) }}"
class="d-inline" class="d-inline js-swal-confirm"
onsubmit="return confirm('Luluskan rekod {{ $line['norujukan'] ?? '' }}?');"> data-title="Luluskan Rekod?"
data-html="Luluskan rekod <b>{{ $line['norujukan'] ?? '-' }}</b>?"
data-confirm="Ya, Lulus">
@csrf @csrf
<input type="hidden" name="batch_id" value="{{ $batch['id'] }}"> <input type="hidden" name="batch_id" value="{{ $batch['id'] }}">
<button type="submit" class="btn btn-success btn-sm">Lulus</button> <button type="submit" class="btn btn-success btn-sm">Lulus</button>
@@ -133,7 +137,7 @@
class="btn btn-dark btn-sm btn-paid" class="btn btn-dark btn-sm btn-paid"
data-id="{{ $line['id'] }}" data-id="{{ $line['id'] }}"
data-norujukan="{{ $line['norujukan'] ?? '' }}"> data-norujukan="{{ $line['norujukan'] ?? '' }}">
Dibayar + Bukti Tanda sebagai dibayar
</button> </button>
<button type="button" <button type="button"
class="btn btn-outline-danger btn-sm btn-reject" class="btn btn-outline-danger btn-sm btn-reject"
@@ -226,12 +230,12 @@
<div class="form-group"> <div class="form-group">
<label>Bukti Bayaran <span class="text-danger">*</span></label> <label>Bukti Bayaran <span class="text-danger">*</span></label>
<input type="file" name="bukti" class="form-control-file" accept=".pdf,.jpg,.jpeg,.png,.gif" required> <input type="file" name="bukti" class="form-control-file" accept=".pdf,.jpg,.jpeg,.png,.gif" required>
<small class="text-muted">PDF / imej slip pindahan bank</small> <small class="text-muted">PDF / imej slip bank</small>
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Batal</button> <button type="button" class="btn btn-secondary" data-dismiss="modal">Batal</button>
<button type="submit" class="btn btn-dark">Simpan sebagai dibayar</button> <button type="submit" class="btn btn-dark">Simpan</button>
</div> </div>
</div> </div>
</form> </form>
@@ -267,7 +271,7 @@
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Batal</button> <button type="button" class="btn btn-secondary" data-dismiss="modal">Batal</button>
<button type="submit" class="btn btn-dark">Simpan semua sebagai dibayar</button> <button type="submit" class="btn btn-dark">Simpan</button>
</div> </div>
</div> </div>
</form> </form>
@@ -316,5 +320,25 @@
$('#paidNorujukan').text($(this).data('norujukan')); $('#paidNorujukan').text($(this).data('norujukan'));
$('#paidModal').modal('show'); $('#paidModal').modal('show');
}); });
$(document).on('submit', '.js-swal-confirm', function (e) {
e.preventDefault();
var form = this;
Swal.fire({
title: $(form).attr('data-title') || 'Pengesahan',
html: $(form).attr('data-html') || 'Teruskan tindakan ini?',
icon: 'question',
showCancelButton: true,
confirmButtonColor: '#3085d6',
cancelButtonColor: '#d33',
confirmButtonText: $(form).attr('data-confirm') || 'Ya',
cancelButtonText: 'Batal',
allowOutsideClick: false
}).then(function (result) {
if (result.isConfirmed) {
form.submit();
}
});
});
</script> </script>
@endsection @endsection
@@ -36,6 +36,17 @@
$lineRows = collect($lines); $lineRows = collect($lines);
$verifiedCount = $lineRows->where('status', 'VERIFIED_PC')->count(); $verifiedCount = $lineRows->where('status', 'VERIFIED_PC')->count();
$unverifiedCount = $lineRows->count() - $verifiedCount; $unverifiedCount = $lineRows->count() - $verifiedCount;
$slotArr = is_array($slot) ? $slot : [];
$slotName = $slotArr['slot'] ?? 'BATCH1';
$slotCutoff = $slotArr['cutoff'] ?? '11:00';
$slotProses = $slotArr['proses'] ?? '12:30';
$slotLewat = !empty($slotArr['lewat']);
$slotJadual = $slotArr['jadual'] ?? [
['slot' => 'BATCH1', 'label' => 'Batch 1', 'mula' => '00:00', 'cutoff' => '11:00', 'proses' => '12:30'],
['slot' => 'BATCH2', 'label' => 'Batch 2', 'mula' => '11:00', 'cutoff' => '15:00', 'proses' => '16:30'],
];
$slotBakiMinit = (int) ($slotArr['baki_minit'] ?? 0);
$slotHampirTamat = !$slotLewat && $slotBakiMinit > 0 && $slotBakiMinit <= 30;
@endphp @endphp
@if($isClosed) @if($isClosed)
@@ -59,6 +70,66 @@
</div> </div>
@endif @endif
<div class="alert {{ $slotLewat ? 'alert-warning' : ($slotHampirTamat ? 'alert-warning' : 'alert-info') }} mb-3">
<div class="d-flex justify-content-between align-items-start flex-wrap">
<div class="mb-2 mr-3">
<strong>Jadual Slot Batch</strong>
<div class="mt-1">
PC / PPC sila semak dan <b>hantar senarai sebelum tamat slot</b>.
Rekod yang dihantar selepas cutoff mungkin diproses pada slot atau hari berikutnya.
</div>
</div>
<div class="mb-2 text-md-right">
@if($slotLewat)
Slot semasa: <b>{{ $slotName }}</b> (selepas cutoff {{ $slotCutoff }})
@else
Slot semasa: <b>{{ $slotName }}</b>
&middot; Hantar sebelum <b>{{ $slotCutoff }}</b>
<div class="small" id="slotCountdown"
data-cutoff="{{ $slotCutoff }}"
data-lewat="{{ $slotLewat ? '1' : '0' }}">
@if($slotBakiMinit > 0)
Baki masa: {{ intdiv($slotBakiMinit, 60) }}j {{ $slotBakiMinit % 60 }}m
@endif
</div>
@endif
</div>
</div>
<div class="table-responsive mb-0">
<table class="table table-sm table-bordered mb-0 bg-white">
<thead class="thead-light">
<tr>
<th>Slot</th>
<th>Hantar senarai sebelum</th>
<th>Proses Operasi</th>
</tr>
</thead>
<tbody>
@foreach($slotJadual as $row)
@php
$rowSlot = $row['slot'] ?? '';
$isCurrent = $rowSlot === $slotName && !$slotLewat;
$isDone = $slotLewat
|| ($slotName === 'BATCH2' && $rowSlot === 'BATCH1');
@endphp
<tr class="{{ $isCurrent ? 'table-primary' : '' }}">
<td>
{{ $row['label'] ?? $rowSlot }}
@if($isCurrent)
<span class="badge badge-primary">Semasa</span>
@elseif($isDone)
<span class="badge badge-secondary">Tamat</span>
@endif
</td>
<td>{{ $row['cutoff'] ?? '-' }}</td>
<td>~ {{ $row['proses'] ?? '-' }}</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
<div class="row mb-3"> <div class="row mb-3">
<div class="col-md-4"> <div class="col-md-4">
<div class="card"> <div class="card">
@@ -85,7 +156,7 @@
<div class="col-md-4"> <div class="col-md-4">
<div class="card"> <div class="card">
<div class="card-body text-center"> <div class="card-body text-center">
<div class="text-muted">Queue PC</div> <div class="text-muted">Senarai Pelanggan menunggu semakan</div>
<h4 class="mb-0">{{ count($lines) }} rekod</h4> <h4 class="mb-0">{{ count($lines) }} rekod</h4>
<small> <small>
{{ $verifiedCount }} sudah sah {{ $verifiedCount }} sudah sah
@@ -96,11 +167,19 @@
</div> </div>
</div> </div>
<div class="col-md-4"> <div class="col-md-4">
<div class="card"> <div class="card {{ $slotLewat || $slotHampirTamat ? 'border-warning' : '' }}">
<div class="card-body text-center"> <div class="card-body text-center">
<div class="text-muted">Slot Cadangan</div> <div class="text-muted">Slot Semasa</div>
<h4 class="mb-0">{{ $slot['slot'] ?? '-' }}</h4> <h4 class="mb-0">{{ $slotName }}</h4>
<small>{{ $slot['nota'] ?? '' }}</small> <small>
@if($slotLewat)
Cutoff {{ $slotCutoff }} sudah tamat<br>
Masih boleh hantar; proses mungkin esok
@else
Hantar sebelum <b>{{ $slotCutoff }}</b>
&middot; Proses ~{{ $slotProses }}
@endif
</small>
</div> </div>
</div> </div>
</div> </div>
@@ -115,10 +194,15 @@
<p class="small text-muted"> <p class="small text-muted">
Semak setiap rekod <b>satu-satu</b> (maklumat pelanggan, bank, amaun). Semak setiap rekod <b>satu-satu</b> (maklumat pelanggan, bank, amaun).
Hanya rekod berstatus <span class="badge badge-success">SAH</span> boleh dipilih dan dihantar ke Operasi. Hanya rekod berstatus <span class="badge badge-success">SAH</span> boleh dipilih dan dihantar ke Operasi.
@if($slotLewat)
Cutoff {{ $slotName }} ({{ $slotCutoff }}) sudah tamat — hantar sekarang jika perlu; Operasi mungkin proses esok.
@else
Sila hantar senarai <b>{{ $slotName }}</b> sebelum <b>{{ $slotCutoff }}</b>.
@endif
</p> </p>
<form id="formSubmitBatch" method="post" action="{{ route('pembiayaan.online.pc.submit') }}"> <form id="formSubmitBatch" method="post" action="{{ route('pembiayaan.online.pc.submit') }}">
@csrf @csrf
<input type="hidden" name="slot" value="{{ $slot['slot'] ?? 'BATCH1' }}"> <input type="hidden" name="slot" value="{{ $slotName }}">
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-striped table-hover table-sm text-center" id="queueTable"> <table class="table table-striped table-hover table-sm text-center" id="queueTable">
<thead class="thead-dark"> <thead class="thead-dark">
@@ -440,17 +524,46 @@
}); });
$('#btnSubmitBatch').on('click', function () { $('#btnSubmitBatch').on('click', function () {
if ($('.row-check:not(:disabled):checked').length === 0) return; var n = $('.row-check:not(:disabled):checked').length;
if (!confirm('Hantar rekod yang sudah disahkan ke Operasi?')) return; if (n === 0) return;
$('#formSubmitBatch').submit(); Swal.fire({
title: 'Hantar Batch ke Operasi?',
html: n + ' rekod yang sudah SAH akan dihantar sebagai <b>{{ $slotName }}</b>.'
+ '<br>Sila hantar sebelum cutoff <b>{{ $slotCutoff }}</b>.',
icon: 'question',
showCancelButton: true,
confirmButtonColor: '#3085d6',
cancelButtonColor: '#d33',
confirmButtonText: 'Ya, Hantar',
cancelButtonText: 'Batal',
allowOutsideClick: false
}).then(function (result) {
if (result.isConfirmed) {
$('#formSubmitBatch').submit();
}
});
}); });
$('.btn-unverify').on('click', function () { $('.btn-unverify').on('click', function () {
var id = $(this).data('id'); var id = $(this).data('id');
var sag = $(this).data('norujukan') || ''; var sag = $(this).data('norujukan') || '';
if (!confirm('Batal semakan ' + sag + '? Rekod ini tidak boleh dihantar sehingga disemak semula.')) return; Swal.fire({
$('#formUnverify').attr('action', "{{ url('/pembiayaan-online/pc/lines') }}/" + id + "/unverify"); title: 'Batal Semakan?',
$('#formUnverify').submit(); html: 'Batal semakan <b>' + (sag || 'rekod ini') + '</b>?'
+ '<br>Rekod ini tidak boleh dihantar sehingga disemak semula.',
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#3085d6',
cancelButtonColor: '#d33',
confirmButtonText: 'Ya, Batal',
cancelButtonText: 'Tutup',
allowOutsideClick: false
}).then(function (result) {
if (result.isConfirmed) {
$('#formUnverify').attr('action', "{{ url('/pembiayaan-online/pc/lines') }}/" + id + "/unverify");
$('#formUnverify').submit();
}
});
}); });
$('.btn-verify').on('click', function () { $('.btn-verify').on('click', function () {
@@ -513,5 +626,32 @@
$('#editKodbank').on('change', function () { $('#editKodbank').on('change', function () {
$('#editNamabank').val($('#editKodbank option:selected').data('nama') || ''); $('#editNamabank').val($('#editKodbank option:selected').data('nama') || '');
}); });
function pad2(n) {
return n < 10 ? '0' + n : String(n);
}
function tickSlotCountdown() {
var el = document.getElementById('slotCountdown');
if (!el) return;
if (el.getAttribute('data-lewat') === '1') return;
var cutoff = el.getAttribute('data-cutoff') || '';
var parts = cutoff.split(':');
if (parts.length < 2) return;
var now = new Date();
var end = new Date(now.getFullYear(), now.getMonth(), now.getDate(), parseInt(parts[0], 10), parseInt(parts[1], 10), 0);
var diff = end - now;
if (diff <= 0) {
el.textContent = 'Cutoff telah tamat — sila muat semula halaman';
return;
}
var h = Math.floor(diff / 3600000);
var m = Math.floor((diff % 3600000) / 60000);
var s = Math.floor((diff % 60000) / 1000);
el.textContent = 'Baki masa: ' + h + 'j ' + pad2(m) + 'm ' + pad2(s) + 's';
}
tickSlotCountdown();
setInterval(tickSlotCountdown, 1000);
</script> </script>
@endsection @endsection
@@ -131,7 +131,7 @@
</div> </div>
</div> </div>
<div class="card"> {{-- <div class="card">
<div class="card-header"> <div class="card-header">
<h5 class="mb-0">Tutup Modal Online (EOD)</h5> <h5 class="mb-0">Tutup Modal Online (EOD)</h5>
</div> </div>
@@ -153,7 +153,7 @@
</form> </form>
@endif @endif
</div> </div>
</div> </div> --}}
</div> </div>
<div class="col-md-7 mb-3"> <div class="col-md-7 mb-3">
@@ -195,7 +195,7 @@
/> />
<div class="title"> <div class="title">
<h1>Borang Pengesahan Penerimaan Pembiayaan<br>Secara Online (Akaun Bank)</h1> <h1>Borang Pengesahan Penerimaan Pembiayaan<br>Secara Atas Talian (Online)</h1>
<p>Untuk diisi dan ditandatangani oleh pelanggan semasa transaksi gadai.</p> <p>Untuk diisi dan ditandatangani oleh pelanggan semasa transaksi gadai.</p>
</div> </div>