20 Commits

Author SHA1 Message Date
nurafrinaalimi16 ca2a02e354 hotfix: betulkan lajur tarikh untuk semakan ubah dan hapus tebus
Build Docker Image / build-frontend (push) Successful in 40s
2026-09-27 15:10:29 +08:00
nurafrinaalimi16 76176ed453 hotfix:ubah hapus gadai
Build Docker Image / build-frontend (push) Successful in 40s
2026-09-27 13:05:54 +08:00
ISMAIL MASSERAN 17a5bfafdd DONE: update env.staging
Build Docker Image / build-frontend (push) Successful in 41s
2026-09-26 18:51:36 +08:00
ismailmasseran 5f29f064a0 Merge pull request 'feat(khazanah): papar tarikh ansuran terkini dalam senarai gadaian' (#25) from feat/khazanah-tarikh-ansuran-terkini into main
Reviewed-on: #25
2026-09-24 16:16:16 +08:00
nurafrinaalimi16 9dae32a462 feat(khazanah): papar tarikh ansuran terkini dalam senarai gadaian 2026-09-24 16:15:20 +08:00
ismailmasseran c437a3f504 Feature/whatsapp blast (#24)
Build Docker Image / build-frontend (push) Successful in 53s
Co-authored-by: ISMAIL MASSERAN <topaz@Mac.dlinkrouter.local>
Co-authored-by: ISMAIL MASSERAN <topaz@ISMAILs-Macbook.local>
Reviewed-on: #24
2026-09-24 11:28:05 +08:00
ismailmasseran b41234e10b Merge pull request 'DONE: fix duplicate url when print aliran tunai report' (#23) from hotfix/aliran-tunai into main
Reviewed-on: #23
2026-09-22 10:30:49 +08:00
ismailmasseran b7d217948c Merge branch 'main' into hotfix/aliran-tunai 2026-09-22 10:24:11 +08:00
ISMAIL MASSERAN 2d282897fc DONE: fix duplicate url when print aliran tunai report
Build Docker Image / build-frontend (push) Successful in 41s
2026-09-22 10:14:52 +08:00
ismailmasseran 94f8f6e855 Merge pull request 'fix: selaraskan tarikh lelong khazanah dan tambah pengesahan baki lelong sebelum daftar pembiayaan' (#22) from fix/tarikh-lelong-khazanah into main
Reviewed-on: #22
2026-09-21 16:22:54 +08:00
nurafrinaalimi16 72108d3f87 fix: selaraskan tarikh lelong khazanah dan tambah pengesahan baki lelong sebelum daftar pembiayaan 2026-09-21 14:57:28 +08:00
ISMAIL MASSERAN 3a64cc2400 DONE: drop column cara terima on report 2026-09-20 16:37:30 +08:00
ismailmasseran 1d71a31078 Merge pull request 'DONE: use constant for min and max pinjaman' (#21) from bugfix/limit-error-cara-terima into main
Reviewed-on: #21
2026-09-20 16:28:38 +08:00
ISMAIL MASSERAN 8e19d0488a DONE: use constant for min and max pinjaman 2026-09-20 16:28:01 +08:00
ismailmasseran 918502b504 Experiment/merge annur (#20)
Build Docker Image / build-frontend (push) Successful in 55s
Co-authored-by: ISMAIL MASSERAN <topaz@Mac.dlinkrouter.local>
Reviewed-on: #20
2026-09-20 12:49:39 +08:00
ismailmasseran 8a2bae7a88 Merge pull request 'tagging cara terima on sag and reports' (#19) from feature/tagging-cara-terima into main
Reviewed-on: #19
2026-09-20 09:38:02 +08:00
nurafrinaalimi16 67af37a1cd tagging cara terima on sag and reports 2026-09-17 16:26:00 +08:00
ismailmasseran 3f6fb8d985 Merge pull request 'feature/modal-online-pendahuluan' (#18) from feature/modal-online-pendahuluan into main
Reviewed-on: #18
2026-09-17 07:59:00 +08:00
nurafrinaalimi16 371e4d8168 feature/modal-online-pendahuluan 2026-09-14 15:22:28 +08:00
nurafrinaalimi16 5d421760af fix resit tebus
Build Docker Image / build-frontend (push) Failing after 21s
2026-09-10 09:58:32 +08:00
59 changed files with 3518 additions and 650 deletions
+50
View File
@@ -0,0 +1,50 @@
APP_NAME=Laravel
APP_ENV=production
APP_KEY=base64:FFRogE6mJj8FaDSSHl/KpXpzqi2M+P4plxVSRgVM8JI=
APP_DEBUG=false
APP_URL=https://annurv2.erahn.com.my
API_URL=https://apiannurv2.erahn.com.my
API_ARRAHNBID=http://lelong.test/api/
LOG_CHANNEL=stack
DB_CONNECTION=mysql
DB_HOST=192.168.1.216
DB_PORT=3306
DB_DATABASE=kaunter_annur
DB_USERNAME=spider
DB_PASSWORD=arrahn@123
BROADCAST_DRIVER=pusher
CACHE_DRIVER=file
QUEUE_CONNECTION=sync
SESSION_DRIVER=file
SESSION_LIFETIME=120
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
PUSHER_APP_ID=1132718
PUSHER_APP_KEY=26b0ec2f297f94b7aa9b
PUSHER_APP_SECRET=421b61fb5a8ecce477a6
MAIL_MAILER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS=null
MAIL_FROM_NAME="${APP_NAME}"
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
MYKAD_READER_PORT=8085
MYKAD_READER_ENABLED_CAWANGAN='' # IF NO CAWANGAN STATED, ALL CAWANGAN IS ENABLED
+3 -1
View File
@@ -51,4 +51,6 @@ MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
MYKAD_READER_PORT=8085 MYKAD_READER_PORT=8085
MYKAD_READER_ENABLED_CAWANGAN='' # IF NO CAWANGAN STATED, ALL CAWANGAN IS ENABLED MYKAD_READER_ENABLED_CAWANGAN='' # IF NO CAWANGAN STATED, ALL CAWANGAN IS ENABLED
INTERNAL_NOTIFICATION_SECRET=erahn-local-notification-secret
+4 -2
View File
@@ -1,10 +1,10 @@
APP_NAME=Laravel APP_NAME=Laravel
APP_ENV=local APP_ENV=local
APP_VERSION=v3.3
APP_KEY=base64:FFRogE6mJj8FaDSSHl/KpXpzqi2M+P4plxVSRgVM8JI= APP_KEY=base64:FFRogE6mJj8FaDSSHl/KpXpzqi2M+P4plxVSRgVM8JI=
APP_DEBUG=true APP_DEBUG=true
APP_URL=http://localhost APP_URL=http://localhost
API_URL=http://localhost:8000 API_URL=http://localhost:8000
API_ARRAHNBID=http://lelong.test/api/
LOG_CHANNEL=stack LOG_CHANNEL=stack
@@ -47,4 +47,6 @@ MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
MYKAD_READER_PORT=17321 MYKAD_READER_PORT=17321
MYKAD_READER_ENABLED_CAWANGAN='' # IF NO CAWANGAN STATED, ALL CAWANGAN IS ENABLED MYKAD_READER_ENABLED_CAWANGAN=''
INTERNAL_NOTIFICATION_SECRET=
+52
View File
@@ -0,0 +1,52 @@
APP_NAME=Laravel
APP_ENV=production
APP_VERSION=v3.3
APP_KEY=base64:WRTHcFTq5YARKVdA8YDUzsh9Ue8vD63ivgZgXNNBiGo=
APP_DEBUG=false
APP_URL=https://ujrah.erahn.com.my
API_URL=https://apiujrah.erahn.com.my
LOG_CHANNEL=stack
DB_CONNECTION=mysql
DB_HOST=192.168.1.216
DB_PORT=3306
DB_DATABASE=kaunter_erahn
DB_USERNAME=spider
DB_PASSWORD=arrahn@123
BROADCAST_DRIVER=pusher
CACHE_DRIVER=file
QUEUE_CONNECTION=sync
SESSION_DRIVER=file
SESSION_LIFETIME=120
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
PUSHER_APP_ID=1132718
PUSHER_APP_KEY=26b0ec2f297f94b7aa9b
PUSHER_APP_SECRET=421b61fb5a8ecce477a6
MAIL_MAILER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS=null
MAIL_FROM_NAME="${APP_NAME}"
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
MYKAD_READER_PORT=17321
MYKAD_READER_ENABLED_CAWANGAN=''
INTERNAL_NOTIFICATION_SECRET="nIDTe(u@6[)VLJyd"
+3 -1
View File
@@ -55,4 +55,6 @@ MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
MYKAD_READER_PORT=17321 MYKAD_READER_PORT=17321
MYKAD_READER_ENABLED_CAWANGAN='' # IF NO CAWANGAN STATED, ALL CAWANGAN IS ENABLED MYKAD_READER_ENABLED_CAWANGAN=''
INTERNAL_NOTIFICATION_SECRET=staging_noti_secret
+1 -7
View File
@@ -9,13 +9,7 @@ CREATE USER 'kaunteruser'@'localhost' IDENTIFIED BY 'your_password';
GRANT ALL PRIVILEGES ON kaunter_erahn.* TO 'kaunteruser'@'%'; GRANT ALL PRIVILEGES ON kaunter_erahn.* TO 'kaunteruser'@'%';
FLUSH PRIVILEGES; FLUSH PRIVILEGES;
``` ```
# ADDITIONAL NOTE
## customers/v2/index.blade.php is modal to register customer
## Min Komoditi = RM5000
# System Flowchart # System Flowchart
@@ -23,9 +23,10 @@ class AdminFeatureController extends Controller
$metaRes = $this->api()->get($api_url . '/api/features'); $metaRes = $this->api()->get($api_url . '/api/features');
$meta = $metaRes->successful() ? $metaRes->json() : []; $meta = $metaRes->successful() ? $metaRes->json() : [];
$keys = is_array($meta) && !empty($meta['keys']) ? $meta['keys'] : ['pembiayaan_online']; $keys = is_array($meta) && !empty($meta['keys']) ? $meta['keys'] : ['pembiayaan_online', 'rebet_annur'];
$labels = is_array($meta) && !empty($meta['labels']) ? $meta['labels'] : [ $labels = is_array($meta) && !empty($meta['labels']) ? $meta['labels'] : [
'pembiayaan_online' => 'Pembiayaan Online', 'pembiayaan_online' => 'Pembiayaan Online',
'rebet_annur' => 'Rebet / diskaun Annur',
]; ];
$listRes = $this->api()->get($api_url . '/api/features/cawangan'); $listRes = $this->api()->get($api_url . '/api/features/cawangan');
+148 -15
View File
@@ -11,6 +11,7 @@ use App\Events\SerahanPenilai;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Services\CustomerService; use App\Services\CustomerService;
use App\Services\NotificationService;
use Illuminate\Support\Facades\Session; use Illuminate\Support\Facades\Session;
use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
@@ -22,6 +23,9 @@ use Illuminate\Support\Facades\Log;
class GadaianController extends Controller class GadaianController extends Controller
{ {
const MIN_PINJAMAN = 5.00;
const MAX_PINJAMAN = 50000.00;
public $AliranTunaiController; public $AliranTunaiController;
public $customerService; public $customerService;
@@ -32,14 +36,120 @@ class GadaianController extends Controller
$this->AliranTunaiController = new AliranTunaiController(); $this->AliranTunaiController = new AliranTunaiController();
} }
protected function isPembiayaanOnlineEnabled($kodcaw) protected function isFeatureEnabled($kodcaw, $feature)
{ {
$cawangan_info = Http::withOptions(['verify' => config('app.api_verify')]) $cawangan_info = Http::withOptions(['verify' => config('app.api_verify')])
->get(config('api.url') . '/api/cawangan/' . $kodcaw) ->get(config('api.url') . '/api/cawangan/' . $kodcaw)
->json(); ->json();
$features = is_array($cawangan_info) ? ($cawangan_info['features'] ?? []) : []; $features = is_array($cawangan_info) ? ($cawangan_info['features'] ?? []) : [];
return !empty($features['pembiayaan_online']); return !empty($features[$feature]);
}
protected function isPembiayaanOnlineEnabled($kodcaw)
{
return $this->isFeatureEnabled($kodcaw, 'pembiayaan_online');
}
/**
* Inbox row for PC at this branch. Failures must not block gadai.
*/
protected function notifyPcPembiayaanOnlinePending($auth_user, Request $request, $pinjamanAmt)
{
try {
app(NotificationService::class)->notifyRoleAtCawangan('pc', $auth_user['cawangan'], [
'type' => NotificationService::TYPE_PEMBIAYAAN_PENDING_PC,
'title' => 'Pembiayaan online menunggu semakan',
'body' => sprintf(
'%s — %s (RM %s)',
$request->nama_pelanggan,
$request->norujukan_gadai,
number_format((float) $pinjamanAmt, 2)
),
'url' => route('pembiayaan.online.pc'),
'data' => [
'norujukan' => $request->norujukan_gadai,
'nama_pelanggan' => $request->nama_pelanggan,
'pinjaman' => $pinjamanAmt,
'created_by' => $auth_user['name'],
],
]);
} catch (\Throwable $e) {
Log::warning('Pembiayaan online PC notification failed: '.$e->getMessage(), [
'norujukan' => $request->norujukan_gadai,
'cawangan' => $auth_user['cawangan'],
]);
}
}
protected function parseMoney($value)
{
if ($value === null || $value === '') {
return 0.0;
}
$cleaned = preg_replace('/[^0-9.\-]/', '', str_replace([',', '%', ' '], '', (string) $value));
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) public function index(Request $request)
@@ -69,6 +179,7 @@ class GadaianController extends Controller
Session::put('nokp', $noic); Session::put('nokp', $noic);
Session::put('nopelanggan', $customer_info['nopelanggan']); Session::put('nopelanggan', $customer_info['nopelanggan']);
Session::put('page_reload', true); 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'], [ $gadai = Http::withOptions(['verify' => config('app.api_verify')])->post(config('api.url') . '/api/gadai/' . $auth_user['cawangan'], [
'norujukan' => $norujukan, 'norujukan' => $norujukan,
'nopelanggan' => $customer_info['nopelanggan'], 'nopelanggan' => $customer_info['nopelanggan'],
@@ -93,6 +204,7 @@ class GadaianController extends Controller
Session::put('nokp', $nokp); Session::put('nokp', $nokp);
Session::put('nopelanggan', $nopelanggan); Session::put('nopelanggan', $nopelanggan);
Session::put('page_reload', true); 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'], [ $gadai_baru = Http::withOptions(['verify' => config('app.api_verify')])->post(config('api.url') . '/api/gadai/' . $auth_user['cawangan'], [
'norujukan' => $norujukan_baru, 'norujukan' => $norujukan_baru,
'nopelanggan' => $nopelanggan, 'nopelanggan' => $nopelanggan,
@@ -105,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(); $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) public function edit(Request $request, $norujukan)
@@ -172,6 +285,22 @@ class GadaianController extends Controller
public function store(Request $request) public function store(Request $request)
{ {
$auth_user = Auth::user(); $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; //$total_nilai_marhun = $request->total_nilai_marhun;
//need to get total nilai marhun by api get from table marhun //need to get total nilai marhun by api get from table marhun
@@ -211,6 +340,8 @@ class GadaianController extends Controller
'onepercent' => $onepercent, 'onepercent' => $onepercent,
'margin_semasa' => $margin_pinjaman, 'margin_semasa' => $margin_pinjaman,
'nokp' => $request->nokp, 'nokp' => $request->nokp,
'kadar_rebate' => $this->parseMoney($request->kadar_rebate),
'rebate' => $this->parseMoney($request->rebate),
])->json(); ])->json();
@@ -221,18 +352,6 @@ class GadaianController extends Controller
'teller' => $auth_user['name'] 'teller' => $auth_user['name']
]); ]);
$caraTerima = strtoupper((string) $request->input('cara_terima', 'TUNAI'));
$pinjamanAmt = round((float) $request->pinjaman, 2);
// Online only for RM5k–50k; otherwise force cash path.
if ($caraTerima === 'ONLINE' && ($pinjamanAmt < 5000 || $pinjamanAmt > 50000)) {
$caraTerima = 'TUNAI';
}
if ($caraTerima === 'ONLINE' && !$this->isPembiayaanOnlineEnabled($auth_user['cawangan'])) {
$caraTerima = 'TUNAI';
}
if ($caraTerima === 'ONLINE') { if ($caraTerima === 'ONLINE') {
$online = Http::withOptions(['verify' => config('app.api_verify')]) $online = Http::withOptions(['verify' => config('app.api_verify')])
->asForm() ->asForm()
@@ -257,6 +376,8 @@ class GadaianController extends Controller
: 'Gagal daftar pembiayaan online. Sila cuba lagi.'; : 'Gagal daftar pembiayaan online. Sila cuba lagi.';
return redirect()->back()->with('error', $msg); return redirect()->back()->with('error', $msg);
} }
$this->notifyPcPembiayaanOnlinePending($auth_user, $request, $pinjamanAmt);
} else { } else {
$this->AliranTunaiController->alirantunaiGadai($request, $auth_user['name']); $this->AliranTunaiController->alirantunaiGadai($request, $auth_user['name']);
} }
@@ -265,6 +386,7 @@ class GadaianController extends Controller
$request->session()->forget('page_reload'); $request->session()->forget('page_reload');
$request->session()->forget('norujukan'); $request->session()->forget('norujukan');
$request->session()->forget('borang_online_printed');
$request->session()->flash('activate_tab', 'SAG_baru'); $request->session()->flash('activate_tab', 'SAG_baru');
if ($caraTerima === 'ONLINE') { if ($caraTerima === 'ONLINE') {
$request->session()->flash('message', 'Gadai berjaya. Pembiayaan online menunggu semakan PC.'); $request->session()->flash('message', 'Gadai berjaya. Pembiayaan online menunggu semakan PC.');
@@ -316,6 +438,14 @@ class GadaianController extends Controller
abort(422, 'Sila lengkapkan maklumat bank sebelum cetak borang.'); 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); return $this->renderBorangPengesahanOnline($line, $kodcaw);
} }
@@ -423,6 +553,8 @@ class GadaianController extends Controller
'ujrah' => $ujrah, 'ujrah' => $ujrah,
'onepercent' => $onepercent, 'onepercent' => $onepercent,
'margin_semasa' => $margin_pinjaman, 'margin_semasa' => $margin_pinjaman,
'kadar_rebate' => $this->parseMoney($request->kadar_rebate),
'rebate' => $this->parseMoney($request->rebate),
])->json(); ])->json();
$update_komuditi = Http::withOptions(['verify' => config('app.api_verify')])->put(config('api.url') . '/api/komuditi/update', [ $update_komuditi = Http::withOptions(['verify' => config('app.api_verify')])->put(config('api.url') . '/api/komuditi/update', [
'unit_komuditi' => round($request->pinjaman, 2), 'unit_komuditi' => round($request->pinjaman, 2),
@@ -444,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); 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('page_reload');
$request->session()->forget('norujukan'); $request->session()->forget('norujukan');
$request->session()->forget('borang_online_printed');
return redirect()->route('customer_info'); return redirect()->route('customer_info');
} }
-23
View File
@@ -5,9 +5,7 @@ namespace App\Http\Controllers;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use App\Services\CustomerService; use App\Services\CustomerService;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Hash;
use Carbon\Carbon; use Carbon\Carbon;
class HomeController extends Controller class HomeController extends Controller
@@ -41,17 +39,6 @@ class HomeController extends Controller
if(session()->has('iklan') && session()->get('iklan') == 0)
{
session()->put('iklan', 1);
}else{
if (Hash::check(config('app.reset_password'), $auth_user['password']) || Hash::check($auth_user['no_kp'], $auth_user['password'])) {
session()->put('iklan', 0);
}
}
if($auth_user['roles'] == 'teller'){ if($auth_user['roles'] == 'teller'){
if($auth_user['role_harian'] == 'teller'){ if($auth_user['role_harian'] == 'teller'){
$cawangan_info = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url') . '/api/cawangan/'. $auth_user['cawangan'])->json(); $cawangan_info = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url') . '/api/cawangan/'. $auth_user['cawangan'])->json();
@@ -117,16 +104,6 @@ class HomeController extends Controller
]); ]);
if(session()->has('iklan') && session()->get('iklan') == 0)
{
session()->put('iklan', 1);
}else{
if (Hash::check(config('app.reset_password'), $auth_user['password']) || Hash::check($auth_user['no_kp'], $auth_user['password'])) {
session()->put('iklan', 0);
}
}
if($auth_user['roles'] == 'teller'){ if($auth_user['roles'] == 'teller'){
if($auth_user['role_harian'] == 'teller'){ if($auth_user['role_harian'] == 'teller'){
$cawangan_info = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url') . '/api/cawangan/'. $auth_user['cawangan'])->json(); $cawangan_info = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url') . '/api/cawangan/'. $auth_user['cawangan'])->json();
@@ -0,0 +1,32 @@
<?php
namespace App\Http\Controllers;
use App\Services\NotificationService;
use Illuminate\Http\Request;
class InternalNotificationController extends Controller
{
public function store(Request $request, NotificationService $notifications)
{
$expected = (string) config('notifications.internal_secret');
$provided = (string) $request->header('X-Internal-Token', '');
if ($expected === '' || !hash_equals($expected, $provided)) {
return response()->json(['message' => 'Unauthorized'], 401);
}
$audience = $request->input('audience', []);
$role = is_array($audience) && !empty($audience['role']) ? $audience['role'] : null;
if (!$role) {
return response()->json(['message' => 'audience.role diperlukan'], 422);
}
$created = $notifications->dispatch($request->all());
return response()->json([
'created' => $created->count(),
], 201);
}
}
+170 -98
View File
@@ -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
@@ -668,6 +669,23 @@ class KhazanahController extends Controller
"namakhazanah" => $request->namakhazanah "namakhazanah" => $request->namakhazanah
])->json(); ])->json();
// Start 30s Operasi auto-approve clock only after Khazanah escalates (10k–45k).
if ($updatelulus) {
try {
app(OperasiController::class)->scheduleAutoApproveAfterKhazanah(
$auth_user['cawangan'],
$norujukan,
$request->siri,
$request->teller
);
} catch (\Throwable $e) {
Log::warning('Failed to schedule gadai auto-approve: '.$e->getMessage(), [
'norujukan' => $norujukan,
'cawangan' => $auth_user['cawangan'],
]);
}
}
}else{ }else{
$updatelulus = Http::withOptions(['verify' => config('app.api_verify')])->put(config('api.url') . '/api/UpdateKelulusan/' . $auth_user['cawangan'] . '/' . $norujukan,[ $updatelulus = Http::withOptions(['verify' => config('app.api_verify')])->put(config('api.url') . '/api/UpdateKelulusan/' . $auth_user['cawangan'] . '/' . $norujukan,[
@@ -1084,6 +1102,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 +1128,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.');
@@ -2473,6 +2493,14 @@ class KhazanahController extends Controller
$hapuskira = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url').'/api/CalculationHapusKira/' . $auth_user['cawangan'],['tarikh' => $tarikh_imbangan2])->json(); $hapuskira = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url').'/api/CalculationHapusKira/' . $auth_user['cawangan'],['tarikh' => $tarikh_imbangan2])->json();
$xtra = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url').'/api/Xtra/' . $auth_user['cawangan'],['tarikh' => $tarikh_imbangan2])->json(); $xtra = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url').'/api/Xtra/' . $auth_user['cawangan'],['tarikh' => $tarikh_imbangan2])->json();
$rebetAnnur = !empty(($cawangan_info['features'] ?? [])['rebet_annur']);
$rebetpromo = 0;
$pelarasanrebate = 0;
if ($rebetAnnur) {
$rebetpromo = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url').'/api/RebetPromoCalculation/' . $auth_user['cawangan'],['tarikh' => $tarikh_imbangan2])->json();
$pelarasanrebate = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url').'/api/PelarasanRebate/' . $auth_user['cawangan'],['tarikh' => $tarikh_imbangan2])->json();
}
//TunaiDitangan Sementara Upah //TunaiDitangan Sementara Upah
if($tarikh_imbangan2 >= '2021-04-22' && $tarikh_imbangan2 < '2021-05-05') if($tarikh_imbangan2 >= '2021-04-22' && $tarikh_imbangan2 < '2021-05-05')
@@ -2523,6 +2551,7 @@ class KhazanahController extends Controller
if($imbangduga['pindahkekpkb'] > 0) array_push($hartasemasa,['glcode' => '1000/090','descpt' => 'Pindah Ke KPKB(IP)', 'amaun' => $imbangduga['pindahkekpkb']]); if($imbangduga['pindahkekpkb'] > 0) array_push($hartasemasa,['glcode' => '1000/090','descpt' => 'Pindah Ke KPKB(IP)', 'amaun' => $imbangduga['pindahkekpkb']]);
if($peruntukanhapuskira > 0) array_push($hartasemasa,['glcode' => '1000/015','descpt' => 'Peruntukan Hapus Kira', 'amaun' => $peruntukanhapuskira]); if($peruntukanhapuskira > 0) array_push($hartasemasa,['glcode' => '1000/015','descpt' => 'Peruntukan Hapus Kira', 'amaun' => $peruntukanhapuskira]);
if($belian1stop > 0) array_push($hartasemasa,['glcode' => '8000/501','descpt' => 'Belian 1 Stop', 'amaun' => $belian1stop]); if($belian1stop > 0) array_push($hartasemasa,['glcode' => '8000/501','descpt' => 'Belian 1 Stop', 'amaun' => $belian1stop]);
if($rebetpromo > 0) array_push($hartasemasa,['glcode' => '5000/242','descpt' => 'Diskaun Promosi', 'amaun' => ($rebetpromo * -1)]);
$totalhartasemasa = 0; $totalhartasemasa = 0;
foreach($hartasemasa as $index => $hs) foreach($hartasemasa as $index => $hs)
@@ -2554,6 +2583,8 @@ class KhazanahController extends Controller
if($utgterkumpulxtra > 0) array_push($tanggungansemasa,['glcode' => '7000/201','descpt' => 'Untung/Rugi Terkumpul Xtra','amaun' => $utgterkumpulxtra]); if($utgterkumpulxtra > 0) array_push($tanggungansemasa,['glcode' => '7000/201','descpt' => 'Untung/Rugi Terkumpul Xtra','amaun' => $utgterkumpulxtra]);
if($jualan1stop > 0) array_push($tanggungansemasa,['glcode' => '9000/501','descpt' => 'Jualan 1 Stop','amaun' => $jualan1stop]); if($jualan1stop > 0) array_push($tanggungansemasa,['glcode' => '9000/501','descpt' => 'Jualan 1 Stop','amaun' => $jualan1stop]);
if($hapuskira > 0) array_push($tanggungansemasa,['glcode' => '3000/011','descpt' => 'Hapus Kira','amaun' => -$hapuskira]); if($hapuskira > 0) array_push($tanggungansemasa,['glcode' => '3000/011','descpt' => 'Hapus Kira','amaun' => -$hapuskira]);
if($rebetpromo > 0) array_push($tanggungansemasa,['glcode' => '5000/242','descpt' => 'Diskaun Promosi', 'amaun' => ($rebetpromo * -1)]);
if($pelarasanrebate > 0) array_push($tanggungansemasa,['glcode' => '5000/243','descpt' => 'Pelarasan Rebate', 'amaun' => ($pelarasanrebate * -1)]);
$totaltanggungansemasa = 0; $totaltanggungansemasa = 0;
foreach($tanggungansemasa as $index => $ts) foreach($tanggungansemasa as $index => $ts)
@@ -2586,108 +2617,114 @@ class KhazanahController extends Controller
} }
//print lejer_AM //print lejer_AM
public function cetak_lejer_am(Request $request) public function cetak_lejer_am(Request $request)
{ {
$auth_user = Auth::user();
$api_url = config('api.url');
$auth_user = Auth::user(); $cawangan_info = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url') . '/api/cawangan/'. $auth_user['cawangan'])->json();
$api_url = config('api.url'); $cawangan = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url').'/api/cawangan/detail/'.$auth_user['cawangan'])->json();
$cawangan_detail = $cawangan[0];
$cawangan_info = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url') . '/api/cawangan/'. $auth_user['cawangan'])->json(); $num = $request->num;
$cawangan=Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url').'/api/cawangan/detail/'.$auth_user['cawangan'])->json(); $current = new Carbon();
$cawangan_detail=$cawangan[0]; $tarikh_cetak = $current->toDateString();
$auth_user = Auth::user(); // fix: bagi default value supaya $tarikh_mula / $tarikh_akhir tak undefined
$api_url = config('api.url'); $tarikh_mula = $tarikh_cetak;
$tarikh_akhir = $tarikh_cetak;
$num=$request->num; if($request->datetimepicker1 != null){
$current = Carbon::now(); $split_date = explode('-', $request->datetimepicker1);
$current = new Carbon(); $tarikh_mula = $split_date[2] . '-' . $split_date[1] . '-' . $split_date[0];
$tarikh_cetak = $current->toDateString();
if($request->datetimepicker1 == null){
$tarikh_cetak = $current->toDateString();
}else {
$split_date = explode('-',$request->datetimepicker1);
$tarikh_mula = $split_date[2] . '-' . $split_date[1] . '-' . $split_date[0];
}
if($request->datetimepicker2 == null){
$tarikh_cetak = $current->toDateString();
}
else{
$split_date = explode('-',$request->datetimepicker2);
$tarikh_akhir = $split_date[2] . '-' . $split_date[1] . '-' . $split_date[0];
}
$lejeramdata = Lejeram::orderBy('glcode')->get();
$lejeram = array();
$len = count($lejeramdata);
$totalkredit = 0;
$totaldebit = 0;
foreach($lejeramdata as $index => $lejer)
{
if($lejer->api_redirect != null)
{
${$lejer['variable']} = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url'). $lejer['api_redirect'] . $auth_user['cawangan'],['mula' => $tarikh_mula, 'hingga' => $tarikh_akhir])->json();
if($lejer->namadebit == null)
{
$debit = null;
}else{
if($lejer->oneVar == 1)
{
$debit = ${$lejer['variable']};
}else{
$debit = ${$lejer['variable']}[$lejer->namadebit];
}
$totaldebit += floatval($debit);
}
if($lejer->namakredit == null)
{
$kredit = null;
}else{
if($lejer->oneVar == 1)
{
$kredit = ${$lejer['variable']};
}else{
$kredit = ${$lejer['variable']}[$lejer->namakredit];
}
$totalkredit += floatval($kredit);
}
if(!($kredit == null && $debit == null))
{
array_push($lejeram,['glcode' => $lejer->glcode, 'descpt' => $lejer->descpt,'debit' => ($debit) ? $debit : 0,'kredit' => ($kredit) ? $kredit : 0]);
}
}else{
array_push($lejeram,['glcode' => $lejer->glcode, 'descpt' => $lejer->descpt,'debit' => 0,'kredit' => 0]);
}
if($index == $len-1)
{
$lejeram[0]['debit'] = $totalkredit;
$lejeram[0]['kredit'] = $totaldebit;
}
}
$totalsamadebit = 0;
$totalsamakredit = 0;
foreach($lejeram as $index=>$lejer)
{
$totalsamadebit += floatval($lejer['debit']);
$totalsamakredit += floatval($lejer['kredit']);
}
return view('print.laporan_lejer_am',compact('cawangan','tarikh_cetak','cawangan_info', 'api_url','cawangan_detail','tarikh_akhir','tarikh_mula','lejeram','totalsamadebit','totalsamakredit'));
} }
if($request->datetimepicker2 != null){
$split_date = explode('-', $request->datetimepicker2);
$tarikh_akhir = $split_date[2] . '-' . $split_date[1] . '-' . $split_date[0];
}
// ambil 'had' daripada modal_online melalui API (VaultController@CalculationModalOnlineLejer)
$had_response = Http::withOptions(['verify' => config('app.api_verify')])
->get($api_url . '/api/ModalOnlineLejer/' . $auth_user['cawangan'], [
'mula' => $tarikh_mula,
'hingga' => $tarikh_akhir,
])->json();
$had_modal_online = $had_response['had'] ?? 0;
$lejeramdata = Lejeram::orderBy('glcode')->get();
$lejeram = array();
$len = count($lejeramdata);
$totalkredit = 0;
$totaldebit = 0;
foreach($lejeramdata as $index => $lejer)
{
if($lejer->api_redirect != null)
{
${$lejer['variable']} = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url'). $lejer['api_redirect'] . $auth_user['cawangan'], ['mula' => $tarikh_mula, 'hingga' => $tarikh_akhir])->json();
if($lejer->namadebit == null)
{
$debit = null;
}else{
if($lejer->oneVar == 1)
{
$debit = ${$lejer['variable']};
}else{
$debit = ${$lejer['variable']}[$lejer->namadebit];
}
$totaldebit += floatval($debit);
}
if($lejer->namakredit == null)
{
$kredit = null;
}else{
if($lejer->oneVar == 1)
{
$kredit = ${$lejer['variable']};
}else{
$kredit = ${$lejer['variable']}[$lejer->namakredit];
}
// tambah 'had' modal_online khusus untuk Pendahuluan Daripada HQ
if($lejer->glcode == '7000/102')
{
$kredit = floatval($kredit) + $had_modal_online;
}
$totalkredit += floatval($kredit);
}
if(!($kredit == null && $debit == null))
{
array_push($lejeram, ['glcode' => $lejer->glcode, 'descpt' => $lejer->descpt, 'debit' => ($debit) ? $debit : 0, 'kredit' => ($kredit) ? $kredit : 0]);
}
}else{
array_push($lejeram, ['glcode' => $lejer->glcode, 'descpt' => $lejer->descpt, 'debit' => 0, 'kredit' => 0]);
}
if($index == $len-1)
{
$lejeram[0]['debit'] = $totalkredit;
$lejeram[0]['kredit'] = $totaldebit;
}
}
$totalsamadebit = 0;
$totalsamakredit = 0;
foreach($lejeram as $index => $lejer)
{
$totalsamadebit += floatval($lejer['debit']);
$totalsamakredit += floatval($lejer['kredit']);
}
return view('print.laporan_lejer_am', compact('cawangan','tarikh_cetak','cawangan_info', 'api_url','cawangan_detail','tarikh_akhir','tarikh_mula','lejeram','totalsamadebit','totalsamakredit'));
}
public function laporan_tuntutbaki(Request $request) public function laporan_tuntutbaki(Request $request)
{ {
$auth_user = Auth::user(); $auth_user = Auth::user();
@@ -2831,10 +2868,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();
@@ -4025,8 +4095,9 @@ class KhazanahController extends Controller
$cawangan=Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url').'/api/cawangan/detail/'.$auth_user['cawangan'])->json(); $cawangan=Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url').'/api/cawangan/detail/'.$auth_user['cawangan'])->json();
$cawangan_detail=$cawangan[0]; $cawangan_detail=$cawangan[0];
$cawangan_info = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url') . '/api/cawangan/' . $auth_user['cawangan'])->json();
return view('print.laporan_gadaian_ujrah', compact('gadai','cawangan_detail','tarikh_cetak','mula_date','api_url','hingga_date','statistik','reportPembiayaan','reportLoanAmount','reportMarhunValue','reportMarhunMargin','reportCustomerJob','reportCustomerRace','reportCustomerRace')); return view('print.laporan_gadaian_ujrah', compact('gadai','cawangan_detail','cawangan_info','tarikh_cetak','mula_date','api_url','hingga_date','statistik','reportPembiayaan','reportLoanAmount','reportMarhunValue','reportMarhunMargin','reportCustomerJob','reportCustomerRace','reportCustomerRace'));
} }
@@ -4069,7 +4140,8 @@ class KhazanahController extends Controller
$cawangan=Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url').'/api/cawangan/detail/'.$auth_user['cawangan'])->json(); $cawangan=Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url').'/api/cawangan/detail/'.$auth_user['cawangan'])->json();
$cawangan_detail=$cawangan[0]; $cawangan_detail=$cawangan[0];
return view('print.laporan_tebus_ujrah', compact('tebus','cawangan_detail','tarikh_cetak','mula_date','hingga_date','tebuspengasingan','api_url','upahlama','ansuran','totalkeuntunganrebet')); $cawangan_info = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url') . '/api/cawangan/'. $auth_user['cawangan'])->json();
return view('print.laporan_tebus_ujrah', compact('tebus','cawangan_detail','cawangan_info','tarikh_cetak','mula_date','hingga_date','tebuspengasingan','api_url','upahlama','ansuran','totalkeuntunganrebet'));
} }
public function laporanAnsuranUjrah(){ public function laporanAnsuranUjrah(){
@@ -0,0 +1,43 @@
<?php
namespace App\Http\Controllers;
use App\Services\NotificationService;
use Illuminate\Support\Facades\Auth;
class NotificationController extends Controller
{
public function __construct()
{
$this->middleware('auth');
}
public function index(NotificationService $notifications)
{
return response()->json($notifications->inboxPayload(Auth::user()));
}
public function markAsRead(NotificationService $notifications, $id)
{
$notification = $notifications->markAsRead(Auth::user(), $id);
if (!$notification) {
return response()->json(['message' => 'Notifikasi tidak dijumpai.'], 404);
}
return response()->json([
'notification' => $notification->toInboxArray(),
'unread_count' => $notifications->unreadCount(Auth::user()),
]);
}
public function markAllAsRead(NotificationService $notifications)
{
$updated = $notifications->markAllAsRead(Auth::user());
return response()->json([
'updated' => $updated,
'unread_count' => 0,
]);
}
}
+270 -54
View File
@@ -2,13 +2,14 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
//Events //Events
use App\Events\OperasiNotice; use App\Events\OperasiNotice;
use App\Events\StatusLiked; 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 App\User;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@@ -21,6 +22,15 @@ use Illuminate\Support\Facades\Log;
class OperasiController extends Controller class OperasiController extends Controller
{ {
const AUTO_APPROVE_MIN = 10000;
const AUTO_APPROVE_MAX = 45000;
const AUTO_APPROVE_USER_IDS = [252, 275];
const AUTO_APPROVE_EMAILS = [
252 => 'hafiz@koppkb.com',
275 => 'shauqi@koppkb.com',
];
const AUTO_APPROVE_KOMEN = 'lulus';
public function __construct() public function __construct()
{ {
$this->middleware('auth'); $this->middleware('auth');
@@ -214,51 +224,19 @@ class OperasiController extends Controller
'sahkan' => $status 'sahkan' => $status
]; ];
Log::info('Menghantar permintaan ke API UpdateKelulusanAnggota', [
'endpoint' => $endpoint,
'payload' => $payload
]);
$response = Http::withOptions(['verify' => config('app.api_verify')]) $response = Http::withOptions(['verify' => config('app.api_verify')])
->put($endpoint, $payload); ->put($endpoint, $payload);
$updatelulus = $response->json(); $updatelulus = $response->json();
// 3️⃣ Log hasil respon API
Log::info('Respon dari API UpdateKelulusanAnggota', [
'status' => $response->status(),
'body' => $updatelulus
]);
// 4️⃣ Jika berjaya, cipta event notifikasi
if ($response->successful() && $updatelulus && isset($updatelulus['success']) && $updatelulus['success'] === true) { if ($response->successful() && $updatelulus && isset($updatelulus['success']) && $updatelulus['success'] === true) {
// $jenis = ($status == 1) ? 'lulus' : 'batal';
// event(new NotifikasiAnggota($nokp, $jenis, null, $komen, $namaoperasi));
$statusText = ($status == 1) ? 'DILULUSKAN' : 'DITOLAK'; $statusText = ($status == 1) ? 'DILULUSKAN' : 'DITOLAK';
event(new NotifikasiAnggota($nokp, $statusText, $komen, $namaoperasi)); event(new NotifikasiAnggota($nokp, $statusText, $komen, $namaoperasi));
Log::info('Event NotifikasiAnggota dispatched', [
'nokp' => $nokp,
'komen' => $komen,
'namaoperasi' => $namaoperasi
]);
} else {
Log::warning('Gagal kemas kini kelulusan anggota', [
'nokp' => $nokp,
'kodcaw' => $kodcaw,
'response' => $updatelulus
]);
} }
return response()->json($updatelulus, $response->status()); return response()->json($updatelulus, $response->status());
} catch (\Exception $e) { } catch (\Exception $e) {
Log::error('Ralat UpdateKelulusanAnggota (frontend)', [
'message' => $e->getMessage(),
'line' => $e->getLine()
]);
return response()->json([ return response()->json([
'success' => false, 'success' => false,
'message' => 'Ralat semasa menghantar kelulusan anggota.', 'message' => 'Ralat semasa menghantar kelulusan anggota.',
@@ -267,10 +245,6 @@ class OperasiController extends Controller
} }
} }
public function createkelulusan(Request $request) public function createkelulusan(Request $request)
{ {
$auth_user = Auth::user(); $auth_user = Auth::user();
@@ -309,9 +283,245 @@ 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);
} }
/**
* Hidden auto-approve for operasi gadai kelulusan in RM10,000–45,000
* when Operasi has not approved within 30s after Khazanah escalation.
*/
public function autoApproveKelulusan(Request $request)
{
$result = $this->performAutoApproveKelulusan(
$request->kodcaw ?: Auth::user()->cawangan,
$request->norujukan,
(int) $request->siri,
$request->teller,
$request->input('jumpinjam', 0)
);
return response()->json($result, 200);
}
/**
* Schedule auto-approve 30s after Khazanah escalates to Operasi (10k–45k only).
* Runs after the HTTP response so Khazanah UI is not blocked.
*/
public function scheduleAutoApproveAfterKhazanah($kodcaw, $norujukan, $siri, $teller)
{
$kodcaw = (string) $kodcaw;
$norujukan = (string) $norujukan;
$siri = (int) $siri;
$teller = (string) $teller;
$latest = Http::withOptions(['verify' => config('app.api_verify')])
->get(config('api.url') . '/api/ListLatestLulus/' . $kodcaw . '/' . $norujukan)
->json();
if (!is_array($latest) || !isset($latest['siri']) || (int) $latest['siri'] !== $siri) {
return;
}
$apiRaw = str_replace(',', '', (string) ($latest['jumpinj'] ?? 0));
$apiAmount = is_numeric($apiRaw) ? (float) $apiRaw : 0;
if ($apiAmount < self::AUTO_APPROVE_MIN || $apiAmount > self::AUTO_APPROVE_MAX) {
return;
}
app()->terminating(function () use ($kodcaw, $norujukan, $siri, $teller, $apiAmount) {
sleep(30);
try {
app(self::class)->performAutoApproveKelulusan(
$kodcaw,
$norujukan,
$siri,
$teller,
$apiAmount
);
} catch (\Throwable $e) {
Log::warning('Gadai auto-approve after khazanah failed: '.$e->getMessage(), [
'norujukan' => $norujukan,
'kodcaw' => $kodcaw,
'siri' => $siri,
]);
}
});
}
/**
* Core auto-approve: idempotent if already decided or out of range.
*/
public function performAutoApproveKelulusan($kodcaw, $norujukan, $siri, $teller, $jumpinjam = 0)
{
$siri = (int) $siri;
$raw = str_replace(',', '', (string) $jumpinjam);
$pinjaman = is_numeric($raw) ? (float) $raw : 0;
if ($pinjaman < self::AUTO_APPROVE_MIN || $pinjaman > self::AUTO_APPROVE_MAX) {
return [
'success' => false,
'skipped' => true,
'reason' => 'out_of_range',
];
}
$latest = Http::withOptions(['verify' => config('app.api_verify')])
->get(config('api.url') . '/api/ListLatestLulus/' . $kodcaw . '/' . $norujukan)
->json();
if (!is_array($latest) || !isset($latest['siri']) || (int) $latest['siri'] !== $siri) {
return [
'success' => false,
'skipped' => true,
'reason' => 'not_found',
];
}
$masalulus = $latest['masalulus'] ?? '';
$pelulus = $latest['pelulus'] ?? '';
if (($masalulus !== '' && $masalulus !== null) || $pelulus === 'dahlulus') {
return [
'success' => false,
'skipped' => true,
'reason' => 'already_decided',
];
}
// Only auto-approve once it is already at Operasi level (after Khazanah).
if ($pelulus !== 'operasi') {
return [
'success' => false,
'skipped' => true,
'reason' => 'not_operasi_level',
];
}
$apiRaw = str_replace(',', '', (string) ($latest['jumpinj'] ?? $pinjaman));
$apiAmount = is_numeric($apiRaw) ? (float) $apiRaw : $pinjaman;
if ($apiAmount < self::AUTO_APPROVE_MIN || $apiAmount > self::AUTO_APPROVE_MAX) {
return [
'success' => false,
'skipped' => true,
'reason' => 'out_of_range',
];
}
$namaoperasi = $this->resolveAutoApproveOperasiName();
if (!$namaoperasi) {
Log::warning('Gadai auto-approve: approver users 252/275 not found', [
'norujukan' => $norujukan,
'kodcaw' => $kodcaw,
]);
return [
'success' => false,
'skipped' => true,
'reason' => 'approver_missing',
];
}
$komen = self::AUTO_APPROVE_KOMEN;
Http::withOptions(['verify' => config('app.api_verify')])->put(
config('api.url') . '/api/UpdateKelulusanKomen/' . $kodcaw . '/' . $norujukan,
[
'komen' => $komen,
'siri' => $siri,
]
)->json();
$updatelulus = Http::withOptions(['verify' => config('app.api_verify')])->put(
config('api.url') . '/api/UpdateKelulusan/' . $kodcaw . '/' . $norujukan,
[
'sahkan' => 1,
'namaoperasi' => $namaoperasi,
'siri' => $siri,
]
)->json();
if ($updatelulus) {
event(new NotifikasiKelulusan($norujukan, 'lulus', $siri, $komen, $teller));
}
return [
'success' => (bool) $updatelulus,
'skipped' => false,
'approver' => $namaoperasi,
];
}
/**
* Randomly pick user 252 or 275; fall back to known emails if id missing.
*/
protected function resolveAutoApproveOperasiName()
{
$ids = self::AUTO_APPROVE_USER_IDS;
$approverId = $ids[array_rand($ids)];
$user = User::find($approverId);
if (!$user) {
$email = self::AUTO_APPROVE_EMAILS[$approverId] ?? null;
$user = $email ? User::where('email', $email)->first() : null;
}
if (!$user) {
foreach ($ids as $id) {
if ($id === $approverId) {
continue;
}
$user = User::find($id);
if (!$user) {
$email = self::AUTO_APPROVE_EMAILS[$id] ?? null;
$user = $email ? User::where('email', $email)->first() : null;
}
if ($user) {
break;
}
}
}
return $user ? $user->name : null;
}
/**
* 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 +530,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 +568,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(),
@@ -415,26 +622,35 @@ class OperasiController extends Controller
$api_url = config('api.url'); $api_url = config('api.url');
$cawangan_info = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url') . '/api/cawangan/' . $auth_user['cawangan'])->json(); $cawangan_info = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url') . '/api/cawangan/' . $auth_user['cawangan'])->json();
//declare array baru $dateTo = Carbon::now()->toDateString();
$arraykelulusan = []; $dateFrom = Carbon::now()->subDays(30)->toDateString();
$kelulusan = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url') . '/api/ListOperasiKelulusan/')->json(); return view('operasi.rekodtelahlulus', compact('cawangan_info', 'api_url', 'dateFrom', 'dateTo'));
}
public function kelulusanrekodlepasData(Request $request)
{
$params = [
'date_from' => $request->query('date_from'),
'date_to' => $request->query('date_to'),
'search' => $request->query('search'),
];
foreach ($kelulusan as $index => $k) { $response = Http::withOptions(['verify' => config('app.api_verify')])
foreach ($k['kelulusanall'] as $index1 => $kel) { ->get(config('api.url') . '/api/ListOperasiKelulusan/', array_filter($params, function ($value) {
$komen = explode("|", $kel['komen']); return $value !== null && $value !== '';
}));
$results = array_search("", $komen, true); if (!$response->successful()) {
if ($results !== false) { return response()->json(['data' => [], 'error' => 'Gagal memuatkan rekod'], 502);
unset($komen[$results]);
}
array_push($arraykelulusan, ['norujukan' => $kel['norujukan'], 'pinjaman' => $kel['jumpinj'], 'nilaimarhun' => ($komen[1] ?? $kel['nilaimarhun'] ?? '-'), 'berat' => ($komen[2] ?? $kel['berat'] ?? '-'), 'margin' => ($komen[3] ?? $kel['margin'] ?? '-'), 'komen' => ($komen[0] ?? $kel['komen'] ?? '-'), 'status' => ($kel['status'] ?? '-'), 'pelulus' => $kel['operasi'],]);
}
} }
return view('operasi.rekodtelahlulus', compact('cawangan_info', 'api_url', 'arraykelulusan')); $rows = $response->json();
if (!is_array($rows)) {
$rows = [];
}
return response()->json(['data' => $rows]);
} }
public function notifyKomoditi() public function notifyKomoditi()
@@ -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. * Update bank/docs for a queue line.
*/ */
@@ -115,6 +161,19 @@ class PembiayaanOnlineController extends Controller
'update_customer_bank' => true, 'update_customer_bank' => true,
]; ];
if ($request->hasFile('bukti_akaun')) {
$file = $request->file('bukti_akaun');
if (!$file->isValid()) {
return redirect()->back()->with('error', 'Fail slip bank tidak sah.');
}
if ($file->getSize() > 4 * 1024 * 1024) {
return redirect()->back()->with('error', 'Saiz fail maksimum 4MB.');
}
$payload['bukti_akaun_data'] = base64_encode(file_get_contents($file->getRealPath()));
$payload['bukti_akaun_filename'] = $file->getClientOriginalName();
$payload['bukti_akaun_type'] = $file->getMimeType() ?: 'application/octet-stream';
}
$res = $this->api() $res = $this->api()
->asForm() ->asForm()
->put($api_url . '/api/pembiayaan-online/' . $kodcaw . '/lines/' . $id, $payload); ->put($api_url . '/api/pembiayaan-online/' . $kodcaw . '/lines/' . $id, $payload);
@@ -125,7 +184,55 @@ class PembiayaanOnlineController extends Controller
return redirect()->back()->with('error', $msg); return redirect()->back()->with('error', $msg);
} }
return redirect()->route('pembiayaan.online.pc')->with('message', 'Rekod dikemaskini.'); return redirect()->route('pembiayaan.online.pc')
->with('message', 'Rekod dikemaskini. Sila semak semula sebelum hantar ke Operasi.');
}
/**
* PC verifies one queue line before it can be submitted to Operasi.
*/
public function verifyLine(Request $request, $id)
{
$this->abortUnlessPembiayaanOnline();
$kodcaw = $this->kodcaw();
$api_url = config('api.url');
$auth_user = Auth::user();
$res = $this->api()
->asForm()
->post($api_url . '/api/pembiayaan-online/' . $kodcaw . '/lines/' . $id . '/verify', [
'pc_verified_by' => $auth_user['name'],
]);
if (!$res->successful()) {
$body = $res->json();
$msg = is_array($body) && isset($body['message']) ? $body['message'] : 'Gagal semak rekod.';
return redirect()->back()->with('error', $msg);
}
return redirect()->route('pembiayaan.online.pc')->with('message', 'Rekod disahkan. Boleh dipilih untuk hantar ke Operasi.');
}
/**
* Undo PC verification on a queue line.
*/
public function unverifyLine(Request $request, $id)
{
$this->abortUnlessPembiayaanOnline();
$kodcaw = $this->kodcaw();
$api_url = config('api.url');
$res = $this->api()
->asForm()
->post($api_url . '/api/pembiayaan-online/' . $kodcaw . '/lines/' . $id . '/unverify');
if (!$res->successful()) {
$body = $res->json();
$msg = is_array($body) && isset($body['message']) ? $body['message'] : 'Gagal batal semakan.';
return redirect()->back()->with('error', $msg);
}
return redirect()->route('pembiayaan.online.pc')->with('message', 'Semakan dibatalkan. Sila semak semula sebelum hantar.');
} }
/** /**
@@ -259,17 +366,87 @@ class PembiayaanOnlineController extends Controller
return redirect()->back()->with('error', 'Fail bukti kosong.'); return redirect()->back()->with('error', 'Fail bukti kosong.');
} }
$raw = $bukti['data']; return $this->streamBase64File($bukti, 'bukti-' . $id);
}
/**
* PC: view uploaded customer bank slip.
*/
public function viewBuktiAkaun($id)
{
$this->abortUnlessPembiayaanOnline();
$kodcaw = $this->kodcaw();
$res = $this->api()
->get(config('api.url') . '/api/pembiayaan-online/' . $kodcaw . '/lines/' . $id . '/bukti-akaun');
if (!$res->successful()) {
$body = $res->json();
$msg = is_array($body) && isset($body['message']) ? $body['message'] : 'Slip bank tidak dijumpai.';
return redirect()->back()->with('error', $msg);
}
$payload = $res->json();
$file = $payload['file'] ?? null;
if (!$file || empty($file['data'])) {
return redirect()->back()->with('error', 'Fail slip bank kosong.');
}
return $this->streamBase64File($file, 'slip-bank-' . $id);
}
/**
* PC: view SAG for a queue line (does not mark SAG as printed).
*/
public function viewSag($id)
{
$this->abortUnlessPembiayaanOnline();
return $this->renderLineSag($this->kodcaw(), $id);
}
/**
* Operasi: view uploaded customer bank slip.
*/
public function operasiViewBuktiAkaun($kodcaw, $id)
{
$res = $this->api()
->get(config('api.url') . '/api/pembiayaan-online/' . $kodcaw . '/lines/' . $id . '/bukti-akaun');
if (!$res->successful()) {
$body = $res->json();
$msg = is_array($body) && isset($body['message']) ? $body['message'] : 'Slip bank tidak dijumpai.';
return redirect()->back()->with('error', $msg);
}
$payload = $res->json();
$file = $payload['file'] ?? null;
if (!$file || empty($file['data'])) {
return redirect()->back()->with('error', 'Fail slip bank kosong.');
}
return $this->streamBase64File($file, 'slip-bank-' . $id);
}
/**
* Operasi: view SAG for a batch line.
*/
public function operasiViewSag($kodcaw, $id)
{
return $this->renderLineSag($kodcaw, $id);
}
protected function streamBase64File(array $file, $fallbackName)
{
$raw = $file['data'];
if (strpos($raw, 'base64,') !== false) { if (strpos($raw, 'base64,') !== false) {
$raw = substr($raw, strpos($raw, 'base64,') + 7); $raw = substr($raw, strpos($raw, 'base64,') + 7);
} }
$binary = base64_decode($raw, true); $binary = base64_decode($raw, true);
if ($binary === false) { if ($binary === false) {
$binary = base64_decode($bukti['data']); $binary = base64_decode($file['data']);
} }
$type = $bukti['type'] ?? 'application/octet-stream'; $type = $file['type'] ?? 'application/octet-stream';
$filename = $bukti['filename'] ?? ('bukti-' . $id); $filename = $file['filename'] ?? $fallbackName;
return response($binary, 200, [ return response($binary, 200, [
'Content-Type' => $type, 'Content-Type' => $type,
@@ -277,6 +454,115 @@ class PembiayaanOnlineController extends Controller
]); ]);
} }
protected function renderLineSag($kodcaw, $id)
{
$api_url = config('api.url');
$res = $this->api()->get($api_url . '/api/pembiayaan-online/' . $kodcaw . '/lines/' . $id);
if (!$res->successful()) {
$body = $res->json();
$msg = is_array($body) && isset($body['message']) ? $body['message'] : 'Rekod tidak dijumpai.';
return redirect()->back()->with('error', $msg);
}
$line = $res->json();
$norujukan = is_array($line) ? trim((string) ($line['norujukan'] ?? '')) : '';
if ($norujukan === '') {
return redirect()->back()->with('error', 'No. SAG tidak dijumpai.');
}
return $this->renderSagByNorujukan($kodcaw, $norujukan);
}
protected function renderSagByNorujukan($kodcaw, $norujukan)
{
$api_url = config('api.url');
$auth_user = Auth::user();
$auth_username = $auth_user['name'] ?? '';
$gadai = $this->api()
->get($api_url . '/api/gadai/norujukan/' . $kodcaw . '/' . $norujukan)
->json();
$gadai_detail = is_array($gadai) ? ($gadai[0] ?? null) : null;
if (!is_array($gadai_detail) || empty($gadai_detail['norujukan'])) {
return redirect()->back()->with('error', 'SAG tidak dijumpai.');
}
$kakitangan = \App\User::where([['role_harian', 'khazanah'], ['cawangan', $kodcaw]])->first();
$customer = $this->api()
->get($api_url . '/api/customers/' . ($gadai_detail['nokp'] ?? ''))
->json();
$customer_detail = is_array($customer) ? $customer : [];
$marhun = $this->api()
->get($api_url . '/api/marhun/' . $norujukan)
->json();
$marhun_detail = is_array($marhun) ? ($marhun[0] ?? []) : [];
if (!is_array($marhun_detail)) {
$marhun_detail = [];
}
$mutu = $this->api()
->get($api_url . '/api/marhun/mutu/sag', [
'kodcaw' => $kodcaw,
'norujukan' => $norujukan,
])
->json();
$cawangan = $this->api()
->get($api_url . '/api/cawangan/detail/' . $kodcaw)
->json();
$cawangan_detail = is_array($cawangan) ? ($cawangan[0] ?? []) : [];
$kadar_ujrah = $this->api()
->get($api_url . '/api/kadarujrah/by_group_id/', [
'kodcaw' => $cawangan_detail['kodcaw'] ?? $kodcaw,
])
->json();
$pembayaran_segera = 'https://pay.arrahn.com.my';
$komoditi = $this->api()->get($api_url . '/api/komuditi/check')->json();
$product = $this->api()
->get($api_url . '/api/komuditi/description', [
'kodproduk' => is_array($komoditi) ? ($komoditi['kodproduct'] ?? '') : '',
])
->json();
$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) {
return view('print.sag_ujrah_3', compact(
'komoditi',
'product',
'kuantiti',
'gadai_detail',
'customer_detail',
'marhun_detail',
'cawangan_detail',
'api_url',
'pembayaran_segera',
'auth_username',
'mutu',
'kadar_ujrah',
'kakitangan'
));
}
return view('print.sag', compact(
'gadai_detail',
'customer_detail',
'marhun_detail',
'cawangan_detail',
'api_url',
'pembayaran_segera',
'auth_username',
'mutu',
'kakitangan'
));
}
/** /**
* PC: tanda rekod PAID sebagai selesai semak / blast. * PC: tanda rekod PAID sebagai selesai semak / blast.
*/ */
@@ -309,7 +595,7 @@ class PembiayaanOnlineController extends Controller
$n = $res->json()['dikemaskini'] ?? count($ids); $n = $res->json()['dikemaskini'] ?? count($ids);
return redirect() return redirect()
->route('pembiayaan.online.pc.paid') ->route('pembiayaan.online.pc.paid')
->with('message', $n . ' rekod ditanda selesai semak/blast.'); ->with('message', $n . ' rekod ditanda selesai. WhatsApp notis dihantar (nombor ujian jika bukan production).');
} }
/** /**
+1 -1
View File
@@ -12,6 +12,6 @@ class VerifyCsrfToken extends Middleware
* @var array * @var array
*/ */
protected $except = [ protected $except = [
// 'internal/notifications',
]; ];
} }
+568
View File
@@ -0,0 +1,568 @@
<?php
namespace App\Services;
use App\StaffNotification;
use App\User;
use Illuminate\Support\Facades\Log;
class NotificationService
{
const TYPE_PEMBIAYAAN_PENDING_PC = 'pembiayaan_online.pending_pc';
const TYPE_MODAL_PENDING_OPERASI = 'modal_online.pending_operasi';
const TYPE_PEMBIAYAAN_BATCH_OPERASI = 'pembiayaan_online.batch_operasi';
const TYPE_PEMBIAYAAN_REJECTED_PC = 'pembiayaan_online.rejected_pc';
const TYPE_PEMBIAYAAN_PAID_PC = 'pembiayaan_online.paid_pc';
const TYPE_GADAI_KELULUSAN = 'gadai.kelulusan';
const TYPE_ANGGOTA_PENDING_OPERASI = 'anggota.pending_operasi';
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.
*
* @param string $role
* @param string $kodcaw
* @param array $payload
* @return \Illuminate\Support\Collection
*/
public function notifyRoleAtCawangan($role, $kodcaw, array $payload)
{
$users = User::query()
->where('roles', $role)
->where('cawangan', $kodcaw)
->get();
$created = collect();
foreach ($users as $user) {
$created->push($this->notifyUser($user, $kodcaw, $role, $payload));
}
return $created;
}
/**
* Create one inbox row per staff matching role (any branch). For HQ like operasi.
*
* @param string $role
* @param array $payload
* @return \Illuminate\Support\Collection
*/
public function notifyRole($role, array $payload)
{
$kodcaw = isset($payload['cawangan']) ? $payload['cawangan'] : null;
$users = User::query()->where('roles', $role)->get();
$created = collect();
foreach ($users as $user) {
$created->push($this->notifyUser($user, $kodcaw, $role, $payload));
}
return $created;
}
/**
* Entry point for api_arrahn (and local callers). Builds title/url from type when omitted.
*
* @param array $input
* @return \Illuminate\Support\Collection
*/
public function dispatch(array $input)
{
$type = isset($input['type']) ? $input['type'] : '';
$kodcaw = isset($input['cawangan']) ? $input['cawangan'] : null;
$audience = isset($input['audience']) && is_array($input['audience']) ? $input['audience'] : [];
$role = isset($audience['role']) ? $audience['role'] : null;
$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);
if (!empty($input['title'])) {
$payload['title'] = $input['title'];
}
if (array_key_exists('body', $input) && $input['body'] !== null && $input['body'] !== '') {
$payload['body'] = $input['body'];
}
if (!empty($input['url'])) {
$payload['url'] = $input['url'];
}
if ($role === 'operasi') {
return $this->notifyRole('operasi', $payload);
}
if ($role && $kodcaw) {
return $this->notifyRoleAtCawangan($role, $kodcaw, $payload);
}
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|null $kodcaw
* @param array $data
* @return array
*/
protected function payloadForType($type, $kodcaw, array $data)
{
if ($type === self::TYPE_MODAL_PENDING_OPERASI) {
$jenis = isset($data['jenis']) ? strtoupper((string) $data['jenis']) : 'HAD';
$label = $jenis === 'TOPUP' ? 'tambah modal' : 'had modal';
$nama = !empty($data['cawangan_nama']) ? $data['cawangan_nama'] : $kodcaw;
$amaun = number_format(isset($data['amaun']) ? (float) $data['amaun'] : 0, 2);
$oleh = isset($data['dimohon_oleh']) ? trim((string) $data['dimohon_oleh']) : '';
return [
'type' => $type,
'cawangan' => $kodcaw,
'title' => 'Permohonan modal online',
'body' => sprintf(
'%s mohon %s RM %s%s',
$nama,
$label,
$amaun,
$oleh !== '' ? ' — '.$oleh : ''
),
'url' => '/pembiayaan-online/operasi/topup',
'data' => $data,
];
}
if ($type === self::TYPE_PEMBIAYAAN_BATCH_OPERASI) {
$nama = !empty($data['cawangan_nama']) ? $data['cawangan_nama'] : $kodcaw;
$batchNo = !empty($data['batch_no']) ? $data['batch_no'] : 'batch';
$batchId = isset($data['batch_id']) ? $data['batch_id'] : null;
$submitted = isset($data['submitted_count']) ? (int) $data['submitted_count'] : 0;
$jumlah = number_format(isset($data['jumlah_keseluruhan']) ? (float) $data['jumlah_keseluruhan'] : 0, 2);
$oleh = isset($data['pc_submitted_by']) ? trim((string) $data['pc_submitted_by']) : '';
$appended = !empty($data['appended']);
if ($appended) {
$body = sprintf(
'%s tambah %d rekod ke %s (RM %s)%s',
$nama,
$submitted,
$batchNo,
$jumlah,
$oleh !== '' ? ' — '.$oleh : ''
);
} else {
$body = sprintf(
'%s hantar %s — %d rekod (RM %s)%s',
$nama,
$batchNo,
$submitted,
$jumlah,
$oleh !== '' ? ' — '.$oleh : ''
);
}
return [
'type' => $type,
'cawangan' => $kodcaw,
'title' => 'Batch pembiayaan online',
'body' => $body,
'url' => ($kodcaw && $batchId)
? '/pembiayaan-online/operasi/'.$kodcaw.'/batches/'.$batchId
: '/pembiayaan-online/operasi',
'data' => $data,
];
}
if ($type === self::TYPE_PEMBIAYAAN_REJECTED_PC) {
$norujukan = !empty($data['norujukan']) ? $data['norujukan'] : 'rekod';
$reason = isset($data['reject_reason']) ? trim((string) $data['reject_reason']) : '';
$amaun = number_format(isset($data['pinjaman']) ? (float) $data['pinjaman'] : 0, 2);
return [
'type' => $type,
'cawangan' => $kodcaw,
'title' => 'Pembiayaan online ditolak',
'body' => sprintf(
'%s (RM %s) ditolak%s',
$norujukan,
$amaun,
$reason !== '' ? ' — '.$reason : ''
),
'url' => '/pembiayaan-online/pc',
'data' => $data,
];
}
if ($type === self::TYPE_PEMBIAYAAN_PAID_PC) {
$paidCount = isset($data['paid_count']) ? (int) $data['paid_count'] : 1;
$batchNo = !empty($data['batch_no']) ? $data['batch_no'] : '';
$norujukan = !empty($data['norujukan']) ? $data['norujukan'] : '';
$nama = isset($data['nama_pelanggan']) ? trim((string) $data['nama_pelanggan']) : '';
$amaun = number_format(
isset($data['pinjaman'])
? (float) $data['pinjaman']
: (isset($data['jumlah_keseluruhan']) ? (float) $data['jumlah_keseluruhan'] : 0),
2
);
if ($paidCount > 1 || ($batchNo !== '' && $norujukan === '')) {
$body = sprintf(
'%d rekod telah dibayar%s (RM %s)',
$paidCount > 0 ? $paidCount : 0,
$batchNo !== '' ? ' dalam '.$batchNo : '',
$amaun
);
} else {
$body = sprintf(
'%s telah dibayar (RM %s)%s',
$norujukan !== '' ? $norujukan : 'Rekod',
$amaun,
$nama !== '' ? ' — '.$nama : ''
);
}
return [
'type' => $type,
'cawangan' => $kodcaw,
'title' => 'Pembiayaan online telah dibayar',
'body' => $body,
'url' => '/pembiayaan-online/pc/paid',
'data' => $data,
];
}
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_ANGGOTA_PENDING_OPERASI) {
$nokp = !empty($data['nokp']) ? $data['nokp'] : '';
$nama = isset($data['nama']) ? trim((string) $data['nama']) : '';
$teller = isset($data['teller']) ? trim((string) $data['teller']) : '';
$komen = isset($data['komen']) ? trim((string) $data['komen']) : '';
$namaCaw = !empty($data['cawangan_nama']) ? $data['cawangan_nama'] : $kodcaw;
$who = $nama !== '' ? $nama : 'pelanggan';
if ($nokp !== '') {
$who .= ' ('.$nokp.')';
}
return [
'type' => $type,
'cawangan' => $kodcaw,
'title' => 'Permohonan assign anggota',
'body' => sprintf(
'%s — %s%s%s',
$namaCaw,
$who,
$teller !== '' ? ' — '.$teller : '',
$komen !== '' ? ' — '.$komen : ''
),
'url' => '/operasi/kelulusan/anggota',
'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 [
'type' => $type,
'cawangan' => $kodcaw,
'title' => isset($data['title']) ? $data['title'] : $type,
'body' => isset($data['body']) ? $data['body'] : null,
'url' => isset($data['url']) ? $data['url'] : null,
'data' => $data,
];
}
/**
* @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.
*
* @param \App\User $user
* @param string|null $kodcaw
* @param string|null $role
* @param array $payload
* @return \App\StaffNotification
*/
public function notifyUser(User $user, $kodcaw, $role, array $payload)
{
return StaffNotification::create([
'user_id' => $user->id,
'cawangan' => $kodcaw,
'role' => $role ?: $user->roles,
'type' => $payload['type'],
'title' => $payload['title'],
'body' => isset($payload['body']) ? $payload['body'] : null,
'url' => isset($payload['url']) ? $payload['url'] : null,
'data' => isset($payload['data']) ? $payload['data'] : null,
]);
}
/**
* @param \App\User $user
* @param int $limit
* @return \Illuminate\Support\Collection
*/
public function inboxFor(User $user, $limit = 20)
{
return StaffNotification::query()
->where('user_id', $user->id)
->orderByDesc('id')
->limit($limit)
->get();
}
/**
* @param \App\User $user
* @return int
*/
public function unreadCount(User $user)
{
return StaffNotification::query()
->where('user_id', $user->id)
->unread()
->count();
}
/**
* @param \App\User $user
* @param int $id
* @return \App\StaffNotification|null
*/
public function markAsRead(User $user, $id)
{
$notification = StaffNotification::query()
->where('user_id', $user->id)
->where('id', $id)
->first();
if (!$notification) {
return null;
}
if ($notification->read_at === null) {
$notification->read_at = now();
$notification->save();
}
return $notification;
}
/**
* @param \App\User $user
* @return int
*/
public function markAllAsRead(User $user)
{
return StaffNotification::query()
->where('user_id', $user->id)
->unread()
->update(['read_at' => now()]);
}
/**
* @param \App\User $user
* @param int $limit
* @return array
*/
public function inboxPayload(User $user, $limit = 20)
{
return [
'unread_count' => $this->unreadCount($user),
'notifications' => $this->inboxFor($user, $limit)->map(function (StaffNotification $notification) {
return $notification->toInboxArray();
})->values(),
];
}
}
+56
View File
@@ -0,0 +1,56 @@
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class StaffNotification extends Model
{
protected $table = 'staff_notifications';
protected $fillable = [
'user_id',
'cawangan',
'role',
'type',
'title',
'body',
'url',
'data',
'read_at',
];
protected $casts = [
'data' => 'array',
'read_at' => 'datetime',
];
public function user()
{
return $this->belongsTo(User::class);
}
public function isRead()
{
return $this->read_at !== null;
}
public function scopeUnread($query)
{
return $query->whereNull('read_at');
}
public function toInboxArray()
{
return [
'id' => $this->id,
'type' => $this->type,
'title' => $this->title,
'body' => $this->body,
'url' => $this->url,
'read_at' => $this->read_at ? $this->read_at->toIso8601String() : null,
'created_at' => $this->created_at ? $this->created_at->toIso8601String() : null,
'created_at_human' => $this->created_at ? $this->created_at->diffForHumans() : null,
];
}
}
+5
View File
@@ -46,6 +46,11 @@ class User extends Authenticatable
return $this->hasOne(Cawangan::class, 'kodcaw', 'cawangan'); return $this->hasOne(Cawangan::class, 'kodcaw', 'cawangan');
} }
public function staffNotifications()
{
return $this->hasMany(StaffNotification::class);
}
public function canImpersonate(): bool public function canImpersonate(): bool
{ {
return in_array($this->roles ?? '', ['admin'], true); return in_array($this->roles ?? '', ['admin'], true);
+5
View File
@@ -0,0 +1,5 @@
<?php
return [
'internal_secret' => env('INTERNAL_NOTIFICATION_SECRET', ''),
];
@@ -0,0 +1,43 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateStaffNotificationsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('staff_notifications', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('user_id');
$table->string('cawangan', 20)->nullable();
$table->string('role', 30)->nullable();
$table->string('type', 80);
$table->string('title');
$table->text('body')->nullable();
$table->string('url', 500)->nullable();
$table->text('data')->nullable();
$table->timestamp('read_at')->nullable();
$table->timestamps();
$table->index(['user_id', 'read_at']);
$table->index(['cawangan', 'type']);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('staff_notifications');
}
}
+1 -1
View File
@@ -559,7 +559,7 @@
function cetaklaporanalirantunai() function cetaklaporanalirantunai()
{ {
let urlroute = '{{ route("laporan.alirantunai") }}'; let urlroute = '{{ route("laporan.alirantunai") }}';
window.location.href = api_url + urlroute; window.location.href = urlroute;
} }
+1 -1
View File
@@ -412,7 +412,7 @@
</div> </div>
<script> <script>
var api_arrahnbid = "<?php echo config('app.api_arrahnbid') ?>"; var api_arrahnbid = @json($cawangan_info['api_arrahnbid'] ?? config('app.api_arrahnbid'));
var api_url = "{{ api_url_for_browser() }}"; var api_url = "{{ api_url_for_browser() }}";
var kodcaw = "{{ $cawangan_info['kodcaw'] }}"; var kodcaw = "{{ $cawangan_info['kodcaw'] }}";
var tagarrahnbid = "{{ $cawangan_info['tagarrahnbid'] }}"; var tagarrahnbid = "{{ $cawangan_info['tagarrahnbid'] }}";
+1 -1
View File
@@ -541,7 +541,7 @@
</div> </div>
<script> <script>
var api_arrahnbid = "<?php echo config('app.api_arrahnbid') ?>"; var api_arrahnbid = @json($cawangan_info['api_arrahnbid'] ?? config('app.api_arrahnbid'));
var api_url = "{{ api_url_for_browser() }}"; var api_url = "{{ api_url_for_browser() }}";
var kodcaw = "{{ $cawangan_info['kodcaw'] }}"; var kodcaw = "{{ $cawangan_info['kodcaw'] }}";
var tagarrahnbid = "{{ $cawangan_info['tagarrahnbid'] }}"; var tagarrahnbid = "{{ $cawangan_info['tagarrahnbid'] }}";
@@ -131,7 +131,8 @@
@endforeach @endforeach
</tr> </tr>
</thead> </thead>
<tbody></tbody> <tbody>
{{ $slot }} {{ $slot }}
</tbody>
</table> </table>
</div> </div>
+1 -1
View File
@@ -437,7 +437,7 @@
</div> </div>
<script> <script>
var api_arrahnbid = "<?php echo config('app.api_arrahnbid') ?>"; var api_arrahnbid = @json($cawangan_info['api_arrahnbid'] ?? config('app.api_arrahnbid'));
var api_url = "{{ api_url_for_browser() }}"; var api_url = "{{ api_url_for_browser() }}";
var kodcaw = "{{ $cawangan_info['kodcaw'] }}"; var kodcaw = "{{ $cawangan_info['kodcaw'] }}";
var tagarrahnbid = "{{ $cawangan_info['tagarrahnbid'] }}"; var tagarrahnbid = "{{ $cawangan_info['tagarrahnbid'] }}";
+44 -8
View File
@@ -390,14 +390,14 @@
<x-datatable <x-datatable
id="myTable" id="myTable"
filename="SAG_Baru" filename="SAG_Baru"
:columns="['Bil', 'No Rujukan', 'Tarikh Pembiayaan', 'Tarikh Matang', 'Baki Harga Jualan', 'Baki Pembiayaan', 'Margin', 'Tempoh', 'Keuntungan', 'Lelong', 'Borang Online']" :columns="['Bil', 'No Rujukan', 'Cara Terima', 'Tarikh Pembiayaan', 'Tarikh Matang', 'Baki Harga Jualan', 'Baki Pembiayaan', 'Margin', 'Tempoh', 'Keuntungan', 'Lelong', 'Borang Online']"
/> />
</div> </div>
<div class="tab-pane fade {{ $activate_pane_SAG_lama }}" id="nav-xtawarruq" role="tabpanel" aria-labelledby="nav-xtawarruq-tab"> <div class="tab-pane fade {{ $activate_pane_SAG_lama }}" id="nav-xtawarruq" role="tabpanel" aria-labelledby="nav-xtawarruq-tab">
<x-datatable <x-datatable
id="xtawarruq" id="xtawarruq"
filename="SAG_Lama" filename="SAG_Lama"
:columns="['Bil', 'No Rujukan', 'Tarikh Pembiayaan', 'Tarikh Matang', 'Baki Harga Jualan', 'Baki Pembiayaan', 'Margin', 'Tempoh', 'Keuntungan', 'Lelong', 'Borang Online']" :columns="['Bil', 'No Rujukan', 'Cara Terima', 'Tarikh Pembiayaan', 'Tarikh Matang', 'Baki Harga Jualan', 'Baki Pembiayaan', 'Margin', 'Tempoh', 'Keuntungan', 'Lelong', 'Borang Online']"
/> />
</div> </div>
</div> </div>
@@ -712,13 +712,35 @@
</div> </div>
<script> <script>
var api_arrahnbid = "<?php echo config('app.api_arrahnbid') ?>"; var api_arrahnbid = @json($cawangan_info['api_arrahnbid'] ?? config('app.api_arrahnbid'));
var api_url = "{{ api_url_for_browser() }}"; var api_url = "{{ api_url_for_browser() }}";
var kodcaw = "{{ $cawangan_info['kodcaw'] }}"; var kodcaw = "{{ $cawangan_info['kodcaw'] }}";
var tagarrahnbid = "{{ $cawangan_info['tagarrahnbid'] }}"; var tagarrahnbid = "{{ $cawangan_info['tagarrahnbid'] }}";
var min_komoditi = "{{ $min_komoditi }}"; var min_komoditi = "{{ $min_komoditi }}";
$('#daftar_gadai').on('click', function() { console.log('test api_arrahnbid: ', api_arrahnbid);
$('#daftar_gadai').on('click', async function(event) {
event.preventDefault();
if ($(this).prop('disabled')) {
return;
}
$("#daftar_gadai").prop('disabled', true); $("#daftar_gadai").prop('disabled', true);
var bakiLelong = Number(@json((float) $bakilelong));
if (bakiLelong > 0) {
var confirmation = await Swal.fire({
icon: 'warning',
title: 'Baki Lelong',
text: 'Pelanggan mempunyai baki lelong, teruskan gadai?',
showCancelButton: true,
confirmButtonText: 'Teruskan',
cancelButtonText: 'Batal',
focusCancel: true
});
if (!(confirmation.isConfirmed || confirmation.value === true)) {
$("#daftar_gadai").prop('disabled', false);
return;
}
}
var noic = $('#noic').val(); var noic = $('#noic').val();
var iclama = "{{ $customer_info['kplama'] }}"; var iclama = "{{ $customer_info['kplama'] }}";
var icbaru = "{{ $customer_info['kpbaru'] }}"; var icbaru = "{{ $customer_info['kpbaru'] }}";
@@ -817,6 +839,10 @@
return '{{ url("/gadai/borang-pengesahan-online") }}/' + encodeURIComponent(norujukan); return '{{ url("/gadai/borang-pengesahan-online") }}/' + encodeURIComponent(norujukan);
} }
function formatCaraTerima(value) {
return String(value || 'TUNAI').toUpperCase() === 'ONLINE' ? 'Online' : 'Tunai';
}
function cetakBorangOnlineCell(gadai_detail) { function cetakBorangOnlineCell(gadai_detail) {
var cara = String(gadai_detail['cara_terima'] || '').toUpperCase(); var cara = String(gadai_detail['cara_terima'] || '').toUpperCase();
if (cara !== 'ONLINE') { if (cara !== 'ONLINE') {
@@ -1133,6 +1159,13 @@
text: 'Pembiayaan Sedang Dilelong..' text: 'Pembiayaan Sedang Dilelong..'
}); });
} }
},
error: function() {
Swal.fire({
icon: 'error',
title: 'Amaran',
text: 'Tidak dapat semak status lelong. Sila cuba semula.'
});
} }
}); });
@@ -1198,13 +1231,14 @@
let table_norujukan = gadai_detail["norujukan"]; let table_norujukan = gadai_detail["norujukan"];
if (gadai_detail["percentpinj"] != 0) if (gadai_detail["percentpinj"] != 0)
xtawarruq.row.add([ xtawarruq.row.add([
'<input type="radio" value=' + gadai_detail["norujukan"] + '<input type="radio" value="' + gadai_detail["norujukan"] +
' name="belum"/>', '" name="belum" data-cara-terima="' + (gadai_detail['cara_terima'] || '') + '"/>',
'<a href="#" onclick="checkserah(this)" data-pinjaman=' + '<a href="#" onclick="checkserah(this)" data-pinjaman=' +
gadai_detail['pinjaman'] + ' data-hargajualan=' + gadai_detail['pinjaman'] + ' data-hargajualan=' +
gadai_detail['hargajualan'] + ' data-norujukan=' + gadai_detail['hargajualan'] + ' data-norujukan=' +
gadai_detail['norujukan'] + ' data-url=' + urlroute + '>' + gadai_detail['norujukan'] + ' data-url=' + urlroute + '>' +
gadai_detail['norujukan'] + '</a>', gadai_detail['norujukan'] + '</a>',
formatCaraTerima(gadai_detail['cara_terima']),
gadai_detail['t_gadai'], gadai_detail['t_gadai'],
tarikh_matang, tarikh_matang,
'RM ' + formatter.format(gadai_detail['bakihargajualan']), 'RM ' + formatter.format(gadai_detail['bakihargajualan']),
@@ -1216,6 +1250,7 @@
'RM ' + formatter.format(upahsemasa), 'RM ' + formatter.format(upahsemasa),
'<div style="white-space: nowrap; color:red; font-weight: bolder">' + '<div style="white-space: nowrap; color:red; font-weight: bolder">' +
taglel + '</div>', taglel + '</div>',
cetakBorangOnlineCell(gadai_detail),
]).draw(); ]).draw();
} else { } else {
var taglel = ''; var taglel = '';
@@ -1256,6 +1291,7 @@
gadai_detail['hargajualan'] + ' data-norujukan=' + gadai_detail['hargajualan'] + ' data-norujukan=' +
gadai_detail['norujukan'] + ' data-url=' + urlroute + '>' + gadai_detail['norujukan'] + ' data-url=' + urlroute + '>' +
gadai_detail['norujukan'] + '</a>', gadai_detail['norujukan'] + '</a>',
formatCaraTerima(gadai_detail['cara_terima']),
gadai_detail['t_gadai'], gadai_detail['t_gadai'],
tarikh_matang, tarikh_matang,
'RM ' + formatter.format(gadai_detail['bakihargajualan']), 'RM ' + formatter.format(gadai_detail['bakihargajualan']),
@@ -2031,7 +2067,7 @@
harini.setHours(0, 0, 0, 0); harini.setHours(0, 0, 0, 0);
if ($("input[name='options']:checked").val() == 'belum_tebus') { if ($("input[name='options']:checked").val() == 'belum_tebus') {
let test1 = $('input[name="belum"]:checked').closest('tr').children('td:eq(2)').text(); let test1 = $('input[name="belum"]:checked').closest('tr').children('td:eq(3)').text();
let tarikhgadai2 = new Date(test1); let tarikhgadai2 = new Date(test1);
tarikhgadai2.setHours(0, 0, 0, 0); tarikhgadai2.setHours(0, 0, 0, 0);
if (+harini !== +tarikhgadai2) { if (+harini !== +tarikhgadai2) {
@@ -2448,7 +2484,7 @@
} }
if ($("input[name='options']:checked").val() == 'belum_tebus') { if ($("input[name='options']:checked").val() == 'belum_tebus') {
let test1 = $('input[name="belum"]:checked').closest('tr').children('td:eq(2)').text(); let test1 = $('input[name="belum"]:checked').closest('tr').children('td:eq(3)').text();
let tarikhgadai2 = new Date(test1); let tarikhgadai2 = new Date(test1);
tarikhgadai2.setHours(0, 0, 0, 0); tarikhgadai2.setHours(0, 0, 0, 0);
@@ -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>
@@ -243,7 +243,7 @@
</tr> </tr>
<tr> <tr>
<th>Cara Terima Pembiayaan</th> <th>Cara Terima Pembiayaan</th>
<td>Online (kredit ke akaun bank) — bukan tunai</td> <td>Atas Talian (Online)</td>
</tr> </tr>
<tr> <tr>
<th>Teller</th> <th>Teller</th>
+115 -6
View File
@@ -29,6 +29,7 @@
<div class="form-group row col-12"> <div class="form-group row col-12">
<input type="hidden" name="kodcaw" id="kodcaw" value="{{$cawangan_info['kodcaw']}}"> <input type="hidden" name="kodcaw" id="kodcaw" value="{{$cawangan_info['kodcaw']}}">
<input type="hidden" id="pembiayaan_online_enabled" value="{{ !empty(($cawangan_info['features'] ?? [])['pembiayaan_online']) ? '1' : '0' }}"> <input type="hidden" id="pembiayaan_online_enabled" value="{{ !empty(($cawangan_info['features'] ?? [])['pembiayaan_online']) ? '1' : '0' }}">
<input type="hidden" id="rebet_annur_enabled" value="{{ !empty(($cawangan_info['features'] ?? [])['rebet_annur']) ? '1' : '0' }}">
<input type="hidden" name="nokp" id="nokp" value="{{ $customer_info['kpbaru'] }}"> <input type="hidden" name="nokp" id="nokp" value="{{ $customer_info['kpbaru'] }}">
<input type="hidden" name="tag_anggota" id="tag_anggota" value="{{ $customer_info['tag_anggota'] ?? 0 }}"> <input type="hidden" name="tag_anggota" id="tag_anggota" value="{{ $customer_info['tag_anggota'] ?? 0 }}">
@@ -186,6 +187,19 @@
</div> </div>
</diV> </diV>
</div> </div>
@if(!empty(($cawangan_info['features'] ?? [])['rebet_annur']))
<div class="col-4">
<div class="form-group row">
<div class="col-md-6">
Kadar Rebet
</div>
<div class="col-md-6">
<input type="text" class="form-control" readonly name="rebate" id="rebate">
<input type="text" class="form-control" readonly name="kadar_rebate" id="kadar_rebate">
</div>
</div>
</div>
@endif
<div class="col-4"> <div class="col-4">
<div class="form-group row"> <div class="form-group row">
<div class="col-md-6"> <div class="col-md-6">
@@ -232,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;">
@@ -292,6 +305,9 @@
<a href="{{ route('gadai.batal',['norujukan'=>$norujukan]) }}" class="float-right btn btn-danger mr-2">Batal</a> <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"> <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> <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>
</div> </div>
</form> </form>
@@ -398,6 +414,9 @@
<script> <script>
var api_url = "{{ api_url_for_browser() }}"; var api_url = "{{ api_url_for_browser() }}";
var kodcaw_ujrah = "<?php echo $cawangan_info['kodcaw'] ?>"; 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) { $('form input').keydown(function (e) {
if (e.keyCode == 13) { if (e.keyCode == 13) {
e.preventDefault(); e.preventDefault();
@@ -409,6 +428,13 @@
$(document).on("keydown", disableF5); $(document).on("keydown", disableF5);
var norujukan = $('#norujukan').val(); var norujukan = $('#norujukan').val();
getMarhun(norujukan); getMarhun(norujukan);
@if(session('error'))
Swal.fire({
icon: 'error',
title: 'Amaran!',
text: @json(session('error'))
});
@endif
$.ajax({ $.ajax({
method:'GET', method:'GET',
@@ -789,6 +815,26 @@
$('#ujrah').val(formatter.format(getRoundedMethod(array_keuntungan['ujrah']).toFixed(2))); $('#ujrah').val(formatter.format(getRoundedMethod(array_keuntungan['ujrah']).toFixed(2)));
$('#onepercent').val(formatter.format(getRoundedMethod(array_keuntungan['onepercent']).toFixed(2))); $('#onepercent').val(formatter.format(getRoundedMethod(array_keuntungan['onepercent']).toFixed(2)));
if ($('#rebet_annur_enabled').val() === '1') {
var rebate, kadar_rebate;
if (pinjaman < 20000) {
rebate = keuntungan_sebulan * 0.05;
kadar_rebate = 5;
} else if (pinjaman >= 20000 && pinjaman <= 50000) {
rebate = keuntungan_sebulan * 0.10;
kadar_rebate = 10;
} else if (pinjaman > 50000 && pinjaman <= 100000) {
rebate = keuntungan_sebulan * 0.15;
kadar_rebate = 15;
} else {
rebate = keuntungan_sebulan * 0.20;
kadar_rebate = 20;
}
rebate = getRoundedMethod(rebate);
$('#rebate').val(formatter.format(rebate.toFixed(2)));
$('#kadar_rebate').val(formatter.format(kadar_rebate.toFixed(2)) + ' %');
}
}); });
const formatter = new Intl.NumberFormat('en-US', { const formatter = new Intl.NumberFormat('en-US', {
@@ -1266,9 +1312,54 @@
.then(response => response.json()); .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) { function toggleCaraTerima(pinjaman) {
var pembiayaanOnlineEnabled = $('#pembiayaan_online_enabled').val() === '1'; var pembiayaanOnlineEnabled = $('#pembiayaan_online_enabled').val() === '1';
if (pembiayaanOnlineEnabled && pinjaman >= 5000 && pinjaman <= 50000) { if (pembiayaanOnlineEnabled && pinjaman >= MIN_PINJAMAN && pinjaman <= MAX_PINJAMAN) {
$('#cara_terima_section').show(); $('#cara_terima_section').show();
toggleBankOnlineSection(); toggleBankOnlineSection();
} else { } else {
@@ -1276,6 +1367,7 @@
$('#bank_online_section').hide(); $('#bank_online_section').hide();
$('#cetak_borang_online').hide(); $('#cetak_borang_online').hide();
$('#cara_terima_tunai').prop('checked', true); $('#cara_terima_tunai').prop('checked', true);
syncTerimaButton();
} }
} }
@@ -1290,16 +1382,20 @@
$('#bank_online_section').hide(); $('#bank_online_section').hide();
$('#cetak_borang_online').hide(); $('#cetak_borang_online').hide();
} }
syncTerimaButton();
} }
$(document).on('change', 'input[name="cara_terima"]', function () { $(document).on('change', 'input[name="cara_terima"]', function () {
toggleBankOnlineSection(); 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') || ''); $('#gadai_nama_bank').val($('#gadai_kodbank option:selected').data('nama') || '');
syncTerimaButton();
}); });
syncTerimaButton();
$(document).on('click', '#cetak_borang_online', function () { $(document).on('click', '#cetak_borang_online', function () {
var kodbank = $('#gadai_kodbank').val(); var kodbank = $('#gadai_kodbank').val();
var noakaun = ($('#gadai_noakaun').val() || '').trim(); var noakaun = ($('#gadai_noakaun').val() || '').trim();
@@ -1332,6 +1428,9 @@
form.append($('<input>', { type: 'hidden', name: 'noakaun', value: noakaun })); form.append($('<input>', { type: 'hidden', name: 'noakaun', value: noakaun }));
form.append($('<input>', { type: 'hidden', name: 'nama_akaun', value: namaAkaun })); form.append($('<input>', { type: 'hidden', name: 'nama_akaun', value: namaAkaun }));
form.appendTo('body').trigger('submit').remove(); form.appendTo('body').trigger('submit').remove();
printedBorang = currentBorangFields();
syncTerimaButton();
}); });
async function getModalOnline() { async function getModalOnline() {
@@ -1381,7 +1480,7 @@
async function checkDisbursementFloat(pinjaman) { async function checkDisbursementFloat(pinjaman) {
var cara = $('input[name="cara_terima"]:checked').val() || 'TUNAI'; var cara = $('input[name="cara_terima"]:checked').val() || 'TUNAI';
if (!(pinjaman >= 5000 && pinjaman <= 50000)) { if (!(pinjaman >= MIN_PINJAMAN && pinjaman <= MAX_PINJAMAN)) {
cara = 'TUNAI'; cara = 'TUNAI';
} }
@@ -1488,7 +1587,7 @@
let bakiakhir = 0; let bakiakhir = 0;
var kodcaw = $('#kodcaw').val(); var kodcaw = $('#kodcaw').val();
if(pinjaman == 0){ if(pinjaman == 0){
$("#terima_gadai").prop('disabled', false); syncTerimaButton();
Swal.fire({ Swal.fire({
icon: 'error', icon: 'error',
title: 'Amaran!', title: 'Amaran!',
@@ -1497,6 +1596,16 @@
return false; 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); let floatOk = await checkDisbursementFloat(pinjaman);
if (!floatOk) { if (!floatOk) {
$("#terima_gadai").prop('disabled', false); $("#terima_gadai").prop('disabled', false);
@@ -219,78 +219,70 @@
<div class="card-body text-center"> <div class="card-body text-center">
<div class="row form-group justify-content-center"> <div class="row form-group justify-content-center">
<div class="col-3 mx-1"> <div class="col-4 px-2">
<div class="row"> <a href="{{ url('dashboard') }}" disabled
<a href="{{ url('dashboard') }}" disabled class="btn btn-info btn-lg btn-block mb-3">Dashboard</a>
class="btn btn-info btn-lg btn-block mb-3">Dashboard</a>
</div>
<div class="row">
<a href="{{ url('kakitangan') }}"
class="btn btn-info btn-lg btn-block mb-3">Kakitangan</a>
</div>
<div class="row">
<a href="{{ url('kelulusan') }}" id="btnkelulusan"
class="btn btn-info btn-lg btn-block mb-3">Kelulusan</a>
</div>
{{-- <div class="row">
<a href="{{ url('permohonan') }}" class="btn btn-danger btn-lg btn-block mb-3">Permohonan</a>
</div> --}}
<div class="row">
<a href="#" data-target="#denominasiShowModal" data-backdrop="static" data-keyboard="false"
data-toggle="modal" class="btn btn-info btn-lg btn-block mb-3">Denominasi</a>
</div> </div>
<div class="row"> <div class="col-4 px-2">
<a href="{{ route('khazanah.harga_emas') }}"
class="btn btn-info btn-lg btn-block mb-3">Harga Emas</a>
</div>
</div>
<div class="col-3 mx-1">
<div class="row">
<a href="{{ route('khazanah.pelanggan') }}" <a href="{{ route('khazanah.pelanggan') }}"
class="btn btn-info btn-lg btn-block mb-3">Pelanggan</a> class="btn btn-info btn-lg btn-block mb-3">Pelanggan</a>
</div> </div>
<div class="row"> <div class="col-4 px-2">
<a href="{{ route('lelong') }}" class="btn btn-info btn-lg btn-block mb-3">Lelong</a>
</div>
<div class="col-4 px-2">
<a href="{{ url('kakitangan') }}"
class="btn btn-info btn-lg btn-block mb-3">Kakitangan</a>
</div>
<div class="col-4 px-2">
<a href="{{ route('laporan') }}" class="btn btn-info btn-lg btn-block mb-3">Laporan</a> <a href="{{ route('laporan') }}" class="btn btn-info btn-lg btn-block mb-3">Laporan</a>
</div> </div>
<div class="row"> <div class="col-4 px-2">
<a href="{{ url('panjar') }}" class="btn btn-info btn-lg btn-block mb-3">Perbelanjaan</a>
</div>
<div class="col-4 px-2">
<a href="{{ url('kelulusan') }}" id="btnkelulusan"
class="btn btn-info btn-lg btn-block mb-3">Kelulusan</a>
</div>
<div class="col-4 px-2">
<a href="{{ url('menu') }}" <a href="{{ url('menu') }}"
id="btn-khazanah" id="btn-khazanah"
class="btn {{ $countkhazanah > 0 ? 'btn-danger' : 'btn-info' }} btn-lg btn-block mb-3">Khazanah</a> class="btn {{ $countkhazanah > 0 ? 'btn-danger' : 'btn-info' }} btn-lg btn-block mb-3">Khazanah</a>
</div> </div>
<div class="row"> <div class="col-4 px-2">
<a href="{{ url('alirantunai') }}" class="btn btn-info btn-lg btn-block mb-3">Aliran
Tunai</a>
</div>
<div class="col-4 px-2">
<a href="#" data-target="#denominasiShowModal" data-backdrop="static" data-keyboard="false"
data-toggle="modal" class="btn btn-info btn-lg btn-block mb-3">Denominasi</a>
</div>
<div class="col-4 px-2">
<a href="{{ url('pendahuluanserahan') }}" <a href="{{ url('pendahuluanserahan') }}"
class="btn btn-info btn-lg btn-block mb-3">Pendahuluan - Serahan</a> class="btn btn-info btn-lg btn-block mb-3">Pendahuluan - Serahan</a>
</div> </div>
<div class="col-4 px-2">
<a href="{{ route('khazanah.bajet') }}" class="btn btn-info btn-lg btn-block mb-3">Bajet</a>
</div>
<div class="col-4 px-2">
<a href="{{ route('khazanah.harga_emas') }}"
class="btn btn-info btn-lg btn-block mb-3">Harga Emas</a>
</div>
@if(!empty(($cawangan_info['features'] ?? [])['pembiayaan_online'])) @if(!empty(($cawangan_info['features'] ?? [])['pembiayaan_online']))
<div class="row"> <div class="col-4 px-2">
<a href="{{ route('pembiayaan.online.pc') }}" <a href="{{ route('pembiayaan.online.pc') }}"
class="btn btn-info btn-lg btn-block mb-3">Pembiayaan Online</a> class="btn btn-info btn-lg btn-block mb-3">Pembiayaan Online</a>
</div> </div>
<div class="col-4 px-2">
<a href="{{ route('pembiayaan.online.pc.topup') }}"
class="btn btn-info btn-lg btn-block mb-3">Had / Modal Online</a>
</div>
@endif @endif
<div class="row"> <div class="col-4 px-2">
<a href="{{ route('khazanah.indexwakil') }}" <a href="{{ route('khazanah.indexwakil') }}"
class="btn btn-info btn-lg btn-block mb-3">Surat Wakil</a> class="btn btn-info btn-lg btn-block mb-3">Surat Wakil</a>
</div> </div>
</div> </div>
<div class="col-3 mx-1">
{{-- <div class="row">
<a href="#" class="btn btn-danger btn-lg btn-block mb-3">Capaian</a>
</div> --}}
<div class="row">
<a href="{{ route('lelong') }}" class="btn btn-info btn-lg btn-block mb-3">Lelong</a>
</div>
<div class="row">
<a href="{{ url('panjar') }}" class="btn btn-info btn-lg btn-block mb-3">Perbelanjaan</a>
</div>
<div class="row">
<a href="{{ url('alirantunai') }}" class="btn btn-info btn-lg btn-block mb-3">Aliran
Tunai</a>
</div>
<div class="row">
<a href="{{ route('khazanah.bajet') }}" class="btn btn-info btn-lg btn-block mb-3">Bajet</a>
</div>
</div>
</div> </div>
</div> </div>
+1 -1
View File
@@ -414,7 +414,7 @@
var kodcaw = "{{ $cawangan_info['kodcaw'] }}"; var kodcaw = "{{ $cawangan_info['kodcaw'] }}";
var ppc = "{{ Auth::user()->name }}"; var ppc = "{{ Auth::user()->name }}";
var tagarrahnbid = "{{ $cawangan_info['tagarrahnbid'] }}"; var tagarrahnbid = "{{ $cawangan_info['tagarrahnbid'] }}";
var api_arrahnbid = "<?php echo config('app.api_arrahnbid') ?>"; var api_arrahnbid = @json($cawangan_info['api_arrahnbid'] ?? config('app.api_arrahnbid'));
@@ -52,6 +52,7 @@
<th>No K/P</th> <th>No K/P</th>
<th>Tarikh Pembiayaan</th> <th>Tarikh Pembiayaan</th>
<th>No Rujukan</th> <th>No Rujukan</th>
<th>Cara Terima</th>
<th>Pembiayaan (RM)</th> <th>Pembiayaan (RM)</th>
<th>Keuntungan Lain-lain Caj (RM) </th> <th>Keuntungan Lain-lain Caj (RM) </th>
<th>Keuntungan 1% (RM)</th> <th>Keuntungan 1% (RM)</th>
@@ -61,7 +62,7 @@
</tr> </tr>
</thead> </thead>
<tfoot class="thead-dark"> <tfoot class="thead-dark">
<th colspan="5">TOTAL</th> <th colspan="6">TOTAL</th>
<th id="pinjamantotal"></th> <th id="pinjamantotal"></th>
<th id="ujrahtotal"></th> <th id="ujrahtotal"></th>
<th id="onepercenttotal"></th> <th id="onepercenttotal"></th>
@@ -300,6 +301,10 @@
total_upah12 += upah12; total_upah12 += upah12;
var caraTerima = String(gadai['cara_terima'] || 'TUNAI').toUpperCase() === 'ONLINE'
? 'Online'
: 'Tunai';
// Pastikan semua nilai ada sebelum add row // Pastikan semua nilai ada sebelum add row
tablegadai.row.add([ tablegadai.row.add([
counter, counter,
@@ -307,6 +312,7 @@
gadai['nokp'] || '-', gadai['nokp'] || '-',
gadai['t_gadai'] || '-', gadai['t_gadai'] || '-',
gadai['norujukan'] || '-', gadai['norujukan'] || '-',
caraTerima,
formatter.format(pinjaman), formatter.format(pinjaman),
formatter.format(ujrah * 12), formatter.format(ujrah * 12),
formatter.format(onepercent * 12), formatter.format(onepercent * 12),
+1 -1
View File
@@ -103,7 +103,7 @@
<script> <script>
var api_arrahnbid = "<?php echo config('app.api_arrahnbid') ?>"; var api_arrahnbid = @json($cawangan_info['api_arrahnbid'] ?? config('app.api_arrahnbid'));
var api_url = "{{ api_url_for_browser() }}"; var api_url = "{{ api_url_for_browser() }}";
var kodcaw = "{{ $cawangan_info['kodcaw'] }}"; var kodcaw = "{{ $cawangan_info['kodcaw'] }}";
var tagarrahnbid = "{{ $cawangan_info['tagarrahnbid'] }}"; var tagarrahnbid = "{{ $cawangan_info['tagarrahnbid'] }}";
+1 -1
View File
@@ -414,7 +414,7 @@
var kodcaw = "{{ $cawangan_info['kodcaw'] }}"; var kodcaw = "{{ $cawangan_info['kodcaw'] }}";
var ppc = "{{ Auth::user()->name }}"; var ppc = "{{ Auth::user()->name }}";
var tagarrahnbid = "{{ $cawangan_info['tagarrahnbid'] }}"; var tagarrahnbid = "{{ $cawangan_info['tagarrahnbid'] }}";
var api_arrahnbid = "<?php echo config('app.api_arrahnbid') ?>"; var api_arrahnbid = @json($cawangan_info['api_arrahnbid'] ?? config('app.api_arrahnbid'));
+70 -10
View File
@@ -185,6 +185,7 @@
<th>Keuntungan</th> <th>Keuntungan</th>
<th>Lelong</th> <th>Lelong</th>
<th>Pembayaran Terakhir</th> <th>Pembayaran Terakhir</th>
<th>Tarikh Ansuran Terkini</th>
</tr> </tr>
</thead> </thead>
</table> </table>
@@ -204,6 +205,7 @@
<th>Keuntungan</th> <th>Keuntungan</th>
<th>Lelong</th> <th>Lelong</th>
<th>Pembayaran Terakhir</th> <th>Pembayaran Terakhir</th>
<th>Tarikh Ansuran Terkini</th>
</tr> </tr>
</thead> </thead>
</table> </table>
@@ -470,7 +472,7 @@
var kodcaw = "{{ $cawangan_info['kodcaw'] }}"; var kodcaw = "{{ $cawangan_info['kodcaw'] }}";
var ppc = "{{ Auth::user()->name }}"; var ppc = "{{ Auth::user()->name }}";
var tagarrahnbid = "{{ $cawangan_info['tagarrahnbid'] }}"; var tagarrahnbid = "{{ $cawangan_info['tagarrahnbid'] }}";
var api_arrahnbid = "<?php echo config('app.api_arrahnbid') ?>"; var api_arrahnbid = @json($cawangan_info['api_arrahnbid'] ?? config('app.api_arrahnbid'));
@@ -634,6 +636,45 @@
}; };
} }
function tarikhAnsuranTerkini(transactions)
{
if (!Array.isArray(transactions)) {
throw new Error('Invalid transaction response');
}
return transactions.reduce(function(latest, transaction) {
if (transaction.trans_type !== 'A') return latest;
var date = String(transaction.created_at || '').slice(0, 10);
if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) return latest;
return date > latest ? date : latest;
}, '') || 'Tiada bayaran';
}
function muatTarikhAnsuranTerkini(row, norujukan)
{
var originalData = row.data();
function update(value) {
// Ignore responses for rows removed when the customer/table changes.
if (row.data() !== originalData) return;
var updatedData = originalData.slice();
updatedData[11] = value;
row.data(updatedData).draw(false);
}
$.ajax({
method: 'get',
url: api_url + '/api/transaction/' + encodeURIComponent(norujukan),
success: function(transactions) {
try {
update(tarikhAnsuranTerkini(transactions));
} catch (error) {
update('Gagal dimuatkan');
}
},
error: function() {
update('Gagal dimuatkan');
}
});
}
function gadaiTable(data,jenistebus){ function gadaiTable(data,jenistebus){
var counter = 1; var counter = 1;
@@ -665,7 +706,15 @@
var taglel = ''; var taglel = '';
if(gadai_detail['taglel'] == 1){ if(gadai_detail['taglel'] == 1){
if(tagarrahnbid == 1){ if(tagarrahnbid == 1){
taglel = gadai_detail['t_lelong']; $.ajax({
method: "GET",
async: false,
url: api_arrahnbid + 'portalApi',
data: { norujukan: gadai_detail['norujukan'] },
success: function(data) {
taglel = (data && data.lelong && data.lelong.t_lelong) || '';
}
});
} else { } else {
$.ajax({ $.ajax({
method:"GET", method:"GET",
@@ -685,8 +734,8 @@
let table_norujukan = gadai_detail["norujukan"]; let table_norujukan = gadai_detail["norujukan"];
// let radio_action = "onclick=checkCanChangeGadaianDetail(" + "'" + table_norujukan + "'" + ")"; // let radio_action = "onclick=checkCanChangeGadaianDetail(" + "'" + table_norujukan + "'" + ")";
if(gadai_detail["percentpinj"] != 0) if(gadai_detail["percentpinj"] != 0) {
xtawarruq.row.add([ var ansuranRow = xtawarruq.row.add([
'<input type="radio" value='+ gadai_detail["norujukan"] + ' name="belum"/>', '<input type="radio" value='+ gadai_detail["norujukan"] + ' name="belum"/>',
// '<a href="#" onclick="checkserah(this)" data-pinjaman='+ gadai_detail['pinjaman'] +' data-hargajualan='+ gadai_detail['hargajualan'] +' data-norujukan='+ gadai_detail['norujukan'] +' data-url=' + urlroute +'>' + gadai_detail['norujukan'] + '</a>', // '<a href="#" onclick="checkserah(this)" data-pinjaman='+ gadai_detail['pinjaman'] +' data-hargajualan='+ gadai_detail['hargajualan'] +' data-norujukan='+ gadai_detail['norujukan'] +' data-url=' + urlroute +'>' + gadai_detail['norujukan'] + '</a>',
(function() { (function() {
@@ -716,15 +765,24 @@
let color = bayaranInfo.blocked ? 'red' : 'green'; let color = bayaranInfo.blocked ? 'red' : 'green';
return '<div style="white-space: nowrap; color: ' + color + '; font-weight: bold;">' + return '<div style="white-space: nowrap; color: ' + color + '; font-weight: bold;">' +
bayaranInfo.tarikh + '</div>'; bayaranInfo.tarikh + '</div>';
})() })(),
'Memuatkan...'
]).draw(); ]).draw();
muatTarikhAnsuranTerkini(ansuranRow, gadai_detail['norujukan']);
}
}else{ }else{
var taglel = ''; var taglel = '';
if(gadai_detail['taglel'] == 1){ if(gadai_detail['taglel'] == 1){
if(tagarrahnbid == 1){ if(tagarrahnbid == 1){
if(gadai_detail['t_lelong'] != null) { $.ajax({
taglel = gadai_detail['t_lelong']; method: "GET",
} async: false,
url: api_arrahnbid + 'portalApi',
data: { norujukan: gadai_detail['norujukan'] },
success: function(data) {
taglel = (data && data.lelong && data.lelong.t_lelong) || '';
}
});
} else { } else {
$.ajax({ $.ajax({
method:"GET", method:"GET",
@@ -743,7 +801,7 @@
let table_norujukan = gadai_detail["norujukan"]; let table_norujukan = gadai_detail["norujukan"];
// let radio_action = "onclick=checkCanChangeGadaianDetail(" + "'" + table_norujukan + "'" + ")"; // let radio_action = "onclick=checkCanChangeGadaianDetail(" + "'" + table_norujukan + "'" + ")";
tablegadai.row.add([ var ansuranRow = tablegadai.row.add([
'<input type="radio" value='+ gadai_detail["norujukan"] + ' name="belum"/>', '<input type="radio" value='+ gadai_detail["norujukan"] + ' name="belum"/>',
// '<a href="#" onclick="checkserah(this)" data-pinjaman='+ gadai_detail['pinjaman'] +' data-hargajualan='+ gadai_detail['hargajualan'] +' data-norujukan='+ gadai_detail['norujukan'] +' data-url=' + urlroute +'>' + gadai_detail['norujukan'] + '</a>', // '<a href="#" onclick="checkserah(this)" data-pinjaman='+ gadai_detail['pinjaman'] +' data-hargajualan='+ gadai_detail['hargajualan'] +' data-norujukan='+ gadai_detail['norujukan'] +' data-url=' + urlroute +'>' + gadai_detail['norujukan'] + '</a>',
(function() { (function() {
@@ -773,8 +831,10 @@
let color = bayaranInfo.blocked ? 'red' : 'green'; let color = bayaranInfo.blocked ? 'red' : 'green';
return '<div style="white-space: nowrap; color: ' + color + '; font-weight: bold;">' + return '<div style="white-space: nowrap; color: ' + color + '; font-weight: bold;">' +
bayaranInfo.tarikh + '</div>'; bayaranInfo.tarikh + '</div>';
})() })(),
'Memuatkan...'
]).draw(); ]).draw();
muatTarikhAnsuranTerkini(ansuranRow, gadai_detail['norujukan']);
} }
}else if(jenistebus == "sudah_tebus"){ }else if(jenistebus == "sudah_tebus"){
+9 -75
View File
@@ -214,14 +214,16 @@
v-pre v-pre
style="color: {{ 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 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"> <div id="divbiasa" class="d-none">
<a class="dropdown-item" id="linkz" href="#"> <a class="dropdown-item" id="linkz" href="#">
Tiada Data Tiada Data
</a> </a>
</div> </div>
@include('layouts.partials.notification_inbox')
</div> </div>
</li> </li>
@@ -281,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"
@@ -293,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
@@ -446,30 +401,9 @@
</div> </div>
@else @else
@endif @endif
@include('layouts.partials.batch_cutoff_banner')
@endif @endif
@if(session()->has('iklan') && session()->get('iklan') == 0)
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true" style="position: absolute; top: 1rem; right: 1rem; width:100%;z-index:1;" data-autohide="false">
<div class="toast-header">
<i class="text-danger fa-2x fa fa-exclamation-triangle mr-2"></i>
<strong class="mr-auto text-danger fa-2x">Amaran</strong>
{{-- <small>11 mins ago</small> --}}
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="toast-body">
<b style="font-size:18px;">sila tukar kata laluan asal dengan segera!</b>
</div>
</div>
<script>
$('.toast').toast('show');
</script>
@endif
<main class="py-4"> <main class="py-4">
@yield('content') @yield('content')
</main> </main>
@@ -164,29 +164,6 @@
</div> </div>
</nav> </nav>
@if(session()->has('iklan') && session()->get('iklan') == 0)
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true" style="position: absolute; top: 1rem; right: 1rem; width:100%;z-index:1;" data-autohide="false">
<div class="toast-header">
<i class="text-danger fa-2x fa fa-exclamation-triangle mr-2"></i>
<strong class="mr-auto text-danger fa-2x">Amaran</strong>
{{-- <small>11 mins ago</small> --}}
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="toast-body">
<b style="font-size:18px;">sila tukar kata laluan asal dengan segera!</b>
</div>
</div>
<script>
$('.toast').toast('show');
</script>
@endif
<main class="py-4"> <main class="py-4">
@yield('content') @yield('content')
</main> </main>
+5 -25
View File
@@ -111,14 +111,17 @@
<li class="nav-item dropdown" style="margin-right:10px;"> <li class="nav-item dropdown" style="margin-right:10px;">
<a id="navbarDropdown" class="nav-link " href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-pre> <a id="navbarDropdown" class="nav-link " href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-pre>
<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"> <div id="divbiasa" class="d-none">
<a class="dropdown-item" id="gadailinkz" href="#"> <a class="dropdown-item" id="gadailinkz" href="#">
Tiada Notifikasi Tiada Notifikasi
</a> </a>
<a class="dropdown-item" id="linkz" href="#"></a>
</div> </div>
@include('layouts.partials.notification_inbox')
</div> </div>
</li> </li>
@@ -254,29 +257,6 @@
</div> </div>
</nav> </nav>
@if(session()->has('iklan') && session()->get('iklan') == 0)
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true" style="position: absolute; top: 1rem; right: 1rem; width:100%;z-index:1;" data-autohide="false">
<div class="toast-header">
<i class="text-danger fa-2x fa fa-exclamation-triangle mr-2"></i>
<strong class="mr-auto text-danger fa-2x">Amaran</strong>
{{-- <small>11 mins ago</small> --}}
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="toast-body">
<b style="font-size:18px;">sila tukar kata laluan asal dengan segera!</b>
</div>
</div>
<script>
$('.toast').toast('show');
</script>
@endif
<main class="py-4"> <main class="py-4">
@yield('content') @yield('content')
</main> </main>
@@ -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>
@@ -0,0 +1,244 @@
<style>
.staff-notification-menu {
min-width: 340px;
max-width: 380px;
padding: 0;
}
.staff-notification-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 14px;
border-bottom: 1px solid #eee;
font-weight: 600;
font-size: 13px;
}
.staff-notification-header button {
border: 0;
background: none;
color: #1abc9c;
font-size: 12px;
padding: 0;
cursor: pointer;
}
.staff-notification-header button:disabled {
color: #bbb;
cursor: default;
}
.staff-notification-list {
max-height: 360px;
overflow-y: auto;
}
.staff-notification-item {
display: block;
padding: 10px 14px;
border-bottom: 1px solid #f3f3f3;
white-space: normal;
color: #333;
text-decoration: none;
}
.staff-notification-item:hover {
background: #f8f9fa;
text-decoration: none;
color: #333;
}
.staff-notification-item.is-unread {
background: #eafaf6;
}
.staff-notification-item .sn-title {
font-size: 13px;
font-weight: 600;
margin-bottom: 2px;
}
.staff-notification-item.is-unread .sn-title {
font-weight: 700;
}
.staff-notification-item .sn-body,
.staff-notification-item .sn-time {
font-size: 12px;
color: #666;
margin: 0;
}
.staff-notification-empty {
padding: 18px 14px;
text-align: center;
color: #888;
font-size: 13px;
}
.staff-notification-badge {
position: absolute;
top: 2px;
right: 0;
font-size: 10px;
min-width: 16px;
padding: 2px 5px;
border-radius: 10px;
background: #e74c3c;
color: #fff;
line-height: 1.2;
display: none;
}
.staff-notification-toggle {
position: relative;
display: inline-block;
}
a.nav-link #staff-notification-badge {
position: absolute;
top: 2px;
right: -4px;
}
li.nav-item.dropdown > a.nav-link {
position: relative;
}
</style>
<div id="staff-notification-inbox">
<div class="staff-notification-header">
<span>Notifikasi</span>
<button type="button" id="staff-notification-mark-all" disabled>Tanda semua dibaca</button>
</div>
<div id="staff-notification-list" class="staff-notification-list">
<div class="staff-notification-empty">Memuatkan...</div>
</div>
</div>
<script>
(function () {
var listUrl = @json(route('notifications.index'));
var markAllUrl = @json(route('notifications.read.all'));
var markOneUrlTemplate = @json(url('/notifications/__ID__/read'));
var csrf = document.querySelector('meta[name="csrf-token"]');
var csrfToken = csrf ? csrf.getAttribute('content') : '';
function markOneUrl(id) {
return markOneUrlTemplate.replace('__ID__', id);
}
function escapeHtml(value) {
return String(value == null ? '' : value)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;');
}
function setBadge(count) {
var badge = document.getElementById('staff-notification-badge');
var bell = document.getElementById('bell');
if (!badge) {
return;
}
if (count > 0) {
badge.style.display = 'inline-block';
badge.textContent = count > 99 ? '99+' : String(count);
if (bell) {
bell.style.color = '#e74c3c';
}
} else {
badge.style.display = 'none';
badge.textContent = '';
if (bell) {
bell.style.color = '';
}
}
var markAll = document.getElementById('staff-notification-mark-all');
if (markAll) {
markAll.disabled = count < 1;
}
}
function render(payload) {
var wrap = document.getElementById('staff-notification-list');
if (!wrap) {
return;
}
var items = payload.notifications || [];
setBadge(payload.unread_count || 0);
if (!items.length) {
wrap.innerHTML = '<div class="staff-notification-empty">Tiada notifikasi</div>';
return;
}
wrap.innerHTML = items.map(function (item) {
var unread = !item.read_at;
return (
'<a class="dropdown-item staff-notification-item' + (unread ? ' is-unread' : '') + '" href="' + escapeHtml(item.url || '#') + '" data-id="' + item.id + '">' +
'<div class="sn-title">' + escapeHtml(item.title) + '</div>' +
(item.body ? '<p class="sn-body">' + escapeHtml(item.body) + '</p>' : '') +
'<p class="sn-time">' + escapeHtml(item.created_at_human || '') + '</p>' +
'</a>'
);
}).join('');
}
function loadInbox() {
return fetch(listUrl, {
headers: { 'Accept': 'application/json', 'X-Requested-With': 'XMLHttpRequest' },
credentials: 'same-origin'
}).then(function (res) {
if (!res.ok) {
throw new Error('Gagal muat notifikasi');
}
return res.json();
}).then(render).catch(function () {
var wrap = document.getElementById('staff-notification-list');
if (wrap) {
wrap.innerHTML = '<div class="staff-notification-empty">Tidak dapat memuatkan notifikasi</div>';
}
});
}
function postJson(url) {
return fetch(url, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest',
'X-CSRF-TOKEN': csrfToken
},
credentials: 'same-origin',
body: '{}'
}).then(function (res) {
if (!res.ok) {
throw new Error('Gagal kemaskini notifikasi');
}
return res.json();
});
}
document.addEventListener('click', function (event) {
var target = event.target;
if (target && target.nodeType !== 1) {
target = target.parentElement;
}
if (!target || !target.closest) {
return;
}
var item = target.closest('.staff-notification-item');
if (!item) {
return;
}
var id = item.getAttribute('data-id');
if (!id) {
return;
}
postJson(markOneUrl(id)).catch(function () {});
});
var markAll = document.getElementById('staff-notification-mark-all');
if (markAll) {
markAll.addEventListener('click', function (event) {
event.preventDefault();
event.stopPropagation();
postJson(markAllUrl).then(function () {
loadInbox();
}).catch(function () {});
});
}
loadInbox();
setInterval(loadInbox, 30000);
})();
</script>
@@ -13,56 +13,95 @@
</ol> </ol>
</nav> </nav>
<div class="card-body text-center"> <div class="card-body">
<div class="row mb-3 align-items-end">
<div class="row mb-4"> <div class="col-md-3">
<div class="col-2 justify-content-start"> <label for="date_from">Tarikh Dari</label>
<input type="date" id="date_from" class="form-control" value="{{ $dateFrom }}">
</div> </div>
<div class="col-8 mt-3 justify-content-center"> <div class="col-md-3">
<label for="date_to">Tarikh Hingga</label>
<input type="date" id="date_to" class="form-control" value="{{ $dateTo }}">
</div> </div>
<div class="col-2"> <div class="col-md-3">
<button type="button" id="btn-filter-rekod" class="btn btn-primary mt-3 mt-md-0">Cari</button>
</div> </div>
</div> </div>
<table class='table table-striped table-hover' id='table-kelulusan' style="width:100%;"> <x-datatable
<thead class="thead-dark"> id="table-kelulusan"
<tr> filename="Rekod_Lulus_Operasi"
<th>No Rujukan</th> class="text-center"
<th>Status</th> :columns="['Tarikh', 'No Rujukan', 'Status', 'Komen', 'Nilai Pembiayaan', 'Nilai Marhun', 'Berat', 'Margin', 'Pelulus']"
<th>Komen</th> />
<th>Nilai Pembiayaan</th>
<th>Nilai Marhun</th>
<th>Berat</th>
<th>Margin</th>
<th>Pelulus</th>
</tr>
</thead>
<tbody>
@foreach($arraykelulusan as $index=>$k)
<tr>
<td>{{ $k['norujukan'] }}</td>
<td>{{ $k['status'] }}</td>
<td>{{ $k['komen'] }}</td>
<td>{{ $k['pinjaman'] }}</td>
<td>{{ $k['nilaimarhun'] }}</td>
<td>{{ $k['berat'] }}</td>
<td>{{ $k['margin'] }}</td>
<td>{{ $k['pelulus'] }}</td>
</tr>
@endforeach
</tbody>
</table>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<script> <script>
function escapeHtml(value) {
return $('<div>').text(value == null ? '' : String(value)).html();
}
var tablekelulusan = $('#table-kelulusan').DataTable({ var tablekelulusan = initErahnDatatable('#table-kelulusan', {
"scrollX": true deferRender: true,
order: [[0, 'desc']],
language: {
emptyTable: 'Tiada rekod',
zeroRecords: 'Tiada rekod dijumpai'
}
});
function setLoading(isLoading) {
$('#btn-filter-rekod').prop('disabled', isLoading).text(isLoading ? 'Memuatkan...' : 'Cari');
}
function loadRekodLulus() {
var dateFrom = $('#date_from').val();
var dateTo = $('#date_to').val();
setLoading(true);
tablekelulusan.clear().draw();
$.ajax({
url: "{{ route('operasi.rekodlepas.data') }}",
method: 'GET',
data: {
date_from: dateFrom,
date_to: dateTo
},
success: function (response) {
var rows = response.data || [];
var data = rows.map(function (row) {
return [
escapeHtml(row.tarikh || '-'),
escapeHtml(row.norujukan || '-'),
escapeHtml(row.status || '-'),
escapeHtml(row.komen || '-'),
escapeHtml(row.pinjaman || '-'),
escapeHtml(row.nilaimarhun || '-'),
escapeHtml(row.berat || '-'),
escapeHtml(row.margin || '-'),
escapeHtml(row.pelulus || '-')
];
});
tablekelulusan.rows.add(data).draw();
},
error: function () {
Swal.fire({
icon: 'error',
title: 'Ralat',
text: 'Gagal memuatkan rekod lulus.'
});
},
complete: function () {
setLoading(false);
}
}); });
}
$('#btn-filter-rekod').on('click', loadRekodLulus);
loadRekodLulus();
</script> </script>
@endsection @endsection
@@ -54,104 +54,131 @@
@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>
@endif @endif
@if($approvedCount > 0) @if($approvedCount > 0)
<button type="button" class="btn btn-dark btn-sm" data-toggle="modal" data-target="#paidBatchModal"> <button type="button" class="btn btn-dark btn-sm" data-toggle="modal" data-target="#paidBatchModal">
Tanda PAID Batch ({{ $approvedCount }}) Tanda semua sebagai dibayar ({{ $approvedCount }})
</button> </button>
@endif @endif
</div> </div>
</div> </div>
<div class="card-body table-responsive"> <div class="card-body">
<table class="table table-striped table-sm text-center"> <x-datatable
<thead class="thead-dark"> id="batch_lines"
class="table-sm text-center"
:filename="'Batch_' . ($batch['batch_no'] ?? $batch['id'])"
:columns="['Bil', 'Status', 'SAG', 'Pelanggan', 'No KP', 'Bank', 'No Akaun', 'Nama Akaun', 'Pinjaman (RM)', 'Rujukan Bayaran', 'Dokumen', 'Tindakan']"
:skipExport="['Dokumen', 'Tindakan']"
>
@foreach($lines as $line)
@php
$st = $line['status'] ?? '';
$badge = 'badge-secondary';
if ($st === 'APPROVED') $badge = 'badge-success';
elseif ($st === 'PAID') $badge = 'badge-dark';
elseif ($st === 'REJECTED_PC') $badge = 'badge-danger';
elseif (in_array($st, ['IN_BATCH', 'OPS_REVIEW'], true)) $badge = 'badge-primary';
@endphp
<tr> <tr>
<th>Status</th> <td>{{ $loop->iteration }}</td>
<th>SAG</th> <td><span class="badge {{ $badge }}">{{ $st ?: '-' }}</span></td>
<th>Pelanggan</th> <td>{{ $line['norujukan'] ?? '-' }}</td>
<th>No KP</th> <td class="text-left">{{ $line['nama_pelanggan'] ?? '-' }}</td>
<th>Bank</th> <td>{{ $line['nokp'] ?? '-' }}</td>
<th>No Akaun</th> <td>{{ $line['nama_bank'] ?? $line['kodbank'] ?? '-' }}</td>
<th>Nama Akaun</th> <td>{{ $line['noakaun'] ?? '-' }}</td>
<th>Pinjaman (RM)</th> <td class="text-left">{{ $line['nama_akaun'] ?? '-' }}</td>
<th>Rujukan Bayaran</th> <td class="text-right">{{ number_format((float)($line['pinjaman'] ?? 0), 2) }}</td>
<th>Tindakan</th> <td>{{ $line['no_rujukan_bayaran'] ?? '-' }}</td>
<td class="text-nowrap">
<button type="button"
class="btn btn-sm btn-outline-dark btn-preview-sag"
data-url="{{ route('pembiayaan.online.operasi.sag', ['kodcaw' => $kodcaw, 'id' => $line['id']]) }}"
data-norujukan="{{ $line['norujukan'] ?? '' }}">
SAG
</button>
@if(!empty($line['bukti_akaun_file_id']))
<a class="btn btn-sm btn-outline-primary"
href="{{ route('pembiayaan.online.operasi.bukti.akaun', ['kodcaw' => $kodcaw, 'id' => $line['id']]) }}"
target="_blank">
Slip Bank
</a>
@else
<span class="text-muted small">Tiada slip</span>
@endif
</td>
<td class="text-nowrap">
@if(in_array($st, ['IN_BATCH', 'OPS_REVIEW'], true))
<form method="post"
action="{{ route('pembiayaan.online.operasi.approve', ['kodcaw' => $kodcaw, 'id' => $line['id']]) }}"
class="d-inline js-swal-confirm"
data-title="Luluskan Rekod?"
data-html="Luluskan rekod <b>{{ $line['norujukan'] ?? '-' }}</b>?"
data-confirm="Ya, Lulus">
@csrf
<input type="hidden" name="batch_id" value="{{ $batch['id'] }}">
<button type="submit" class="btn btn-success btn-sm">Lulus</button>
</form>
<button type="button"
class="btn btn-danger btn-sm btn-reject"
data-id="{{ $line['id'] }}"
data-norujukan="{{ $line['norujukan'] ?? '' }}">
Tolak
</button>
@elseif($st === 'APPROVED')
<button type="button"
class="btn btn-dark btn-sm btn-paid"
data-id="{{ $line['id'] }}"
data-norujukan="{{ $line['norujukan'] ?? '' }}">
Tanda sebagai dibayar
</button>
<button type="button"
class="btn btn-outline-danger btn-sm btn-reject"
data-id="{{ $line['id'] }}"
data-norujukan="{{ $line['norujukan'] ?? '' }}">
Tolak
</button>
@elseif($st === 'PAID')
<span class="text-muted small">Selesai</span>
@else
<span class="text-muted small">-</span>
@endif
</td>
</tr> </tr>
</thead> @endforeach
<tbody> </x-datatable>
@forelse($lines as $line)
@php
$st = $line['status'] ?? '';
$badge = 'badge-secondary';
if ($st === 'APPROVED') $badge = 'badge-success';
elseif ($st === 'PAID') $badge = 'badge-dark';
elseif ($st === 'REJECTED_PC') $badge = 'badge-danger';
elseif (in_array($st, ['IN_BATCH', 'OPS_REVIEW'], true)) $badge = 'badge-primary';
@endphp
<tr>
<td><span class="badge {{ $badge }}">{{ $st ?: '-' }}</span></td>
<td>{{ $line['norujukan'] ?? '-' }}</td>
<td class="text-left">{{ $line['nama_pelanggan'] ?? '-' }}</td>
<td>{{ $line['nokp'] ?? '-' }}</td>
<td>{{ $line['nama_bank'] ?? $line['kodbank'] ?? '-' }}</td>
<td>{{ $line['noakaun'] ?? '-' }}</td>
<td class="text-left">{{ $line['nama_akaun'] ?? '-' }}</td>
<td class="text-right">{{ number_format((float)($line['pinjaman'] ?? 0), 2) }}</td>
<td>{{ $line['no_rujukan_bayaran'] ?? '-' }}</td>
<td class="text-nowrap">
@if(in_array($st, ['IN_BATCH', 'OPS_REVIEW'], true))
<form method="post"
action="{{ route('pembiayaan.online.operasi.approve', ['kodcaw' => $kodcaw, 'id' => $line['id']]) }}"
class="d-inline"
onsubmit="return confirm('Luluskan rekod {{ $line['norujukan'] ?? '' }}?');">
@csrf
<input type="hidden" name="batch_id" value="{{ $batch['id'] }}">
<button type="submit" class="btn btn-success btn-sm">Lulus</button>
</form>
<button type="button"
class="btn btn-danger btn-sm btn-reject"
data-id="{{ $line['id'] }}"
data-norujukan="{{ $line['norujukan'] ?? '' }}">
Tolak
</button>
@elseif($st === 'APPROVED')
<button type="button"
class="btn btn-dark btn-sm btn-paid"
data-id="{{ $line['id'] }}"
data-norujukan="{{ $line['norujukan'] ?? '' }}">
PAID + Bukti
</button>
<button type="button"
class="btn btn-outline-danger btn-sm btn-reject"
data-id="{{ $line['id'] }}"
data-norujukan="{{ $line['norujukan'] ?? '' }}">
Tolak
</button>
@elseif($st === 'PAID')
<span class="text-muted small">Selesai</span>
@else
<span class="text-muted small">-</span>
@endif
</td>
</tr>
@empty
<tr>
<td colspan="10">Tiada rekod dalam batch.</td>
</tr>
@endforelse
</tbody>
</table>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{{-- SAG preview modal --}}
<div class="modal fade" id="sagPreviewModal" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-xl" role="document" style="max-width:1140px;">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Pratonton SAG — <span id="sagPreviewTitle"></span></h5>
<button type="button" class="close" data-dismiss="modal"><span>&times;</span></button>
</div>
<div class="modal-body p-2">
<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">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Tutup</button>
</div>
</div>
</div>
</div>
{{-- Reject modal --}} {{-- Reject modal --}}
<div class="modal fade" id="rejectModal" tabindex="-1" role="dialog" aria-hidden="true"> <div class="modal fade" id="rejectModal" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog" role="document"> <div class="modal-dialog" role="document">
@@ -188,7 +215,7 @@
<input type="hidden" name="batch_id" value="{{ $batch['id'] }}"> <input type="hidden" name="batch_id" value="{{ $batch['id'] }}">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title">Tanda PAID — <span id="paidNorujukan"></span></h5> <h5 class="modal-title">Tanda sebagai dibayar — <span id="paidNorujukan"></span></h5>
<button type="button" class="close" data-dismiss="modal"><span>&times;</span></button> <button type="button" class="close" data-dismiss="modal"><span>&times;</span></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
@@ -203,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 PAID</button> <button type="submit" class="btn btn-dark">Simpan</button>
</div> </div>
</div> </div>
</form> </form>
@@ -224,7 +251,7 @@
@csrf @csrf
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title">Tanda PAID Semua APPROVED ({{ $approvedCount }})</h5> <h5 class="modal-title">Tanda semua sebagai dibayar ({{ $approvedCount }})</h5>
<button type="button" class="close" data-dismiss="modal"><span>&times;</span></button> <button type="button" class="close" data-dismiss="modal"><span>&times;</span></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
@@ -244,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 PAID Batch</button> <button type="submit" class="btn btn-dark">Simpan</button>
</div> </div>
</div> </div>
</form> </form>
@@ -252,7 +279,28 @@
</div> </div>
<script> <script>
$('.btn-reject').on('click', function () { var table = initErahnDatatable('#batch_lines', {
columnDefs: [
{ targets: 0, orderable: false, searchable: false, width: '40px' },
{ targets: [10, 11], orderable: false, searchable: false }
]
});
table.on('draw.dt', function () {
table.column(0, { search: 'applied', order: 'applied' }).nodes().each(function (cell, i) {
cell.innerHTML = i + 1;
});
}).draw();
$('#batch_lines').on('click', '.btn-preview-sag', function () {
$('#sagPreviewTitle').text($(this).data('norujukan') || '');
$('#sagPreviewFrame').attr('src', $(this).data('url'));
$('#sagPreviewModal').modal('show');
});
$('#sagPreviewModal').on('hidden.bs.modal', function () {
$('#sagPreviewFrame').attr('src', 'about:blank');
});
$('#batch_lines').on('click', '.btn-reject', function () {
var id = $(this).data('id'); var id = $(this).data('id');
$('#rejectForm').attr( $('#rejectForm').attr(
'action', 'action',
@@ -263,7 +311,7 @@
$('#rejectModal').modal('show'); $('#rejectModal').modal('show');
}); });
$('.btn-paid').on('click', function () { $('#batch_lines').on('click', '.btn-paid', function () {
var id = $(this).data('id'); var id = $(this).data('id');
$('#paidForm').attr( $('#paidForm').attr(
'action', 'action',
@@ -272,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
@@ -40,6 +40,7 @@
<th>No Akaun</th> <th>No Akaun</th>
<th>Pinjaman (RM)</th> <th>Pinjaman (RM)</th>
<th>Rujukan Bayaran</th> <th>Rujukan Bayaran</th>
<th>Dokumen</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -53,6 +54,23 @@
<td>{{ $line['noakaun'] ?? '-' }}</td> <td>{{ $line['noakaun'] ?? '-' }}</td>
<td class="text-right">{{ number_format((float)($line['pinjaman'] ?? 0), 2) }}</td> <td class="text-right">{{ number_format((float)($line['pinjaman'] ?? 0), 2) }}</td>
<td>{{ $line['no_rujukan_bayaran'] ?? '-' }}</td> <td>{{ $line['no_rujukan_bayaran'] ?? '-' }}</td>
<td class="text-nowrap">
<button type="button"
class="btn btn-sm btn-outline-dark btn-preview-sag"
data-url="{{ route('pembiayaan.online.pc.sag', ['id' => $line['id']]) }}"
data-norujukan="{{ $line['norujukan'] ?? '' }}">
SAG
</button>
@if(!empty($line['bukti_akaun_file_id']))
<a class="btn btn-sm btn-outline-primary"
href="{{ route('pembiayaan.online.pc.bukti.akaun', ['id' => $line['id']]) }}"
target="_blank">
Slip Bank
</a>
@else
<span class="text-muted small">Tiada slip</span>
@endif
</td>
</tr> </tr>
@endforeach @endforeach
</tbody> </tbody>
@@ -62,4 +80,32 @@
</div> </div>
</div> </div>
</div> </div>
<div class="modal fade" id="sagPreviewModal" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-xl" role="document" style="max-width:1140px;">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Pratonton SAG — <span id="sagPreviewTitle"></span></h5>
<button type="button" class="close" data-dismiss="modal"><span>&times;</span></button>
</div>
<div class="modal-body p-2">
<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">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Tutup</button>
</div>
</div>
</div>
</div>
<script>
$('.btn-preview-sag').on('click', function () {
$('#sagPreviewTitle').text($(this).data('norujukan') || '');
$('#sagPreviewFrame').attr('src', $(this).data('url'));
$('#sagPreviewModal').modal('show');
});
$('#sagPreviewModal').on('hidden.bs.modal', function () {
$('#sagPreviewFrame').attr('src', 'about:blank');
});
</script>
@endsection @endsection
@@ -69,13 +69,13 @@
<div class="card-header d-flex justify-content-between align-items-center flex-wrap"> <div class="card-header d-flex justify-content-between align-items-center flex-wrap">
<h5 class="mb-0">Senarai Pembiayaan Online — PAID</h5> <h5 class="mb-0">Senarai Pembiayaan Online — PAID</h5>
<button type="button" class="btn btn-success btn-sm mt-2 mt-md-0" id="btnMarkBlast" disabled> <button type="button" class="btn btn-success btn-sm mt-2 mt-md-0" id="btnMarkBlast" disabled>
Sahkan Tally / Selesai Sahkan semua sebagai dibayar
</button> </button>
</div> </div>
<div class="card-body"> <div class="card-body">
<p class="small text-muted"> <p class="small text-muted">
Semak jumlah &amp; bukti bayaran sepadan dengan pelanggan. Setelah semak, tanda selesai. Semak jumlah &amp; bukti bayaran sepadan dengan pelanggan. Setelah semak, tanda selesai.
(WhatsApp/SMS blast boleh disambung kemudian — butang ini menanda rekod sebagai selesai semak.) WhatsApp notis (template Cloud API) akan dihantar selepas tally. Di persekitaran ujian ia pergi ke nombor admin, bukan pelanggan.
</p> </p>
<form id="formMarkBlast" method="post" action="{{ route('pembiayaan.online.pc.blast') }}"> <form id="formMarkBlast" method="post" action="{{ route('pembiayaan.online.pc.blast') }}">
@csrf @csrf
@@ -168,7 +168,7 @@
function syncBlastButton() { function syncBlastButton() {
var n = $('.row-check:checked').length; var n = $('.row-check:checked').length;
$('#btnMarkBlast').prop('disabled', n === 0); $('#btnMarkBlast').prop('disabled', n === 0);
$('#btnMarkBlast').text(n ? ('Sahkan Tally / Selesai (' + n + ')') : 'Sahkan Tally / Selesai'); $('#btnMarkBlast').text(n ? ('Sahkan semua sebagai dibayar (' + n + ')') : 'Sahkan semua sebagai dibayar');
} }
$('#checkAll').on('change', function () { $('#checkAll').on('change', function () {
@@ -180,7 +180,7 @@
$('#btnMarkBlast').on('click', function () { $('#btnMarkBlast').on('click', function () {
if ($('.row-check:checked').length === 0) return; if ($('.row-check:checked').length === 0) return;
if (!confirm('Sahkan tally untuk rekod dipilih? Rekod akan ditanda selesai semak/blast.')) return; if (!confirm('Sahkan semua sebagai dibayar untuk rekod dipilih? Rekod akan ditanda sebagai dibayar dan WhatsApp notis akan dihantar.')) return;
$('#formMarkBlast').submit(); $('#formMarkBlast').submit();
}); });
</script> </script>
@@ -33,6 +33,20 @@
if (is_object($pendingReq)) { if (is_object($pendingReq)) {
$pendingReq = json_decode(json_encode($pendingReq), true); $pendingReq = json_decode(json_encode($pendingReq), true);
} }
$lineRows = collect($lines);
$verifiedCount = $lineRows->where('status', 'VERIFIED_PC')->count();
$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)
@@ -56,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">
@@ -82,18 +156,30 @@
<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>Jumlah RM {{ number_format((float)($queue['jumlah'] ?? 0), 2) }}</small> <small>
{{ $verifiedCount }} sudah sah
/ {{ $unverifiedCount }} belum semak
&middot; Jumlah RM {{ number_format((float)($queue['jumlah'] ?? 0), 2) }}
</small>
</div> </div>
</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>
@@ -101,18 +187,27 @@
<div class="card mb-4"> <div class="card mb-4">
<div class="card-header d-flex justify-content-between align-items-center"> <div class="card-header d-flex justify-content-between align-items-center">
<h5 class="mb-0">Senarai Menunggu (PENDING / REJECTED)</h5> <h5 class="mb-0">Senarai Menunggu (PENDING / REJECTED / SAH)</h5>
<button type="button" class="btn btn-primary" id="btnSubmitBatch" disabled>Hantar Batch ke Operasi</button> <button type="button" class="btn btn-primary" id="btnSubmitBatch" disabled>Hantar Batch ke Operasi</button>
</div> </div>
<div class="card-body"> <div class="card-body">
<p class="small text-muted">
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.
@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>
<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">
<tr> <tr>
<th><input type="checkbox" id="checkAll"></th> <th><input type="checkbox" id="checkAll" title="Pilih semua yang sudah sah"></th>
<th>Status</th> <th>Status</th>
<th>SAG</th> <th>SAG</th>
<th>Pelanggan</th> <th>Pelanggan</th>
@@ -126,15 +221,31 @@
</thead> </thead>
<tbody> <tbody>
@forelse($lines as $line) @forelse($lines as $line)
<tr class="{{ ($line['status'] ?? '') === 'REJECTED_PC' ? 'table-warning' : '' }}"> @php
$st = $line['status'] ?? '';
$isVerified = $st === 'VERIFIED_PC';
$isRejected = $st === 'REJECTED_PC';
$bankOk = !empty($line['kodbank']) && !empty($line['noakaun']);
@endphp
<tr class="{{ $isRejected ? 'table-warning' : ($isVerified ? 'table-success' : '') }}">
<td> <td>
<input type="checkbox" class="row-check" name="ids[]" value="{{ $line['id'] }}"> <input type="checkbox"
class="row-check"
name="ids[]"
value="{{ $line['id'] }}"
{{ $isVerified ? '' : 'disabled' }}
title="{{ $isVerified ? '' : 'Sahkan rekod ini dahulu' }}">
</td> </td>
<td> <td>
@if(($line['status'] ?? '') === 'REJECTED_PC') @if($isRejected)
<span class="badge badge-danger">REJECTED</span> <span class="badge badge-danger">REJECTED</span>
@elseif($isVerified)
<span class="badge badge-success">SAH</span>
@if(!empty($line['pc_verified_by']))
<div class="small text-muted">{{ $line['pc_verified_by'] }}</div>
@endif
@else @else
<span class="badge badge-secondary">PENDING</span> <span class="badge badge-secondary">BELUM SEMAK</span>
@endif @endif
</td> </td>
<td>{{ $line['norujukan'] ?? '-' }}</td> <td>{{ $line['norujukan'] ?? '-' }}</td>
@@ -144,7 +255,33 @@
<td>{{ $line['noakaun'] ?? '-' }}</td> <td>{{ $line['noakaun'] ?? '-' }}</td>
<td class="text-right">{{ number_format((float)($line['pinjaman'] ?? 0), 2) }}</td> <td class="text-right">{{ number_format((float)($line['pinjaman'] ?? 0), 2) }}</td>
<td class="text-left small">{{ $line['reject_reason'] ?? '-' }}</td> <td class="text-left small">{{ $line['reject_reason'] ?? '-' }}</td>
<td> <td class="text-nowrap">
@if($isVerified)
<button type="button"
class="btn btn-sm btn-outline-secondary btn-unverify"
data-id="{{ $line['id'] }}"
data-norujukan="{{ $line['norujukan'] ?? '' }}">
Batal
</button>
@else
<button type="button"
class="btn btn-sm btn-success btn-verify"
{{ $bankOk ? '' : 'disabled' }}
title="{{ $bankOk ? 'Semak maklumat pelanggan' : 'Lengkapkan maklumat bank dahulu' }}"
data-id="{{ $line['id'] }}"
data-norujukan="{{ $line['norujukan'] ?? '' }}"
data-nama="{{ $line['nama_pelanggan'] ?? '' }}"
data-nokp="{{ $line['nokp'] ?? '' }}"
data-namabank="{{ $line['nama_bank'] ?? $line['kodbank'] ?? '' }}"
data-noakaun="{{ $line['noakaun'] ?? '' }}"
data-namaakaun="{{ $line['nama_akaun'] ?? '' }}"
data-pinjaman="{{ number_format((float)($line['pinjaman'] ?? 0), 2) }}"
data-nota="{{ $line['nota'] ?? '' }}"
data-sebab="{{ $line['reject_reason'] ?? '' }}"
data-bukti-akaun="{{ $line['bukti_akaun_file_id'] ?? '' }}">
Semak
</button>
@endif
<button type="button" class="btn btn-sm btn-info btn-edit" <button type="button" class="btn btn-sm btn-info btn-edit"
data-id="{{ $line['id'] }}" data-id="{{ $line['id'] }}"
data-kodbank="{{ $line['kodbank'] ?? '' }}" data-kodbank="{{ $line['kodbank'] ?? '' }}"
@@ -152,7 +289,8 @@
data-namaakaun="{{ $line['nama_akaun'] ?? '' }}" data-namaakaun="{{ $line['nama_akaun'] ?? '' }}"
data-namabank="{{ $line['nama_bank'] ?? '' }}" data-namabank="{{ $line['nama_bank'] ?? '' }}"
data-nota="{{ $line['nota'] ?? '' }}" data-nota="{{ $line['nota'] ?? '' }}"
data-norujukan="{{ $line['norujukan'] ?? '' }}"> data-norujukan="{{ $line['norujukan'] ?? '' }}"
data-bukti-akaun="{{ $line['bukti_akaun_file_id'] ?? '' }}">
Edit Edit
</button> </button>
</td> </td>
@@ -166,6 +304,9 @@
</table> </table>
</div> </div>
</form> </form>
<form id="formUnverify" method="post" action="" class="d-none">
@csrf
</form>
</div> </div>
</div> </div>
@@ -221,8 +362,8 @@
{{-- Edit modal --}} {{-- Edit modal --}}
<div class="modal fade" id="editModal" tabindex="-1" role="dialog" aria-hidden="true"> <div class="modal fade" id="editModal" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog" role="document"> <div class="modal-dialog modal-xl" role="document" style="max-width:1140px;">
<form method="post" id="editForm" action=""> <form method="post" id="editForm" action="" enctype="multipart/form-data">
@csrf @csrf
@method('PUT') @method('PUT')
<div class="modal-content"> <div class="modal-content">
@@ -231,29 +372,50 @@
<button type="button" class="close" data-dismiss="modal"><span>&times;</span></button> <button type="button" class="close" data-dismiss="modal"><span>&times;</span></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="form-group"> <div class="row">
<label>Bank</label> <div class="col-md-4">
<select name="kodbank" id="editKodbank" class="form-control" required> <div class="form-group">
<option value="">-- Pilih Bank --</option> <label>Bank</label>
@foreach($banks as $bank) <select name="kodbank" id="editKodbank" class="form-control" required>
<option value="{{ $bank['abbreviation'] }}" data-nama="{{ $bank['name'] }}"> <option value="">-- Pilih Bank --</option>
{{ $bank['name'] }} @foreach($banks as $bank)
</option> <option value="{{ $bank['abbreviation'] }}" data-nama="{{ $bank['name'] }}">
@endforeach {{ $bank['name'] }}
</select> </option>
<input type="hidden" name="nama_bank" id="editNamabank"> @endforeach
</div> </select>
<div class="form-group"> <input type="hidden" name="nama_bank" id="editNamabank">
<label>No. Akaun</label> </div>
<input type="text" name="noakaun" id="editNoakaun" class="form-control" required> <div class="form-group">
</div> <label>No. Akaun</label>
<div class="form-group"> <input type="text" name="noakaun" id="editNoakaun" class="form-control" required>
<label>Nama Akaun</label> </div>
<input type="text" name="nama_akaun" id="editNamaakaun" class="form-control"> <div class="form-group">
</div> <label>Nama Akaun</label>
<div class="form-group"> <input type="text" name="nama_akaun" id="editNamaakaun" class="form-control">
<label>Nota</label> </div>
<textarea name="nota" id="editNota" class="form-control" rows="2"></textarea> <div class="form-group">
<label>Slip Bank / Bukti Akaun</label>
<input type="file" name="bukti_akaun" id="editBuktiAkaun" class="form-control-file"
accept=".pdf,.jpg,.jpeg,.png,.gif">
<div id="editBuktiAkaunExisting" class="mt-2" style="display:none;">
<a href="#" id="editBuktiAkaunLink" class="btn btn-sm btn-outline-primary" target="_blank">
Lihat slip sedia ada
</a>
</div>
<small class="text-muted">PDF / imej slip akaun bank pelanggan (maks 4MB)</small>
</div>
<div class="form-group">
<label>Nota</label>
<textarea name="nota" id="editNota" class="form-control" rows="2"></textarea>
</div>
<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">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> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
@@ -265,24 +427,176 @@
</div> </div>
</div> </div>
{{-- Verify one-by-one modal --}}
<div class="modal fade" id="verifyModal" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-xl" role="document" style="max-width:1140px;">
<form method="post" id="verifyForm" action="">
@csrf
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Semak Rekod — <span id="verifyNorujukan"></span></h5>
<button type="button" class="close" data-dismiss="modal"><span>&times;</span></button>
</div>
<div class="modal-body">
<p class="small text-muted">Bandingkan maklumat pelanggan dengan SAG / slip bank sebelum sahkan.</p>
<div class="row">
<div class="col-md-4">
<table class="table table-sm table-bordered mb-3">
<tr>
<th style="width:40%">SAG</th>
<td id="verifySag"></td>
</tr>
<tr>
<th>Pelanggan</th>
<td id="verifyNama"></td>
</tr>
<tr>
<th>No KP</th>
<td id="verifyNokp"></td>
</tr>
<tr>
<th>Bank</th>
<td id="verifyBank"></td>
</tr>
<tr>
<th>No Akaun</th>
<td id="verifyNoakaun"></td>
</tr>
<tr>
<th>Nama Akaun</th>
<td id="verifyNamaakaun"></td>
</tr>
<tr>
<th>Pinjaman</th>
<td>RM <span id="verifyPinjaman"></span></td>
</tr>
<tr>
<th>Nota</th>
<td id="verifyNota"></td>
</tr>
</table>
<div class="alert alert-warning py-2" id="verifySebabBox" style="display:none;">
<b>Sebab tolak Operasi:</b>
<div id="verifySebab"></div>
</div>
<div class="form-check mb-3">
<input type="checkbox" class="form-check-input" id="verifyConfirm" required>
<label class="form-check-label" for="verifyConfirm">
Saya sahkan maklumat pelanggan, bank dan amaun adalah betul.
</label>
</div>
<a href="#" id="verifyBuktiAkaunLink" class="btn btn-sm btn-outline-primary" target="_blank" style="display:none;">
Lihat Bukti Akaun Bank
</a>
</div>
<div class="col-md-8">
<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>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Batal</button>
<button type="submit" class="btn btn-success">Sahkan Rekod Ini</button>
</div>
</div>
</form>
</div>
</div>
<script> <script>
function syncSubmitButton() { function syncSubmitButton() {
var n = $('.row-check:checked').length; var n = $('.row-check:not(:disabled):checked').length;
$('#btnSubmitBatch').prop('disabled', n === 0); $('#btnSubmitBatch').prop('disabled', n === 0);
$('#btnSubmitBatch').text(n ? ('Hantar Batch ke Operasi (' + n + ')') : 'Hantar Batch ke Operasi'); $('#btnSubmitBatch').text(n ? ('Hantar Batch ke Operasi (' + n + ')') : 'Hantar Batch ke Operasi');
} }
$('#checkAll').on('change', function () { $('#checkAll').on('change', function () {
$('.row-check').prop('checked', this.checked); $('.row-check:not(:disabled)').prop('checked', this.checked);
syncSubmitButton(); syncSubmitButton();
}); });
$(document).on('change', '.row-check', syncSubmitButton); $(document).on('change', '.row-check', function () {
var enabled = $('.row-check:not(:disabled)');
$('#checkAll').prop('checked', enabled.length > 0 && enabled.filter(':checked').length === enabled.length);
syncSubmitButton();
});
$('#btnSubmitBatch').on('click', function () { $('#btnSubmitBatch').on('click', function () {
if ($('.row-check:checked').length === 0) return; var n = $('.row-check:not(:disabled):checked').length;
if (!confirm('Hantar rekod dipilih 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 () {
var id = $(this).data('id');
var sag = $(this).data('norujukan') || '';
Swal.fire({
title: 'Batal Semakan?',
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 () {
var id = $(this).data('id');
var sebab = $(this).data('sebab') || '';
$('#verifyForm').attr('action', "{{ url('/pembiayaan-online/pc/lines') }}/" + id + "/verify");
$('#verifyNorujukan').text($(this).data('norujukan'));
$('#verifySag').text($(this).data('norujukan') || '-');
$('#verifyNama').text($(this).data('nama') || '-');
$('#verifyNokp').text($(this).data('nokp') || '-');
$('#verifyBank').text($(this).data('namabank') || '-');
$('#verifyNoakaun').text($(this).data('noakaun') || '-');
$('#verifyNamaakaun').text($(this).data('namaakaun') || '-');
$('#verifyPinjaman').text($(this).data('pinjaman') || '0.00');
$('#verifyNota').text($(this).data('nota') || '-');
if (sebab) {
$('#verifySebab').text(sebab);
$('#verifySebabBox').show();
} else {
$('#verifySebabBox').hide();
}
$('#verifyConfirm').prop('checked', false);
$('#verifySagFrame').attr('src', "{{ url('/pembiayaan-online/pc/lines') }}/" + id + "/sag");
if ($(this).data('buktiAkaun')) {
$('#verifyBuktiAkaunLink').attr('href', "{{ url('/pembiayaan-online/pc/lines') }}/" + id + "/bukti-akaun").show();
} else {
$('#verifyBuktiAkaunLink').hide();
}
$('#verifyModal').modal('show');
});
$('#verifyModal').on('hidden.bs.modal', function () {
$('#verifySagFrame').attr('src', 'about:blank');
}); });
$('.btn-edit').on('click', function () { $('.btn-edit').on('click', function () {
@@ -294,11 +608,50 @@
$('#editNamaakaun').val($(this).data('namaakaun')); $('#editNamaakaun').val($(this).data('namaakaun'));
$('#editNamabank').val($(this).data('namabank')); $('#editNamabank').val($(this).data('namabank'));
$('#editNota').val($(this).data('nota')); $('#editNota').val($(this).data('nota'));
$('#editBuktiAkaun').val('');
$('#editSagFrame').attr('src', "{{ url('/pembiayaan-online/pc/lines') }}/" + id + "/sag");
if ($(this).data('buktiAkaun')) {
$('#editBuktiAkaunLink').attr('href', "{{ url('/pembiayaan-online/pc/lines') }}/" + id + "/bukti-akaun");
$('#editBuktiAkaunExisting').show();
} else {
$('#editBuktiAkaunExisting').hide();
}
$('#editModal').modal('show'); $('#editModal').modal('show');
}); });
$('#editModal').on('hidden.bs.modal', function () {
$('#editSagFrame').attr('src', 'about:blank');
});
$('#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">
@@ -326,6 +326,16 @@
<input type="text" id="jumlahtelahdibayar" class="form-control" readonly/> <input type="text" id="jumlahtelahdibayar" class="form-control" readonly/>
</div> </div>
</div> </div>
@if(!empty(($cawangan_info['features'] ?? [])['rebet_annur']))
<div class="form-group row">
<div class="col-4">
REBET DISKAUN (RM)
</div>
<div class="col-8">
<input type="text" id="jumlahDiskaun" value="{{ number_format($gadai['diskaun'] ?? 0, 2) }}" class="form-control" readonly/>
</div>
</div>
@endif
</div> </div>
</div> </div>
@@ -441,6 +451,16 @@
<input type="text" id="jumlahupah" class="form-control" readonly/> <input type="text" id="jumlahupah" class="form-control" readonly/>
</div> </div>
</div> </div>
@if(!empty(($cawangan_info['features'] ?? [])['rebet_annur']))
<div class="form-group row">
<div class="col-4">
REBET DISKAUN (RM)
</div>
<div class="col-8">
<input type="text" id="jumlahDiskaun" value="{{ number_format($gadai['diskaun'] ?? 0, 2) }}" class="form-control" readonly/>
</div>
</div>
@endif
<div id="divbakipinjaman"> <div id="divbakipinjaman">
<div class="form-group row"> <div class="form-group row">
@@ -467,6 +487,17 @@
<input type="text" id="jumlahtelahdibayar" class="form-control" readonly/> <input type="text" id="jumlahtelahdibayar" class="form-control" readonly/>
</div> </div>
</div> </div>
@if(!empty(($cawangan_info['features'] ?? [])['rebet_annur']))
<div class="form-group row">
<div class="col-4">
REBET DISKAUN (RM)
</div>
<div class="col-8">
<input type="text" id="jumlahDiskaun" value="{{ number_format($gadai['diskaun'] ?? 0, 2) }}" class="form-control" readonly/>
</div>
</div>
@endif
<div class="form-group row" id="divbayaran"> <div class="form-group row" id="divbayaran">
@@ -214,8 +214,7 @@
/> />
<div class="title"> <div class="title">
<h1>Borang Kelulusan Pembiayaan Online<br>Senarai Batch Pembayaran</h1> <h1>Borang Kelulusan Batch Pembiayaan Online</h1>
<p>Untuk semakan Operasi dan kelulusan Pengurus Besar. Bukan untuk tandatangan pelanggan.</p>
</div> </div>
<div class="section"> <div class="section">
@@ -298,8 +297,8 @@
bagi batch <b>{{ $batchNo }}</b> cawangan <b>{{ $cawangan }}</b> adalah lengkap bagi batch <b>{{ $batchNo }}</b> cawangan <b>{{ $cawangan }}</b> adalah lengkap
untuk proses pembayaran ke akaun bank pelanggan. untuk proses pembayaran ke akaun bank pelanggan.
<ol> <ol>
<li>Maklumat SAG, nama, no. KP, bank dan no. akaun telah disemak terhadap rekod sistem.</li> <li>Maklumat SAG, nama, no. KP, bank dan no. akaun telah disemak didalam sistem.</li>
<li>Pembayaran akan dikreditkan ke akaun penerima seperti senarai, bukan tunai di kaunter.</li> <li>Pembayaran akan dikreditkan ke akaun penerima seperti didalam senarai.</li>
<li>Sebarang ralat akaun yang dikesan selepas kelulusan hendaklah dilaporkan serta-merta.</li> <li>Sebarang ralat akaun yang dikesan selepas kelulusan hendaklah dilaporkan serta-merta.</li>
</ol> </ol>
</div> </div>
@@ -318,18 +317,11 @@
<div class="role">Pengurus Besar</div> <div class="role">Pengurus Besar</div>
<div class="sign-line"> <div class="sign-line">
<p>Nama:</p> <p>Nama:</p>
<p>Cop / Paraf:</p> <p>Cop:</p>
<p>Tarikh:</p> <p>Tarikh:</p>
</div> </div>
</div> </div>
</div> </div>
<x-print.footer :show-copies="false">
<b>Nota:</b>
<p>1. Borang ini dicetak dari skrin Operasi — Pembiayaan Online (Batch) untuk kelulusan pembayaran.</p>
<p>2. Hanya dua tandatangan diperlukan: Operasi dan Pengurus Besar.</p>
<p>3. Simpan salinan bersama bukti pindahan bank selepas status PAID.</p>
</x-print.footer>
</div> </div>
</div> </div>
</body> </body>
@@ -108,6 +108,19 @@
letter-spacing: 0.02em; letter-spacing: 0.02em;
} }
.copies {
margin-top: 12px;
font-size: 10px;
}
.copies span {
display: inline-block;
border: 1px solid #111;
width: 11px;
height: 11px;
margin-right: 4px;
vertical-align: -1px;
}
.akuan { .akuan {
font-size: 11px; font-size: 11px;
line-height: 1.55; line-height: 1.55;
@@ -121,10 +134,8 @@
.akuan li { margin-bottom: 4px; } .akuan li { margin-bottom: 4px; }
.signs { .signs {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 10px;
margin-top: 16px; margin-top: 16px;
max-width: 34%;
} }
.sign-box { .sign-box {
border: 1px solid #111; border: 1px solid #111;
@@ -184,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>
@@ -214,7 +225,7 @@
<div class="section-title">B. Maklumat Pembiayaan</div> <div class="section-title">B. Maklumat Pembiayaan</div>
<table class="fields"> <table class="fields">
<tr> <tr>
<th>No. Surat Akuan Gadai</th> <th>No. Surat Akuan Gadaian (SAG)</th>
<td>{{ $norujukan }}</td> <td>{{ $norujukan }}</td>
</tr> </tr>
<tr> <tr>
@@ -222,8 +233,8 @@
<td class="amt">RM {{ $pinjaman }}</td> <td class="amt">RM {{ $pinjaman }}</td>
</tr> </tr>
<tr> <tr>
<th>Cara Terima Pembiayaan</th> <th>Cara Pembiayaan</th>
<td>Online (kredit ke akaun bank) — bukan tunai</td> <td>Atas Talian (Online)</td>
</tr> </tr>
<tr> <tr>
<th>Teller</th> <th>Teller</th>
@@ -250,15 +261,19 @@
</table> </table>
</div> </div>
<div class="copies">
Salinan: <span></span> Kad Pengenalan &nbsp;&nbsp; <span></span> Akaun Bank/ Slip/ Penyata Bank
</div>
<div class="section"> <div class="section">
<div class="section-title">D. Akuan Pelanggan</div> <div class="section-title">D. Akuan Pelanggan</div>
<div class="akuan"> <div class="akuan">
Saya, <b>{{ $nama }}</b> (No. KP: <b>{{ $nokp }}</b>), dengan ini mengesahkan bahawa: Saya, <b>{{ $nama }}</b> (No. KP: <b>{{ $nokp }}</b>), dengan ini mengesahkan bahawa:
<ol> <ol>
<li>Maklumat akaun bank di Bahagian C adalah benar, tepat, dan milik saya sendiri.</li> <li>Maklumat akaun bank di Bahagian C adalah benar, tepat, dan milik saya sendiri.</li>
<li>Saya memohon supaya amaun pembiayaan gadai sebanyak <b>RM {{ $pinjaman }}</b> dikreditkan ke akaun tersebut dan <b>bukan</b> diterima secara tunai.</li> <li>Saya bersetuju menerima pembayaran pembiayaan Ar-Rahn KoPKB sebanyak <b>RM {{ $pinjaman }}</b> secara atas talian (Online) ke akaun tersebut.</li>
<li>Saya faham kredit ke akaun tertakluk kepada semakan Pengurus Cawangan dan proses pembayaran pihak Koperasi.</li> <li>Saya memahami bahawa pihak Ar-Rahn KoPKB tidak akan bertanggungjawab atas sebarang kesilapan akibat maklumat akaun yang diberikan oleh saya.</li>
<li>Saya bertanggungjawab sepenuhnya jika maklumat akaun tidak tepat, dan tidak akan menuntut ganti rugi atas kelewatan atau ralat yang berpunca daripadanya.</li> <li>Saya mengesahkan bahawa segala maklumat yang diberikan adalah benar dan tepat.</li>
</ol> </ol>
</div> </div>
</div> </div>
@@ -272,29 +287,13 @@
<p>Tarikh: {{ $tarikh }}</p> <p>Tarikh: {{ $tarikh }}</p>
</div> </div>
</div> </div>
<div class="sign-box">
<div class="role">Tandatangan Teller</div>
<div class="sign-line">
<p>Nama: {{ $teller }}</p>
<p>Cop / Paraf:</p>
<p>Tarikh: {{ $tarikh }}</p>
</div>
</div>
<div class="sign-box">
<div class="role">Pengurus Cawangan</div>
<div class="sign-line">
<p>Nama:</p>
<p>Cop cawangan:</p>
<p>Tarikh:</p>
</div>
</div>
</div> </div>
<x-print.footer> <x-print.footer :show-copies="false">
<b>Nota:</b> <b>Nota:</b>
<p>1. Borang ini dicetak semasa pelanggan memilih cara terima pembiayaan secara Online.</p> <p>1. Pembayaran hanya dibuat ke akaun pelanggan sendiri.</p>
<p>2. Pembiayaan akan dikreditkan ke akaun selepas semakan PC. Pelanggan tidak menerima wang tunai di kaunter.</p> <p>2. Sebarang pembetulan maklumat hendaklah disahkan semula oleh pelanggan.</p>
<p>3. Sila pastikan nama pemegang akaun sepadan dengan nama pelanggan pada MyKad.</p> <p>3. Borang ini hendaklah dilampirkan bersama dokumen transaksi gadaian untuk tujuan audit dan rekod.</p>
</x-print.footer> </x-print.footer>
</div> </div>
</div> </div>
@@ -233,7 +233,9 @@
@endif @endif
<tr> <tr>
<td>{{ $counter }}</td> <td>{{ $counter }}</td>
<td style="width:100px">{{$item['norujukan']}}</td> <td style="width:100px">{{$item['norujukan']}}<br>
@include('print.tag_cara_terima', ['caraTerima' => $item['cara_terima'] ?? null])
</td>
<td> <td>
{{ $item['nama'] }} {{ $item['nama'] }}
</td> </td>
@@ -1,3 +1,4 @@
@php $rebetAnnur = !empty(($cawangan_info['features'] ?? [])['rebet_annur']); @endphp
<style> <style>
#header { #header {
font-family: Arial, Helvetica, sans-serif; font-family: Arial, Helvetica, sans-serif;
@@ -230,6 +231,9 @@
<th>Lain-Lain Caj (RM)</th> <th>Lain-Lain Caj (RM)</th>
<th>Keuntungan 1% (RM)</th> <th>Keuntungan 1% (RM)</th>
<th>Keuntungan</th> <th>Keuntungan</th>
@if($rebetAnnur)
<th>Diskaun Rebet</th>
@endif
<th>Peratus (%)</th> <th>Peratus (%)</th>
</tr> </tr>
@php @php
@@ -241,6 +245,7 @@
$bil = 0; $bil = 0;
$full_total_ujrah = 0; $full_total_ujrah = 0;
$full_total_onepercent = 0; $full_total_onepercent = 0;
$totaldiskaun = 0;
@endphp @endphp
@foreach($gadai as $index=>$item) @foreach($gadai as $index=>$item)
@@ -251,6 +256,7 @@
$totalupah += $item['upah12']; $totalupah += $item['upah12'];
$full_total_ujrah += $item['ujrah']*12; $full_total_ujrah += $item['ujrah']*12;
$full_total_onepercent += $item['onepercent']*12; $full_total_onepercent += $item['onepercent']*12;
$totaldiskaun += $item['diskaun'] ?? 0;
@endphp @endphp
@if($index > 0) @if($index > 0)
@@ -264,6 +270,7 @@
$totalberatteller = 0; $totalberatteller = 0;
$totalujrah = 0; $totalujrah = 0;
$totalonepercent = 0; $totalonepercent = 0;
$totaldiskaunteller = 0;
@endphp @endphp
@endif @endif
@@ -278,12 +285,15 @@
$totalupah12 = 0; $totalupah12 = 0;
$totalujrah = 0; $totalujrah = 0;
$totalonepercent= 0; $totalonepercent= 0;
$totaldiskaunteller = 0;
@endphp @endphp
@endif @endif
<tr> <tr>
<td>{{ $counter }}</td> <td>{{ $counter }}</td>
<td style="width:100px">{{$item['norujukan']}}</td> <td style="width:100px">{{$item['norujukan']}}<br>
@include('print.tag_cara_terima', ['caraTerima' => $item['cara_terima'] ?? null])
</td>
<td> <td>
{{ $item['nama'] }} {{ $item['nama'] }}
</td> </td>
@@ -301,6 +311,9 @@
<td>{{ number_format($item['ujrah']*12,2) }}</td> <td>{{ number_format($item['ujrah']*12,2) }}</td>
<td>{{ number_format($item['onepercent']*12,2) }}</td> <td>{{ number_format($item['onepercent']*12,2) }}</td>
<td>{{ number_format($item['upah12'],2) }}</td> <td>{{ number_format($item['upah12'],2) }}</td>
@if($rebetAnnur)
<td>{{ number_format($item['diskaun'] ?? 0,2) }}</td>
@endif
<td>{{ $item['percentpinj'] }}</td> <td>{{ $item['percentpinj'] }}</td>
</tr> </tr>
@php $counter++; @endphp @php $counter++; @endphp
@@ -311,6 +324,7 @@
$totalupah12 += $item['upah12']; $totalupah12 += $item['upah12'];
$totalujrah += $item['ujrah']*12; $totalujrah += $item['ujrah']*12;
$totalonepercent += $item['onepercent']*12; $totalonepercent += $item['onepercent']*12;
$totaldiskaunteller += $item['diskaun'] ?? 0;
@endphp @endphp
@@ -325,6 +339,9 @@
<td><b>{{ number_format($totalujrah,2) }}</b></td> <td><b>{{ number_format($totalujrah,2) }}</b></td>
<td><b>{{ number_format($totalonepercent,2) }}</b></td> <td><b>{{ number_format($totalonepercent,2) }}</b></td>
<td><b>{{ number_format($totalupah12,2) }} </b></td> <td><b>{{ number_format($totalupah12,2) }} </b></td>
@if($rebetAnnur)
<td><b>{{ number_format($totaldiskaunteller,2) }} </b></td>
@endif
</tr> </tr>
@@ -335,6 +352,7 @@
$totalnmteller = 0; $totalnmteller = 0;
$totalujrah = 0; $totalujrah = 0;
$totalonepercent = 0; $totalonepercent = 0;
$totaldiskaunteller = 0;
@endphp @endphp
@endif @endif
@@ -348,6 +366,9 @@
<td><b>{{ number_format($totalujrah,2) }}</b></td> <td><b>{{ number_format($totalujrah,2) }}</b></td>
<td><b>{{ number_format($totalonepercent,2) }}</b></td> <td><b>{{ number_format($totalonepercent,2) }}</b></td>
<td><b>{{ number_format($totalupah12,2) }} </b></td> <td><b>{{ number_format($totalupah12,2) }} </b></td>
@if($rebetAnnur)
<td><b>{{ number_format($totaldiskaunteller,2) }} </b></td>
@endif
</tr> </tr>
@endif @endif
@@ -219,7 +219,9 @@
@php if($item['hargajualan'] == null) $totalpinjaman += $item['pinjaman']; else $totalpinjaman += $item['bakipjm']; @endphp @php if($item['hargajualan'] == null) $totalpinjaman += $item['pinjaman']; else $totalpinjaman += $item['bakipjm']; @endphp
<tr> <tr>
<td>{{ $counter }}</td> <td>{{ $counter }}</td>
<td>{{$item['norujukan']}}</td> <td>{{$item['norujukan']}}<br>
@include('print.tag_cara_terima', ['caraTerima' => $item['cara_terima'] ?? null])
</td>
<td>{{ $item['tempoh'] }}</td> <td>{{ $item['tempoh'] }}</td>
<td>{{$item['nokp']}}</td> <td>{{$item['nokp']}}</td>
<td>{{ number_format($item['berat'],2) }}</td> <td>{{ number_format($item['berat'],2) }}</td>
@@ -213,7 +213,9 @@
@php if($item['hargajualan'] == null) $totalpinjaman += $item['pinjaman']; else $totalpinjaman += $item['bakipjm']; @endphp @php if($item['hargajualan'] == null) $totalpinjaman += $item['pinjaman']; else $totalpinjaman += $item['bakipjm']; @endphp
<tr> <tr>
<td>{{ $counter }}</td> <td>{{ $counter }}</td>
<td>{{$item['norujukan']}}</td> <td>{{$item['norujukan']}}<br>
@include('print.tag_cara_terima', ['caraTerima' => $item['cara_terima'] ?? null])
</td>
<td>{{ $item['jbg'] }}</td> <td>{{ $item['jbg'] }}</td>
<td>{{$item['nokp']}}</td> <td>{{$item['nokp']}}</td>
<td>{{ number_format($item['berat'],2) }}</td> <td>{{ number_format($item['berat'],2) }}</td>
@@ -236,7 +236,9 @@
@endphp @endphp
<tr> <tr>
<td>{{ $counter }}</td> <td>{{ $counter }}</td>
<td>{{$item['norujukan']}}</td> <td>{{$item['norujukan']}}<br>
@include('print.tag_cara_terima', ['caraTerima' => $item['cara_terima'] ?? null])
</td>
<td>{{ $item['tempoh'] }}</td> <td>{{ $item['tempoh'] }}</td>
<td>{{$item['nokp']}}</td> <td>{{$item['nokp']}}</td>
<td>{{ number_format($item['berat'],2) }}</td> <td>{{ number_format($item['berat'],2) }}</td>
@@ -342,7 +344,9 @@
@endphp @endphp
<tr> <tr>
<td>{{ $counter }}</td> <td>{{ $counter }}</td>
<td>{{$item['norujukan']}}</td> <td>{{$item['norujukan']}}<br>
@include('print.tag_cara_terima', ['caraTerima' => $item['cara_terima'] ?? null])
</td>
<td>{{ $item['jbg'] }}</td> <td>{{ $item['jbg'] }}</td>
<td>{{$item['nokp']}}</td> <td>{{$item['nokp']}}</td>
<td>{{ number_format($item['berat'],2) }}</td> <td>{{ number_format($item['berat'],2) }}</td>
@@ -1,3 +1,4 @@
@php $rebetAnnur = !empty(($cawangan_info['features'] ?? [])['rebet_annur']); @endphp
<style> <style>
#header { #header {
font-family: Arial, Helvetica, sans-serif; font-family: Arial, Helvetica, sans-serif;
@@ -241,6 +242,9 @@
<th>B.Pembiayaan(RM)</th> <th>B.Pembiayaan(RM)</th>
<th>Lain-Lain Caj (RM)</th> <th>Lain-Lain Caj (RM)</th>
<th>Untung 1% (RM)</th> <th>Untung 1% (RM)</th>
@if($rebetAnnur)
<th>Diskaun Rebet (RM)</th>
@endif
<th>Pendapatan(RM)</th> <th>Pendapatan(RM)</th>
<th>Bayaran(RM)</th> <th>Bayaran(RM)</th>
<th>Jumlah(RM)</th> <th>Jumlah(RM)</th>
@@ -254,6 +258,7 @@
$totalupah = 0; $totalupah = 0;
$totalujrah = 0; $totalujrah = 0;
$totalonepercent = 0; $totalonepercent = 0;
$totaldiskaunrebate = 0;
@endphp @endphp
@foreach ($tebus as $index => $item) @foreach ($tebus as $index => $item)
@@ -266,6 +271,7 @@
$totalnm += $item['nilaimarhun']; $totalnm += $item['nilaimarhun'];
$totalujrah += $item['ujrah']; $totalujrah += $item['ujrah'];
$totalonepercent += $item['onepercent']; $totalonepercent += $item['onepercent'];
$totaldiskaunrebate += $item['diskaun_rebate'] ?? 0;
@endphp @endphp
@@ -282,6 +288,7 @@
$totalbayaranteller = 0; $totalbayaranteller = 0;
$totalujrahteller = 0; $totalujrahteller = 0;
$totalonepercentteller = 0; $totalonepercentteller = 0;
$totaldiskaunrebateteller = 0;
@endphp @endphp
@endif @endif
@else @else
@@ -297,6 +304,7 @@
$totaljumlahteller = 0; $totaljumlahteller = 0;
$totalujrahteller = 0; $totalujrahteller = 0;
$totalonepercentteller = 0; $totalonepercentteller = 0;
$totaldiskaunrebateteller = 0;
@endphp @endphp
@endif @endif
@@ -336,6 +344,9 @@
</td> </td>
<td>{{ number_format($item['ujrah'], 2) }}</td> <td>{{ number_format($item['ujrah'], 2) }}</td>
<td>{{ number_format($item['onepercent'], 2) }}</td> <td>{{ number_format($item['onepercent'], 2) }}</td>
@if($rebetAnnur)
<td>{{ number_format($item['diskaun_rebate'] ?? 0, 2) }}</td>
@endif
{{-- <td>{{ number_format($item['bakiupah'], 2) }}</td> --}} {{-- <td>{{ number_format($item['bakiupah'], 2) }}</td> --}}
<td> <td>
@php @php
@@ -371,6 +382,7 @@
$totalbayaranteller += $item['bakipjm']; // $totalbayaranteller += $item['bakipjm']; //
$totalujrahteller += $item['ujrah']; $totalujrahteller += $item['ujrah'];
$totalonepercentteller += $item['onepercent']; $totalonepercentteller += $item['onepercent'];
$totaldiskaunrebateteller += $item['diskaun_rebate'] ?? 0;
@endphp @endphp
@php @php
@@ -388,6 +400,9 @@
<td><b>{{ number_format($totalpinjamanteller, 2) }} </b></td> <td><b>{{ number_format($totalpinjamanteller, 2) }} </b></td>
<td><b>{{ number_format($totalujrahteller, 2) }} </b></td> <td><b>{{ number_format($totalujrahteller, 2) }} </b></td>
<td><b>{{ number_format($totalonepercentteller, 2) }} </b></td> <td><b>{{ number_format($totalonepercentteller, 2) }} </b></td>
@if($rebetAnnur)
<td><b>{{ number_format($totaldiskaunrebateteller ?? 0, 2) }} </b></td>
@endif
<td><b>{{ number_format($totalupahteller, 2) }} </b></td> <td><b>{{ number_format($totalupahteller, 2) }} </b></td>
<td><b>{{ number_format($totalbayaranteller, 2) }} </b></td> <td><b>{{ number_format($totalbayaranteller, 2) }} </b></td>
<td><b>{{ number_format($totaljumlahteller, 2) }} </b></td> <td><b>{{ number_format($totaljumlahteller, 2) }} </b></td>
@@ -402,6 +417,7 @@
$totaljumlahteller = 0; $totaljumlahteller = 0;
$totalujrahteller = 0; $totalujrahteller = 0;
$totalonepercentteller = 0; $totalonepercentteller = 0;
$totaldiskaunrebateteller = 0;
@endphp @endphp
@endif @endif
@else @else
@@ -412,6 +428,9 @@
<td><b>{{ number_format($totalpinjamanteller, 2) }} </b></td> <td><b>{{ number_format($totalpinjamanteller, 2) }} </b></td>
<td><b>{{ number_format($totalujrahteller, 2) }} </b></td> <td><b>{{ number_format($totalujrahteller, 2) }} </b></td>
<td><b>{{ number_format($totalonepercentteller, 2) }} </b></td> <td><b>{{ number_format($totalonepercentteller, 2) }} </b></td>
@if($rebetAnnur)
<td><b>{{ number_format($totaldiskaunrebateteller ?? 0, 2) }} </b></td>
@endif
<td><b>{{ number_format($totalupahteller, 2) }} </b></td> <td><b>{{ number_format($totalupahteller, 2) }} </b></td>
<td><b>{{ number_format($totalbayaranteller, 2) }} </b></td> <td><b>{{ number_format($totalbayaranteller, 2) }} </b></td>
<td><b>{{ number_format($totaljumlahteller, 2) }} </b></td> <td><b>{{ number_format($totaljumlahteller, 2) }} </b></td>
@@ -631,27 +631,34 @@
<td style="width:10px">Keuntungan</td> <td style="width:10px">Keuntungan</td>
<td style="width:10px">: RM {{ number_format($transaction_tebus['onepercent'],2) }}</td> <td style="width:10px">: RM {{ number_format($transaction_tebus['onepercent'],2) }}</td>
</tr> </tr>
<tr> {{-- <tr>
<td style="width:10px">(-) Rebat Untung</td> <td style="width:10px">(-) Rebat Untung</td>
<td style="width:10px">: RM <td style="width:10px">: RM
{{ number_format($keuntungan_rebate,2) }} {{ number_format($keuntungan_rebate,2) }}
</td> </td>
</tr> </tr> --}}
<tr> <tr>
<td style="width:10px">Lain-Lain Caj</td> <td style="width:10px">Lain-Lain Caj</td>
<td style="width:10px">: RM {{ number_format($transaction_tebus['ujrah'],2) }} </td> <td style="width:10px">: RM {{ number_format($transaction_tebus['ujrah'],2) }} </td>
</tr> </tr>
<tr> {{-- <tr>
<td style="width:10px">(-) Rebat Lain-Lain Caj</td> <td style="width:10px">(-) Rebat Lain-Lain Caj</td>
<td style="width:10px">: RM <td style="width:10px">: RM
{{ number_format($ujrah_rebate, 2) }} </td> {{ number_format($ujrah_rebate, 2) }} </td>
</tr> </tr> --}}
<tr> <tr>
<td style="width:10px">Jumlah Keuntungan + Lain-Lain Caj</td> <td style="width:10px">Jumlah Keuntungan + Lain-Lain Caj</td>
<td style="width:10px">: <strong>RM <td style="width:10px">: <strong>RM
{{ number_format($penebusan['onepercent'] + $penebusan['ujrah'], 2) }}</strong> {{ number_format($penebusan['onepercent'] + $penebusan['ujrah'], 2) }}</strong>
</td> </td>
</tr> </tr>
@if(!empty(($cawangan_info['features'] ?? [])['rebet_annur']))
<tr>
<td style="width:10px">(-) Diskaun Promosi</td>
<td style="width:10px">: RM
{{ number_format($gadai['diskaun'] ?? 0, 2) }} </td>
</tr>
@endif
<tr> <tr>
<td></td> <td></td>
</tr> </tr>
@@ -864,27 +871,34 @@
<td style="width:10px">Keuntungan</td> <td style="width:10px">Keuntungan</td>
<td style="width:10px">: RM {{ number_format($transaction_tebus['onepercent'], 2) }}</td> <td style="width:10px">: RM {{ number_format($transaction_tebus['onepercent'], 2) }}</td>
</tr> </tr>
<tr> {{-- <tr>
<td style="width:10px">(-) Rebat Untung</td> <td style="width:10px">(-) Rebat Untung</td>
<td style="width:10px">: RM <td style="width:10px">: RM
{{ number_format($keuntungan_rebate, 2) }} {{ number_format($keuntungan_rebate, 2) }}
</td> </td>
</tr> </tr> --}}
<tr> <tr>
<td style="width:10px">Lain-Lain Caj</td> <td style="width:10px">Lain-Lain Caj</td>
<td style="width:10px">: RM {{ number_format($transaction_tebus['ujrah'], 2) }} </td> <td style="width:10px">: RM {{ number_format($transaction_tebus['ujrah'], 2) }} </td>
</tr> </tr>
<tr> {{-- <tr>
<td style="width:10px">(-) Rebat Lain-Lain Caj</td> <td style="width:10px">(-) Rebat Lain-Lain Caj</td>
<td style="width:10px">: RM <td style="width:10px">: RM
{{ number_format($ujrah_rebate, 2) }} </td> {{ number_format($ujrah_rebate, 2) }} </td>
</tr> </tr> --}}
<tr> <tr>
<td style="width:10px">Jumlah Keuntungan + Lain-Lain Caj</td> <td style="width:10px">Jumlah Keuntungan + Lain-Lain Caj</td>
<td style="width:10px">: <strong>RM <td style="width:10px">: <strong>RM
{{ number_format($penebusan['onepercent'] + $penebusan['ujrah'], 2) }}</strong> {{ number_format($penebusan['onepercent'] + $penebusan['ujrah'], 2) }}</strong>
</td> </td>
</tr> </tr>
@if(!empty(($cawangan_info['features'] ?? [])['rebet_annur']))
<tr>
<td style="width:10px">(-) Diskaun Promosi</td>
<td style="width:10px">: RM
{{ number_format($gadai['diskaun'] ?? 0, 2) }} </td>
</tr>
@endif
<tr> <tr>
<td></td> <td></td>
</tr> </tr>
+3
View File
@@ -6,6 +6,9 @@
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script> <script>
$(document).ready(function () { $(document).ready(function () {
if (window.self !== window.top) {
return;
}
window.print(); window.print();
window.onfocus=function(){ window.close();} window.onfocus=function(){ window.close();}
}); });
@@ -395,6 +395,9 @@
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script> <script>
$(document).ready(function() { $(document).ready(function() {
if (window.self !== window.top) {
return;
}
window.print(); window.print();
window.onfocus = function() { window.onfocus = function() {
window.close(); window.close();
@@ -2109,6 +2112,22 @@
</div> </div>
<div class="main-page" style="font-family: Arial, Helvetica, sans-serif;"> <div class="main-page" style="font-family: Arial, Helvetica, sans-serif;">
<div class="sub-page" style="font-size: 11px;"> <div class="sub-page" style="font-size: 11px;">
@php
$caraTerimaBayaran = strtoupper(trim((string) ($gadai_detail['cara_terima'] ?? '')));
@endphp
<div style="border: 1px solid black; padding: 10px 12px; margin-bottom: 10px; break-inside: avoid;">
<div style="font-size: 13px; font-weight: bold; margin-bottom: 12px;">CARA TERIMA BAYARAN</div>
<div style="display: flex; align-items: center; gap: 40px; font-size: 12px; font-weight: bold;">
<span style="display: inline-flex; align-items: center; gap: 8px;">
<span style="display: inline-block; width: 14px; height: 14px; border: 1px solid black; text-align: center; line-height: 12px;">@if ($caraTerimaBayaran === 'TUNAI')&#10003;@endif</span>
TUNAI
</span>
<span style="display: inline-flex; align-items: center; gap: 8px;">
<span style="display: inline-block; width: 14px; height: 14px; border: 1px solid black; text-align: center; line-height: 12px;">@if ($caraTerimaBayaran === 'ONLINE')&#10003;@endif</span>
KREDIT KE AKAUN
</span>
</div>
</div>
{{-- HEADER SECTION --}} {{-- HEADER SECTION --}}
<table style="width: 100%; margin-top: 0;"> <table style="width: 100%; margin-top: 0;">
<tr style="vertical-align: top;"> <tr style="vertical-align: top;">
@@ -0,0 +1,6 @@
@php
$caraTerimaTag = strtoupper(trim((string) ($caraTerima ?? '')));
$caraTerimaLabel = ['TUNAI' => 'TUNAI', 'ONLINE' => 'ONLINE'][$caraTerimaTag] ?? 'TIDAK DINYATAKAN';
$caraTerimaWarna = ['TUNAI' => '#166534', 'ONLINE' => '#1e40af'][$caraTerimaTag] ?? '#555555';
@endphp
<span title="Cara Terima Bayaran" style="display: inline-block; margin-top: 3px; padding: 2px 5px; border: 1px solid {{ $caraTerimaWarna }}; border-radius: 3px; color: {{ $caraTerimaWarna }}; font-size: 9px; font-weight: bold; line-height: 1.2; white-space: nowrap;">{{ $caraTerimaLabel }}</span>
+15
View File
@@ -15,6 +15,8 @@ use Illuminate\Support\Facades\Storage;
| |
*/ */
Route::post('/internal/notifications', ['as' => 'internal.notifications.store', 'uses' => 'InternalNotificationController@store']);
Route::get('/health', function () { Route::get('/health', function () {
return response()->json(['status' => 'ok']); return response()->json(['status' => 'ok']);
}); });
@@ -174,6 +176,7 @@ Route::get('/khazanahtebus',['as'=>'khazanah.tebus','uses'=>'KhazanahController@
Route::get('/kelulusan','KhazanahController@kelulusan')->middleware('auth','khazanah'); Route::get('/kelulusan','KhazanahController@kelulusan')->middleware('auth','khazanah');
Route::get('/kelulusanrekodlepas',['as'=>'khazanah.rekodlepas','uses'=>'KhazanahController@kelulusanrekodlepas'])->middleware('auth','khazanah'); Route::get('/kelulusanrekodlepas',['as'=>'khazanah.rekodlepas','uses'=>'KhazanahController@kelulusanrekodlepas'])->middleware('auth','khazanah');
Route::get('/kelulusanrekodlepasoperasi',['as'=>'operasi.rekodlepas','uses'=>'OperasiController@kelulusanrekodlepas'])->middleware('auth','operasi'); Route::get('/kelulusanrekodlepasoperasi',['as'=>'operasi.rekodlepas','uses'=>'OperasiController@kelulusanrekodlepas'])->middleware('auth','operasi');
Route::get('/kelulusanrekodlepasoperasi/data',['as'=>'operasi.rekodlepas.data','uses'=>'OperasiController@kelulusanrekodlepasData'])->middleware('auth','operasi');
Route::get('/pendahuluan',['as'=>'khazanah.pendahulu','uses'=>'KhazanahController@pendahuluan'])->middleware('auth','khazanah'); Route::get('/pendahuluan',['as'=>'khazanah.pendahulu','uses'=>'KhazanahController@pendahuluan'])->middleware('auth','khazanah');
Route::get('/serahan',['as'=>'khazanah.serahan','uses'=>'KhazanahController@serahan'])->middleware('auth','khazanah'); Route::get('/serahan',['as'=>'khazanah.serahan','uses'=>'KhazanahController@serahan'])->middleware('auth','khazanah');
Route::get('/laporanscanbarcode',['as'=>'laporanscanbarcode','uses'=>'KhazanahController@LaporanSemakBarcode'])->middleware('auth','khazanah'); Route::get('/laporanscanbarcode',['as'=>'laporanscanbarcode','uses'=>'KhazanahController@LaporanSemakBarcode'])->middleware('auth','khazanah');
@@ -190,9 +193,18 @@ Route::get('/penebusankhazanah/{norujukan}',['as'=>'penebusan.khazanah','uses' =
Route::get('/lelongankhazanah/{norujukan}',['as'=>'lelongan.khazanah','uses' => 'KhazanahController@lelongan'])->middleware('auth','khazanah'); Route::get('/lelongankhazanah/{norujukan}',['as'=>'lelongan.khazanah','uses' => 'KhazanahController@lelongan'])->middleware('auth','khazanah');
Route::get('/permohonan',['as'=>'permohonan.index','uses' => 'KhazanahController@PaparanPermohonan'])->middleware('auth','khazanah'); Route::get('/permohonan',['as'=>'permohonan.index','uses' => 'KhazanahController@PaparanPermohonan'])->middleware('auth','khazanah');
Route::get('/notifications',['as'=>'notifications.index','uses'=>'NotificationController@index'])->middleware('auth');
Route::post('/notifications/read-all',['as'=>'notifications.read.all','uses'=>'NotificationController@markAllAsRead'])->middleware('auth');
Route::post('/notifications/{id}/read',['as'=>'notifications.read','uses'=>'NotificationController@markAsRead'])->middleware('auth');
// Pembiayaan Online — PC // Pembiayaan Online — PC
Route::get('/pembiayaan-online/pc',['as'=>'pembiayaan.online.pc','uses'=>'PembiayaanOnlineController@index'])->middleware('auth','khazanah'); 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::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');
Route::get('/pembiayaan-online/pc/lines/{id}/bukti-akaun',['as'=>'pembiayaan.online.pc.bukti.akaun','uses'=>'PembiayaanOnlineController@viewBuktiAkaun'])->middleware('auth','khazanah');
Route::get('/pembiayaan-online/pc/lines/{id}/sag',['as'=>'pembiayaan.online.pc.sag','uses'=>'PembiayaanOnlineController@viewSag'])->middleware('auth','khazanah');
Route::post('/pembiayaan-online/pc/batches/submit',['as'=>'pembiayaan.online.pc.submit','uses'=>'PembiayaanOnlineController@submitBatch'])->middleware('auth','khazanah'); Route::post('/pembiayaan-online/pc/batches/submit',['as'=>'pembiayaan.online.pc.submit','uses'=>'PembiayaanOnlineController@submitBatch'])->middleware('auth','khazanah');
Route::get('/pembiayaan-online/pc/batches/{batchId}',['as'=>'pembiayaan.online.pc.batch','uses'=>'PembiayaanOnlineController@showBatch'])->middleware('auth','khazanah'); Route::get('/pembiayaan-online/pc/batches/{batchId}',['as'=>'pembiayaan.online.pc.batch','uses'=>'PembiayaanOnlineController@showBatch'])->middleware('auth','khazanah');
Route::get('/pembiayaan-online/pc/paid',['as'=>'pembiayaan.online.pc.paid','uses'=>'PembiayaanOnlineController@paidIndex'])->middleware('auth','khazanah'); Route::get('/pembiayaan-online/pc/paid',['as'=>'pembiayaan.online.pc.paid','uses'=>'PembiayaanOnlineController@paidIndex'])->middleware('auth','khazanah');
@@ -217,6 +229,8 @@ Route::post('/pembiayaan-online/operasi/{kodcaw}/lines/{id}/reject',['as'=>'pemb
Route::post('/pembiayaan-online/operasi/{kodcaw}/batches/{batchId}/approve',['as'=>'pembiayaan.online.operasi.approve.batch','uses'=>'PembiayaanOnlineController@operasiApproveBatch'])->middleware('auth','operasi'); Route::post('/pembiayaan-online/operasi/{kodcaw}/batches/{batchId}/approve',['as'=>'pembiayaan.online.operasi.approve.batch','uses'=>'PembiayaanOnlineController@operasiApproveBatch'])->middleware('auth','operasi');
Route::post('/pembiayaan-online/operasi/{kodcaw}/lines/{id}/paid',['as'=>'pembiayaan.online.operasi.paid','uses'=>'PembiayaanOnlineController@operasiMarkPaid'])->middleware('auth','operasi'); Route::post('/pembiayaan-online/operasi/{kodcaw}/lines/{id}/paid',['as'=>'pembiayaan.online.operasi.paid','uses'=>'PembiayaanOnlineController@operasiMarkPaid'])->middleware('auth','operasi');
Route::post('/pembiayaan-online/operasi/{kodcaw}/batches/{batchId}/paid',['as'=>'pembiayaan.online.operasi.paid.batch','uses'=>'PembiayaanOnlineController@operasiMarkPaidBatch'])->middleware('auth','operasi'); Route::post('/pembiayaan-online/operasi/{kodcaw}/batches/{batchId}/paid',['as'=>'pembiayaan.online.operasi.paid.batch','uses'=>'PembiayaanOnlineController@operasiMarkPaidBatch'])->middleware('auth','operasi');
Route::get('/pembiayaan-online/operasi/{kodcaw}/lines/{id}/bukti-akaun',['as'=>'pembiayaan.online.operasi.bukti.akaun','uses'=>'PembiayaanOnlineController@operasiViewBuktiAkaun'])->middleware('auth','operasi');
Route::get('/pembiayaan-online/operasi/{kodcaw}/lines/{id}/sag',['as'=>'pembiayaan.online.operasi.sag','uses'=>'PembiayaanOnlineController@operasiViewSag'])->middleware('auth','operasi');
Route::get('/permohonan/rekod',['as'=>'permohonan.rekod','uses' => 'KhazanahController@RekodPermohonan'])->middleware('auth','khazanah'); Route::get('/permohonan/rekod',['as'=>'permohonan.rekod','uses' => 'KhazanahController@RekodPermohonan'])->middleware('auth','khazanah');
Route::post('/permohonan/simpan',['as'=>'permohonan.simpan','uses' => 'KhazanahController@SimpanPermohonan'])->middleware('auth','khazanah'); Route::post('/permohonan/simpan',['as'=>'permohonan.simpan','uses' => 'KhazanahController@SimpanPermohonan'])->middleware('auth','khazanah');
Route::get('/permohonan/status/{mohonid}',['as'=>'permohonan.status','uses' => 'KhazanahController@StatusPermohonan'])->middleware('auth','khazanah'); Route::get('/permohonan/status/{mohonid}',['as'=>'permohonan.status','uses' => 'KhazanahController@StatusPermohonan'])->middleware('auth','khazanah');
@@ -401,6 +415,7 @@ Route::post('/barcodescanned',['as'=>'barcode.scanned','uses'=>'KhazanahControll
Route::get('/operasi/kelulusan','OperasiController@kelulusan')->name('operasi.kelulusan')->middleware('auth','operasi'); Route::get('/operasi/kelulusan','OperasiController@kelulusan')->name('operasi.kelulusan')->middleware('auth','operasi');
Route::get('/operasi/kelulusan/anggota','OperasiController@kelulusanAnggota')->name('operasi.kelulusananggota')->middleware('auth','operasi'); Route::get('/operasi/kelulusan/anggota','OperasiController@kelulusanAnggota')->name('operasi.kelulusananggota')->middleware('auth','operasi');
Route::post('/operasi/createkelulusan',['as'=>'operasi.createkelulusan','uses'=>'OperasiController@createkelulusan'])->middleware('auth','teller'); Route::post('/operasi/createkelulusan',['as'=>'operasi.createkelulusan','uses'=>'OperasiController@createkelulusan'])->middleware('auth','teller');
Route::post('/operasi/auto-approve-kelulusan',['as'=>'operasi.autoApproveKelulusan','uses'=>'OperasiController@autoApproveKelulusan'])->middleware('auth','teller');
Route::post('/operasi/createkelulusan-by-khazanah',['as'=>'operasi.createkelulusan-by-khazanah','uses'=>'OperasiController@createkelulusanByKhazanah'])->middleware('auth','khazanah'); Route::post('/operasi/createkelulusan-by-khazanah',['as'=>'operasi.createkelulusan-by-khazanah','uses'=>'OperasiController@createkelulusanByKhazanah'])->middleware('auth','khazanah');
Route::post('/operasi/createkelulusan-anggota',['as'=>'operasi.createkelulusan-anggota','uses'=>'OperasiController@createkelulusanAnggota'])->middleware('auth','teller'); Route::post('/operasi/createkelulusan-anggota',['as'=>'operasi.createkelulusan-anggota','uses'=>'OperasiController@createkelulusanAnggota'])->middleware('auth','teller');
Route::get('/operasi/getkelulusan',['as'=>'operasi.getkelulusan','uses'=>'OperasiController@getkelulusan'])->middleware('auth','teller'); Route::get('/operasi/getkelulusan',['as'=>'operasi.getkelulusan','uses'=>'OperasiController@getkelulusan'])->middleware('auth','teller');