Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c437a3f504 | |||
| b41234e10b | |||
| b7d217948c | |||
| 94f8f6e855 | |||
| 72108d3f87 | |||
| 3a64cc2400 | |||
| 1d71a31078 | |||
| 8e19d0488a | |||
| 918502b504 | |||
| 8a2bae7a88 | |||
| 67af37a1cd | |||
| 3f6fb8d985 | |||
| 371e4d8168 |
+50
@@ -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
@@ -51,4 +51,6 @@ 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
|
||||
MYKAD_READER_ENABLED_CAWANGAN='' # IF NO CAWANGAN STATED, ALL CAWANGAN IS ENABLED
|
||||
|
||||
INTERNAL_NOTIFICATION_SECRET=erahn-local-notification-secret
|
||||
+4
-2
@@ -1,10 +1,10 @@
|
||||
APP_NAME=Laravel
|
||||
APP_ENV=local
|
||||
APP_VERSION=v3.3
|
||||
APP_KEY=base64:FFRogE6mJj8FaDSSHl/KpXpzqi2M+P4plxVSRgVM8JI=
|
||||
APP_DEBUG=true
|
||||
APP_URL=http://localhost
|
||||
API_URL=http://localhost:8000
|
||||
API_ARRAHNBID=http://lelong.test/api/
|
||||
|
||||
LOG_CHANNEL=stack
|
||||
|
||||
@@ -47,4 +47,6 @@ MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
|
||||
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
|
||||
|
||||
MYKAD_READER_PORT=17321
|
||||
MYKAD_READER_ENABLED_CAWANGAN='' # IF NO CAWANGAN STATED, ALL CAWANGAN IS ENABLED
|
||||
MYKAD_READER_ENABLED_CAWANGAN=''
|
||||
|
||||
INTERNAL_NOTIFICATION_SECRET=
|
||||
|
||||
@@ -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"
|
||||
@@ -9,13 +9,7 @@ CREATE USER 'kaunteruser'@'localhost' IDENTIFIED BY 'your_password';
|
||||
|
||||
GRANT ALL PRIVILEGES ON kaunter_erahn.* TO 'kaunteruser'@'%';
|
||||
FLUSH PRIVILEGES;
|
||||
```
|
||||
|
||||
# ADDITIONAL NOTE
|
||||
|
||||
## customers/v2/index.blade.php is modal to register customer
|
||||
|
||||
## Min Komoditi = RM5000
|
||||
```
|
||||
|
||||
# System Flowchart
|
||||
|
||||
|
||||
@@ -23,9 +23,10 @@ class AdminFeatureController extends Controller
|
||||
|
||||
$metaRes = $this->api()->get($api_url . '/api/features');
|
||||
$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'] : [
|
||||
'pembiayaan_online' => 'Pembiayaan Online',
|
||||
'rebet_annur' => 'Rebet / diskaun Annur',
|
||||
];
|
||||
|
||||
$listRes = $this->api()->get($api_url . '/api/features/cawangan');
|
||||
|
||||
@@ -11,6 +11,7 @@ use App\Events\SerahanPenilai;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Services\CustomerService;
|
||||
use App\Services\NotificationService;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
@@ -22,6 +23,9 @@ use Illuminate\Support\Facades\Log;
|
||||
|
||||
class GadaianController extends Controller
|
||||
{
|
||||
const MIN_PINJAMAN = 5.00;
|
||||
const MAX_PINJAMAN = 50000.00;
|
||||
|
||||
public $AliranTunaiController;
|
||||
public $customerService;
|
||||
|
||||
@@ -32,14 +36,120 @@ class GadaianController extends Controller
|
||||
$this->AliranTunaiController = new AliranTunaiController();
|
||||
}
|
||||
|
||||
protected function isPembiayaanOnlineEnabled($kodcaw)
|
||||
protected function isFeatureEnabled($kodcaw, $feature)
|
||||
{
|
||||
$cawangan_info = Http::withOptions(['verify' => config('app.api_verify')])
|
||||
->get(config('api.url') . '/api/cawangan/' . $kodcaw)
|
||||
->json();
|
||||
$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)
|
||||
@@ -69,6 +179,7 @@ class GadaianController extends Controller
|
||||
Session::put('nokp', $noic);
|
||||
Session::put('nopelanggan', $customer_info['nopelanggan']);
|
||||
Session::put('page_reload', true);
|
||||
Session::forget('borang_online_printed');
|
||||
$gadai = Http::withOptions(['verify' => config('app.api_verify')])->post(config('api.url') . '/api/gadai/' . $auth_user['cawangan'], [
|
||||
'norujukan' => $norujukan,
|
||||
'nopelanggan' => $customer_info['nopelanggan'],
|
||||
@@ -93,6 +204,7 @@ class GadaianController extends Controller
|
||||
Session::put('nokp', $nokp);
|
||||
Session::put('nopelanggan', $nopelanggan);
|
||||
Session::put('page_reload', true);
|
||||
Session::forget('borang_online_printed');
|
||||
$gadai_baru = Http::withOptions(['verify' => config('app.api_verify')])->post(config('api.url') . '/api/gadai/' . $auth_user['cawangan'], [
|
||||
'norujukan' => $norujukan_baru,
|
||||
'nopelanggan' => $nopelanggan,
|
||||
@@ -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();
|
||||
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)
|
||||
@@ -172,6 +285,22 @@ class GadaianController extends Controller
|
||||
public function store(Request $request)
|
||||
{
|
||||
$auth_user = Auth::user();
|
||||
$caraTerima = strtoupper((string) $request->input('cara_terima', 'TUNAI'));
|
||||
$pinjamanAmt = round((float) $request->pinjaman, 2);
|
||||
|
||||
// Online only within min/max pinjaman; otherwise force cash path.
|
||||
if ($caraTerima === 'ONLINE' && ($pinjamanAmt < self::MIN_PINJAMAN || $pinjamanAmt > self::MAX_PINJAMAN)) {
|
||||
$caraTerima = 'TUNAI';
|
||||
}
|
||||
|
||||
if ($caraTerima === 'ONLINE' && !$this->isPembiayaanOnlineEnabled($auth_user['cawangan'])) {
|
||||
$caraTerima = 'TUNAI';
|
||||
}
|
||||
|
||||
if ($caraTerima === 'ONLINE' && !$this->hasMatchingBorangOnlinePrint($request)) {
|
||||
return redirect()->back()->with('error', 'Sila cetak borang pengesahan pelanggan sebelum tekan Terima.');
|
||||
}
|
||||
|
||||
//$total_nilai_marhun = $request->total_nilai_marhun;
|
||||
//need to get total nilai marhun by api get from table marhun
|
||||
|
||||
@@ -211,6 +340,8 @@ class GadaianController extends Controller
|
||||
'onepercent' => $onepercent,
|
||||
'margin_semasa' => $margin_pinjaman,
|
||||
'nokp' => $request->nokp,
|
||||
'kadar_rebate' => $this->parseMoney($request->kadar_rebate),
|
||||
'rebate' => $this->parseMoney($request->rebate),
|
||||
])->json();
|
||||
|
||||
|
||||
@@ -221,18 +352,6 @@ class GadaianController extends Controller
|
||||
'teller' => $auth_user['name']
|
||||
]);
|
||||
|
||||
$caraTerima = strtoupper((string) $request->input('cara_terima', 'TUNAI'));
|
||||
$pinjamanAmt = round((float) $request->pinjaman, 2);
|
||||
|
||||
// Online only 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') {
|
||||
$online = Http::withOptions(['verify' => config('app.api_verify')])
|
||||
->asForm()
|
||||
@@ -257,6 +376,8 @@ class GadaianController extends Controller
|
||||
: 'Gagal daftar pembiayaan online. Sila cuba lagi.';
|
||||
return redirect()->back()->with('error', $msg);
|
||||
}
|
||||
|
||||
$this->notifyPcPembiayaanOnlinePending($auth_user, $request, $pinjamanAmt);
|
||||
} else {
|
||||
$this->AliranTunaiController->alirantunaiGadai($request, $auth_user['name']);
|
||||
}
|
||||
@@ -265,6 +386,7 @@ class GadaianController extends Controller
|
||||
|
||||
$request->session()->forget('page_reload');
|
||||
$request->session()->forget('norujukan');
|
||||
$request->session()->forget('borang_online_printed');
|
||||
$request->session()->flash('activate_tab', 'SAG_baru');
|
||||
if ($caraTerima === 'ONLINE') {
|
||||
$request->session()->flash('message', 'Gadai berjaya. Pembiayaan online menunggu semakan PC.');
|
||||
@@ -316,6 +438,14 @@ class GadaianController extends Controller
|
||||
abort(422, 'Sila lengkapkan maklumat bank sebelum cetak borang.');
|
||||
}
|
||||
|
||||
$this->markBorangOnlinePrinted($this->normalizeBorangOnlineFields(
|
||||
$line['norujukan'],
|
||||
$line['pinjaman'],
|
||||
$line['kodbank'],
|
||||
$line['noakaun'],
|
||||
$line['nama_akaun'] ?: $line['nama_pelanggan']
|
||||
));
|
||||
|
||||
return $this->renderBorangPengesahanOnline($line, $kodcaw);
|
||||
}
|
||||
|
||||
@@ -423,6 +553,8 @@ class GadaianController extends Controller
|
||||
'ujrah' => $ujrah,
|
||||
'onepercent' => $onepercent,
|
||||
'margin_semasa' => $margin_pinjaman,
|
||||
'kadar_rebate' => $this->parseMoney($request->kadar_rebate),
|
||||
'rebate' => $this->parseMoney($request->rebate),
|
||||
])->json();
|
||||
$update_komuditi = Http::withOptions(['verify' => config('app.api_verify')])->put(config('api.url') . '/api/komuditi/update', [
|
||||
'unit_komuditi' => round($request->pinjaman, 2),
|
||||
@@ -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);
|
||||
$request->session()->forget('page_reload');
|
||||
$request->session()->forget('norujukan');
|
||||
$request->session()->forget('borang_online_printed');
|
||||
return redirect()->route('customer_info');
|
||||
}
|
||||
|
||||
|
||||
@@ -5,9 +5,7 @@ namespace App\Http\Controllers;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use App\Services\CustomerService;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Carbon\Carbon;
|
||||
|
||||
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['role_harian'] == 'teller'){
|
||||
$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['role_harian'] == 'teller'){
|
||||
$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);
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,7 @@ use Carbon\Carbon;
|
||||
use PDF;
|
||||
use DateTime;
|
||||
use App\Helper;
|
||||
use App\Services\NotificationService;
|
||||
use Log;
|
||||
|
||||
class KhazanahController extends Controller
|
||||
@@ -668,6 +669,23 @@ class KhazanahController extends Controller
|
||||
"namakhazanah" => $request->namakhazanah
|
||||
])->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{
|
||||
|
||||
$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'){
|
||||
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');
|
||||
}else{
|
||||
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'){
|
||||
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');
|
||||
}else{
|
||||
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();
|
||||
$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
|
||||
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($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($rebetpromo > 0) array_push($hartasemasa,['glcode' => '5000/242','descpt' => 'Diskaun Promosi', 'amaun' => ($rebetpromo * -1)]);
|
||||
|
||||
$totalhartasemasa = 0;
|
||||
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($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($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;
|
||||
foreach($tanggungansemasa as $index => $ts)
|
||||
@@ -2586,108 +2617,114 @@ class KhazanahController extends Controller
|
||||
}
|
||||
|
||||
//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();
|
||||
$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 = 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();
|
||||
$cawangan=Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url').'/api/cawangan/detail/'.$auth_user['cawangan'])->json();
|
||||
$cawangan_detail=$cawangan[0];
|
||||
$num = $request->num;
|
||||
$current = new Carbon();
|
||||
$tarikh_cetak = $current->toDateString();
|
||||
|
||||
$auth_user = Auth::user();
|
||||
$api_url = config('api.url');
|
||||
// fix: bagi default value supaya $tarikh_mula / $tarikh_akhir tak undefined
|
||||
$tarikh_mula = $tarikh_cetak;
|
||||
$tarikh_akhir = $tarikh_cetak;
|
||||
|
||||
$num=$request->num;
|
||||
$current = Carbon::now();
|
||||
$current = new Carbon();
|
||||
$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->datetimepicker1 != null){
|
||||
$split_date = explode('-', $request->datetimepicker1);
|
||||
$tarikh_mula = $split_date[2] . '-' . $split_date[1] . '-' . $split_date[0];
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
$auth_user = Auth::user();
|
||||
@@ -2831,10 +2868,43 @@ class KhazanahController extends Controller
|
||||
]);
|
||||
|
||||
event(new PendahuluanSerahan("Batal","all",$auth_user['cawangan'],$kodlaluan));
|
||||
$this->notifyTellerAliranTunai('Batal', 'all', $kodcaw, $kodlaluan);
|
||||
|
||||
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)
|
||||
{
|
||||
$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_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_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(){
|
||||
|
||||
@@ -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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -2,13 +2,14 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
|
||||
//Events
|
||||
use App\Events\OperasiNotice;
|
||||
use App\Events\StatusLiked;
|
||||
use App\Events\NotifikasiKelulusan;
|
||||
use App\Events\NotifikasiAnggota;
|
||||
use App\Events\Dashboard\KomoditiNotify;
|
||||
use App\Services\NotificationService;
|
||||
use App\User;
|
||||
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -21,6 +22,15 @@ use Illuminate\Support\Facades\Log;
|
||||
|
||||
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()
|
||||
{
|
||||
$this->middleware('auth');
|
||||
@@ -214,51 +224,19 @@ class OperasiController extends Controller
|
||||
'sahkan' => $status
|
||||
];
|
||||
|
||||
Log::info('Menghantar permintaan ke API UpdateKelulusanAnggota', [
|
||||
'endpoint' => $endpoint,
|
||||
'payload' => $payload
|
||||
]);
|
||||
|
||||
$response = Http::withOptions(['verify' => config('app.api_verify')])
|
||||
->put($endpoint, $payload);
|
||||
|
||||
$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) {
|
||||
// $jenis = ($status == 1) ? 'lulus' : 'batal';
|
||||
// event(new NotifikasiAnggota($nokp, $jenis, null, $komen, $namaoperasi));
|
||||
|
||||
$statusText = ($status == 1) ? 'DILULUSKAN' : 'DITOLAK';
|
||||
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());
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Ralat UpdateKelulusanAnggota (frontend)', [
|
||||
'message' => $e->getMessage(),
|
||||
'line' => $e->getLine()
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Ralat semasa menghantar kelulusan anggota.',
|
||||
@@ -267,10 +245,6 @@ class OperasiController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public function createkelulusan(Request $request)
|
||||
{
|
||||
$auth_user = Auth::user();
|
||||
@@ -309,9 +283,245 @@ class OperasiController extends Controller
|
||||
|
||||
event(new StatusLiked($auth_user['cawangan'], $auth_user['name']));
|
||||
|
||||
$this->notifyGadaiAboveTenThousand($auth_user, $request, $cawangan_info);
|
||||
|
||||
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)
|
||||
{
|
||||
@@ -320,8 +530,6 @@ class OperasiController extends Controller
|
||||
$api_url = config('api.url');
|
||||
$current = Carbon::now();
|
||||
|
||||
Log::info('createkelulusanAnggota', $request->all());
|
||||
|
||||
// Dapatkan maklumat cawangan
|
||||
$cawangan_info = Http::withOptions(['verify' => config('app.api_verify')])
|
||||
->get($api_url . '/api/cawangan/' . $auth_user['cawangan'])
|
||||
@@ -360,7 +568,6 @@ class OperasiController extends Controller
|
||||
'body' => $response->body(),
|
||||
]);
|
||||
} catch (\Throwable $th) {
|
||||
Log::error('❌ createkelulusanAnggota error: ' . $th->getMessage());
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => $th->getMessage(),
|
||||
@@ -415,26 +622,35 @@ class OperasiController extends Controller
|
||||
$api_url = config('api.url');
|
||||
$cawangan_info = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url') . '/api/cawangan/' . $auth_user['cawangan'])->json();
|
||||
|
||||
//declare array baru
|
||||
$arraykelulusan = [];
|
||||
$dateTo = Carbon::now()->toDateString();
|
||||
$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) {
|
||||
foreach ($k['kelulusanall'] as $index1 => $kel) {
|
||||
$komen = explode("|", $kel['komen']);
|
||||
$response = Http::withOptions(['verify' => config('app.api_verify')])
|
||||
->get(config('api.url') . '/api/ListOperasiKelulusan/', array_filter($params, function ($value) {
|
||||
return $value !== null && $value !== '';
|
||||
}));
|
||||
|
||||
$results = array_search("", $komen, true);
|
||||
if ($results !== false) {
|
||||
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'],]);
|
||||
}
|
||||
if (!$response->successful()) {
|
||||
return response()->json(['data' => [], 'error' => 'Gagal memuatkan rekod'], 502);
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
@@ -115,6 +161,19 @@ class PembiayaanOnlineController extends Controller
|
||||
'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()
|
||||
->asForm()
|
||||
->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()->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.');
|
||||
}
|
||||
|
||||
$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) {
|
||||
$raw = substr($raw, strpos($raw, 'base64,') + 7);
|
||||
}
|
||||
$binary = base64_decode($raw, true);
|
||||
if ($binary === false) {
|
||||
$binary = base64_decode($bukti['data']);
|
||||
$binary = base64_decode($file['data']);
|
||||
}
|
||||
|
||||
$type = $bukti['type'] ?? 'application/octet-stream';
|
||||
$filename = $bukti['filename'] ?? ('bukti-' . $id);
|
||||
$type = $file['type'] ?? 'application/octet-stream';
|
||||
$filename = $file['filename'] ?? $fallbackName;
|
||||
|
||||
return response($binary, 200, [
|
||||
'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.
|
||||
*/
|
||||
@@ -309,7 +595,7 @@ class PembiayaanOnlineController extends Controller
|
||||
$n = $res->json()['dikemaskini'] ?? count($ids);
|
||||
return redirect()
|
||||
->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).');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -12,6 +12,6 @@ class VerifyCsrfToken extends Middleware
|
||||
* @var array
|
||||
*/
|
||||
protected $except = [
|
||||
//
|
||||
'internal/notifications',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -46,6 +46,11 @@ class User extends Authenticatable
|
||||
return $this->hasOne(Cawangan::class, 'kodcaw', 'cawangan');
|
||||
}
|
||||
|
||||
public function staffNotifications()
|
||||
{
|
||||
return $this->hasMany(StaffNotification::class);
|
||||
}
|
||||
|
||||
public function canImpersonate(): bool
|
||||
{
|
||||
return in_array($this->roles ?? '', ['admin'], true);
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
@@ -412,7 +412,7 @@
|
||||
</div>
|
||||
|
||||
<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 kodcaw = "{{ $cawangan_info['kodcaw'] }}";
|
||||
var tagarrahnbid = "{{ $cawangan_info['tagarrahnbid'] }}";
|
||||
|
||||
@@ -541,7 +541,7 @@
|
||||
</div>
|
||||
|
||||
<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 kodcaw = "{{ $cawangan_info['kodcaw'] }}";
|
||||
var tagarrahnbid = "{{ $cawangan_info['tagarrahnbid'] }}";
|
||||
|
||||
@@ -131,7 +131,8 @@
|
||||
@endforeach
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
{{ $slot }}
|
||||
<tbody>
|
||||
{{ $slot }}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@@ -437,7 +437,7 @@
|
||||
</div>
|
||||
|
||||
<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 kodcaw = "{{ $cawangan_info['kodcaw'] }}";
|
||||
var tagarrahnbid = "{{ $cawangan_info['tagarrahnbid'] }}";
|
||||
|
||||
@@ -390,14 +390,14 @@
|
||||
<x-datatable
|
||||
id="myTable"
|
||||
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 class="tab-pane fade {{ $activate_pane_SAG_lama }}" id="nav-xtawarruq" role="tabpanel" aria-labelledby="nav-xtawarruq-tab">
|
||||
<x-datatable
|
||||
id="xtawarruq"
|
||||
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>
|
||||
@@ -712,13 +712,35 @@
|
||||
</div>
|
||||
|
||||
<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 kodcaw = "{{ $cawangan_info['kodcaw'] }}";
|
||||
var tagarrahnbid = "{{ $cawangan_info['tagarrahnbid'] }}";
|
||||
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);
|
||||
|
||||
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 iclama = "{{ $customer_info['kplama'] }}";
|
||||
var icbaru = "{{ $customer_info['kpbaru'] }}";
|
||||
@@ -817,6 +839,10 @@
|
||||
return '{{ url("/gadai/borang-pengesahan-online") }}/' + encodeURIComponent(norujukan);
|
||||
}
|
||||
|
||||
function formatCaraTerima(value) {
|
||||
return String(value || 'TUNAI').toUpperCase() === 'ONLINE' ? 'Online' : 'Tunai';
|
||||
}
|
||||
|
||||
function cetakBorangOnlineCell(gadai_detail) {
|
||||
var cara = String(gadai_detail['cara_terima'] || '').toUpperCase();
|
||||
if (cara !== 'ONLINE') {
|
||||
@@ -1133,6 +1159,13 @@
|
||||
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"];
|
||||
if (gadai_detail["percentpinj"] != 0)
|
||||
xtawarruq.row.add([
|
||||
'<input type="radio" value=' + gadai_detail["norujukan"] +
|
||||
' name="belum"/>',
|
||||
'<input type="radio" value="' + gadai_detail["norujukan"] +
|
||||
'" name="belum" data-cara-terima="' + (gadai_detail['cara_terima'] || '') + '"/>',
|
||||
'<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>',
|
||||
formatCaraTerima(gadai_detail['cara_terima']),
|
||||
gadai_detail['t_gadai'],
|
||||
tarikh_matang,
|
||||
'RM ' + formatter.format(gadai_detail['bakihargajualan']),
|
||||
@@ -1216,6 +1250,7 @@
|
||||
'RM ' + formatter.format(upahsemasa),
|
||||
'<div style="white-space: nowrap; color:red; font-weight: bolder">' +
|
||||
taglel + '</div>',
|
||||
cetakBorangOnlineCell(gadai_detail),
|
||||
]).draw();
|
||||
} else {
|
||||
var taglel = '';
|
||||
@@ -1256,6 +1291,7 @@
|
||||
gadai_detail['hargajualan'] + ' data-norujukan=' +
|
||||
gadai_detail['norujukan'] + ' data-url=' + urlroute + '>' +
|
||||
gadai_detail['norujukan'] + '</a>',
|
||||
formatCaraTerima(gadai_detail['cara_terima']),
|
||||
gadai_detail['t_gadai'],
|
||||
tarikh_matang,
|
||||
'RM ' + formatter.format(gadai_detail['bakihargajualan']),
|
||||
|
||||
@@ -204,7 +204,7 @@
|
||||
/>
|
||||
|
||||
<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>
|
||||
</div>
|
||||
|
||||
@@ -243,7 +243,7 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Cara Terima Pembiayaan</th>
|
||||
<td>Online (kredit ke akaun bank) — bukan tunai</td>
|
||||
<td>Atas Talian (Online)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Teller</th>
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
<div class="form-group row col-12">
|
||||
<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="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="tag_anggota" id="tag_anggota" value="{{ $customer_info['tag_anggota'] ?? 0 }}">
|
||||
|
||||
@@ -186,6 +187,19 @@
|
||||
</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="form-group row">
|
||||
<div class="col-md-6">
|
||||
@@ -232,9 +246,8 @@
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<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>
|
||||
<small class="form-text text-muted">Pilihan Online untuk pembiayaan RM5,000 – RM50,000 sahaja.</small>
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
<input type="submit" name="terima_gadai" id="terima_gadai" class="float-right btn btn-primary mr-2" value="Terima">
|
||||
<button type="button" id="cetak_borang_online" class="float-right btn btn-info mr-2" style="display:none;">Cetak Borang</button>
|
||||
<div id="cetak_borang_hint" class="text-danger text-right mt-2" style="clear:both; display:none;">
|
||||
Sila cetak borang pengesahan pelanggan dahulu
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@@ -398,6 +414,9 @@
|
||||
<script>
|
||||
var api_url = "{{ api_url_for_browser() }}";
|
||||
var kodcaw_ujrah = "<?php echo $cawangan_info['kodcaw'] ?>";
|
||||
const MIN_PINJAMAN = {{ \App\Http\Controllers\GadaianController::MIN_PINJAMAN }};
|
||||
const MAX_PINJAMAN = {{ \App\Http\Controllers\GadaianController::MAX_PINJAMAN }};
|
||||
var printedBorang = @json($borang_online_printed ?? null);
|
||||
$('form input').keydown(function (e) {
|
||||
if (e.keyCode == 13) {
|
||||
e.preventDefault();
|
||||
@@ -409,6 +428,13 @@
|
||||
$(document).on("keydown", disableF5);
|
||||
var norujukan = $('#norujukan').val();
|
||||
getMarhun(norujukan);
|
||||
@if(session('error'))
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Amaran!',
|
||||
text: @json(session('error'))
|
||||
});
|
||||
@endif
|
||||
|
||||
$.ajax({
|
||||
method:'GET',
|
||||
@@ -789,6 +815,26 @@
|
||||
$('#ujrah').val(formatter.format(getRoundedMethod(array_keuntungan['ujrah']).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', {
|
||||
@@ -1266,9 +1312,54 @@
|
||||
.then(response => response.json());
|
||||
}
|
||||
|
||||
function currentBorangFields() {
|
||||
var pinjaman = Number(String($('#pinjaman').val() || '').replace(/,/g, ''));
|
||||
if (!isFinite(pinjaman)) {
|
||||
pinjaman = 0;
|
||||
}
|
||||
return {
|
||||
norujukan: ($('#norujukan_gadai').val() || $('#norujukan').val() || '').trim(),
|
||||
pinjaman: pinjaman.toFixed(2),
|
||||
kodbank: ($('#gadai_kodbank').val() || '').trim().toUpperCase(),
|
||||
noakaun: ($('#gadai_noakaun').val() || '').replace(/\s+/g, ''),
|
||||
nama_akaun: ($('#gadai_nama_akaun').val() || $('input[name="nama_pelanggan"]').val() || '').trim().toUpperCase()
|
||||
};
|
||||
}
|
||||
|
||||
function isOnlineCaraTerima() {
|
||||
var pembiayaanOnlineEnabled = $('#pembiayaan_online_enabled').val() === '1';
|
||||
var pinjaman = Number(String($('#pinjaman').val() || '').replace(/,/g, ''));
|
||||
return pembiayaanOnlineEnabled
|
||||
&& pinjaman >= MIN_PINJAMAN
|
||||
&& pinjaman <= MAX_PINJAMAN
|
||||
&& $('input[name="cara_terima"]:checked').val() === 'ONLINE';
|
||||
}
|
||||
|
||||
function hasMatchingPrint() {
|
||||
if (!printedBorang) {
|
||||
return false;
|
||||
}
|
||||
var cur = currentBorangFields();
|
||||
return printedBorang.norujukan === cur.norujukan
|
||||
&& printedBorang.pinjaman === cur.pinjaman
|
||||
&& printedBorang.kodbank === cur.kodbank
|
||||
&& printedBorang.noakaun === cur.noakaun
|
||||
&& printedBorang.nama_akaun === cur.nama_akaun;
|
||||
}
|
||||
|
||||
function syncTerimaButton() {
|
||||
var lock = isOnlineCaraTerima() && !hasMatchingPrint();
|
||||
$('#terima_gadai').prop('disabled', lock);
|
||||
if (lock) {
|
||||
$('#cetak_borang_hint').show();
|
||||
} else {
|
||||
$('#cetak_borang_hint').hide();
|
||||
}
|
||||
}
|
||||
|
||||
function toggleCaraTerima(pinjaman) {
|
||||
var pembiayaanOnlineEnabled = $('#pembiayaan_online_enabled').val() === '1';
|
||||
if (pembiayaanOnlineEnabled && pinjaman >= 5000 && pinjaman <= 50000) {
|
||||
if (pembiayaanOnlineEnabled && pinjaman >= MIN_PINJAMAN && pinjaman <= MAX_PINJAMAN) {
|
||||
$('#cara_terima_section').show();
|
||||
toggleBankOnlineSection();
|
||||
} else {
|
||||
@@ -1276,6 +1367,7 @@
|
||||
$('#bank_online_section').hide();
|
||||
$('#cetak_borang_online').hide();
|
||||
$('#cara_terima_tunai').prop('checked', true);
|
||||
syncTerimaButton();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1290,16 +1382,20 @@
|
||||
$('#bank_online_section').hide();
|
||||
$('#cetak_borang_online').hide();
|
||||
}
|
||||
syncTerimaButton();
|
||||
}
|
||||
|
||||
$(document).on('change', 'input[name="cara_terima"]', function () {
|
||||
toggleBankOnlineSection();
|
||||
});
|
||||
|
||||
$(document).on('change', '#gadai_kodbank', function () {
|
||||
$(document).on('change input', '#gadai_kodbank, #gadai_noakaun, #gadai_nama_akaun', function () {
|
||||
$('#gadai_nama_bank').val($('#gadai_kodbank option:selected').data('nama') || '');
|
||||
syncTerimaButton();
|
||||
});
|
||||
|
||||
syncTerimaButton();
|
||||
|
||||
$(document).on('click', '#cetak_borang_online', function () {
|
||||
var kodbank = $('#gadai_kodbank').val();
|
||||
var noakaun = ($('#gadai_noakaun').val() || '').trim();
|
||||
@@ -1332,6 +1428,9 @@
|
||||
form.append($('<input>', { type: 'hidden', name: 'noakaun', value: noakaun }));
|
||||
form.append($('<input>', { type: 'hidden', name: 'nama_akaun', value: namaAkaun }));
|
||||
form.appendTo('body').trigger('submit').remove();
|
||||
|
||||
printedBorang = currentBorangFields();
|
||||
syncTerimaButton();
|
||||
});
|
||||
|
||||
async function getModalOnline() {
|
||||
@@ -1381,7 +1480,7 @@
|
||||
|
||||
async function checkDisbursementFloat(pinjaman) {
|
||||
var cara = $('input[name="cara_terima"]:checked').val() || 'TUNAI';
|
||||
if (!(pinjaman >= 5000 && pinjaman <= 50000)) {
|
||||
if (!(pinjaman >= MIN_PINJAMAN && pinjaman <= MAX_PINJAMAN)) {
|
||||
cara = 'TUNAI';
|
||||
}
|
||||
|
||||
@@ -1488,7 +1587,7 @@
|
||||
let bakiakhir = 0;
|
||||
var kodcaw = $('#kodcaw').val();
|
||||
if(pinjaman == 0){
|
||||
$("#terima_gadai").prop('disabled', false);
|
||||
syncTerimaButton();
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Amaran!',
|
||||
@@ -1497,6 +1596,16 @@
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isOnlineCaraTerima() && !hasMatchingPrint()) {
|
||||
syncTerimaButton();
|
||||
Swal.fire({
|
||||
icon: 'warning',
|
||||
title: 'Borang belum dicetak',
|
||||
text: 'Sila cetak borang pengesahan pelanggan sebelum tekan Terima.'
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
let floatOk = await checkDisbursementFloat(pinjaman);
|
||||
if (!floatOk) {
|
||||
$("#terima_gadai").prop('disabled', false);
|
||||
|
||||
@@ -219,78 +219,70 @@
|
||||
<div class="card-body text-center">
|
||||
|
||||
<div class="row form-group justify-content-center">
|
||||
<div class="col-3 mx-1">
|
||||
<div class="row">
|
||||
<a href="{{ url('dashboard') }}" disabled
|
||||
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 class="col-4 px-2">
|
||||
<a href="{{ url('dashboard') }}" disabled
|
||||
class="btn btn-info btn-lg btn-block mb-3">Dashboard</a>
|
||||
</div>
|
||||
<div class="row">
|
||||
<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">
|
||||
<div class="col-4 px-2">
|
||||
<a href="{{ route('khazanah.pelanggan') }}"
|
||||
class="btn btn-info btn-lg btn-block mb-3">Pelanggan</a>
|
||||
</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>
|
||||
</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') }}"
|
||||
id="btn-khazanah"
|
||||
class="btn {{ $countkhazanah > 0 ? 'btn-danger' : 'btn-info' }} btn-lg btn-block mb-3">Khazanah</a>
|
||||
</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') }}"
|
||||
class="btn btn-info btn-lg btn-block mb-3">Pendahuluan - Serahan</a>
|
||||
</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']))
|
||||
<div class="row">
|
||||
<div class="col-4 px-2">
|
||||
<a href="{{ route('pembiayaan.online.pc') }}"
|
||||
class="btn btn-info btn-lg btn-block mb-3">Pembiayaan Online</a>
|
||||
</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
|
||||
<div class="row">
|
||||
<div class="col-4 px-2">
|
||||
<a href="{{ route('khazanah.indexwakil') }}"
|
||||
class="btn btn-info btn-lg btn-block mb-3">Surat Wakil</a>
|
||||
</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>
|
||||
|
||||
@@ -414,7 +414,7 @@
|
||||
var kodcaw = "{{ $cawangan_info['kodcaw'] }}";
|
||||
var ppc = "{{ Auth::user()->name }}";
|
||||
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>Tarikh Pembiayaan</th>
|
||||
<th>No Rujukan</th>
|
||||
<th>Cara Terima</th>
|
||||
<th>Pembiayaan (RM)</th>
|
||||
<th>Keuntungan Lain-lain Caj (RM) </th>
|
||||
<th>Keuntungan 1% (RM)</th>
|
||||
@@ -61,7 +62,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tfoot class="thead-dark">
|
||||
<th colspan="5">TOTAL</th>
|
||||
<th colspan="6">TOTAL</th>
|
||||
<th id="pinjamantotal"></th>
|
||||
<th id="ujrahtotal"></th>
|
||||
<th id="onepercenttotal"></th>
|
||||
@@ -300,6 +301,10 @@
|
||||
|
||||
total_upah12 += upah12;
|
||||
|
||||
var caraTerima = String(gadai['cara_terima'] || 'TUNAI').toUpperCase() === 'ONLINE'
|
||||
? 'Online'
|
||||
: 'Tunai';
|
||||
|
||||
// Pastikan semua nilai ada sebelum add row
|
||||
tablegadai.row.add([
|
||||
counter,
|
||||
@@ -307,6 +312,7 @@
|
||||
gadai['nokp'] || '-',
|
||||
gadai['t_gadai'] || '-',
|
||||
gadai['norujukan'] || '-',
|
||||
caraTerima,
|
||||
formatter.format(pinjaman),
|
||||
formatter.format(ujrah * 12),
|
||||
formatter.format(onepercent * 12),
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
|
||||
|
||||
<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 kodcaw = "{{ $cawangan_info['kodcaw'] }}";
|
||||
var tagarrahnbid = "{{ $cawangan_info['tagarrahnbid'] }}";
|
||||
|
||||
@@ -414,7 +414,7 @@
|
||||
var kodcaw = "{{ $cawangan_info['kodcaw'] }}";
|
||||
var ppc = "{{ Auth::user()->name }}";
|
||||
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'));
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -470,7 +470,7 @@
|
||||
var kodcaw = "{{ $cawangan_info['kodcaw'] }}";
|
||||
var ppc = "{{ Auth::user()->name }}";
|
||||
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'));
|
||||
|
||||
|
||||
|
||||
@@ -665,7 +665,15 @@
|
||||
var taglel = '';
|
||||
if(gadai_detail['taglel'] == 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 {
|
||||
$.ajax({
|
||||
method:"GET",
|
||||
@@ -722,9 +730,15 @@
|
||||
var taglel = '';
|
||||
if(gadai_detail['taglel'] == 1){
|
||||
if(tagarrahnbid == 1){
|
||||
if(gadai_detail['t_lelong'] != null) {
|
||||
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 {
|
||||
$.ajax({
|
||||
method:"GET",
|
||||
|
||||
@@ -214,14 +214,16 @@
|
||||
v-pre
|
||||
style="color: {{ isset($cawangan_info['details_caw']) && $cawangan_info['details_caw'] == 'Putatan 2' ? 'black' : '#1abc9c' }};">
|
||||
<i id="bell" class="fa fa-bell"></i>
|
||||
<span id="staff-notification-badge" class="staff-notification-badge"></span>
|
||||
</a>
|
||||
|
||||
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown">
|
||||
<div id="divbiasa">
|
||||
<div class="dropdown-menu dropdown-menu-right staff-notification-menu" aria-labelledby="navbarDropdown">
|
||||
<div id="divbiasa" class="d-none">
|
||||
<a class="dropdown-item" id="linkz" href="#">
|
||||
Tiada Data
|
||||
</a>
|
||||
</div>
|
||||
@include('layouts.partials.notification_inbox')
|
||||
</div>
|
||||
</li>
|
||||
|
||||
@@ -281,7 +283,7 @@
|
||||
});
|
||||
|
||||
</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;">
|
||||
<a id="navbarDropdown"
|
||||
class="nav-link"
|
||||
@@ -293,60 +295,13 @@
|
||||
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') }};">
|
||||
<i id="bell" class="fa fa-bell"></i>
|
||||
<span id="staff-notification-badge" class="staff-notification-badge"></span>
|
||||
</a>
|
||||
|
||||
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown">
|
||||
<div id="divbiasa">
|
||||
<a class="dropdown-item" id="gadailinkz" href="#">
|
||||
Tiada Data
|
||||
</a>
|
||||
</div>
|
||||
<div class="dropdown-menu dropdown-menu-right staff-notification-menu" aria-labelledby="navbarDropdown">
|
||||
@include('layouts.partials.notification_inbox')
|
||||
</div>
|
||||
</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
|
||||
|
||||
|
||||
@@ -446,30 +401,9 @@
|
||||
</div>
|
||||
@else
|
||||
@endif
|
||||
@include('layouts.partials.batch_cutoff_banner')
|
||||
@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">×</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">
|
||||
@yield('content')
|
||||
</main>
|
||||
|
||||
@@ -164,29 +164,6 @@
|
||||
</div>
|
||||
</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">×</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">
|
||||
@yield('content')
|
||||
</main>
|
||||
|
||||
@@ -111,14 +111,17 @@
|
||||
<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>
|
||||
<i id="bell" class="fa fa-bell"></i>
|
||||
<span id="staff-notification-badge" class="staff-notification-badge"></span>
|
||||
</a>
|
||||
|
||||
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown">
|
||||
<div id="divbiasa">
|
||||
<div class="dropdown-menu dropdown-menu-right staff-notification-menu" aria-labelledby="navbarDropdown">
|
||||
<div id="divbiasa" class="d-none">
|
||||
<a class="dropdown-item" id="gadailinkz" href="#">
|
||||
Tiada Notifikasi
|
||||
</a>
|
||||
<a class="dropdown-item" id="linkz" href="#"></a>
|
||||
</div>
|
||||
@include('layouts.partials.notification_inbox')
|
||||
</div>
|
||||
</li>
|
||||
|
||||
@@ -254,29 +257,6 @@
|
||||
</div>
|
||||
</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">×</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">
|
||||
@yield('content')
|
||||
</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, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"');
|
||||
}
|
||||
|
||||
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>
|
||||
</nav>
|
||||
|
||||
<div class="card-body text-center">
|
||||
|
||||
<div class="row mb-4">
|
||||
<div class="col-2 justify-content-start">
|
||||
<div class="card-body">
|
||||
<div class="row mb-3 align-items-end">
|
||||
<div class="col-md-3">
|
||||
<label for="date_from">Tarikh Dari</label>
|
||||
<input type="date" id="date_from" class="form-control" value="{{ $dateFrom }}">
|
||||
</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 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>
|
||||
|
||||
<table class='table table-striped table-hover' id='table-kelulusan' style="width:100%;">
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<th>No Rujukan</th>
|
||||
<th>Status</th>
|
||||
<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>
|
||||
|
||||
<x-datatable
|
||||
id="table-kelulusan"
|
||||
filename="Rekod_Lulus_Operasi"
|
||||
class="text-center"
|
||||
:columns="['Tarikh', 'No Rujukan', 'Status', 'Komen', 'Nilai Pembiayaan', 'Nilai Marhun', 'Berat', 'Margin', 'Pelulus']"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
function escapeHtml(value) {
|
||||
return $('<div>').text(value == null ? '' : String(value)).html();
|
||||
}
|
||||
|
||||
var tablekelulusan = $('#table-kelulusan').DataTable({
|
||||
"scrollX": true
|
||||
var tablekelulusan = initErahnDatatable('#table-kelulusan', {
|
||||
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>
|
||||
@endsection
|
||||
|
||||
@@ -54,104 +54,131 @@
|
||||
@if($pendingCount > 0)
|
||||
<form method="post"
|
||||
action="{{ route('pembiayaan.online.operasi.approve.batch', ['kodcaw' => $kodcaw, 'batchId' => $batch['id']]) }}"
|
||||
class="d-inline"
|
||||
onsubmit="return confirm('Luluskan semua {{ $pendingCount }} rekod menunggu dalam batch ini?');">
|
||||
class="d-inline js-swal-confirm"
|
||||
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
|
||||
<button type="submit" class="btn btn-success btn-sm">Lulus Semua Menunggu ({{ $pendingCount }})</button>
|
||||
</form>
|
||||
@endif
|
||||
@if($approvedCount > 0)
|
||||
<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>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body table-responsive">
|
||||
<table class="table table-striped table-sm text-center">
|
||||
<thead class="thead-dark">
|
||||
<div class="card-body">
|
||||
<x-datatable
|
||||
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>
|
||||
<th>Status</th>
|
||||
<th>SAG</th>
|
||||
<th>Pelanggan</th>
|
||||
<th>No KP</th>
|
||||
<th>Bank</th>
|
||||
<th>No Akaun</th>
|
||||
<th>Nama Akaun</th>
|
||||
<th>Pinjaman (RM)</th>
|
||||
<th>Rujukan Bayaran</th>
|
||||
<th>Tindakan</th>
|
||||
<td>{{ $loop->iteration }}</td>
|
||||
<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">
|
||||
<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>
|
||||
</thead>
|
||||
<tbody>
|
||||
@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>
|
||||
@endforeach
|
||||
</x-datatable>
|
||||
</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>×</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 --}}
|
||||
<div class="modal fade" id="rejectModal" tabindex="-1" role="dialog" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
@@ -188,7 +215,7 @@
|
||||
<input type="hidden" name="batch_id" value="{{ $batch['id'] }}">
|
||||
<div class="modal-content">
|
||||
<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>×</span></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
@@ -203,12 +230,12 @@
|
||||
<div class="form-group">
|
||||
<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>
|
||||
<small class="text-muted">PDF / imej slip pindahan bank</small>
|
||||
<small class="text-muted">PDF / imej slip bank</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<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>
|
||||
</form>
|
||||
@@ -224,7 +251,7 @@
|
||||
@csrf
|
||||
<div class="modal-content">
|
||||
<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>×</span></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
@@ -244,7 +271,7 @@
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<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>
|
||||
</form>
|
||||
@@ -252,7 +279,28 @@
|
||||
</div>
|
||||
|
||||
<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');
|
||||
$('#rejectForm').attr(
|
||||
'action',
|
||||
@@ -263,7 +311,7 @@
|
||||
$('#rejectModal').modal('show');
|
||||
});
|
||||
|
||||
$('.btn-paid').on('click', function () {
|
||||
$('#batch_lines').on('click', '.btn-paid', function () {
|
||||
var id = $(this).data('id');
|
||||
$('#paidForm').attr(
|
||||
'action',
|
||||
@@ -272,5 +320,25 @@
|
||||
$('#paidNorujukan').text($(this).data('norujukan'));
|
||||
$('#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>
|
||||
@endsection
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
<th>No Akaun</th>
|
||||
<th>Pinjaman (RM)</th>
|
||||
<th>Rujukan Bayaran</th>
|
||||
<th>Dokumen</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -53,6 +54,23 @@
|
||||
<td>{{ $line['noakaun'] ?? '-' }}</td>
|
||||
<td class="text-right">{{ number_format((float)($line['pinjaman'] ?? 0), 2) }}</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>
|
||||
@endforeach
|
||||
</tbody>
|
||||
@@ -62,4 +80,32 @@
|
||||
</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>×</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
|
||||
|
||||
@@ -69,13 +69,13 @@
|
||||
<div class="card-header d-flex justify-content-between align-items-center flex-wrap">
|
||||
<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>
|
||||
Sahkan Tally / Selesai
|
||||
Sahkan semua sebagai dibayar
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="small text-muted">
|
||||
Semak jumlah & 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>
|
||||
<form id="formMarkBlast" method="post" action="{{ route('pembiayaan.online.pc.blast') }}">
|
||||
@csrf
|
||||
@@ -168,7 +168,7 @@
|
||||
function syncBlastButton() {
|
||||
var n = $('.row-check:checked').length;
|
||||
$('#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 () {
|
||||
@@ -180,7 +180,7 @@
|
||||
|
||||
$('#btnMarkBlast').on('click', function () {
|
||||
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();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -33,6 +33,20 @@
|
||||
if (is_object($pendingReq)) {
|
||||
$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
|
||||
|
||||
@if($isClosed)
|
||||
@@ -56,6 +70,66 @@
|
||||
</div>
|
||||
@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>
|
||||
· 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="col-md-4">
|
||||
<div class="card">
|
||||
@@ -82,18 +156,30 @@
|
||||
<div class="col-md-4">
|
||||
<div class="card">
|
||||
<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>
|
||||
<small>Jumlah RM {{ number_format((float)($queue['jumlah'] ?? 0), 2) }}</small>
|
||||
<small>
|
||||
{{ $verifiedCount }} sudah sah
|
||||
/ {{ $unverifiedCount }} belum semak
|
||||
· Jumlah RM {{ number_format((float)($queue['jumlah'] ?? 0), 2) }}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card">
|
||||
<div class="card {{ $slotLewat || $slotHampirTamat ? 'border-warning' : '' }}">
|
||||
<div class="card-body text-center">
|
||||
<div class="text-muted">Slot Cadangan</div>
|
||||
<h4 class="mb-0">{{ $slot['slot'] ?? '-' }}</h4>
|
||||
<small>{{ $slot['nota'] ?? '' }}</small>
|
||||
<div class="text-muted">Slot Semasa</div>
|
||||
<h4 class="mb-0">{{ $slotName }}</h4>
|
||||
<small>
|
||||
@if($slotLewat)
|
||||
Cutoff {{ $slotCutoff }} sudah tamat<br>
|
||||
Masih boleh hantar; proses mungkin esok
|
||||
@else
|
||||
Hantar sebelum <b>{{ $slotCutoff }}</b>
|
||||
· Proses ~{{ $slotProses }}
|
||||
@endif
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -101,18 +187,27 @@
|
||||
|
||||
<div class="card mb-4">
|
||||
<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>
|
||||
</div>
|
||||
<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') }}">
|
||||
@csrf
|
||||
<input type="hidden" name="slot" value="{{ $slot['slot'] ?? 'BATCH1' }}">
|
||||
<input type="hidden" name="slot" value="{{ $slotName }}">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover table-sm text-center" id="queueTable">
|
||||
<thead class="thead-dark">
|
||||
<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>SAG</th>
|
||||
<th>Pelanggan</th>
|
||||
@@ -126,15 +221,31 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
@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>
|
||||
<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>
|
||||
@if(($line['status'] ?? '') === 'REJECTED_PC')
|
||||
@if($isRejected)
|
||||
<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
|
||||
<span class="badge badge-secondary">PENDING</span>
|
||||
<span class="badge badge-secondary">BELUM SEMAK</span>
|
||||
@endif
|
||||
</td>
|
||||
<td>{{ $line['norujukan'] ?? '-' }}</td>
|
||||
@@ -144,7 +255,33 @@
|
||||
<td>{{ $line['noakaun'] ?? '-' }}</td>
|
||||
<td class="text-right">{{ number_format((float)($line['pinjaman'] ?? 0), 2) }}</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"
|
||||
data-id="{{ $line['id'] }}"
|
||||
data-kodbank="{{ $line['kodbank'] ?? '' }}"
|
||||
@@ -152,7 +289,8 @@
|
||||
data-namaakaun="{{ $line['nama_akaun'] ?? '' }}"
|
||||
data-namabank="{{ $line['nama_bank'] ?? '' }}"
|
||||
data-nota="{{ $line['nota'] ?? '' }}"
|
||||
data-norujukan="{{ $line['norujukan'] ?? '' }}">
|
||||
data-norujukan="{{ $line['norujukan'] ?? '' }}"
|
||||
data-bukti-akaun="{{ $line['bukti_akaun_file_id'] ?? '' }}">
|
||||
Edit
|
||||
</button>
|
||||
</td>
|
||||
@@ -166,6 +304,9 @@
|
||||
</table>
|
||||
</div>
|
||||
</form>
|
||||
<form id="formUnverify" method="post" action="" class="d-none">
|
||||
@csrf
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -221,8 +362,8 @@
|
||||
|
||||
{{-- Edit modal --}}
|
||||
<div class="modal fade" id="editModal" tabindex="-1" role="dialog" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<form method="post" id="editForm" action="">
|
||||
<div class="modal-dialog modal-xl" role="document" style="max-width:1140px;">
|
||||
<form method="post" id="editForm" action="" enctype="multipart/form-data">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
<div class="modal-content">
|
||||
@@ -231,29 +372,50 @@
|
||||
<button type="button" class="close" data-dismiss="modal"><span>×</span></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label>Bank</label>
|
||||
<select name="kodbank" id="editKodbank" class="form-control" required>
|
||||
<option value="">-- Pilih Bank --</option>
|
||||
@foreach($banks as $bank)
|
||||
<option value="{{ $bank['abbreviation'] }}" data-nama="{{ $bank['name'] }}">
|
||||
{{ $bank['name'] }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<input type="hidden" name="nama_bank" id="editNamabank">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>No. Akaun</label>
|
||||
<input type="text" name="noakaun" id="editNoakaun" class="form-control" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Nama Akaun</label>
|
||||
<input type="text" name="nama_akaun" id="editNamaakaun" class="form-control">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Nota</label>
|
||||
<textarea name="nota" id="editNota" class="form-control" rows="2"></textarea>
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="form-group">
|
||||
<label>Bank</label>
|
||||
<select name="kodbank" id="editKodbank" class="form-control" required>
|
||||
<option value="">-- Pilih Bank --</option>
|
||||
@foreach($banks as $bank)
|
||||
<option value="{{ $bank['abbreviation'] }}" data-nama="{{ $bank['name'] }}">
|
||||
{{ $bank['name'] }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<input type="hidden" name="nama_bank" id="editNamabank">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>No. Akaun</label>
|
||||
<input type="text" name="noakaun" id="editNoakaun" class="form-control" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Nama Akaun</label>
|
||||
<input type="text" name="nama_akaun" id="editNamaakaun" class="form-control">
|
||||
</div>
|
||||
<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 class="modal-footer">
|
||||
@@ -265,24 +427,176 @@
|
||||
</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>×</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>
|
||||
function syncSubmitButton() {
|
||||
var n = $('.row-check:checked').length;
|
||||
var n = $('.row-check:not(:disabled):checked').length;
|
||||
$('#btnSubmitBatch').prop('disabled', n === 0);
|
||||
$('#btnSubmitBatch').text(n ? ('Hantar Batch ke Operasi (' + n + ')') : 'Hantar Batch ke Operasi');
|
||||
}
|
||||
|
||||
$('#checkAll').on('change', function () {
|
||||
$('.row-check').prop('checked', this.checked);
|
||||
$('.row-check:not(:disabled)').prop('checked', this.checked);
|
||||
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 () {
|
||||
if ($('.row-check:checked').length === 0) return;
|
||||
if (!confirm('Hantar rekod dipilih ke Operasi?')) return;
|
||||
$('#formSubmitBatch').submit();
|
||||
var n = $('.row-check:not(:disabled):checked').length;
|
||||
if (n === 0) return;
|
||||
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 () {
|
||||
@@ -294,11 +608,50 @@
|
||||
$('#editNamaakaun').val($(this).data('namaakaun'));
|
||||
$('#editNamabank').val($(this).data('namabank'));
|
||||
$('#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').on('hidden.bs.modal', function () {
|
||||
$('#editSagFrame').attr('src', 'about:blank');
|
||||
});
|
||||
|
||||
$('#editKodbank').on('change', function () {
|
||||
$('#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>
|
||||
@endsection
|
||||
|
||||
@@ -131,7 +131,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
{{-- <div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">Tutup Modal Online (EOD)</h5>
|
||||
</div>
|
||||
@@ -153,7 +153,7 @@
|
||||
</form>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div> --}}
|
||||
</div>
|
||||
|
||||
<div class="col-md-7 mb-3">
|
||||
|
||||
@@ -326,6 +326,16 @@
|
||||
<input type="text" id="jumlahtelahdibayar" class="form-control" readonly/>
|
||||
</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>
|
||||
@@ -441,6 +451,16 @@
|
||||
<input type="text" id="jumlahupah" class="form-control" readonly/>
|
||||
</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 class="form-group row">
|
||||
@@ -467,6 +487,17 @@
|
||||
<input type="text" id="jumlahtelahdibayar" class="form-control" readonly/>
|
||||
</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">
|
||||
|
||||
@@ -214,8 +214,7 @@
|
||||
/>
|
||||
|
||||
<div class="title">
|
||||
<h1>Borang Kelulusan Pembiayaan Online<br>Senarai Batch Pembayaran</h1>
|
||||
<p>Untuk semakan Operasi dan kelulusan Pengurus Besar. Bukan untuk tandatangan pelanggan.</p>
|
||||
<h1>Borang Kelulusan Batch Pembiayaan Online</h1>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
@@ -298,8 +297,8 @@
|
||||
bagi batch <b>{{ $batchNo }}</b> cawangan <b>{{ $cawangan }}</b> adalah lengkap
|
||||
untuk proses pembayaran ke akaun bank pelanggan.
|
||||
<ol>
|
||||
<li>Maklumat SAG, nama, no. KP, bank dan no. akaun telah disemak terhadap rekod sistem.</li>
|
||||
<li>Pembayaran akan dikreditkan ke akaun penerima seperti senarai, bukan tunai di kaunter.</li>
|
||||
<li>Maklumat SAG, nama, no. KP, bank dan no. akaun telah disemak didalam sistem.</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>
|
||||
</ol>
|
||||
</div>
|
||||
@@ -318,18 +317,11 @@
|
||||
<div class="role">Pengurus Besar</div>
|
||||
<div class="sign-line">
|
||||
<p>Nama:</p>
|
||||
<p>Cop / Paraf:</p>
|
||||
<p>Cop:</p>
|
||||
<p>Tarikh:</p>
|
||||
</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>
|
||||
</body>
|
||||
|
||||
@@ -108,6 +108,19 @@
|
||||
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 {
|
||||
font-size: 11px;
|
||||
line-height: 1.55;
|
||||
@@ -121,10 +134,8 @@
|
||||
.akuan li { margin-bottom: 4px; }
|
||||
|
||||
.signs {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
gap: 10px;
|
||||
margin-top: 16px;
|
||||
max-width: 34%;
|
||||
}
|
||||
.sign-box {
|
||||
border: 1px solid #111;
|
||||
@@ -184,7 +195,7 @@
|
||||
/>
|
||||
|
||||
<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>
|
||||
</div>
|
||||
|
||||
@@ -214,7 +225,7 @@
|
||||
<div class="section-title">B. Maklumat Pembiayaan</div>
|
||||
<table class="fields">
|
||||
<tr>
|
||||
<th>No. Surat Akuan Gadai</th>
|
||||
<th>No. Surat Akuan Gadaian (SAG)</th>
|
||||
<td>{{ $norujukan }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -222,8 +233,8 @@
|
||||
<td class="amt">RM {{ $pinjaman }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Cara Terima Pembiayaan</th>
|
||||
<td>Online (kredit ke akaun bank) — bukan tunai</td>
|
||||
<th>Cara Pembiayaan</th>
|
||||
<td>Atas Talian (Online)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Teller</th>
|
||||
@@ -250,15 +261,19 @@
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="copies">
|
||||
Salinan: <span></span> Kad Pengenalan <span></span> Akaun Bank/ Slip/ Penyata Bank
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="section-title">D. Akuan Pelanggan</div>
|
||||
<div class="akuan">
|
||||
Saya, <b>{{ $nama }}</b> (No. KP: <b>{{ $nokp }}</b>), dengan ini mengesahkan bahawa:
|
||||
<ol>
|
||||
<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 faham kredit ke akaun tertakluk kepada semakan Pengurus Cawangan dan proses pembayaran pihak Koperasi.</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 bersetuju menerima pembayaran pembiayaan Ar-Rahn KoPKB sebanyak <b>RM {{ $pinjaman }}</b> secara atas talian (Online) ke akaun tersebut.</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 mengesahkan bahawa segala maklumat yang diberikan adalah benar dan tepat.</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
@@ -272,29 +287,13 @@
|
||||
<p>Tarikh: {{ $tarikh }}</p>
|
||||
</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>
|
||||
|
||||
<x-print.footer>
|
||||
<x-print.footer :show-copies="false">
|
||||
<b>Nota:</b>
|
||||
<p>1. Borang ini dicetak semasa pelanggan memilih cara terima pembiayaan secara Online.</p>
|
||||
<p>2. Pembiayaan akan dikreditkan ke akaun selepas semakan PC. Pelanggan tidak menerima wang tunai di kaunter.</p>
|
||||
<p>3. Sila pastikan nama pemegang akaun sepadan dengan nama pelanggan pada MyKad.</p>
|
||||
<p>1. Pembayaran hanya dibuat ke akaun pelanggan sendiri.</p>
|
||||
<p>2. Sebarang pembetulan maklumat hendaklah disahkan semula oleh pelanggan.</p>
|
||||
<p>3. Borang ini hendaklah dilampirkan bersama dokumen transaksi gadaian untuk tujuan audit dan rekod.</p>
|
||||
</x-print.footer>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -233,7 +233,9 @@
|
||||
@endif
|
||||
<tr>
|
||||
<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>
|
||||
{{ $item['nama'] }}
|
||||
</td>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
@php $rebetAnnur = !empty(($cawangan_info['features'] ?? [])['rebet_annur']); @endphp
|
||||
<style>
|
||||
#header {
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
@@ -230,6 +231,9 @@
|
||||
<th>Lain-Lain Caj (RM)</th>
|
||||
<th>Keuntungan 1% (RM)</th>
|
||||
<th>Keuntungan</th>
|
||||
@if($rebetAnnur)
|
||||
<th>Diskaun Rebet</th>
|
||||
@endif
|
||||
<th>Peratus (%)</th>
|
||||
</tr>
|
||||
@php
|
||||
@@ -241,6 +245,7 @@
|
||||
$bil = 0;
|
||||
$full_total_ujrah = 0;
|
||||
$full_total_onepercent = 0;
|
||||
$totaldiskaun = 0;
|
||||
|
||||
@endphp
|
||||
@foreach($gadai as $index=>$item)
|
||||
@@ -251,6 +256,7 @@
|
||||
$totalupah += $item['upah12'];
|
||||
$full_total_ujrah += $item['ujrah']*12;
|
||||
$full_total_onepercent += $item['onepercent']*12;
|
||||
$totaldiskaun += $item['diskaun'] ?? 0;
|
||||
|
||||
@endphp
|
||||
@if($index > 0)
|
||||
@@ -264,6 +270,7 @@
|
||||
$totalberatteller = 0;
|
||||
$totalujrah = 0;
|
||||
$totalonepercent = 0;
|
||||
$totaldiskaunteller = 0;
|
||||
|
||||
@endphp
|
||||
@endif
|
||||
@@ -278,12 +285,15 @@
|
||||
$totalupah12 = 0;
|
||||
$totalujrah = 0;
|
||||
$totalonepercent= 0;
|
||||
$totaldiskaunteller = 0;
|
||||
@endphp
|
||||
|
||||
@endif
|
||||
<tr>
|
||||
<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>
|
||||
{{ $item['nama'] }}
|
||||
</td>
|
||||
@@ -301,6 +311,9 @@
|
||||
<td>{{ number_format($item['ujrah']*12,2) }}</td>
|
||||
<td>{{ number_format($item['onepercent']*12,2) }}</td>
|
||||
<td>{{ number_format($item['upah12'],2) }}</td>
|
||||
@if($rebetAnnur)
|
||||
<td>{{ number_format($item['diskaun'] ?? 0,2) }}</td>
|
||||
@endif
|
||||
<td>{{ $item['percentpinj'] }}</td>
|
||||
</tr>
|
||||
@php $counter++; @endphp
|
||||
@@ -311,6 +324,7 @@
|
||||
$totalupah12 += $item['upah12'];
|
||||
$totalujrah += $item['ujrah']*12;
|
||||
$totalonepercent += $item['onepercent']*12;
|
||||
$totaldiskaunteller += $item['diskaun'] ?? 0;
|
||||
|
||||
|
||||
@endphp
|
||||
@@ -325,6 +339,9 @@
|
||||
<td><b>{{ number_format($totalujrah,2) }}</b></td>
|
||||
<td><b>{{ number_format($totalonepercent,2) }}</b></td>
|
||||
<td><b>{{ number_format($totalupah12,2) }} </b></td>
|
||||
@if($rebetAnnur)
|
||||
<td><b>{{ number_format($totaldiskaunteller,2) }} </b></td>
|
||||
@endif
|
||||
|
||||
|
||||
</tr>
|
||||
@@ -335,6 +352,7 @@
|
||||
$totalnmteller = 0;
|
||||
$totalujrah = 0;
|
||||
$totalonepercent = 0;
|
||||
$totaldiskaunteller = 0;
|
||||
|
||||
@endphp
|
||||
@endif
|
||||
@@ -348,6 +366,9 @@
|
||||
<td><b>{{ number_format($totalujrah,2) }}</b></td>
|
||||
<td><b>{{ number_format($totalonepercent,2) }}</b></td>
|
||||
<td><b>{{ number_format($totalupah12,2) }} </b></td>
|
||||
@if($rebetAnnur)
|
||||
<td><b>{{ number_format($totaldiskaunteller,2) }} </b></td>
|
||||
@endif
|
||||
|
||||
</tr>
|
||||
@endif
|
||||
|
||||
@@ -219,7 +219,9 @@
|
||||
@php if($item['hargajualan'] == null) $totalpinjaman += $item['pinjaman']; else $totalpinjaman += $item['bakipjm']; @endphp
|
||||
<tr>
|
||||
<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['nokp']}}</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
|
||||
<tr>
|
||||
<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['nokp']}}</td>
|
||||
<td>{{ number_format($item['berat'],2) }}</td>
|
||||
|
||||
@@ -236,7 +236,9 @@
|
||||
@endphp
|
||||
<tr>
|
||||
<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['nokp']}}</td>
|
||||
<td>{{ number_format($item['berat'],2) }}</td>
|
||||
@@ -342,7 +344,9 @@
|
||||
@endphp
|
||||
<tr>
|
||||
<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['nokp']}}</td>
|
||||
<td>{{ number_format($item['berat'],2) }}</td>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
@php $rebetAnnur = !empty(($cawangan_info['features'] ?? [])['rebet_annur']); @endphp
|
||||
<style>
|
||||
#header {
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
@@ -241,6 +242,9 @@
|
||||
<th>B.Pembiayaan(RM)</th>
|
||||
<th>Lain-Lain Caj (RM)</th>
|
||||
<th>Untung 1% (RM)</th>
|
||||
@if($rebetAnnur)
|
||||
<th>Diskaun Rebet (RM)</th>
|
||||
@endif
|
||||
<th>Pendapatan(RM)</th>
|
||||
<th>Bayaran(RM)</th>
|
||||
<th>Jumlah(RM)</th>
|
||||
@@ -254,6 +258,7 @@
|
||||
$totalupah = 0;
|
||||
$totalujrah = 0;
|
||||
$totalonepercent = 0;
|
||||
$totaldiskaunrebate = 0;
|
||||
@endphp
|
||||
|
||||
@foreach ($tebus as $index => $item)
|
||||
@@ -266,6 +271,7 @@
|
||||
$totalnm += $item['nilaimarhun'];
|
||||
$totalujrah += $item['ujrah'];
|
||||
$totalonepercent += $item['onepercent'];
|
||||
$totaldiskaunrebate += $item['diskaun_rebate'] ?? 0;
|
||||
|
||||
@endphp
|
||||
|
||||
@@ -282,6 +288,7 @@
|
||||
$totalbayaranteller = 0;
|
||||
$totalujrahteller = 0;
|
||||
$totalonepercentteller = 0;
|
||||
$totaldiskaunrebateteller = 0;
|
||||
@endphp
|
||||
@endif
|
||||
@else
|
||||
@@ -297,6 +304,7 @@
|
||||
$totaljumlahteller = 0;
|
||||
$totalujrahteller = 0;
|
||||
$totalonepercentteller = 0;
|
||||
$totaldiskaunrebateteller = 0;
|
||||
@endphp
|
||||
@endif
|
||||
|
||||
@@ -336,6 +344,9 @@
|
||||
</td>
|
||||
<td>{{ number_format($item['ujrah'], 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>
|
||||
@php
|
||||
@@ -371,6 +382,7 @@
|
||||
$totalbayaranteller += $item['bakipjm']; //
|
||||
$totalujrahteller += $item['ujrah'];
|
||||
$totalonepercentteller += $item['onepercent'];
|
||||
$totaldiskaunrebateteller += $item['diskaun_rebate'] ?? 0;
|
||||
@endphp
|
||||
|
||||
@php
|
||||
@@ -388,6 +400,9 @@
|
||||
<td><b>{{ number_format($totalpinjamanteller, 2) }} </b></td>
|
||||
<td><b>{{ number_format($totalujrahteller, 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($totalbayaranteller, 2) }} </b></td>
|
||||
<td><b>{{ number_format($totaljumlahteller, 2) }} </b></td>
|
||||
@@ -402,6 +417,7 @@
|
||||
$totaljumlahteller = 0;
|
||||
$totalujrahteller = 0;
|
||||
$totalonepercentteller = 0;
|
||||
$totaldiskaunrebateteller = 0;
|
||||
@endphp
|
||||
@endif
|
||||
@else
|
||||
@@ -412,6 +428,9 @@
|
||||
<td><b>{{ number_format($totalpinjamanteller, 2) }} </b></td>
|
||||
<td><b>{{ number_format($totalujrahteller, 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($totalbayaranteller, 2) }} </b></td>
|
||||
<td><b>{{ number_format($totaljumlahteller, 2) }} </b></td>
|
||||
|
||||
@@ -652,6 +652,13 @@
|
||||
{{ number_format($penebusan['onepercent'] + $penebusan['ujrah'], 2) }}</strong>
|
||||
</td>
|
||||
</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>
|
||||
<td></td>
|
||||
</tr>
|
||||
@@ -885,6 +892,13 @@
|
||||
{{ number_format($penebusan['onepercent'] + $penebusan['ujrah'], 2) }}</strong>
|
||||
</td>
|
||||
</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>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
if (window.self !== window.top) {
|
||||
return;
|
||||
}
|
||||
window.print();
|
||||
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>
|
||||
$(document).ready(function() {
|
||||
if (window.self !== window.top) {
|
||||
return;
|
||||
}
|
||||
window.print();
|
||||
window.onfocus = function() {
|
||||
window.close();
|
||||
@@ -2109,6 +2112,22 @@
|
||||
</div>
|
||||
<div class="main-page" style="font-family: Arial, Helvetica, sans-serif;">
|
||||
<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')✓@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')✓@endif</span>
|
||||
KREDIT KE AKAUN
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{{-- HEADER SECTION --}}
|
||||
<table style="width: 100%; margin-top: 0;">
|
||||
<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,6 +15,8 @@ use Illuminate\Support\Facades\Storage;
|
||||
|
|
||||
*/
|
||||
|
||||
Route::post('/internal/notifications', ['as' => 'internal.notifications.store', 'uses' => 'InternalNotificationController@store']);
|
||||
|
||||
Route::get('/health', function () {
|
||||
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('/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/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('/serahan',['as'=>'khazanah.serahan','uses'=>'KhazanahController@serahan'])->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('/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
|
||||
Route::get('/pembiayaan-online/pc',['as'=>'pembiayaan.online.pc','uses'=>'PembiayaanOnlineController@index'])->middleware('auth','khazanah');
|
||||
Route::get('/pembiayaan-online/pc/reminder',['as'=>'pembiayaan.online.pc.reminder','uses'=>'PembiayaanOnlineController@reminderStatus'])->middleware('auth','khazanah');
|
||||
Route::put('/pembiayaan-online/pc/lines/{id}',['as'=>'pembiayaan.online.pc.update','uses'=>'PembiayaanOnlineController@updateLine'])->middleware('auth','khazanah');
|
||||
Route::post('/pembiayaan-online/pc/lines/{id}/verify',['as'=>'pembiayaan.online.pc.verify','uses'=>'PembiayaanOnlineController@verifyLine'])->middleware('auth','khazanah');
|
||||
Route::post('/pembiayaan-online/pc/lines/{id}/unverify',['as'=>'pembiayaan.online.pc.unverify','uses'=>'PembiayaanOnlineController@unverifyLine'])->middleware('auth','khazanah');
|
||||
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::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');
|
||||
@@ -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}/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::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::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');
|
||||
@@ -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/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/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-anggota',['as'=>'operasi.createkelulusan-anggota','uses'=>'OperasiController@createkelulusanAnggota'])->middleware('auth','teller');
|
||||
Route::get('/operasi/getkelulusan',['as'=>'operasi.getkelulusan','uses'=>'OperasiController@getkelulusan'])->middleware('auth','teller');
|
||||
|
||||
Reference in New Issue
Block a user