Feature/whatsapp blast #24
@@ -93,6 +93,65 @@ class GadaianController extends Controller
|
||||
return is_numeric($cleaned) ? (float) $cleaned : 0.0;
|
||||
}
|
||||
|
||||
protected function normalizeBorangOnlineFields($norujukan, $pinjaman, $kodbank, $noakaun, $namaAkaun)
|
||||
{
|
||||
return [
|
||||
'norujukan' => trim((string) $norujukan),
|
||||
'pinjaman' => number_format((float) $this->parseMoney($pinjaman), 2, '.', ''),
|
||||
'kodbank' => strtoupper(trim((string) $kodbank)),
|
||||
'noakaun' => preg_replace('/\s+/', '', (string) $noakaun),
|
||||
'nama_akaun' => strtoupper(trim((string) $namaAkaun)),
|
||||
];
|
||||
}
|
||||
|
||||
protected function borangOnlineFingerprint(array $fields)
|
||||
{
|
||||
return sha1(implode('|', [
|
||||
$fields['norujukan'],
|
||||
$fields['pinjaman'],
|
||||
$fields['kodbank'],
|
||||
$fields['noakaun'],
|
||||
$fields['nama_akaun'],
|
||||
]));
|
||||
}
|
||||
|
||||
protected function markBorangOnlinePrinted(array $fields)
|
||||
{
|
||||
Session::put('borang_online_printed', array_merge($fields, [
|
||||
'fingerprint' => $this->borangOnlineFingerprint($fields),
|
||||
]));
|
||||
}
|
||||
|
||||
protected function hasMatchingBorangOnlinePrint(Request $request)
|
||||
{
|
||||
$printed = $request->session()->get('borang_online_printed');
|
||||
if (!is_array($printed)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$norujukan = (string) $request->input('norujukan_gadai', $request->input('norujukan'));
|
||||
$fields = $this->normalizeBorangOnlineFields(
|
||||
$norujukan,
|
||||
$request->input('pinjaman'),
|
||||
$request->input('kodbank'),
|
||||
$request->input('noakaun'),
|
||||
$request->input('nama_akaun') ?: $request->input('nama_pelanggan')
|
||||
);
|
||||
|
||||
return ($printed['norujukan'] ?? '') === $fields['norujukan']
|
||||
&& ($printed['fingerprint'] ?? '') === $this->borangOnlineFingerprint($fields);
|
||||
}
|
||||
|
||||
protected function borangOnlinePrintedFor($norujukan)
|
||||
{
|
||||
$printed = Session::get('borang_online_printed');
|
||||
if (!is_array($printed) || ($printed['norujukan'] ?? '') !== (string) $norujukan) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $printed;
|
||||
}
|
||||
|
||||
public function index(Request $request)
|
||||
{
|
||||
|
||||
@@ -120,6 +179,7 @@ class GadaianController extends Controller
|
||||
Session::put('nokp', $noic);
|
||||
Session::put('nopelanggan', $customer_info['nopelanggan']);
|
||||
Session::put('page_reload', true);
|
||||
Session::forget('borang_online_printed');
|
||||
$gadai = Http::withOptions(['verify' => config('app.api_verify')])->post(config('api.url') . '/api/gadai/' . $auth_user['cawangan'], [
|
||||
'norujukan' => $norujukan,
|
||||
'nopelanggan' => $customer_info['nopelanggan'],
|
||||
@@ -144,6 +204,7 @@ class GadaianController extends Controller
|
||||
Session::put('nokp', $nokp);
|
||||
Session::put('nopelanggan', $nopelanggan);
|
||||
Session::put('page_reload', true);
|
||||
Session::forget('borang_online_printed');
|
||||
$gadai_baru = Http::withOptions(['verify' => config('app.api_verify')])->post(config('api.url') . '/api/gadai/' . $auth_user['cawangan'], [
|
||||
'norujukan' => $norujukan_baru,
|
||||
'nopelanggan' => $nopelanggan,
|
||||
@@ -156,7 +217,8 @@ class GadaianController extends Controller
|
||||
|
||||
|
||||
$cawangan_info = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url') . '/api/cawangan/' . $auth_user['cawangan'])->json();
|
||||
return view('gadaian.' . $cawangan_info['version'] . '.index', compact('customer_info', 'limit_pinjaman_semasa', 'cawangan_info', 'norujukan', 'api_url', 'current'));
|
||||
$borang_online_printed = $this->borangOnlinePrintedFor($norujukan);
|
||||
return view('gadaian.' . $cawangan_info['version'] . '.index', compact('customer_info', 'limit_pinjaman_semasa', 'cawangan_info', 'norujukan', 'api_url', 'current', 'borang_online_printed'));
|
||||
}
|
||||
|
||||
public function edit(Request $request, $norujukan)
|
||||
@@ -223,6 +285,22 @@ class GadaianController extends Controller
|
||||
public function store(Request $request)
|
||||
{
|
||||
$auth_user = Auth::user();
|
||||
$caraTerima = strtoupper((string) $request->input('cara_terima', 'TUNAI'));
|
||||
$pinjamanAmt = round((float) $request->pinjaman, 2);
|
||||
|
||||
// Online only within min/max pinjaman; otherwise force cash path.
|
||||
if ($caraTerima === 'ONLINE' && ($pinjamanAmt < self::MIN_PINJAMAN || $pinjamanAmt > self::MAX_PINJAMAN)) {
|
||||
$caraTerima = 'TUNAI';
|
||||
}
|
||||
|
||||
if ($caraTerima === 'ONLINE' && !$this->isPembiayaanOnlineEnabled($auth_user['cawangan'])) {
|
||||
$caraTerima = 'TUNAI';
|
||||
}
|
||||
|
||||
if ($caraTerima === 'ONLINE' && !$this->hasMatchingBorangOnlinePrint($request)) {
|
||||
return redirect()->back()->with('error', 'Sila cetak borang pengesahan pelanggan sebelum tekan Terima.');
|
||||
}
|
||||
|
||||
//$total_nilai_marhun = $request->total_nilai_marhun;
|
||||
//need to get total nilai marhun by api get from table marhun
|
||||
|
||||
@@ -274,18 +352,6 @@ class GadaianController extends Controller
|
||||
'teller' => $auth_user['name']
|
||||
]);
|
||||
|
||||
$caraTerima = strtoupper((string) $request->input('cara_terima', 'TUNAI'));
|
||||
$pinjamanAmt = round((float) $request->pinjaman, 2);
|
||||
|
||||
// Online only within min/max pinjaman; otherwise force cash path.
|
||||
if ($caraTerima === 'ONLINE' && ($pinjamanAmt < self::MIN_PINJAMAN || $pinjamanAmt > self::MAX_PINJAMAN)) {
|
||||
$caraTerima = 'TUNAI';
|
||||
}
|
||||
|
||||
if ($caraTerima === 'ONLINE' && !$this->isPembiayaanOnlineEnabled($auth_user['cawangan'])) {
|
||||
$caraTerima = 'TUNAI';
|
||||
}
|
||||
|
||||
if ($caraTerima === 'ONLINE') {
|
||||
$online = Http::withOptions(['verify' => config('app.api_verify')])
|
||||
->asForm()
|
||||
@@ -320,6 +386,7 @@ class GadaianController extends Controller
|
||||
|
||||
$request->session()->forget('page_reload');
|
||||
$request->session()->forget('norujukan');
|
||||
$request->session()->forget('borang_online_printed');
|
||||
$request->session()->flash('activate_tab', 'SAG_baru');
|
||||
if ($caraTerima === 'ONLINE') {
|
||||
$request->session()->flash('message', 'Gadai berjaya. Pembiayaan online menunggu semakan PC.');
|
||||
@@ -371,6 +438,14 @@ class GadaianController extends Controller
|
||||
abort(422, 'Sila lengkapkan maklumat bank sebelum cetak borang.');
|
||||
}
|
||||
|
||||
$this->markBorangOnlinePrinted($this->normalizeBorangOnlineFields(
|
||||
$line['norujukan'],
|
||||
$line['pinjaman'],
|
||||
$line['kodbank'],
|
||||
$line['noakaun'],
|
||||
$line['nama_akaun'] ?: $line['nama_pelanggan']
|
||||
));
|
||||
|
||||
return $this->renderBorangPengesahanOnline($line, $kodcaw);
|
||||
}
|
||||
|
||||
@@ -501,6 +576,7 @@ class GadaianController extends Controller
|
||||
Http::withOptions(['verify' => config('app.api_verify')])->delete(config('api.url') . '/api/gadai/batal/' . $auth_user['cawangan'] . '/' . $norujukan);
|
||||
$request->session()->forget('page_reload');
|
||||
$request->session()->forget('norujukan');
|
||||
$request->session()->forget('borang_online_printed');
|
||||
return redirect()->route('customer_info');
|
||||
}
|
||||
|
||||
|
||||
@@ -97,6 +97,52 @@ class PembiayaanOnlineController extends Controller
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Lightweight payload for the PC layout cutoff banner / browser notification.
|
||||
*/
|
||||
public function reminderStatus()
|
||||
{
|
||||
$cawangan_info = $this->cawanganInfo();
|
||||
if (!$this->isPembiayaanOnlineEnabled($cawangan_info)) {
|
||||
return response()->json(['enabled' => false]);
|
||||
}
|
||||
|
||||
$kodcaw = $this->kodcaw();
|
||||
$api_url = config('api.url');
|
||||
|
||||
$queue = $this->api()
|
||||
->get($api_url . '/api/pembiayaan-online/' . $kodcaw . '/queue')
|
||||
->json();
|
||||
$modal = $this->api()
|
||||
->get($api_url . '/api/modal-online/' . $kodcaw)
|
||||
->json();
|
||||
if (!is_array($queue)) {
|
||||
$queue = [];
|
||||
}
|
||||
if (!is_array($modal)) {
|
||||
$modal = [];
|
||||
}
|
||||
|
||||
$slot = (is_array($queue) && isset($queue['slot_cadangan']) && is_array($queue['slot_cadangan']))
|
||||
? $queue['slot_cadangan']
|
||||
: [];
|
||||
|
||||
return response()->json([
|
||||
'enabled' => true,
|
||||
'ditutup' => (int) ($modal['ditutup'] ?? 0) === 1,
|
||||
'bilangan' => (int) ($queue['bilangan'] ?? 0),
|
||||
'slot' => $slot['slot'] ?? 'BATCH1',
|
||||
'cutoff' => $slot['cutoff'] ?? '11:00',
|
||||
'cutoff_at' => $slot['cutoff_at'] ?? null,
|
||||
'server_now' => $slot['server_now'] ?? null,
|
||||
'lewat' => !empty($slot['lewat']),
|
||||
'baki_minit' => (int) ($slot['baki_minit'] ?? 0),
|
||||
'url' => route('pembiayaan.online.pc'),
|
||||
'remind_minutes' => 30,
|
||||
'last_minute' => 10,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update bank/docs for a queue line.
|
||||
*/
|
||||
|
||||
@@ -305,6 +305,9 @@
|
||||
<a href="{{ route('gadai.batal',['norujukan'=>$norujukan]) }}" class="float-right btn btn-danger mr-2">Batal</a>
|
||||
<input type="submit" name="terima_gadai" id="terima_gadai" class="float-right btn btn-primary mr-2" value="Terima">
|
||||
<button type="button" id="cetak_borang_online" class="float-right btn btn-info mr-2" style="display:none;">Cetak Borang</button>
|
||||
<div id="cetak_borang_hint" class="text-danger text-right mt-2" style="clear:both; display:none;">
|
||||
Sila cetak borang pengesahan pelanggan dahulu
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@@ -413,6 +416,7 @@
|
||||
var kodcaw_ujrah = "<?php echo $cawangan_info['kodcaw'] ?>";
|
||||
const MIN_PINJAMAN = {{ \App\Http\Controllers\GadaianController::MIN_PINJAMAN }};
|
||||
const MAX_PINJAMAN = {{ \App\Http\Controllers\GadaianController::MAX_PINJAMAN }};
|
||||
var printedBorang = @json($borang_online_printed ?? null);
|
||||
$('form input').keydown(function (e) {
|
||||
if (e.keyCode == 13) {
|
||||
e.preventDefault();
|
||||
@@ -424,6 +428,13 @@
|
||||
$(document).on("keydown", disableF5);
|
||||
var norujukan = $('#norujukan').val();
|
||||
getMarhun(norujukan);
|
||||
@if(session('error'))
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Amaran!',
|
||||
text: @json(session('error'))
|
||||
});
|
||||
@endif
|
||||
|
||||
$.ajax({
|
||||
method:'GET',
|
||||
@@ -1301,6 +1312,51 @@
|
||||
.then(response => response.json());
|
||||
}
|
||||
|
||||
function currentBorangFields() {
|
||||
var pinjaman = Number(String($('#pinjaman').val() || '').replace(/,/g, ''));
|
||||
if (!isFinite(pinjaman)) {
|
||||
pinjaman = 0;
|
||||
}
|
||||
return {
|
||||
norujukan: ($('#norujukan_gadai').val() || $('#norujukan').val() || '').trim(),
|
||||
pinjaman: pinjaman.toFixed(2),
|
||||
kodbank: ($('#gadai_kodbank').val() || '').trim().toUpperCase(),
|
||||
noakaun: ($('#gadai_noakaun').val() || '').replace(/\s+/g, ''),
|
||||
nama_akaun: ($('#gadai_nama_akaun').val() || $('input[name="nama_pelanggan"]').val() || '').trim().toUpperCase()
|
||||
};
|
||||
}
|
||||
|
||||
function isOnlineCaraTerima() {
|
||||
var pembiayaanOnlineEnabled = $('#pembiayaan_online_enabled').val() === '1';
|
||||
var pinjaman = Number(String($('#pinjaman').val() || '').replace(/,/g, ''));
|
||||
return pembiayaanOnlineEnabled
|
||||
&& pinjaman >= MIN_PINJAMAN
|
||||
&& pinjaman <= MAX_PINJAMAN
|
||||
&& $('input[name="cara_terima"]:checked').val() === 'ONLINE';
|
||||
}
|
||||
|
||||
function hasMatchingPrint() {
|
||||
if (!printedBorang) {
|
||||
return false;
|
||||
}
|
||||
var cur = currentBorangFields();
|
||||
return printedBorang.norujukan === cur.norujukan
|
||||
&& printedBorang.pinjaman === cur.pinjaman
|
||||
&& printedBorang.kodbank === cur.kodbank
|
||||
&& printedBorang.noakaun === cur.noakaun
|
||||
&& printedBorang.nama_akaun === cur.nama_akaun;
|
||||
}
|
||||
|
||||
function syncTerimaButton() {
|
||||
var lock = isOnlineCaraTerima() && !hasMatchingPrint();
|
||||
$('#terima_gadai').prop('disabled', lock);
|
||||
if (lock) {
|
||||
$('#cetak_borang_hint').show();
|
||||
} else {
|
||||
$('#cetak_borang_hint').hide();
|
||||
}
|
||||
}
|
||||
|
||||
function toggleCaraTerima(pinjaman) {
|
||||
var pembiayaanOnlineEnabled = $('#pembiayaan_online_enabled').val() === '1';
|
||||
if (pembiayaanOnlineEnabled && pinjaman >= MIN_PINJAMAN && pinjaman <= MAX_PINJAMAN) {
|
||||
@@ -1311,6 +1367,7 @@
|
||||
$('#bank_online_section').hide();
|
||||
$('#cetak_borang_online').hide();
|
||||
$('#cara_terima_tunai').prop('checked', true);
|
||||
syncTerimaButton();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1325,16 +1382,20 @@
|
||||
$('#bank_online_section').hide();
|
||||
$('#cetak_borang_online').hide();
|
||||
}
|
||||
syncTerimaButton();
|
||||
}
|
||||
|
||||
$(document).on('change', 'input[name="cara_terima"]', function () {
|
||||
toggleBankOnlineSection();
|
||||
});
|
||||
|
||||
$(document).on('change', '#gadai_kodbank', function () {
|
||||
$(document).on('change input', '#gadai_kodbank, #gadai_noakaun, #gadai_nama_akaun', function () {
|
||||
$('#gadai_nama_bank').val($('#gadai_kodbank option:selected').data('nama') || '');
|
||||
syncTerimaButton();
|
||||
});
|
||||
|
||||
syncTerimaButton();
|
||||
|
||||
$(document).on('click', '#cetak_borang_online', function () {
|
||||
var kodbank = $('#gadai_kodbank').val();
|
||||
var noakaun = ($('#gadai_noakaun').val() || '').trim();
|
||||
@@ -1367,6 +1428,9 @@
|
||||
form.append($('<input>', { type: 'hidden', name: 'noakaun', value: noakaun }));
|
||||
form.append($('<input>', { type: 'hidden', name: 'nama_akaun', value: namaAkaun }));
|
||||
form.appendTo('body').trigger('submit').remove();
|
||||
|
||||
printedBorang = currentBorangFields();
|
||||
syncTerimaButton();
|
||||
});
|
||||
|
||||
async function getModalOnline() {
|
||||
@@ -1523,7 +1587,7 @@
|
||||
let bakiakhir = 0;
|
||||
var kodcaw = $('#kodcaw').val();
|
||||
if(pinjaman == 0){
|
||||
$("#terima_gadai").prop('disabled', false);
|
||||
syncTerimaButton();
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Amaran!',
|
||||
@@ -1532,6 +1596,16 @@
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isOnlineCaraTerima() && !hasMatchingPrint()) {
|
||||
syncTerimaButton();
|
||||
Swal.fire({
|
||||
icon: 'warning',
|
||||
title: 'Borang belum dicetak',
|
||||
text: 'Sila cetak borang pengesahan pelanggan sebelum tekan Terima.'
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
let floatOk = await checkDisbursementFloat(pinjaman);
|
||||
if (!floatOk) {
|
||||
$("#terima_gadai").prop('disabled', false);
|
||||
|
||||
@@ -401,6 +401,7 @@
|
||||
</div>
|
||||
@else
|
||||
@endif
|
||||
@include('layouts.partials.batch_cutoff_banner')
|
||||
@endif
|
||||
|
||||
<main class="py-4">
|
||||
|
||||
@@ -0,0 +1,355 @@
|
||||
<style>
|
||||
#batch-cutoff-banner {
|
||||
position: fixed;
|
||||
top: 112px;
|
||||
right: 24px;
|
||||
z-index: 1080;
|
||||
width: min(380px, calc(100vw - 32px));
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
color: #fff;
|
||||
background: #b91c1c;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 18px 40px rgba(127, 29, 29, 0.35);
|
||||
}
|
||||
#batch-cutoff-banner.batch-cutoff-warn {
|
||||
background: #db2777;
|
||||
box-shadow: 0 18px 40px rgba(190, 24, 93, 0.32);
|
||||
}
|
||||
#batch-cutoff-banner.batch-cutoff-warn .batch-cutoff-link {
|
||||
color: #9d174d !important;
|
||||
}
|
||||
#batch-cutoff-banner.batch-cutoff-urgent {
|
||||
background: #991b1b;
|
||||
}
|
||||
#batch-cutoff-banner .batch-cutoff-inner {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
padding: 14px 16px 16px;
|
||||
}
|
||||
#batch-cutoff-banner .batch-cutoff-copy {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
min-width: 0;
|
||||
}
|
||||
#batch-cutoff-banner .batch-cutoff-icon {
|
||||
flex-shrink: 0;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 999px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(255, 255, 255, 0.14);
|
||||
font-size: 15px;
|
||||
}
|
||||
#batch-cutoff-banner .batch-cutoff-title {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.01em;
|
||||
line-height: 1.3;
|
||||
}
|
||||
#batch-cutoff-banner .batch-cutoff-sub {
|
||||
display: block;
|
||||
margin-top: 3px;
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
color: rgba(255, 255, 255, 0.82);
|
||||
}
|
||||
#batch-cutoff-banner .batch-cutoff-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding-left: 48px;
|
||||
}
|
||||
#batch-cutoff-banner .batch-cutoff-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-width: 64px;
|
||||
justify-content: center;
|
||||
padding: 6px 10px;
|
||||
border-radius: 999px;
|
||||
background: rgba(0, 0, 0, 0.22);
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
font-variant-numeric: tabular-nums;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
#batch-cutoff-banner .batch-cutoff-link,
|
||||
#batch-cutoff-banner .batch-cutoff-notify {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 7px 14px;
|
||||
border: 0;
|
||||
border-radius: 999px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
line-height: 1.2;
|
||||
text-decoration: none !important;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
}
|
||||
#batch-cutoff-banner .batch-cutoff-link {
|
||||
flex: 1;
|
||||
color: #991b1b !important;
|
||||
background: #fff;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
#batch-cutoff-banner .batch-cutoff-link:hover {
|
||||
color: #7f1d1d !important;
|
||||
background: #f8fafc;
|
||||
}
|
||||
#batch-cutoff-banner .batch-cutoff-notify {
|
||||
color: #fff !important;
|
||||
background: transparent;
|
||||
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.55);
|
||||
}
|
||||
#batch-cutoff-banner .batch-cutoff-notify:hover {
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
}
|
||||
@media (max-width: 767px) {
|
||||
#batch-cutoff-banner {
|
||||
top: auto;
|
||||
bottom: 16px;
|
||||
right: 16px;
|
||||
left: 16px;
|
||||
width: auto;
|
||||
}
|
||||
#batch-cutoff-banner .batch-cutoff-actions {
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="batch-cutoff-banner" class="d-none" role="alert">
|
||||
<div class="batch-cutoff-inner">
|
||||
<div class="batch-cutoff-copy">
|
||||
<span class="batch-cutoff-icon" aria-hidden="true"><i class="fas fa-clock"></i></span>
|
||||
<div>
|
||||
<span id="batch-cutoff-title" class="batch-cutoff-title">Hantar senarai pembiayaan online</span>
|
||||
<span id="batch-cutoff-body" class="batch-cutoff-sub"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="batch-cutoff-actions">
|
||||
<span id="batch-cutoff-countdown" class="batch-cutoff-chip">00:00</span>
|
||||
<a id="batch-cutoff-link" href="{{ route('pembiayaan.online.pc') }}" class="batch-cutoff-link">Buka senarai</a>
|
||||
<button type="button" id="batch-cutoff-enable-notify" class="batch-cutoff-notify d-none">Benarkan notifikasi</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
var STATUS_URL = @json(route('pembiayaan.online.pc.reminder'));
|
||||
var POLL_MS = 60000;
|
||||
var TICK_MS = 1000;
|
||||
var state = null;
|
||||
var fetchedAt = 0;
|
||||
var serverNowAtFetch = 0;
|
||||
var banner = document.getElementById('batch-cutoff-banner');
|
||||
var titleEl = document.getElementById('batch-cutoff-title');
|
||||
var bodyEl = document.getElementById('batch-cutoff-body');
|
||||
var linkEl = document.getElementById('batch-cutoff-link');
|
||||
var enableBtn = document.getElementById('batch-cutoff-enable-notify');
|
||||
var countdownEl = document.getElementById('batch-cutoff-countdown');
|
||||
|
||||
if (!banner) {
|
||||
return;
|
||||
}
|
||||
|
||||
function todayKey() {
|
||||
var d = new Date();
|
||||
return d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate());
|
||||
}
|
||||
|
||||
function pad(n) {
|
||||
return n < 10 ? '0' + n : String(n);
|
||||
}
|
||||
|
||||
function storageKey(kind, level) {
|
||||
var slot = (state && state.slot) ? state.slot : 'BATCH';
|
||||
return 'batchCutoff:' + kind + ':' + slot + ':' + todayKey() + ':' + level;
|
||||
}
|
||||
|
||||
function was(kind, level) {
|
||||
try {
|
||||
return sessionStorage.getItem(storageKey(kind, level)) === '1';
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function mark(kind, level) {
|
||||
try {
|
||||
sessionStorage.setItem(storageKey(kind, level), '1');
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
function remainingMs() {
|
||||
if (!state) {
|
||||
return null;
|
||||
}
|
||||
var cutoffAt = state.cutoff_at ? Date.parse(state.cutoff_at) : NaN;
|
||||
if (!isFinite(cutoffAt)) {
|
||||
var parts = String(state.cutoff || '').split(':');
|
||||
if (parts.length < 2) {
|
||||
return null;
|
||||
}
|
||||
var now = new Date();
|
||||
cutoffAt = new Date(now.getFullYear(), now.getMonth(), now.getDate(), parseInt(parts[0], 10), parseInt(parts[1], 10), 0).getTime();
|
||||
}
|
||||
var elapsed = Date.now() - fetchedAt;
|
||||
var nowMs = (serverNowAtFetch || Date.now()) + elapsed;
|
||||
return cutoffAt - nowMs;
|
||||
}
|
||||
|
||||
function canNotify() {
|
||||
return typeof Notification !== 'undefined' && Notification.permission === 'granted';
|
||||
}
|
||||
|
||||
function notifyDefault() {
|
||||
return typeof Notification !== 'undefined' && Notification.permission === 'default';
|
||||
}
|
||||
|
||||
function sendBrowserNotification(level, title, body) {
|
||||
if (!canNotify() || was('notify', level)) {
|
||||
return;
|
||||
}
|
||||
mark('notify', level);
|
||||
try {
|
||||
var n = new Notification(title, {
|
||||
body: body,
|
||||
tag: 'batch-cutoff-' + ((state && state.slot) || '') + '-' + level,
|
||||
requireInteraction: level === 1
|
||||
});
|
||||
n.onclick = function () {
|
||||
window.focus();
|
||||
if (state && state.url) {
|
||||
window.location.href = state.url;
|
||||
}
|
||||
n.close();
|
||||
};
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
function hideBanner() {
|
||||
banner.classList.add('d-none');
|
||||
banner.classList.remove('batch-cutoff-urgent', 'batch-cutoff-warn');
|
||||
}
|
||||
|
||||
function showBanner(level, remaining) {
|
||||
var mins = Math.max(0, Math.floor(remaining / 60000));
|
||||
var secs = Math.max(0, Math.floor((remaining % 60000) / 1000));
|
||||
var slot = (state && state.slot) ? state.slot : 'Batch';
|
||||
var cutoff = (state && state.cutoff) ? state.cutoff : '';
|
||||
var count = (state && state.bilangan) ? state.bilangan : 0;
|
||||
var countdown = pad(mins) + ':' + pad(secs);
|
||||
if (countdownEl) {
|
||||
countdownEl.textContent = countdown;
|
||||
}
|
||||
|
||||
if (level === 1) {
|
||||
titleEl.textContent = 'Sila hantar senarai pembiayaan online sekarang!! 😡 😡';
|
||||
bodyEl.textContent = slot + ' ditutup pada ' + cutoff + ' · ' + count + ' rekod belum dihantar';
|
||||
banner.classList.add('batch-cutoff-urgent');
|
||||
banner.classList.remove('batch-cutoff-warn');
|
||||
} else {
|
||||
titleEl.textContent = 'Sila hantar senarai pembiayaan online 😇 😇';
|
||||
bodyEl.textContent = slot + ' ditutup pada ' + cutoff + ' · ' + count + ' rekod menunggu';
|
||||
banner.classList.add('batch-cutoff-warn');
|
||||
banner.classList.remove('batch-cutoff-urgent');
|
||||
}
|
||||
|
||||
if (state && state.url) {
|
||||
linkEl.href = state.url;
|
||||
}
|
||||
if (notifyDefault()) {
|
||||
enableBtn.classList.remove('d-none');
|
||||
} else {
|
||||
enableBtn.classList.add('d-none');
|
||||
}
|
||||
banner.classList.remove('d-none');
|
||||
}
|
||||
|
||||
function tick() {
|
||||
if (!state || !state.enabled || state.ditutup || state.lewat || !state.bilangan) {
|
||||
hideBanner();
|
||||
return;
|
||||
}
|
||||
|
||||
var remaining = remainingMs();
|
||||
if (remaining === null || remaining <= 0) {
|
||||
hideBanner();
|
||||
return;
|
||||
}
|
||||
|
||||
var remindMs = (state.remind_minutes || 10) * 60000;
|
||||
var lastMs = (state.last_minute || 1) * 60000;
|
||||
if (remaining > remindMs) {
|
||||
hideBanner();
|
||||
return;
|
||||
}
|
||||
|
||||
var level = remaining <= lastMs ? 1 : 10;
|
||||
showBanner(level, remaining);
|
||||
sendBrowserNotification(
|
||||
level,
|
||||
level === 1 ? '1 minit lagi: hantar senarai batch' : 'Hantar senarai pembiayaan online',
|
||||
(state.slot || 'Batch') + ' tamat ' + (state.cutoff || '') + '. ' + state.bilangan + ' rekod belum dihantar.'
|
||||
);
|
||||
}
|
||||
|
||||
function applyStatus(data) {
|
||||
if (!data || !data.enabled) {
|
||||
state = null;
|
||||
hideBanner();
|
||||
return;
|
||||
}
|
||||
state = data;
|
||||
fetchedAt = Date.now();
|
||||
serverNowAtFetch = data.server_now ? Date.parse(data.server_now) : Date.now();
|
||||
if (!isFinite(serverNowAtFetch)) {
|
||||
serverNowAtFetch = Date.now();
|
||||
}
|
||||
tick();
|
||||
}
|
||||
|
||||
function fetchStatus() {
|
||||
fetch(STATUS_URL, {
|
||||
credentials: 'same-origin',
|
||||
headers: { 'Accept': 'application/json', 'X-Requested-With': 'XMLHttpRequest' }
|
||||
}).then(function (res) {
|
||||
if (!res.ok) {
|
||||
throw new Error('reminder status ' + res.status);
|
||||
}
|
||||
return res.json();
|
||||
}).then(applyStatus).catch(function () {});
|
||||
}
|
||||
|
||||
enableBtn.addEventListener('click', function () {
|
||||
if (typeof Notification === 'undefined') {
|
||||
return;
|
||||
}
|
||||
Notification.requestPermission().then(function () {
|
||||
if (canNotify()) {
|
||||
enableBtn.classList.add('d-none');
|
||||
var remaining = remainingMs();
|
||||
var lastMs = ((state && state.last_minute) || 1) * 60000;
|
||||
var level = remaining !== null && remaining <= lastMs ? 1 : 10;
|
||||
try { sessionStorage.removeItem(storageKey('notify', level)); } catch (e) {}
|
||||
tick();
|
||||
} else {
|
||||
enableBtn.classList.add('d-none');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
fetchStatus();
|
||||
setInterval(fetchStatus, POLL_MS);
|
||||
setInterval(tick, TICK_MS);
|
||||
})();
|
||||
</script>
|
||||
@@ -165,11 +165,11 @@
|
||||
<div class="modal-dialog modal-xl" role="document" style="max-width:1140px;">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Pratinjau SAG — <span id="sagPreviewTitle"></span></h5>
|
||||
<h5 class="modal-title">Pratonton SAG — <span id="sagPreviewTitle"></span></h5>
|
||||
<button type="button" class="close" data-dismiss="modal"><span>×</span></button>
|
||||
</div>
|
||||
<div class="modal-body p-2">
|
||||
<iframe id="sagPreviewFrame" title="Pratinjau SAG" src="about:blank"
|
||||
<iframe id="sagPreviewFrame" title="Pratonton SAG" src="about:blank"
|
||||
style="width:100%;height:75vh;border:1px solid #dee2e6;background:#f8f9fa;"></iframe>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
|
||||
@@ -85,11 +85,11 @@
|
||||
<div class="modal-dialog modal-xl" role="document" style="max-width:1140px;">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Pratinjau SAG — <span id="sagPreviewTitle"></span></h5>
|
||||
<h5 class="modal-title">Pratonton SAG — <span id="sagPreviewTitle"></span></h5>
|
||||
<button type="button" class="close" data-dismiss="modal"><span>×</span></button>
|
||||
</div>
|
||||
<div class="modal-body p-2">
|
||||
<iframe id="sagPreviewFrame" title="Pratinjau SAG" src="about:blank"
|
||||
<iframe id="sagPreviewFrame" title="Pratonton SAG" src="about:blank"
|
||||
style="width:100%;height:75vh;border:1px solid #dee2e6;background:#f8f9fa;"></iframe>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
|
||||
@@ -412,8 +412,8 @@
|
||||
<p class="small text-muted mb-0">Jika rekod sudah SAH, simpan akan batal semakan. Sila semak semula sebelum hantar.</p>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<label class="d-block">Pratinjau SAG</label>
|
||||
<iframe id="editSagFrame" title="Pratinjau SAG" src="about:blank"
|
||||
<label class="d-block">Pratonton SAG</label>
|
||||
<iframe id="editSagFrame" title="Pratonton SAG" src="about:blank"
|
||||
style="width:100%;height:70vh;border:1px solid #dee2e6;background:#f8f9fa;"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
@@ -486,12 +486,12 @@
|
||||
</label>
|
||||
</div>
|
||||
<a href="#" id="verifyBuktiAkaunLink" class="btn btn-sm btn-outline-primary" target="_blank" style="display:none;">
|
||||
Lihat Slip Bank
|
||||
Lihat Bukti Akaun Bank
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<label class="d-block">Pratinjau SAG</label>
|
||||
<iframe id="verifySagFrame" title="Pratinjau SAG" src="about:blank"
|
||||
<label class="d-block">Pratonton SAG</label>
|
||||
<iframe id="verifySagFrame" title="Pratonton SAG" src="about:blank"
|
||||
style="width:100%;height:70vh;border:1px solid #dee2e6;background:#f8f9fa;"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -198,6 +198,7 @@ Route::post('/notifications/{id}/read',['as'=>'notifications.read','uses'=>'Noti
|
||||
|
||||
// Pembiayaan Online — PC
|
||||
Route::get('/pembiayaan-online/pc',['as'=>'pembiayaan.online.pc','uses'=>'PembiayaanOnlineController@index'])->middleware('auth','khazanah');
|
||||
Route::get('/pembiayaan-online/pc/reminder',['as'=>'pembiayaan.online.pc.reminder','uses'=>'PembiayaanOnlineController@reminderStatus'])->middleware('auth','khazanah');
|
||||
Route::put('/pembiayaan-online/pc/lines/{id}',['as'=>'pembiayaan.online.pc.update','uses'=>'PembiayaanOnlineController@updateLine'])->middleware('auth','khazanah');
|
||||
Route::post('/pembiayaan-online/pc/lines/{id}/verify',['as'=>'pembiayaan.online.pc.verify','uses'=>'PembiayaanOnlineController@verifyLine'])->middleware('auth','khazanah');
|
||||
Route::post('/pembiayaan-online/pc/lines/{id}/unverify',['as'=>'pembiayaan.online.pc.unverify','uses'=>'PembiayaanOnlineController@unverifyLine'])->middleware('auth','khazanah');
|
||||
|
||||
Reference in New Issue
Block a user