Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 934fe839e2 | |||
| 1138e193ae | |||
| 464d3e8dbb | |||
| abccad3219 | |||
| 7ad3fd4f86 | |||
| 713d5a4923 | |||
| b76566a2d6 | |||
| 38f6db2e10 | |||
| f95bdf1214 | |||
| d614677601 | |||
| 7cd21ef610 | |||
| ca2a02e354 | |||
| 76176ed453 | |||
| 17a5bfafdd | |||
| 5f29f064a0 | |||
| 9dae32a462 | |||
| c437a3f504 | |||
| b41234e10b | |||
| b7d217948c | |||
| 2d282897fc | |||
| 94f8f6e855 | |||
| 72108d3f87 | |||
| 3a64cc2400 | |||
| 1d71a31078 | |||
| 8e19d0488a | |||
| 918502b504 | |||
| 8a2bae7a88 | |||
| 67af37a1cd | |||
| 3f6fb8d985 | |||
| 371e4d8168 |
@@ -1,19 +1,19 @@
|
||||
APP_NAME=Laravel
|
||||
APP_ENV=local
|
||||
APP_ENV=production
|
||||
APP_KEY=base64:FFRogE6mJj8FaDSSHl/KpXpzqi2M+P4plxVSRgVM8JI=
|
||||
APP_DEBUG=true
|
||||
APP_URL=http://kaunterv2.localhost
|
||||
API_URL=http://apiv2.localhost
|
||||
API_ARRAHNBID=http://lelong.localhost/api/
|
||||
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=database_zone2
|
||||
DB_HOST=192.168.1.216
|
||||
DB_PORT=3306
|
||||
DB_DATABASE=kaunterpkb
|
||||
DB_USERNAME=root
|
||||
DB_PASSWORD=secret
|
||||
DB_DATABASE=kaunter_annur
|
||||
DB_USERNAME=spider
|
||||
DB_PASSWORD=arrahn@123
|
||||
|
||||
BROADCAST_DRIVER=pusher
|
||||
CACHE_DRIVER=file
|
||||
@@ -47,4 +47,4 @@ 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
|
||||
+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
|
||||
+6
-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,8 @@ 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=
|
||||
|
||||
LOGVIEWER_PASSWORD=
|
||||
|
||||
+12
-9
@@ -1,19 +1,20 @@
|
||||
APP_NAME=Laravel
|
||||
APP_ENV=local
|
||||
APP_ENV=production
|
||||
APP_VERSION=v3.3
|
||||
APP_KEY=base64:WRTHcFTq5YARKVdA8YDUzsh9Ue8vD63ivgZgXNNBiGo=
|
||||
APP_DEBUG=true
|
||||
APP_URL=http://ujrahkopkb.erahn.com.my
|
||||
API_URL=http://apiujrah-kopkb.erahn.com.my
|
||||
#API_ARRAHNBID=http://lelong.test/api/
|
||||
APP_DEBUG=false
|
||||
APP_URL=https://ujrah.erahn.com.my
|
||||
API_URL=https://apiujrah.erahn.com.my
|
||||
|
||||
LOG_CHANNEL=stack
|
||||
LOGVIEWER_PASSWORD="$2y$10$k7eiUyZdm3BruwAaT1gbmelTm.PPgZptOEQT1nlBjyiR84a4KBMlm"
|
||||
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=127.0.0.1
|
||||
DB_HOST=192.168.1.216
|
||||
DB_PORT=3306
|
||||
DB_DATABASE=kaunter_erahn
|
||||
DB_USERNAME=spider
|
||||
DB_PASSWORD=G0dz!ll4
|
||||
DB_PASSWORD=arrahn@123
|
||||
|
||||
BROADCAST_DRIVER=pusher
|
||||
CACHE_DRIVER=file
|
||||
@@ -46,5 +47,7 @@ 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
|
||||
MYKAD_READER_PORT=17321
|
||||
MYKAD_READER_ENABLED_CAWANGAN=''
|
||||
|
||||
INTERNAL_NOTIFICATION_SECRET="nIDTe(u@6[)VLJyd"
|
||||
+5
-6
@@ -4,17 +4,14 @@ APP_VERSION=v2.2-staging
|
||||
APP_KEY=base64:O1XZDqK4H6+3GdrODgCaLr2l+OoWvgVhYr0GJ1KizW8=
|
||||
APP_DEBUG=true
|
||||
APP_URL=https://staging.erahn.com.my
|
||||
API_URL=https://api-staging.erahn.com.my
|
||||
|
||||
HTTP_PORT=8091
|
||||
MYSQL_PUBLISH_PORT=3309
|
||||
|
||||
API_URL=https://api-staging.erahn.com.my
|
||||
API_URL_KOOP=https://api-staging.erahn.com.my
|
||||
API_URL_PKB=https://api-staging.erahn.com.my
|
||||
API_ARRAHNBID=http://lelong.test/api/
|
||||
|
||||
LOG_CHANNEL=stack
|
||||
LOG_LEVEL=debug
|
||||
LOGVIEWER_PASSWORD=test123
|
||||
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=mysql
|
||||
@@ -55,4 +52,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=staging_noti_secret
|
||||
@@ -1,6 +1,9 @@
|
||||
name: Deploy to Staging
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
image_tag:
|
||||
@@ -18,10 +21,22 @@ jobs:
|
||||
runs-on: host
|
||||
|
||||
steps:
|
||||
- name: Resolve image tag
|
||||
id: resolve
|
||||
run: |
|
||||
set -e
|
||||
if [ "${{ gitea.event_name }}" = "workflow_dispatch" ]; then
|
||||
IMAGE_TAG="${{ inputs.image_tag }}"
|
||||
else
|
||||
IMAGE_TAG="${{ gitea.ref_name }}"
|
||||
fi
|
||||
echo "IMAGE_TAG=${IMAGE_TAG}" >> "$GITHUB_OUTPUT"
|
||||
echo "Deploying image tag: ${IMAGE_TAG}"
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.image_tag }}
|
||||
ref: ${{ steps.resolve.outputs.IMAGE_TAG }}
|
||||
|
||||
- name: Login to Docker registry
|
||||
run: |
|
||||
@@ -33,28 +48,35 @@ jobs:
|
||||
- name: Verify image exists in registry
|
||||
run: |
|
||||
set -e
|
||||
IMAGE_TAG="${{ inputs.image_tag }}"
|
||||
IMAGE_TAG="${{ steps.resolve.outputs.IMAGE_TAG }}"
|
||||
IMAGE="${APP_IMAGE}:${IMAGE_TAG}"
|
||||
|
||||
echo "Checking if image exists: ${IMAGE}"
|
||||
if ! docker manifest inspect "${IMAGE}" > /dev/null 2>&1; then
|
||||
echo "ERROR: Image ${IMAGE} does not exist in registry!"
|
||||
echo "Run the Build Docker Image workflow first."
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ ${IMAGE} found"
|
||||
# Tag pushes build in parallel — wait for the image to appear
|
||||
for i in $(seq 1 30); do
|
||||
if docker manifest inspect "${IMAGE}" > /dev/null 2>&1; then
|
||||
echo "✓ ${IMAGE} found"
|
||||
exit 0
|
||||
fi
|
||||
echo "Image not ready yet (attempt ${i}/30), waiting 20s..."
|
||||
sleep 20
|
||||
done
|
||||
|
||||
echo "ERROR: Image ${IMAGE} does not exist in registry after waiting!"
|
||||
echo "Run the Build Docker Image workflow first."
|
||||
exit 1
|
||||
|
||||
- name: Pull Docker image
|
||||
run: |
|
||||
set -e
|
||||
IMAGE_TAG="${{ inputs.image_tag }}"
|
||||
IMAGE_TAG="${{ steps.resolve.outputs.IMAGE_TAG }}"
|
||||
docker pull "${APP_IMAGE}:${IMAGE_TAG}"
|
||||
echo "✓ Image pulled successfully"
|
||||
|
||||
- name: Sync compose file and deploy
|
||||
run: |
|
||||
set -e
|
||||
IMAGE_TAG="${{ inputs.image_tag }}"
|
||||
IMAGE_TAG="${{ steps.resolve.outputs.IMAGE_TAG }}"
|
||||
|
||||
mkdir -p "${DEPLOY_DIR}/docker/nginx" "${DEPLOY_DIR}/public"
|
||||
cp docker-compose.staging.yml "${DEPLOY_DIR}/docker-compose.yml"
|
||||
@@ -62,15 +84,12 @@ jobs:
|
||||
find "${DEPLOY_DIR}/public" -mindepth 1 -delete
|
||||
cp -R public/. "${DEPLOY_DIR}/public/"
|
||||
|
||||
if [ ! -f "${DEPLOY_DIR}/.env.staging" ]; then
|
||||
if [ -f .env.staging ]; then
|
||||
cp .env.staging "${DEPLOY_DIR}/.env.staging"
|
||||
echo "Copied .env.staging from repo (first deploy). Edit ${DEPLOY_DIR}/.env.staging on the server if needed."
|
||||
else
|
||||
echo "ERROR: ${DEPLOY_DIR}/.env.staging is missing on the server"
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -f .env.staging ]; then
|
||||
echo "ERROR: .env.staging is missing in the repository"
|
||||
exit 1
|
||||
fi
|
||||
cp .env.staging "${DEPLOY_DIR}/.env.staging"
|
||||
echo "✓ Synced .env.staging from repo"
|
||||
|
||||
cd "${DEPLOY_DIR}"
|
||||
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -5,15 +5,12 @@ namespace App\Http\Controllers\Auth;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Providers\RouteServiceProvider;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Illuminate\Support\Facades\Redirect;
|
||||
use Illuminate\Foundation\Auth\AuthenticatesUsers;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
use App\Dashboard;
|
||||
use App\User;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class LoginController extends Controller
|
||||
{
|
||||
@@ -144,25 +141,26 @@ class LoginController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
public function logout(){
|
||||
$current = Carbon::now();
|
||||
$current = new Carbon();
|
||||
$current = $current->toDateString();
|
||||
public function logout(Request $request)
|
||||
{
|
||||
$auth_user = Auth::guard('web')->user();
|
||||
Auth::logout();
|
||||
Session::flush();
|
||||
$session = Http::withOptions(['verify' => config('app.api_verify')])->post(config('api.url') . '/api/logoutsession/' . $auth_user['cawangan'], [
|
||||
'id' => $auth_user['id'],
|
||||
'nama' => $auth_user['name'],
|
||||
'nokp' => $auth_user['no_kp'],
|
||||
'email' => $auth_user['email'],
|
||||
'taraf' => $auth_user['role_harian'],
|
||||
'tarikh' => $current,
|
||||
|
||||
|
||||
]);
|
||||
Auth::guard('web')->logout();
|
||||
$request->session()->invalidate();
|
||||
$request->session()->regenerateToken();
|
||||
|
||||
return Redirect::to('/');
|
||||
if ($auth_user) {
|
||||
Http::withOptions(['verify' => config('app.api_verify')])->post(config('api.url') . '/api/logoutsession/' . $auth_user['cawangan'], [
|
||||
'id' => $auth_user['id'],
|
||||
'nama' => $auth_user['name'],
|
||||
'nokp' => $auth_user['no_kp'],
|
||||
'email' => $auth_user['email'],
|
||||
'taraf' => $auth_user['role_harian'],
|
||||
'tarikh' => now()->toDateString(),
|
||||
]);
|
||||
}
|
||||
|
||||
return redirect('/');
|
||||
}
|
||||
|
||||
public function GetRolesHarian(Request $request, $kodcaw){
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
@@ -547,7 +680,11 @@ class GadaianController extends Controller
|
||||
$kuantiti = $gadai_detail['pinjaman'] / $komoditi['nilai'];
|
||||
|
||||
if ($gadai_detail['tagbaru'] == 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'));
|
||||
$cawangan_info = Http::withOptions(['verify' => config('app.api_verify')])
|
||||
->get($api_url . '/api/cawangan/' . $kodcaw)->json();
|
||||
$pembiayaanOnlineEnabled = !empty(($cawangan_info['features'] ?? [])['pembiayaan_online']);
|
||||
|
||||
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'))->with('pembiayaanOnlineEnabled', $pembiayaanOnlineEnabled);
|
||||
} else {
|
||||
return view('print.sag', compact('gadai_detail', 'customer_detail', 'marhun_detail', 'cawangan_detail', 'api_url', 'pembayaran_segera', 'auth_username', 'mutu', 'kakitangan'));
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
@@ -110,11 +156,36 @@ class PembiayaanOnlineController extends Controller
|
||||
'kodbank' => $request->input('kodbank'),
|
||||
'noakaun' => $request->input('noakaun'),
|
||||
'nama_bank' => $request->input('nama_bank'),
|
||||
'nama_akaun' => $request->input('nama_akaun'),
|
||||
'nota' => $request->input('nota'),
|
||||
'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';
|
||||
}
|
||||
|
||||
if ($request->hasFile('borang')) {
|
||||
$file = $request->file('borang');
|
||||
if (!$file->isValid()) {
|
||||
return redirect()->back()->with('error', 'Fail borang pengesahan tidak sah.');
|
||||
}
|
||||
if ($file->getSize() > 4 * 1024 * 1024) {
|
||||
return redirect()->back()->with('error', 'Saiz fail maksimum 4MB.');
|
||||
}
|
||||
$payload['borang_data'] = base64_encode(file_get_contents($file->getRealPath()));
|
||||
$payload['borang_filename'] = $file->getClientOriginalName();
|
||||
$payload['borang_type'] = $file->getMimeType() ?: 'application/octet-stream';
|
||||
}
|
||||
|
||||
$res = $this->api()
|
||||
->asForm()
|
||||
->put($api_url . '/api/pembiayaan-online/' . $kodcaw . '/lines/' . $id, $payload);
|
||||
@@ -125,7 +196,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 +378,138 @@ 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 uploaded Borang Pengesahan Penerimaan Pembiayaan Secara Atas Talian.
|
||||
*/
|
||||
public function viewBorang($id)
|
||||
{
|
||||
$this->abortUnlessPembiayaanOnline();
|
||||
return $this->streamLineFile(
|
||||
$this->kodcaw(),
|
||||
$id,
|
||||
'/borang',
|
||||
'Borang pengesahan tidak dijumpai.',
|
||||
'Fail borang pengesahan kosong.',
|
||||
'borang-pengesahan-' . $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 uploaded Borang Pengesahan Penerimaan Pembiayaan Secara Atas Talian.
|
||||
*/
|
||||
public function operasiViewBorang($kodcaw, $id)
|
||||
{
|
||||
return $this->streamLineFile(
|
||||
$kodcaw,
|
||||
$id,
|
||||
'/borang',
|
||||
'Borang pengesahan tidak dijumpai.',
|
||||
'Fail borang pengesahan kosong.',
|
||||
'borang-pengesahan-' . $id
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Operasi: view SAG for a batch line.
|
||||
*/
|
||||
public function operasiViewSag($kodcaw, $id)
|
||||
{
|
||||
return $this->renderLineSag($kodcaw, $id);
|
||||
}
|
||||
|
||||
protected function streamLineFile($kodcaw, $id, $path, $missingMessage, $emptyMessage, $fallbackName)
|
||||
{
|
||||
$res = $this->api()
|
||||
->get(config('api.url') . '/api/pembiayaan-online/' . $kodcaw . '/lines/' . $id . $path);
|
||||
|
||||
if (!$res->successful()) {
|
||||
$body = $res->json();
|
||||
$msg = is_array($body) && isset($body['message']) ? $body['message'] : $missingMessage;
|
||||
return redirect()->back()->with('error', $msg);
|
||||
}
|
||||
|
||||
$payload = $res->json();
|
||||
$file = $payload['file'] ?? null;
|
||||
if (!$file || empty($file['data'])) {
|
||||
return redirect()->back()->with('error', $emptyMessage);
|
||||
}
|
||||
|
||||
return $this->streamBase64File($file, $fallbackName);
|
||||
}
|
||||
|
||||
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 +517,119 @@ 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) {
|
||||
$cawangan_info = $this->api()
|
||||
->get($api_url . '/api/cawangan/' . $kodcaw)->json();
|
||||
$pembiayaanOnlineEnabled = !empty(($cawangan_info['features'] ?? [])['pembiayaan_online']);
|
||||
|
||||
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'
|
||||
))->with('pembiayaanOnlineEnabled', $pembiayaanOnlineEnabled);
|
||||
}
|
||||
|
||||
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 +662,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).');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -51,7 +51,7 @@ class WakilController extends Controller
|
||||
public function suratwakil(Request $request)
|
||||
{
|
||||
$noic = $request->noic;
|
||||
// $idtype = $request->customerIDType;
|
||||
$idtype = $request->customerIDType ?? 'nokp';
|
||||
Session::put('noic', $noic);
|
||||
$api_url = config('api.url');
|
||||
$auth_user = Auth::user();
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
|
||||
class LogViewerPassword
|
||||
{
|
||||
public function handle($request, Closure $next)
|
||||
{
|
||||
$passwordHash = config('logviewer.password');
|
||||
|
||||
if (!is_string($passwordHash) || $passwordHash === '') {
|
||||
return response('Akses log belum dikonfigurasi.', 503)
|
||||
->header('Cache-Control', 'no-store');
|
||||
}
|
||||
|
||||
$provided = $request->getPassword();
|
||||
if ($request->getUser() !== 'logviewer' || !is_string($provided) ||
|
||||
!password_verify($provided, $passwordHash)) {
|
||||
return response('Kata laluan diperlukan untuk mengakses log.', 401, [
|
||||
'WWW-Authenticate' => 'Basic realm="Log Viewer", charset="UTF-8"',
|
||||
'Cache-Control' => 'no-store',
|
||||
]);
|
||||
}
|
||||
|
||||
$response = $next($request);
|
||||
$response->headers->set('Cache-Control', 'no-store');
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -44,6 +44,12 @@ class RoleAccess
|
||||
return $next($request);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'masteradmin':
|
||||
if ($userRole == 'masteradmin') {
|
||||
return $next($request);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'penilai':
|
||||
if ($userRole == 'penilai') {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'password' => env('LOGVIEWER_PASSWORD'),
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Pattern and storage path settings
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
+3
-10
@@ -5,6 +5,8 @@ services:
|
||||
dockerfile: docker/php/Dockerfile
|
||||
target: development
|
||||
working_dir: /var/www/html
|
||||
ports:
|
||||
- "8081:8000"
|
||||
volumes:
|
||||
- ./:/var/www/html:cached
|
||||
environment:
|
||||
@@ -15,16 +17,7 @@ services:
|
||||
- "host.docker.internal:host-gateway"
|
||||
depends_on:
|
||||
- mysql
|
||||
|
||||
web:
|
||||
image: nginx:1.25-alpine
|
||||
ports:
|
||||
- "8081:80"
|
||||
volumes:
|
||||
- ./:/var/www/html:cached
|
||||
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
|
||||
depends_on:
|
||||
- app
|
||||
command: ["php", "artisan", "serve", "--host=0.0.0.0", "--port=8000"]
|
||||
|
||||
mysql:
|
||||
image: mysql:8.0
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
APP_NAME=Laravel
|
||||
APP_ENV=local
|
||||
APP_KEY=base64:FFRogE6mJj8FaDSSHl/KpXpzqi2M+P4plxVSRgVM8JI=
|
||||
APP_DEBUG=true
|
||||
APP_URL=http://kaunterv2.localhost
|
||||
API_URL=http://apiv2.kgmk.localhost
|
||||
API_ARRAHNBID=http://lelong.localhost/api/
|
||||
|
||||
LOG_CHANNEL=stack
|
||||
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=database_zone_franchise
|
||||
DB_PORT=3306
|
||||
DB_DATABASE=kaunter_koopguru
|
||||
DB_USERNAME=root
|
||||
DB_PASSWORD=secret
|
||||
|
||||
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
|
||||
@@ -1,50 +0,0 @@
|
||||
APP_NAME=Laravel
|
||||
APP_ENV=local
|
||||
APP_KEY=base64:FFRogE6mJj8FaDSSHl/KpXpzqi2M+P4plxVSRgVM8JI=
|
||||
APP_DEBUG=true
|
||||
APP_URL=http://kaunterv2.localhost
|
||||
API_URL=http://apiv2.krk.localhost
|
||||
API_ARRAHNBID=http://lelong.localhost/api/
|
||||
|
||||
LOG_CHANNEL=stack
|
||||
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=database_zone_franchise
|
||||
DB_PORT=3306
|
||||
DB_DATABASE=kaunter_krk
|
||||
DB_USERNAME=root
|
||||
DB_PASSWORD=secret
|
||||
|
||||
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
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 166 KiB |
@@ -559,7 +559,7 @@
|
||||
function cetaklaporanalirantunai()
|
||||
{
|
||||
let urlroute = '{{ route("laporan.alirantunai") }}';
|
||||
window.location.href = api_url + urlroute;
|
||||
window.location.href = urlroute;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -115,6 +115,9 @@
|
||||
</form>
|
||||
</div>
|
||||
<div class="card-footer text-center pt-0 px-lg-2 px-1">
|
||||
<p class="mb-3 text-sm">
|
||||
<a href="{{ route('dasar.privasi') }}" class="text-info font-weight-bold">Dasar Privasi</a>
|
||||
</p>
|
||||
<p class="mb-4 text-sm mx-auto">
|
||||
Masalah untuk log masuk?
|
||||
<a class="text-info text-gradient font-weight-bold">Hubungi
|
||||
|
||||
@@ -883,7 +883,7 @@
|
||||
|
||||
return leapYearCount;
|
||||
}
|
||||
var regex = /^[a-zA-Z0-9]+$/g;
|
||||
var regex = /^[A-Za-z0-9-]+$/;
|
||||
|
||||
function verifynokp(){
|
||||
var id_type = $('#customerIDType').val();
|
||||
|
||||
@@ -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'] }}";
|
||||
@@ -779,9 +801,12 @@
|
||||
}
|
||||
});
|
||||
|
||||
var pembiayaanOnlineEnabled = @json(!empty(($cawangan_info['features'] ?? [])['pembiayaan_online']));
|
||||
|
||||
var tablegadai = initErahnDatatable('#myTable', {
|
||||
order: [[1, 'desc']],
|
||||
columnDefs: [
|
||||
{ targets: 2, visible: pembiayaanOnlineEnabled, className: pembiayaanOnlineEnabled ? '' : 'no-export' },
|
||||
{ targets: 0, orderable: false, width: '40px' },
|
||||
{ targets: -1, orderable: false, searchable: false, width: '70px' }
|
||||
]
|
||||
@@ -789,6 +814,7 @@
|
||||
|
||||
var xtawarruq = initErahnDatatable('#xtawarruq', {
|
||||
columnDefs: [
|
||||
{ targets: 2, visible: pembiayaanOnlineEnabled, className: pembiayaanOnlineEnabled ? '' : 'no-export' },
|
||||
{ targets: 0, orderable: false, width: '40px' }
|
||||
]
|
||||
});
|
||||
@@ -817,6 +843,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 +1163,13 @@
|
||||
text: 'Pembiayaan Sedang Dilelong..'
|
||||
});
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Amaran',
|
||||
text: 'Tidak dapat semak status lelong. Sila cuba semula.'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1198,13 +1235,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 +1254,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 +1295,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']),
|
||||
@@ -2031,7 +2071,8 @@
|
||||
harini.setHours(0, 0, 0, 0);
|
||||
|
||||
if ($("input[name='options']:checked").val() == 'belum_tebus') {
|
||||
let test1 = $('input[name="belum"]:checked').closest('tr').children('td:eq(2)').text();
|
||||
let selectedRow = $('input[name="belum"]:checked').closest('tr');
|
||||
let test1 = selectedRow.length ? selectedRow.closest('table').DataTable().row(selectedRow).data()[3] : '';
|
||||
let tarikhgadai2 = new Date(test1);
|
||||
tarikhgadai2.setHours(0, 0, 0, 0);
|
||||
if (+harini !== +tarikhgadai2) {
|
||||
@@ -2448,7 +2489,8 @@
|
||||
}
|
||||
|
||||
if ($("input[name='options']:checked").val() == 'belum_tebus') {
|
||||
let test1 = $('input[name="belum"]:checked').closest('tr').children('td:eq(2)').text();
|
||||
let selectedRow = $('input[name="belum"]:checked').closest('tr');
|
||||
let test1 = selectedRow.length ? selectedRow.closest('table').DataTable().row(selectedRow).data()[3] : '';
|
||||
let tarikhgadai2 = new Date(test1);
|
||||
tarikhgadai2.setHours(0, 0, 0, 0);
|
||||
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ms">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Dasar Privasi | Kaunter arOS</title>
|
||||
<link rel="icon" href="{{ asset('img/icon_pkb.png') }}">
|
||||
<link href="{{ asset('css/login-v2.css') }}" rel="stylesheet">
|
||||
<style>
|
||||
body { background: #f4f7fb; color: #344767; }
|
||||
.privacy-container { max-width: 900px; margin: 0 auto; padding: 32px 20px; }
|
||||
.privacy-brand { display: flex; align-items: center; gap: 16px; margin-bottom: 24px; }
|
||||
.privacy-brand img { width: 60px; height: auto; }
|
||||
.privacy-card { background: #fff; border-radius: 16px; padding: 40px; box-shadow: 0 8px 32px rgba(24, 49, 83, .08); }
|
||||
.privacy-card h1 { font-size: 2rem; }
|
||||
.privacy-card h2 { font-size: 1.2rem; margin-top: 28px; }
|
||||
.privacy-card p, .privacy-card li { line-height: 1.8; }
|
||||
.privacy-card ul { padding-left: 24px; }
|
||||
.privacy-back { display: inline-block; margin-top: 24px; color: #075985; text-decoration: underline; }
|
||||
a:focus-visible { outline: 3px solid #075985; outline-offset: 4px; }
|
||||
@media (max-width: 576px) {
|
||||
.privacy-container { padding: 20px 16px; }
|
||||
.privacy-card { padding: 24px 20px; }
|
||||
.privacy-card h1 { font-size: 1.7rem; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main class="privacy-container">
|
||||
<div class="privacy-brand">
|
||||
<img src="{{ asset('img/icon_pkb.png') }}" alt="Logo ar-Rahnu">
|
||||
<span class="font-weight-bold">Kaunter arOS · ar-rahn Online System</span>
|
||||
</div>
|
||||
<article class="privacy-card" aria-labelledby="privacy-title">
|
||||
<h1 id="privacy-title">Dasar Privasi</h1>
|
||||
<p class="text-sm text-muted">Versi dasar: 1.0 · Tarikh kemas kini: <time datetime="2026-09-27">27 September 2026</time></p>
|
||||
<h2>1. Pengenalan</h2>
|
||||
<p>Selamat datang ke sistem Kaunter arOS Koperasi Permodalan Kelantan Berhad (KoPKB). Kami amat menghargai kepercayaan yang anda berikan untuk mengendalikan maklumat peribadi anda. Dasar Privasi ini digubal untuk membantu anda memahami bagaimana kami mengumpul, menggunakan, mendedahkan dan melindungi data peribadi anda bagi urusan Ar-Rahn, dengan mengambil kira keperluan Akta Perlindungan Data Peribadi 2010 (PDPA) dan amalan keselamatan maklumat yang berkaitan.</p>
|
||||
<p>Dasar ini terpakai kepada kakitangan, pentadbir dan mana-mana pihak yang diberi kuasa untuk mengakses atau menggunakan sistem Kaunter arOS KoPKB, serta pengendalian data pelanggan dalam perkhidmatan yang diuruskan melalui sistem ini.</p>
|
||||
|
||||
<h2>2. Data yang Dikumpul</h2>
|
||||
<p>Kami mengumpul dan memproses maklumat yang berkaitan dengan urusan sistem, termasuk:</p>
|
||||
<ul>
|
||||
<li><strong>Maklumat Pengenalan Peribadi:</strong> Nama penuh, nombor pengenalan, alamat, alamat e-mel dan nombor telefon pelanggan atau pengguna.</li>
|
||||
<li><strong>Maklumat Akaun:</strong> Maklumat akaun pengguna, cawangan dan peranan akses dalam sistem.</li>
|
||||
<li><strong>Data Teknikal:</strong> Maklumat seperti alamat IP, jenis pelayar dan log aktiviti sistem yang direkodkan bagi tujuan operasi dan keselamatan.</li>
|
||||
<li><strong>Data Transaksi:</strong> Rekod pembiayaan, gadaian, maklumat marhun, pembayaran, penebusan, lelongan dan dokumen sokongan berkaitan.</li>
|
||||
</ul>
|
||||
|
||||
<h2>3. Tujuan Penggunaan Data</h2>
|
||||
<p>Data anda digunakan untuk tujuan berikut:</p>
|
||||
<ul>
|
||||
<li><strong>Operasi Sistem:</strong> Menguruskan akaun pengguna dan memproses urusan pelanggan serta transaksi Ar-Rahn.</li>
|
||||
<li><strong>Pengesahan:</strong> Mengesahkan identiti dan mengawal akses pengguna mengikut peranan serta cawangan yang dibenarkan.</li>
|
||||
<li><strong>Komunikasi:</strong> Menyampaikan notis berkaitan urusan, makluman pentadbiran dan sokongan pelanggan.</li>
|
||||
<li><strong>Pelaporan dan Penambahbaikan:</strong> Menyediakan laporan operasi, menyokong audit serta menambah baik prestasi dan kualiti sistem.</li>
|
||||
</ul>
|
||||
|
||||
<h2>4. Perkongsian & Pendedahan Data</h2>
|
||||
<p>Kami tidak menjual data peribadi anda. Akses, perkongsian atau pendedahan data dihadkan kepada keperluan urusan yang berkaitan, termasuk:</p>
|
||||
<ul>
|
||||
<li><strong>Kakitangan yang Diberi Kuasa:</strong> Kakitangan dan pentadbir KoPKB yang memerlukan maklumat untuk menjalankan tugas.</li>
|
||||
<li><strong>Pembekal Perkhidmatan:</strong> Penyedia teknologi atau perkhidmatan yang menyokong pengehosan, penyelenggaraan sistem dan pemprosesan pembayaran, jika berkaitan.</li>
|
||||
<li><strong>Pematuhan Undang-undang:</strong> Pihak berkuasa atau pihak lain apabila pendedahan diperlukan oleh undang-undang atau perintah mahkamah.</li>
|
||||
</ul>
|
||||
|
||||
<h2>5. Keselamatan Data</h2>
|
||||
<p>Langkah keselamatan teknikal dan pentadbiran digunakan untuk membantu melindungi data yang dikendalikan melalui sistem, termasuk kawalan akses mengikut peranan pengguna dan pengesahan akaun.</p>
|
||||
<p>Pengguna hendaklah mengakses sistem melalui sambungan HTTPS yang selamat, menjaga kerahsiaan kata laluan, tidak berkongsi akaun dan log keluar selepas selesai menggunakan sistem. Sebarang aktiviti yang mencurigakan hendaklah dilaporkan kepada pentadbir sistem.</p>
|
||||
|
||||
<h2>6. Hak Pengguna</h2>
|
||||
<p>Anda boleh menghubungi kami untuk mengemukakan permohonan berkaitan data peribadi anda, termasuk:</p>
|
||||
<ul>
|
||||
<li><strong>Akses & Pembetulan:</strong> Memohon semakan dan pembetulan maklumat peribadi yang tidak tepat atau tidak terkini.</li>
|
||||
<li><strong>Pemadaman:</strong> Memohon pemadaman akaun atau data dalam keadaan yang sesuai, tertakluk kepada keperluan penyimpanan rekod transaksi, audit dan undang-undang.</li>
|
||||
<li><strong>Sekatan:</strong> Memohon supaya pemprosesan data tertentu dihadkan, tertakluk kepada keperluan perkhidmatan dan kewajipan yang terpakai.</li>
|
||||
</ul>
|
||||
<p>Permohonan akan dinilai mengikut keperluan yang terpakai. Pengesahan identiti mungkin diperlukan sebelum permohonan diproses.</p>
|
||||
|
||||
<h2>7. Tempoh Penyimpanan</h2>
|
||||
<p>Data disimpan selama diperlukan untuk pengendalian urusan Ar-Rahn, penyelenggaraan rekod pembiayaan dan transaksi, audit serta pematuhan undang-undang. Selepas keperluan penyimpanan berakhir, data akan diuruskan untuk pemadaman atau penyahkenalpastian mengikut prosedur organisasi yang terpakai.</p>
|
||||
|
||||
<h2>8. Kuki & Teknologi Penjejakan</h2>
|
||||
<p>Sistem Kaunter arOS menggunakan kuki (cookies) dan sesi untuk menyokong log masuk, mengekalkan sesi pengguna dan melindungi permintaan dalam sistem. Anda boleh menguruskan pilihan kuki melalui tetapan pelayar. Menyekat kuki boleh menyebabkan fungsi tertentu, termasuk log masuk, tidak berfungsi dengan baik.</p>
|
||||
|
||||
<h2>9. Perubahan Dasar</h2>
|
||||
<p>Dasar ini boleh dikemas kini dari semasa ke semasa apabila terdapat perubahan pada perkhidmatan, pengendalian sistem atau keperluan organisasi. Versi terkini akan dipaparkan pada halaman ini. Sebarang perubahan besar akan dimaklumkan melalui e-mel atau notis dalam sistem sebelum tarikh kuat kuasa baharu.</p>
|
||||
|
||||
<h2>10. Hubungi Kami</h2>
|
||||
<p>Jika anda mempunyai pertanyaan, komen atau permohonan berkaitan dasar ini dan pengendalian data peribadi dalam sistem Kaunter arOS, sila hubungi:</p>
|
||||
<p><strong>AFRINA ALIMI</strong><br>
|
||||
Koperasi Permodalan Kelantan Berhad (KoPKB)<br>
|
||||
E-mel: <a href="mailto:afrina@koppkb.com">afrina@koppkb.com</a>
|
||||
</p>
|
||||
|
||||
<a class="privacy-back" href="{{ route('login') }}">Kembali ke Log Masuk</a>
|
||||
</article>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -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'));
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -185,6 +185,7 @@
|
||||
<th>Keuntungan</th>
|
||||
<th>Lelong</th>
|
||||
<th>Pembayaran Terakhir</th>
|
||||
<th>Tarikh Ansuran Terkini</th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
@@ -204,6 +205,7 @@
|
||||
<th>Keuntungan</th>
|
||||
<th>Lelong</th>
|
||||
<th>Pembayaran Terakhir</th>
|
||||
<th>Tarikh Ansuran Terkini</th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
@@ -470,7 +472,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'));
|
||||
|
||||
|
||||
|
||||
@@ -634,6 +636,45 @@
|
||||
};
|
||||
}
|
||||
|
||||
function tarikhAnsuranTerkini(transactions)
|
||||
{
|
||||
if (!Array.isArray(transactions)) {
|
||||
throw new Error('Invalid transaction response');
|
||||
}
|
||||
return transactions.reduce(function(latest, transaction) {
|
||||
if (transaction.trans_type !== 'A') return latest;
|
||||
var date = String(transaction.created_at || '').slice(0, 10);
|
||||
if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) return latest;
|
||||
return date > latest ? date : latest;
|
||||
}, '') || 'Tiada bayaran';
|
||||
}
|
||||
|
||||
function muatTarikhAnsuranTerkini(row, norujukan)
|
||||
{
|
||||
var originalData = row.data();
|
||||
function update(value) {
|
||||
// Ignore responses for rows removed when the customer/table changes.
|
||||
if (row.data() !== originalData) return;
|
||||
var updatedData = originalData.slice();
|
||||
updatedData[11] = value;
|
||||
row.data(updatedData).draw(false);
|
||||
}
|
||||
$.ajax({
|
||||
method: 'get',
|
||||
url: api_url + '/api/transaction/' + encodeURIComponent(norujukan),
|
||||
success: function(transactions) {
|
||||
try {
|
||||
update(tarikhAnsuranTerkini(transactions));
|
||||
} catch (error) {
|
||||
update('Gagal dimuatkan');
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
update('Gagal dimuatkan');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function gadaiTable(data,jenistebus){
|
||||
|
||||
var counter = 1;
|
||||
@@ -665,7 +706,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",
|
||||
@@ -685,8 +734,8 @@
|
||||
let table_norujukan = gadai_detail["norujukan"];
|
||||
// let radio_action = "onclick=checkCanChangeGadaianDetail(" + "'" + table_norujukan + "'" + ")";
|
||||
|
||||
if(gadai_detail["percentpinj"] != 0)
|
||||
xtawarruq.row.add([
|
||||
if(gadai_detail["percentpinj"] != 0) {
|
||||
var ansuranRow = xtawarruq.row.add([
|
||||
'<input type="radio" value='+ gadai_detail["norujukan"] + ' name="belum"/>',
|
||||
// '<a href="#" onclick="checkserah(this)" data-pinjaman='+ gadai_detail['pinjaman'] +' data-hargajualan='+ gadai_detail['hargajualan'] +' data-norujukan='+ gadai_detail['norujukan'] +' data-url=' + urlroute +'>' + gadai_detail['norujukan'] + '</a>',
|
||||
(function() {
|
||||
@@ -716,15 +765,24 @@
|
||||
let color = bayaranInfo.blocked ? 'red' : 'green';
|
||||
return '<div style="white-space: nowrap; color: ' + color + '; font-weight: bold;">' +
|
||||
bayaranInfo.tarikh + '</div>';
|
||||
})()
|
||||
})(),
|
||||
'Memuatkan...'
|
||||
]).draw();
|
||||
muatTarikhAnsuranTerkini(ansuranRow, gadai_detail['norujukan']);
|
||||
}
|
||||
}else{
|
||||
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",
|
||||
@@ -743,7 +801,7 @@
|
||||
|
||||
let table_norujukan = gadai_detail["norujukan"];
|
||||
// let radio_action = "onclick=checkCanChangeGadaianDetail(" + "'" + table_norujukan + "'" + ")";
|
||||
tablegadai.row.add([
|
||||
var ansuranRow = tablegadai.row.add([
|
||||
'<input type="radio" value='+ gadai_detail["norujukan"] + ' name="belum"/>',
|
||||
// '<a href="#" onclick="checkserah(this)" data-pinjaman='+ gadai_detail['pinjaman'] +' data-hargajualan='+ gadai_detail['hargajualan'] +' data-norujukan='+ gadai_detail['norujukan'] +' data-url=' + urlroute +'>' + gadai_detail['norujukan'] + '</a>',
|
||||
(function() {
|
||||
@@ -773,8 +831,10 @@
|
||||
let color = bayaranInfo.blocked ? 'red' : 'green';
|
||||
return '<div style="white-space: nowrap; color: ' + color + '; font-weight: bold;">' +
|
||||
bayaranInfo.tarikh + '</div>';
|
||||
})()
|
||||
})(),
|
||||
'Memuatkan...'
|
||||
]).draw();
|
||||
muatTarikhAnsuranTerkini(ansuranRow, gadai_detail['norujukan']);
|
||||
}
|
||||
}else if(jenistebus == "sudah_tebus"){
|
||||
|
||||
|
||||
@@ -198,7 +198,7 @@
|
||||
|
||||
return leapYearCount;
|
||||
}
|
||||
var regex = /^[a-zA-Z0-9]+$/g;
|
||||
var regex = /^[A-Za-z0-9-]+$/;
|
||||
|
||||
function verifynokp(){
|
||||
var id_type = $('#customerIDType').val();
|
||||
|
||||
@@ -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,137 @@
|
||||
@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', '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-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
|
||||
@if(!empty($line['borang_file_id']))
|
||||
<a class="btn btn-sm btn-outline-primary"
|
||||
href="{{ route('pembiayaan.online.operasi.borang', ['kodcaw' => $kodcaw, 'id' => $line['id']]) }}"
|
||||
target="_blank">
|
||||
Borang Pengesahan
|
||||
</a>
|
||||
@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 +221,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 +236,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 +257,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 +277,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 +285,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 +317,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 +326,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,30 @@
|
||||
<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
|
||||
@if(!empty($line['borang_file_id']))
|
||||
<a class="btn btn-sm btn-outline-primary"
|
||||
href="{{ route('pembiayaan.online.pc.borang', ['id' => $line['id']]) }}"
|
||||
target="_blank">
|
||||
Borang Pengesahan
|
||||
</a>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
@@ -62,4 +87,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,15 +255,42 @@
|
||||
<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-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'] ?? '' }}"
|
||||
data-borang-file="{{ $line['borang_file_id'] ?? '' }}">
|
||||
Semak
|
||||
</button>
|
||||
@endif
|
||||
<button type="button" class="btn btn-sm btn-info btn-edit"
|
||||
data-id="{{ $line['id'] }}"
|
||||
data-kodbank="{{ $line['kodbank'] ?? '' }}"
|
||||
data-noakaun="{{ $line['noakaun'] ?? '' }}"
|
||||
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'] ?? '' }}"
|
||||
data-borang-file="{{ $line['borang_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,57 @@
|
||||
<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>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>Borang Pengesahan Penerimaan Pembiayaan Secara Atas Talian (Online)</label>
|
||||
<input type="file" name="borang" id="editBorang" class="form-control-file"
|
||||
accept=".pdf,.jpg,.jpeg,.png,.gif">
|
||||
<div id="editBorangExisting" class="mt-2" style="display:none;">
|
||||
<a href="#" id="editBorangLink" class="btn btn-sm btn-outline-primary" target="_blank">
|
||||
Lihat borang sedia ada
|
||||
</a>
|
||||
</div>
|
||||
<small class="text-muted">PDF / imej borang yang ditandatangani pelanggan (maks 4MB). Kosongkan jika tidak mahu ganti fail sedia ada.</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 +434,179 @@
|
||||
</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>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 mb-2" target="_blank" style="display:none;">
|
||||
Lihat Bukti Akaun Bank
|
||||
</a>
|
||||
<a href="#" id="verifyBorangLink" class="btn btn-sm btn-outline-primary" target="_blank" style="display:none;">
|
||||
Lihat Borang Pengesahan
|
||||
</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') || '-');
|
||||
$('#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();
|
||||
}
|
||||
if ($(this).data('borangFile')) {
|
||||
$('#verifyBorangLink').attr('href', "{{ url('/pembiayaan-online/pc/lines') }}/" + id + "/borang").show();
|
||||
} else {
|
||||
$('#verifyBorangLink').hide();
|
||||
}
|
||||
$('#verifyModal').modal('show');
|
||||
});
|
||||
|
||||
$('#verifyModal').on('hidden.bs.modal', function () {
|
||||
$('#verifySagFrame').attr('src', 'about:blank');
|
||||
});
|
||||
|
||||
$('.btn-edit').on('click', function () {
|
||||
@@ -291,14 +615,59 @@
|
||||
$('#editNorujukan').text($(this).data('norujukan'));
|
||||
$('#editKodbank').val($(this).data('kodbank'));
|
||||
$('#editNoakaun').val($(this).data('noakaun'));
|
||||
$('#editNamaakaun').val($(this).data('namaakaun'));
|
||||
$('#editNamabank').val($(this).data('namabank'));
|
||||
$('#editNota').val($(this).data('nota'));
|
||||
$('#editBuktiAkaun').val('');
|
||||
$('#editBorang').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();
|
||||
}
|
||||
if ($(this).data('borangFile')) {
|
||||
$('#editBorangLink').attr('href', "{{ url('/pembiayaan-online/pc/lines') }}/" + id + "/borang");
|
||||
$('#editBorangExisting').show();
|
||||
} else {
|
||||
$('#editBorangExisting').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">
|
||||
@@ -259,7 +258,7 @@
|
||||
<th style="width:13%">No. KP</th>
|
||||
<th style="width:11%">Bank</th>
|
||||
<th style="width:13%">No. Akaun</th>
|
||||
<th>Nama Akaun</th>
|
||||
<th>No. Telefon</th>
|
||||
<th style="width:12%">Pinjaman (RM)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -272,7 +271,7 @@
|
||||
<td class="num">{{ $line['nokp'] ?? '-' }}</td>
|
||||
<td>{{ $line['nama_bank'] ?? ($line['kodbank'] ?? '-') }}</td>
|
||||
<td class="num">{{ $line['noakaun'] ?? '-' }}</td>
|
||||
<td>{{ $line['nama_akaun'] ?? '-' }}</td>
|
||||
<td>{{ !empty($line['telefon']) ? $line['telefon'] : '-' }}</td>
|
||||
<td class="amt">{{ number_format((float) ($line['pinjaman'] ?? 0), 2) }}</td>
|
||||
</tr>
|
||||
@empty
|
||||
@@ -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>
|
||||
@@ -307,7 +306,7 @@
|
||||
|
||||
<div class="signs">
|
||||
<div class="sign-box">
|
||||
<div class="role">Tandatangan Operasi</div>
|
||||
<div class="role">Disemak oleh</div>
|
||||
<div class="sign-line">
|
||||
<p>Nama: {{ $opsBy }}</p>
|
||||
<p>Jawatan: Operasi</p>
|
||||
@@ -315,21 +314,14 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="sign-box">
|
||||
<div class="role">Pengurus Besar</div>
|
||||
<div class="role">Disahkan oleh</div>
|
||||
<div class="sign-line">
|
||||
<p>Nama:</p>
|
||||
<p>Cop / Paraf:</p>
|
||||
<p>Jawatan:</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,24 @@
|
||||
</div>
|
||||
<div class="main-page" style="font-family: Arial, Helvetica, sans-serif;">
|
||||
<div class="sub-page" style="font-size: 11px;">
|
||||
@if ($pembiayaanOnlineEnabled ?? false)
|
||||
@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>
|
||||
@endif
|
||||
{{-- 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>
|
||||
+26
-1
@@ -15,6 +15,10 @@ use Illuminate\Support\Facades\Storage;
|
||||
|
|
||||
*/
|
||||
|
||||
Route::post('/internal/notifications', ['as' => 'internal.notifications.store', 'uses' => 'InternalNotificationController@store']);
|
||||
|
||||
Route::view('/dasar-privasi', 'dasar-privasi')->name('dasar.privasi');
|
||||
|
||||
Route::get('/health', function () {
|
||||
return response()->json(['status' => 'ok']);
|
||||
});
|
||||
@@ -59,7 +63,13 @@ Route::get('test', function () {
|
||||
return "event test je";
|
||||
});
|
||||
|
||||
Route::get('logs', '\Rap2hpoutre\LaravelLogViewer\LogViewerController@index');
|
||||
Route::get('logs', '\Rap2hpoutre\LaravelLogViewer\LogViewerController@index')
|
||||
->middleware([
|
||||
'auth',
|
||||
'role:admin,masteradmin,operasi',
|
||||
'throttle:10,1',
|
||||
\App\Http\Middleware\LogViewerPassword::class,
|
||||
]);
|
||||
Route::get('change-password', ['as'=>'user.password','uses'=>'ChangePasswordController@index']);
|
||||
Route::post('change-password', ['as'=>'change.password','uses'=>'ChangePasswordController@store']);
|
||||
|
||||
@@ -174,6 +184,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 +201,19 @@ 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}/borang',['as'=>'pembiayaan.online.pc.borang','uses'=>'PembiayaanOnlineController@viewBorang'])->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 +238,9 @@ 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}/borang',['as'=>'pembiayaan.online.operasi.borang','uses'=>'PembiayaanOnlineController@operasiViewBorang'])->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 +425,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');
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Http\Middleware\LogViewerPassword;
|
||||
use Illuminate\Http\Request;
|
||||
use Tests\TestCase;
|
||||
|
||||
class LogViewerPasswordTest extends TestCase
|
||||
{
|
||||
public function test_missing_configuration_denies_access()
|
||||
{
|
||||
config(['logviewer.password' => null]);
|
||||
$response = $this->checkAccess();
|
||||
$this->assertSame(503, $response->getStatusCode());
|
||||
}
|
||||
|
||||
public function test_missing_or_incorrect_credentials_are_rejected()
|
||||
{
|
||||
config(['logviewer.password' => password_hash('test-only-secret', PASSWORD_DEFAULT)]);
|
||||
foreach ([[], ['PHP_AUTH_USER' => 'logs', 'PHP_AUTH_PW' => 'wrong'],
|
||||
['PHP_AUTH_USER' => 'other', 'PHP_AUTH_PW' => 'test-only-secret']] as $server) {
|
||||
$response = $this->checkAccess($server);
|
||||
$this->assertSame(401, $response->getStatusCode());
|
||||
$this->assertStringContainsString('Basic realm=', $response->headers->get('WWW-Authenticate'));
|
||||
}
|
||||
}
|
||||
|
||||
public function test_correct_credentials_allow_access_without_caching()
|
||||
{
|
||||
config(['logviewer.password' => password_hash('test-only-secret', PASSWORD_DEFAULT)]);
|
||||
$response = $this->checkAccess(['PHP_AUTH_USER' => 'logs', 'PHP_AUTH_PW' => 'test-only-secret']);
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$this->assertSame('allowed', $response->getContent());
|
||||
$this->assertStringContainsString('no-store', $response->headers->get('Cache-Control'));
|
||||
}
|
||||
|
||||
public function test_logs_route_has_auth_role_password_and_throttle_middleware()
|
||||
{
|
||||
$route = app('router')->getRoutes()->match(Request::create('/logs'));
|
||||
$middleware = $route->gatherMiddleware();
|
||||
$this->assertContains('auth', $middleware);
|
||||
$this->assertContains('role:admin,masteradmin', $middleware);
|
||||
$this->assertContains(LogViewerPassword::class, $middleware);
|
||||
$this->assertContains('throttle:10,1', $middleware);
|
||||
}
|
||||
|
||||
private function checkAccess(array $server = [])
|
||||
{
|
||||
$request = Request::create('/logs?dl=example', 'GET', [], [], [], $server);
|
||||
return (new LogViewerPassword)->handle($request, function () {
|
||||
return response('allowed');
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user