12 Commits

Author SHA1 Message Date
ISMAIL MASSERAN 834556b65d DONE: add export customer info feature, fix floating point issue in komuditi 2026-09-06 12:39:32 +08:00
ISMAIL MASSERAN 9fbcf86325 DONE: optimize loading page on employee, append the online payment under same batch 2026-09-06 10:21:04 +08:00
ISMAIL MASSERAN effe268e3b DONE: confirmation and list online form 2026-09-03 15:34:52 +08:00
ISMAIL MASSERAN 6f4507dc08 DONE: store mykad image in database 2026-09-03 11:29:20 +08:00
ISMAIL MASSERAN 4bf2de1e74 DONE: use acs mykad reader, fix trailing issue, duplicated line 2026-09-02 11:59:44 +08:00
ISMAIL MASSERAN 541a094ce1 DONE: fix error on nginx, add version on env, enable impersonate on staging 2026-09-02 11:59:44 +08:00
ISMAIL MASSERAN 00f26c2c99 DONE: request online modal from pc to operasi, operasi can edit amount; WIP: customer detail confirmation form, retest pembiayaan online, notification if required 2026-09-02 11:59:44 +08:00
ISMAIL MASSERAN 73d74878d9 DONE: use flexible name for api call in frontend 2026-09-02 11:59:44 +08:00
ismailmasseran 0cb85c2375 Merge pull request 'resolve bugs laporan audit operasi' (#13) from fix/resolve-bugs-laporan-audit-operasi into main
Reviewed-on: #13
2026-09-02 11:58:46 +08:00
nurafrinaalimi16 2a0ffde016 resolve bugs laporan audit operasi 2026-09-02 11:55:28 +08:00
ismailmasseran 92991f0d10 Prod kopkb (#12)
Co-authored-by: dev_kopkb <xtrabiz.sb@gmail.com>
Co-authored-by: nurafrinaalimi16 <nurafrinaalimi16@gmail.com>
Co-authored-by: root <root@erahn.click>
Reviewed-on: #12
2026-09-01 15:10:30 +08:00
ismailmasseran 1f8642e230 DONE: add cicd for staging (#1)
Build Docker Image / build-frontend (push) Successful in 17s
Co-authored-by: ISMAIL MASSERAN <topaz@ISMAILs-Macbook.local>
Reviewed-on: #1
2026-08-26 13:36:18 +08:00
46 changed files with 4183 additions and 740 deletions
+5
View File
@@ -1,6 +1,10 @@
.git .git
.gitea
node_modules node_modules
vendor vendor
.env
.env.*
!.env.example
storage/logs storage/logs
storage/framework/cache storage/framework/cache
storage/framework/sessions storage/framework/sessions
@@ -9,3 +13,4 @@ storage/debugbar
npm-debug.log npm-debug.log
Dockerfile Dockerfile
docker-compose.yml docker-compose.yml
docker-compose.staging.yml
+55
View File
@@ -0,0 +1,55 @@
APP_NAME=Laravel
APP_ENV=local
APP_KEY=base64:O1XZDqK4H6+3GdrODgCaLr2l+OoWvgVhYr0GJ1KizW8=
APP_DEBUG=true
APP_URL=http://localhost
# API_URL=http://api-annurv2.test
API_URL=http://host.docker.internal:8080
# Server-side Http:: from Docker — same rules as API_URL. Defaults in config/api.php use localhost:8000 which fails inside containers.
API_URL_KOOP=http://host.docker.internal:8080
API_URL_PKB=http://host.docker.internal:8080
API_ARRAHNBID=http://lelong.test/api/
LOG_CHANNEL=stack
LOG_LEVEL=debug
DB_CONNECTION=mysql
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=kaunter_erahn
DB_USERNAME=kaunteruser
DB_PASSWORD=password
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 -1
View File
@@ -46,5 +46,5 @@ AWS_BUCKET=
MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}" MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
MYKAD_READER_PORT=8085 MYKAD_READER_PORT=17321
MYKAD_READER_ENABLED_CAWANGAN='' # IF NO CAWANGAN STATED, ALL CAWANGAN IS ENABLED MYKAD_READER_ENABLED_CAWANGAN='' # IF NO CAWANGAN STATED, ALL CAWANGAN IS ENABLED
+58
View File
@@ -0,0 +1,58 @@
APP_NAME=Laravel
APP_ENV=staging
APP_VERSION=v2.2-staging
APP_KEY=base64:O1XZDqK4H6+3GdrODgCaLr2l+OoWvgVhYr0GJ1KizW8=
APP_DEBUG=true
APP_URL=https://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
DB_CONNECTION=mysql
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=kaunter_erahn
DB_USERNAME=kaunteruser
DB_PASSWORD=password
BROADCAST_DRIVER=pusher
CACHE_DRIVER=file
QUEUE_CONNECTION=sync
SESSION_DRIVER=file
SESSION_LIFETIME=120
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
PUSHER_APP_ID=1132718
PUSHER_APP_KEY=26b0ec2f297f94b7aa9b
PUSHER_APP_SECRET=421b61fb5a8ecce477a6
MAIL_MAILER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS=null
MAIL_FROM_NAME="${APP_NAME}"
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
MYKAD_READER_PORT=17321
MYKAD_READER_ENABLED_CAWANGAN='' # IF NO CAWANGAN STATED, ALL CAWANGAN IS ENABLED
+50
View File
@@ -0,0 +1,50 @@
APP_NAME=Laravel
APP_ENV=local
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/
LOG_CHANNEL=stack
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=kaunter_erahn
DB_USERNAME=spider
DB_PASSWORD=G0dz!ll4
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
+49
View File
@@ -0,0 +1,49 @@
name: Build Docker Image
on:
push:
branches:
- staging
tags:
- "v*"
workflow_dispatch:
jobs:
build-frontend:
runs-on: docker
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Login to Docker registry
run: |
echo "${{ secrets.REGISTRY_PASSWORD }}" | \
docker login git.koppkb.com \
-u "${{ secrets.REGISTRY_USERNAME }}" \
--password-stdin
- name: Build & push frontend image
run: |
set -e
IMAGE="git.koppkb.com/kopkb/kaunter"
TAGS="-t ${IMAGE}:${{ gitea.sha }}"
if [ "${{ gitea.ref_type }}" = "tag" ] && echo "${{ gitea.ref_name }}" | grep -q '^v'; then
TAGS="${TAGS} -t ${IMAGE}:${{ gitea.ref_name }}"
fi
if [ "${{ gitea.ref_name }}" = "staging" ]; then
TAGS="${TAGS} -t ${IMAGE}:staging"
fi
echo "Building and pushing frontend: ${TAGS}"
docker buildx build \
--target staging \
--platform linux/amd64 \
--cache-from type=registry,ref=${IMAGE}:buildcache,ignore-error=true \
--cache-to type=registry,ref=${IMAGE}:buildcache,mode=max \
-f docker/php/Dockerfile \
${TAGS} \
--push .
+123
View File
@@ -0,0 +1,123 @@
name: Deploy to Staging
on:
workflow_dispatch:
inputs:
image_tag:
description: "Docker image tag to deploy (commit SHA, staging, or v*)"
required: true
default: "staging"
type: string
env:
APP_IMAGE: git.koppkb.com/kopkb/kaunter
DEPLOY_DIR: /home/arrahn/Project/kaunter
jobs:
deploy:
runs-on: host
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ inputs.image_tag }}
- name: Login to Docker registry
run: |
echo "${{ secrets.REGISTRY_PASSWORD }}" | \
docker login git.koppkb.com \
-u "${{ secrets.REGISTRY_USERNAME }}" \
--password-stdin
- name: Verify image exists in registry
run: |
set -e
IMAGE_TAG="${{ inputs.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"
- name: Pull Docker image
run: |
set -e
IMAGE_TAG="${{ inputs.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 }}"
mkdir -p "${DEPLOY_DIR}/docker/nginx" "${DEPLOY_DIR}/public"
cp docker-compose.staging.yml "${DEPLOY_DIR}/docker-compose.yml"
cp docker/nginx/default.conf "${DEPLOY_DIR}/docker/nginx/default.conf"
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
fi
cd "${DEPLOY_DIR}"
export APP_IMAGE
export IMAGE_TAG
docker compose --env-file .env.staging -f docker-compose.yml pull app
docker compose --env-file .env.staging -f docker-compose.yml up -d --no-build --force-recreate --remove-orphans app web
echo "✓ Deployed IMAGE_TAG=${IMAGE_TAG}"
- name: Verify deployment
run: |
set -e
cd "${DEPLOY_DIR}"
echo "Waiting for services to start..."
sleep 10
for SERVICE in app web mysql; do
STATUS=$(docker compose --env-file .env.staging -f docker-compose.yml ps --status running --format '{{.Name}}' "$SERVICE" 2>/dev/null || true)
if [ -z "$STATUS" ]; then
echo "ERROR: ${SERVICE} is not running"
docker compose --env-file .env.staging -f docker-compose.yml ps
docker compose --env-file .env.staging -f docker-compose.yml logs --tail=50 "$SERVICE" || true
exit 1
fi
echo "✓ ${SERVICE} is running (${STATUS})"
done
HTTP_PORT=$(grep -E '^HTTP_PORT=' .env.staging 2>/dev/null | cut -d= -f2- | tr -d '"' || true)
HTTP_PORT=${HTTP_PORT:-8091}
check_health() {
NAME=$1
URL=$2
for i in 1 2 3 4 5 6; do
if curl -sf "$URL" > /dev/null; then
echo "✓ ${NAME} health check passed (${URL})"
return 0
fi
echo "Waiting for ${NAME}... attempt ${i}/6"
sleep 5
done
echo "ERROR: ${NAME} health check failed (${URL})"
docker compose --env-file .env.staging -f docker-compose.yml logs --tail=50 app web || true
return 1
}
check_health "kaunter" "http://127.0.0.1:${HTTP_PORT}/health"
+118 -2
View File
@@ -358,7 +358,7 @@ class CustomersController extends Controller
])->json(); ])->json();
} }
$customer_info = Http::withOptions(['verify' => config('app.api_verify')])->post(config('api.url') . '/api/customers/create/' . $auth_user['cawangan'], [ $customer_payload = [
'kodcaw' => $auth_user['cawangan'], 'kodcaw' => $auth_user['cawangan'],
'nopelanggan' => $no_pelanggan, 'nopelanggan' => $no_pelanggan,
'nokpbaru' => $nokpbaru, 'nokpbaru' => $nokpbaru,
@@ -385,7 +385,14 @@ class CustomersController extends Controller
'nota_pekerjaan' => $request->nota_pekerjaan, 'nota_pekerjaan' => $request->nota_pekerjaan,
'notaarcc' => $request->notaarcc, 'notaarcc' => $request->notaarcc,
'tujuangadai' => $request->tujuan_gadai, 'tujuangadai' => $request->tujuan_gadai,
]); ];
if ($request->filled('photo_base64')) {
$customer_payload['photo_base64'] = $request->input('photo_base64');
$customer_payload['photo_mime'] = $request->input('photo_mime') ?: 'image/jpeg';
}
$customer_info = Http::withOptions(['verify' => config('app.api_verify')])->post(config('api.url') . '/api/customers/create/' . $auth_user['cawangan'], $customer_payload);
Session::put('customer_info', $customer_info); Session::put('customer_info', $customer_info);
Session::put('noic', $request->noic); Session::put('noic', $request->noic);
@@ -574,4 +581,113 @@ class CustomersController extends Controller
return redirect()->back()->with('error', 'Gagal mengemaskini status anggota. Sila cuba semula.'); return redirect()->back()->with('error', 'Gagal mengemaskini status anggota. Sila cuba semula.');
} }
} }
public function printMaklumat(Request $request)
{
$noic = $request->query('noic') ?: Session::get('noic');
if (empty($noic)) {
return redirect('/pelanggan');
}
$auth_user = Auth::user();
$api_url = config('api.url');
$verify = ['verify' => config('app.api_verify')];
$kodcaw = $auth_user['cawangan'];
$customer = Http::withOptions($verify)->get($api_url . '/api/customers/' . $noic)->json();
if (!is_array($customer) || (empty($customer['nama']) && empty($customer['nopelanggan']) && empty($customer['kpbaru']) && empty($customer['kplama']))) {
abort(404, 'Pelanggan tidak dijumpai.');
}
$cawanganResp = Http::withOptions($verify)->get($api_url . '/api/cawangan/detail/' . $kodcaw)->json();
$cawangan_detail = (is_array($cawanganResp) && isset($cawanganResp[0]))
? $cawanganResp[0]
: (is_array($cawanganResp) ? $cawanganResp : []);
$banks = Http::withOptions($verify)->get($api_url . '/api/bank')->json();
$pekerjaanList = Http::withOptions($verify)->get($api_url . '/api/pekerjaan')->json();
$poskodKod = $customer['poskod'] ?? '';
$poskod = [];
if ($poskodKod !== '' && $poskodKod !== null) {
$poskodResp = Http::withOptions($verify)->get($api_url . '/api/poskod/' . $poskodKod)->json();
if (is_array($poskodResp)) {
$poskod = $poskodResp;
}
}
$daerahNama = 'NIL';
$negeriNama = 'NIL';
if (!empty($poskod['koddaerah'])) {
$daerahResp = Http::withOptions($verify)->get($api_url . '/api/daerah/' . $poskod['koddaerah'])->json();
$daerahNama = is_array($daerahResp) ? ($daerahResp['namadaerah'] ?? 'NIL') : 'NIL';
}
if (!empty($poskod['kodnegeri'])) {
$negeriResp = Http::withOptions($verify)->get($api_url . '/api/negeri/' . $poskod['kodnegeri'])->json();
$negeriNama = is_array($negeriResp) ? ($negeriResp['negeri'] ?? 'NIL') : 'NIL';
}
$bankNama = $this->lookupListValue($banks, 'abbreviation', $customer['kodbank'] ?? null, 'name');
$pekerjaanNama = $this->lookupListValue($pekerjaanList, 'kodkerja', $customer['kodkerja'] ?? null, 'keterangan');
if (($customer['kodkerja'] ?? '') == '6' && !empty($customer['nota_pekerjaan'])) {
$pekerjaanNama = $customer['nota_pekerjaan'];
}
$photo_svg = '<svg xmlns="http://www.w3.org/2000/svg" width="240" height="320" viewBox="0 0 240 320">'
. '<rect fill="#d9dee5" width="240" height="320"/>'
. '<circle cx="120" cy="118" r="52" fill="#8b949e"/>'
. '<ellipse cx="120" cy="286" rx="90" ry="92" fill="#8b949e"/>'
. '</svg>';
$photo_src = 'data:image/svg+xml;base64,' . base64_encode($photo_svg);
if (!empty($customer['photo_base64'])) {
$mime = $customer['photo_mime'] ?? 'image/jpeg';
$photo_src = 'data:' . $mime . ';base64,' . $customer['photo_base64'];
}
return view('print.maklumat_pelanggan', [
'cawangan' => $cawangan_detail['namacaw'] ?? ($cawangan_detail['details_caw'] ?? $kodcaw),
'kodcaw' => $kodcaw,
'alamat1' => $cawangan_detail['alamat1'] ?? '',
'alamat2' => $cawangan_detail['alamat2'] ?? '',
'notel' => $cawangan_detail['notel'] ?? '',
'tarikh' => Carbon::now()->format('d/m/Y'),
'nama' => $this->printBlank($customer['nama'] ?? null),
'nokp' => $this->printBlank(!empty($customer['kpbaru']) ? $customer['kpbaru'] : ($customer['kplama'] ?? $noic)),
'nopelanggan' => $this->printBlank($customer['nopelanggan'] ?? null),
'status' => (!empty($customer['tag_anggota']) && $customer['tag_anggota'] == 1) ? 'Anggota Koperasi' : 'Pelanggan',
'telefon' => $this->printBlank($customer['telefon'] ?? null),
'telefon2' => $this->printBlank($customer['telefon2'] ?? null),
'alamat' => $this->printBlank($customer['alamat1'] ?? null),
'poskod' => $this->printBlank($customer['poskod'] ?? null),
'daerah' => $daerahNama,
'negeri' => $negeriNama,
'bank' => $this->printBlank($bankNama),
'noakaun' => $this->printBlank($customer['noakaun'] ?? null),
'pekerjaan' => $this->printBlank($pekerjaanNama),
'nota' => $this->printBlank($customer['nota'] ?? null),
'photo_src' => $photo_src,
]);
}
private function printBlank($value)
{
if ($value === null) {
return 'NIL';
}
$value = is_string($value) ? trim($value) : $value;
return $value === '' ? 'NIL' : $value;
}
private function lookupListValue($list, $key, $match, $nameKey)
{
if (!is_array($list) || $match === null || $match === '' || $match === '-') {
return '';
}
foreach ($list as $item) {
if (is_array($item) && ($item[$key] ?? null) == $match) {
return $item[$nameKey] ?? '';
}
}
return '';
}
} }
+115
View File
@@ -259,6 +259,121 @@ class GadaianController extends Controller
return redirect()->route('customer_info'); return redirect()->route('customer_info');
} }
public function printBorangPengesahanOnline($norujukan)
{
$auth_user = Auth::user();
$kodcaw = $auth_user['cawangan'];
$onlineResp = Http::withOptions(['verify' => config('app.api_verify')])
->get(config('api.url') . '/api/pembiayaan-online/' . $kodcaw . '/by-norujukan/' . $norujukan);
if (!$onlineResp->successful()) {
abort(404, 'Pembiayaan online tidak dijumpai.');
}
$line = $onlineResp->json();
if (!is_array($line) || empty($line['norujukan'])) {
abort(404, 'Pembiayaan online tidak dijumpai.');
}
return $this->renderBorangPengesahanOnline($line, $kodcaw);
}
public function previewBorangPengesahanOnline(Request $request)
{
$auth_user = Auth::user();
$kodcaw = $auth_user['cawangan'];
$line = [
'norujukan' => $request->input('norujukan'),
'nokp' => $request->input('nokp'),
'nopelanggan' => $request->input('nopelanggan'),
'nama_pelanggan' => $request->input('nama_pelanggan'),
'pinjaman' => $request->input('pinjaman'),
'kodbank' => $request->input('kodbank'),
'nama_bank' => $request->input('nama_bank'),
'noakaun' => $request->input('noakaun'),
'nama_akaun' => $request->input('nama_akaun'),
'created_by' => $auth_user['name'] ?? '',
'created_at' => Carbon::now()->toDateTimeString(),
];
if (empty($line['norujukan']) || empty($line['noakaun'])) {
abort(422, 'Sila lengkapkan maklumat bank sebelum cetak borang.');
}
return $this->renderBorangPengesahanOnline($line, $kodcaw);
}
private function renderBorangPengesahanOnline(array $line, $kodcaw)
{
$auth_user = Auth::user();
$cawanganResp = Http::withOptions(['verify' => config('app.api_verify')])
->get(config('api.url') . '/api/cawangan/detail/' . $kodcaw)
->json();
$cawangan_detail = (is_array($cawanganResp) && isset($cawanganResp[0]))
? $cawanganResp[0]
: (is_array($cawanganResp) ? $cawanganResp : []);
$nokpRaw = $line['nokp'] ?? '';
$customer = [];
if ($nokpRaw !== '') {
$customerResp = Http::withOptions(['verify' => config('app.api_verify')])
->get(config('api.url') . '/api/customers/' . $nokpRaw)
->json();
if (is_array($customerResp)) {
$customer = $customerResp;
}
}
$cawangan = $cawangan_detail['namacaw']
?? $cawangan_detail['details_caw']
?? $kodcaw;
$alamat1 = $cawangan_detail['alamat1'] ?? '';
$alamat2 = $cawangan_detail['alamat2'] ?? '';
$notel = $cawangan_detail['notel'] ?? '';
$nama = $line['nama_pelanggan'] ?? ($customer['nama'] ?? '');
$nokp = !empty($customer['kpbaru']) ? $customer['kpbaru'] : ($customer['kplama'] ?? $nokpRaw);
$nopelanggan = $line['nopelanggan'] ?? ($customer['nopelanggan'] ?? '');
$telefon = $customer['telefon'] ?? '';
$teller = $line['created_by'] ?? ($auth_user['name'] ?? '');
$bank = $line['nama_bank'] ?? ($line['kodbank'] ?? '');
$noakaun = $line['noakaun'] ?? '';
$nama_akaun = $line['nama_akaun'] ?? $nama;
$pinjaman = number_format((float) ($line['pinjaman'] ?? 0), 2);
$norujukan = $line['norujukan'] ?? '';
$tarikhSumber = $line['created_at'] ?? null;
try {
$tarikh = $tarikhSumber
? Carbon::parse($tarikhSumber)->format('d/m/Y')
: Carbon::now()->format('d/m/Y');
} catch (\Exception $e) {
$tarikh = Carbon::now()->format('d/m/Y');
}
return view('print.borang_pengesahan_online', compact(
'cawangan',
'kodcaw',
'alamat1',
'alamat2',
'notel',
'norujukan',
'tarikh',
'teller',
'nama',
'nokp',
'nopelanggan',
'telefon',
'pinjaman',
'bank',
'noakaun',
'nama_akaun'
));
}
public function editstore(Request $request) public function editstore(Request $request)
{ {
$auth_user = Auth::user(); $auth_user = Auth::user();
+36 -3
View File
@@ -20,10 +20,43 @@ class KakitanganController extends Controller
public function daftarKakitangan(){ public function daftarKakitangan(){
$api_url = config('api.url'); $api_url = config('api.url');
$auth_user = Auth::user(); $auth_user = Auth::user();
$cawangan_info = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url') . '/api/cawangan/'. $auth_user['cawangan'])->json(); $http = Http::withOptions(['verify' => config('app.api_verify')]);
$cawangan_all = $http->get($api_url . '/api/cawangan')->json();
$cawangan_all = is_array($cawangan_all) ? $cawangan_all : [];
$cawangan_by_kod = collect($cawangan_all)->keyBy('kodcaw');
$cawangan_info = $cawangan_by_kod->get($auth_user['cawangan'], []);
$role = Role::get(); $role = Role::get();
$kakitangan = User::all(); $kakitangan_rows = User::where('roles', '!=', 'admin')
return view('operasi.daftar_kakitangan',compact('cawangan_info','api_url','role','kakitangan','auth_user')); ->get()
->values()
->map(function ($staff) use ($cawangan_by_kod) {
$kod = (string) $staff->cawangan;
if (in_array($kod, ['operasi', 'callcenter', 'admin'], true)) {
$cawangan_nama = ucwords($kod);
} else {
$cawangan_nama = ucwords((string) data_get($cawangan_by_kod, $kod . '.details_caw', $kod));
}
return [
'id' => $staff->id,
'name' => $staff->name,
'roles' => strtoupper((string) $staff->roles),
'role_harian' => $staff->role_harian ? strtoupper($staff->role_harian) : '-',
'role_asal' => $staff->role_asal ? strtoupper($staff->role_asal) : '-',
'cawangan' => $cawangan_nama,
];
});
return view('operasi.daftar_kakitangan', compact(
'cawangan_info',
'api_url',
'role',
'kakitangan_rows',
'auth_user',
'cawangan_all'
));
} }
public function registerStaff(Request $request){ public function registerStaff(Request $request){
+153 -5
View File
@@ -87,13 +87,19 @@ class OperasiController extends Controller
'siri' => $request->siri 'siri' => $request->siri
])->json(); ])->json();
$updatelulus = Http::withOptions(['verify' => config('app.api_verify')])->put(config('api.url') . '/api/UpdateKelulusan/' . $request->kodcaw . '/' . $norujukan, [ $updateData = [
'sahkan' => 1, 'sahkan' => 1,
'namaoperasi' => $request->namaoperasi, 'namaoperasi' => $request->namaoperasi,
'siri' => $request->siri 'siri' => $request->siri
])->json(); ];
} else {
$updatelulus = Http::withOptions(['verify' => config('app.api_verify')])->put(config('api.url') . '/api/UpdateHapusAnsuran', [ if ($request->has('actions')) {
$updateData['actions'] = $request->actions;
}
$updatelulus = Http::withOptions(['verify' => config('app.api_verify')])->put(config('api.url') . '/api/UpdateKelulusan/' . $request->kodcaw . '/' . $norujukan, $updateData)->json();
}else{
$updatelulus = Http::withOptions(['verify' => config('app.api_verify')])->put(config('api.url') . '/api/UpdateHapusAnsuran',[
'siri' => $request->siri, 'siri' => $request->siri,
"kodcaw" => $request->kodcaw, "kodcaw" => $request->kodcaw,
"norujukan" => $request->norujukan, "norujukan" => $request->norujukan,
@@ -139,6 +145,49 @@ class OperasiController extends Controller
return response()->json($updatelulus, 200); return response()->json($updatelulus, 200);
} }
public function createKelulusanByKhazanah(Request $request)
{
$auth_user = Auth::user();
$api_url = config('api.url');
$current = Carbon::now();
$current = new Carbon();
$norujukan = $request->norujukan;
$cawangan_info = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url') . '/api/cawangan/'. $auth_user['cawangan'])->json();
$getsiri = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url') . '/api/ListLatestLulus/'. $auth_user['cawangan'] . '/' . $norujukan)->json();
if(isset($getsiri['siri']))
{
$siri = $getsiri['siri'] + 1;
}else{
$siri = 1;
}
$createlulus = Http::withOptions(['verify' => config('app.api_verify')])->post(config('api.url') . '/api/CreateKelulusan/' . $auth_user['cawangan'] . '/' . $norujukan . '/roles/khazanah', [
'komen' => $request->komen,
'dategadai' => $current->toDateString(),
'bilgadai' => 1,
'siri' => $siri,
'margin' => $request->margin,
'berat' => $request->berat,
'nilaimarhun' => $request->nilaimarhun,
'status' => $request->status,
'jumpinjam' => $request->jumpinjam,
'jenistebus' => "G",
'teller' => $request->teller,
'peloperasi' => "operasi",
'pelulus' => "operasi",
'actions' => "Mohon Pengesahan Pembayaran Pampasan"
])->json();
event(new OperasiNotice($auth_user['cawangan'],$auth_user['name']));
return response()->json($siri,201);
}
public function updatekelulusananggota(Request $request) public function updatekelulusananggota(Request $request)
{ {
$auth_user = Auth::user(); $auth_user = Auth::user();
@@ -149,7 +198,7 @@ class OperasiController extends Controller
$kodcaw = $request->kodcaw; $kodcaw = $request->kodcaw;
$komen = $request->komenkelulusan; $komen = $request->komenkelulusan;
$namaoperasi = $request->namaoperasi; $namaoperasi = $request->namaoperasi;
$taglulus = $request->taglulus; // contoh: 'Y' = lulus, 'N' = batal $taglulus = $request->taglulus; // contoh: 'Y' = lulus, 'N' = batalmarginmutu()
// 1️⃣ Tentukan status kelulusan // 1️⃣ Tentukan status kelulusan
$status = ($taglulus == 'Y') ? 1 : 2; // 1 = Lulus, 2 = Batal/Tolak $status = ($taglulus == 'Y') ? 1 : 2; // 1 = Lulus, 2 = Batal/Tolak
@@ -404,4 +453,103 @@ class OperasiController extends Controller
return view('operasi.kelulusan_anggota', compact('cawangan_info', 'api_url', 'arraykelulusan')); return view('operasi.kelulusan_anggota', compact('cawangan_info', 'api_url', 'arraykelulusan'));
} }
public function marginmutu()
{
$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();
// Get MaxMutuEmas data from API
$maxmutuemas = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url') . '/api/maxmutuemas')->json();
return view('operasi.marginmutu', compact('cawangan_info', 'api_url', 'maxmutuemas'));
}
public function editMarginMutu()
{
$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();
return view('operasi.edit_marginmutu', compact('cawangan_info', 'api_url'));
}
public function laporanAudit()
{
$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();
return view('homepage.operasi.laporan-audit', compact('cawangan_info', 'api_url'));
}
public function cetakLaporanAuditTrail(Request $request)
{
$api_url = config('api.url');
$auth_user = Auth::user();
$current = Carbon::now();
$current = new Carbon();
$tarikh_cetak = $current->toDateString();
if($request->dateaudit == null){
$date = $current->toDateString();
}else{
$split_date = explode('-', $request->dateaudit);
$date = $split_date[0] . '-' . $split_date[1] . '-' . $split_date[2];
}
if($request->dateaudit_till == null){
$date_till = $current->toDateString();
}else{
$split_date_till = explode('-', $request->dateaudit_till);
$date_till = $split_date_till[0] . '-' . $split_date_till[1] . '-' . $split_date_till[2];
}
$audittrail_raw = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url'). '/api/reports/audittrail/operasi',['date' => $date,'date_till' => $date_till])->json();
if(!$audittrail_raw){
return redirect()->back()->with('errormessage', 'Tiada data pada tarikh tersebut.');
}
// Transform the data to match view format
$audittrail = [];
foreach($audittrail_raw as $item) {
$created_at = \Carbon\Carbon::parse($item['created_at']);
// Format differences for display
$differences_text = '-';
if (isset($item['differences']) && !empty($item['differences'])) {
$diff_parts = [];
foreach ($item['differences'] as $field => $changes) {
$old = $changes['old'] ?? 'null';
$new = $changes['new'] ?? 'null';
$diff_parts[] = "{$field}: {$old}{$new}";
}
$differences_text = implode('; ', $diff_parts);
}
$audittrail[] = [
'tarikh' => $created_at->format('d/m/Y'),
'masa' => $created_at->format('H:i:s'),
'users' => $item['users'],
'actions' => $item['actions'],
'differ' => $differences_text
];
}
$cawangan_detail = [
'kodcaw' => 'operasi',
'namacaw' => 'Operasi Ar-Rahn',
'alamat1' => 'Lot 107, Jalan Parit Dalam,',
'alamat2' => 'Seksyen 8',
'poskod' => '15000',
'bandar' => 'Kota Bahru',
'negeri' => 'Kelantan',
'notel' => '03-12345678',
'nofaks' => '03-12345678',
];
return view('print.laporan_audittrail_operasi', compact('audittrail','cawangan_detail','tarikh_cetak','date','date_till','api_url'));
}
} }
@@ -2,6 +2,7 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use Carbon\Carbon;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Http;
@@ -131,9 +132,14 @@ class PembiayaanOnlineController extends Controller
return redirect()->back()->with('error', $msg); return redirect()->back()->with('error', $msg);
} }
$batchNo = $res->json()['batch']['batch_no'] ?? ''; $payload = $res->json();
return redirect()->route('pembiayaan.online.pc') $batchNo = is_array($payload) ? ($payload['batch']['batch_no'] ?? '') : '';
->with('message', 'Batch dihantar ke Operasi' . ($batchNo ? ': ' . $batchNo : '.')); $appended = is_array($payload) && !empty($payload['appended']);
$message = $appended
? ('Rekod ditambah ke batch' . ($batchNo ? ' ' . $batchNo : '') . '.')
: ('Batch dihantar ke Operasi' . ($batchNo ? ': ' . $batchNo : '.'));
return redirect()->route('pembiayaan.online.pc')->with('message', $message);
} }
/** /**
@@ -346,6 +352,98 @@ class PembiayaanOnlineController extends Controller
]); ]);
} }
/**
* Operasi: printable batch kelulusan form.
*/
public function operasiPrintBatch($kodcaw, $batchId)
{
$api_url = config('api.url');
$detail = $this->api()
->get($api_url . '/api/pembiayaan-online/operasi/' . $kodcaw . '/batches/' . $batchId)
->json();
if (!is_array($detail) || empty($detail['batch'])) {
return redirect()
->route('pembiayaan.online.operasi')
->with('error', 'Batch tidak dijumpai.');
}
$batch = $detail['batch'];
$lines = $detail['lines'] ?? [];
if (!is_array($lines)) {
$lines = [];
}
$cawanganResp = $this->api()
->get($api_url . '/api/cawangan/detail/' . $kodcaw)
->json();
$cawangan_detail = (is_array($cawanganResp) && isset($cawanganResp[0]))
? $cawanganResp[0]
: (is_array($cawanganResp) ? $cawanganResp : []);
$cawangan = $cawangan_detail['namacaw']
?? $cawangan_detail['details_caw']
?? $cawangan_detail['nama']
?? $kodcaw;
$alamat1 = $cawangan_detail['alamat1'] ?? '';
$alamat2 = $cawangan_detail['alamat2'] ?? '';
$notel = $cawangan_detail['notel'] ?? '';
$batchNo = $batch['batch_no'] ?? '-';
$slot = $batch['slot'] ?? '-';
$status = $batch['status'] ?? '-';
$dihantarOleh = $batch['pc_submitted_by'] ?? '-';
$jumlah = (float) ($batch['jumlah_keseluruhan'] ?? 0);
if ($jumlah <= 0 && count($lines) > 0) {
$jumlah = (float) collect($lines)->sum(function ($line) {
return (float) ($line['pinjaman'] ?? 0);
});
}
$opsBy = Auth::user()['name'] ?? '';
foreach ($lines as $line) {
if (!empty($line['ops_by'])) {
$opsBy = $line['ops_by'];
break;
}
}
$tarikh = $this->formatPrintDate($batch['tarikh'] ?? null);
$dihantarPada = $this->formatPrintDate($batch['submitted_at'] ?? null, true);
return view('print.borang_batch_pembiayaan_online', compact(
'cawangan',
'kodcaw',
'alamat1',
'alamat2',
'notel',
'tarikh',
'batchNo',
'slot',
'status',
'dihantarOleh',
'dihantarPada',
'opsBy',
'lines',
'jumlah'
));
}
protected function formatPrintDate($value, $withTime = false)
{
if ($value === null || $value === '' || $value === '-') {
return '-';
}
try {
return Carbon::parse($value)->format($withTime ? 'd/m/Y H:i' : 'd/m/Y');
} catch (\Exception $e) {
return (string) $value;
}
}
public function operasiApproveLine(Request $request, $kodcaw, $id) public function operasiApproveLine(Request $request, $kodcaw, $id)
{ {
$res = $this->api() $res = $this->api()
+1 -1
View File
@@ -12,7 +12,7 @@ class TrustProxies extends Middleware
* *
* @var array|string|null * @var array|string|null
*/ */
protected $proxies; protected $proxies = '*';
/** /**
* The headers that should be used to detect proxies. * The headers that should be used to detect proxies.
+3 -1
View File
@@ -23,6 +23,8 @@ class AppServiceProvider extends ServiceProvider
*/ */
public function boot() public function boot()
{ {
// if (strpos((string) config('app.url'), 'https://') === 0) {
\Illuminate\Support\Facades\URL::forceScheme('https');
}
} }
} }
+1 -1
View File
@@ -1,6 +1,6 @@
<?php <?php
return [ return [
'url' => env('API_URL'), 'url' => env('API_URL'),
'url_koop' => env('API_URL_KOOP','http://localhost:8000'), 'url_koop' => env('API_URL_KOOP','https://apiujrah.erahn.com.my'),
'url_pkb' => env('API_URL_PKB','http://localhost:8000'), 'url_pkb' => env('API_URL_PKB','http://localhost:8000'),
]; ];
+2
View File
@@ -15,6 +15,8 @@ return [
'name' => env('APP_NAME', 'Laravel'), 'name' => env('APP_NAME', 'Laravel'),
'version' => env('APP_VERSION', ''),
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Application Environment | Application Environment
+2 -2
View File
@@ -1,7 +1,7 @@
<?php <?php
$default_port = '8085'; $default_port = '17321';
$getReaderEndpoint = fn($x) => 'http://localhost:' . $x . '/?MYKADJSON'; $getReaderEndpoint = fn($x) => 'http://localhost:' . $x . '/read';
return[ return[
'endpoint' => env('MYKAD_READER_ENDPOINT', $getReaderEndpoint(env('MYKAD_READER_PORT', $default_port))), 'endpoint' => env('MYKAD_READER_ENDPOINT', $getReaderEndpoint(env('MYKAD_READER_PORT', $default_port))),
+62
View File
@@ -0,0 +1,62 @@
name: kaunter_staging
services:
app:
image: ${APP_IMAGE:-git.koppkb.com/kopkb/kaunter}:${IMAGE_TAG:-staging}
build:
context: .
dockerfile: docker/php/Dockerfile
target: staging
container_name: kaunter_staging_app
working_dir: /var/www/html
volumes:
- storage_staging:/var/www/html/storage
- ./.env.staging:/var/www/html/.env.staging:ro
env_file:
- .env.staging
environment:
APP_ENV: staging
USE_STAGING_ENV: "true"
extra_hosts:
- "host.docker.internal:host-gateway"
depends_on:
mysql:
condition: service_healthy
restart: unless-stopped
web:
image: nginx:1.25-alpine
container_name: kaunter_staging_web
ports:
- "${HTTP_PORT:-8091}:80"
volumes:
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
- ./public:/var/www/html/public:ro
depends_on:
- app
restart: unless-stopped
mysql:
image: mysql:8.0
container_name: kaunter_staging_mysql
command: --default-authentication-plugin=mysql_native_password
ports:
- "127.0.0.1:${MYSQL_PUBLISH_PORT:-3309}:3306"
environment:
MYSQL_DATABASE: kaunter_erahn
MYSQL_USER: kaunteruser
MYSQL_PASSWORD: password
MYSQL_ROOT_PASSWORD: root
volumes:
- mysql_staging_data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-uroot", "-proot"]
interval: 5s
timeout: 5s
retries: 30
start_period: 20s
restart: unless-stopped
volumes:
mysql_staging_data:
storage_staging:
+1
View File
@@ -3,6 +3,7 @@ services:
build: build:
context: . context: .
dockerfile: docker/php/Dockerfile dockerfile: docker/php/Dockerfile
target: development
working_dir: /var/www/html working_dir: /var/www/html
volumes: volumes:
- ./:/var/www/html:cached - ./:/var/www/html:cached
+15 -2
View File
@@ -6,9 +6,22 @@ server {
index index.php index.html; index index.php index.html;
charset utf-8; charset utf-8;
client_max_body_size 50M;
location / { location / {
try_files $uri $uri/ /index.php?$query_string; # Production Apache public/.htaccess 301-strips trailing slashes.
# Nginx ignores .htaccess, so without this /path/ 404s while /path works.
rewrite ^/(.*)/$ /$1 last;
try_files $uri @php;
}
location @php {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME /var/www/html/public/index.php;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_pass app:9000;
fastcgi_index index.php;
fastcgi_read_timeout 120s;
} }
location ~ \.php$ { location ~ \.php$ {
@@ -17,10 +30,10 @@ server {
fastcgi_param PATH_INFO $fastcgi_path_info; fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_pass app:9000; fastcgi_pass app:9000;
fastcgi_index index.php; fastcgi_index index.php;
fastcgi_read_timeout 120s;
} }
location ~ /\.(?!well-known).* { location ~ /\.(?!well-known).* {
deny all; deny all;
} }
} }
+23 -2
View File
@@ -1,4 +1,4 @@
FROM php:7.4-fpm FROM php:7.4-fpm AS base
WORKDIR /var/www/html WORKDIR /var/www/html
@@ -25,6 +25,27 @@ RUN apt-get update \
COPY --from=composer:2 /usr/bin/composer /usr/local/bin/composer COPY --from=composer:2 /usr/bin/composer /usr/local/bin/composer
COPY docker/php/php.ini /usr/local/etc/php/conf.d/app.ini COPY docker/php/php.ini /usr/local/etc/php/conf.d/app.ini
COPY docker/php/zz-clear-env.conf /usr/local/etc/php-fpm.d/zz-clear-env.conf
COPY docker/php/entrypoint.sh /usr/local/bin/app-entrypoint.sh
RUN usermod -u 1000 www-data && groupmod -g 1000 www-data RUN chmod +x /usr/local/bin/app-entrypoint.sh \
&& usermod -u 1000 www-data && groupmod -g 1000 www-data
ENTRYPOINT ["app-entrypoint.sh"]
CMD ["php-fpm"]
FROM base AS staging
COPY --chown=www-data:www-data . /var/www/html
RUN composer install --no-interaction --prefer-dist --no-dev --optimize-autoloader --no-scripts \
&& mkdir -p \
storage/logs \
storage/framework/cache/data \
storage/framework/views \
storage/framework/sessions \
storage/app/public \
bootstrap/cache \
&& chown -R www-data:www-data storage bootstrap/cache vendor
FROM base AS development
+35
View File
@@ -0,0 +1,35 @@
#!/bin/sh
set -e
cd /var/www/html
if [ "${USE_STAGING_ENV:-false}" = "true" ] && [ -f .env.staging ]; then
cp .env.staging .env
fi
if [ -f composer.json ]; then
if [ -f vendor/autoload.php ] && [ "${USE_STAGING_ENV:-false}" = "true" ]; then
: # vendor is baked into the staging image
elif [ "${USE_STAGING_ENV:-false}" = "true" ]; then
composer install --no-interaction --prefer-dist --no-dev --optimize-autoloader --no-scripts
else
composer install --no-interaction --prefer-dist
fi
fi
mkdir -p \
storage/logs \
storage/framework/cache/data \
storage/framework/views \
storage/framework/sessions \
storage/app/public \
bootstrap/cache
if [ -f artisan ]; then
php artisan package:discover --ansi
php artisan storage:link --force >/dev/null 2>&1 || true
fi
chown -R www-data:www-data storage bootstrap/cache
exec docker-php-entrypoint "$@"
+3
View File
@@ -0,0 +1,3 @@
[www]
; Pass container env vars (from compose env_file) through to PHP workers.
clear_env = no
+316 -206
View File
@@ -1,131 +1,292 @@
let button_read_mykad = document.getElementById("button_read_mykad"); let button_read_mykad = document.getElementById("button_read_mykad");
let button_close_modal = document.querySelector("#daftar_pelanggan_mykad_reader button.btn.btn-danger"); let button_close_modal = document.querySelector("#daftar_pelanggan_mykad_reader button.btn.btn-danger");
let original_photo_loader = document.querySelector("#photo-mykad").src; let photo_mykad = document.querySelector("#photo-mykad");
let original_photo_loader = photo_mykad ? photo_mykad.src : "";
let showLoader = () => { const MYKAD_READER_TIMEOUT_MS = 45000;
const MYKAD_READER_DEFAULT_ENDPOINT = "http://localhost:17321/read";
let showLoader = () => {
let pageLoader = document.getElementById('page_loader'); let pageLoader = document.getElementById('page_loader');
pageLoader.removeAttribute('class'); if (!pageLoader) {
pageLoader.classList.add('loading');
};
let hideLoader = () => {
let pageLoader = document.getElementById('page_loader');
pageLoader.removeAttribute('class');
};
function handleMyKadReader()
{
showLoader();
WebSocketManager.init("wss://localhost:5963/mykadwebsocket");
WebSocketManager.listReader(callbackDoneGetListCardReader);
}
function callbackDoneGetListCardReader(data, respcode)
{
console.log('done get list card reader');
if(respcode === -1){
console.log({'location': 'callbackDoneGetListCardReader', 'data' : data} );
}
let isDevicesConnected = Object.keys(data).length > 0;
if(! isDevicesConnected){
hideLoader();
Swal.fire({ icon: 'error', title: 'Pembaca MyKad tidak dapat dikesan, sila periksa sambungan', });
return; return;
} }
pageLoader.removeAttribute('class');
pageLoader.classList.add('loading');
};
let slots = data.toString().split(','); let hideLoader = () => {
WebSocketManager.open(doneOpenReaderConnection, slots[0]); let pageLoader = document.getElementById('page_loader');
if (!pageLoader) {
return;
}
pageLoader.removeAttribute('class');
};
function getMyKadReaderEndpoint() {
let endpoint = (typeof mykad_reader_endpoint !== 'undefined' && mykad_reader_endpoint)
? String(mykad_reader_endpoint).trim()
: '';
if (!endpoint || endpoint.includes('MYKADJSON') || endpoint.includes(':8085')) {
return MYKAD_READER_DEFAULT_ENDPOINT;
} }
function doneOpenReaderConnection() return endpoint;
{ }
WebSocketManager.readAllText(callBackAfterReadMykad);
async function parseJsonResponse(response) {
let text = await response.text();
let trimmed = (text || '').trim();
if (!trimmed) {
throw new Error('Pembaca MyKad tidak memulangkan data. Sila cuba lagi.');
} }
function handleMyKadReaderError(sw12AndApiName) if (trimmed.charAt(0) === '<') {
{ throw new Error('Pembaca MyKad memulangkan halaman web, bukan data kad. Sila pastikan perkhidmatan mykad-helper sedang berjalan di localhost:17321.');
hideLoader(); }
let error_handler_dictionary = { try {
'0x80100069' : 'Tiada MyKad dikesan, sila masukkan Mykad untuk dibaca', return JSON.parse(trimmed);
'0x80100066' : 'MyKad mungkin dimasukkan terbalik, sila masukkan dengan betul', } catch (error) {
'default' : sw12AndApiName, throw new Error('Pembaca MyKad memulangkan data yang tidak sah. Sila cuba lagi.');
}
}
function getMyKadNumberWithoutHyphens(nric) {
return String(nric || '').replace(/-/g, '');
}
function titleCase(value) {
return String(value || '')
.toLowerCase()
.split(' ')
.filter(Boolean)
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ');
}
function getCodeFromValue(needle, haystack, fallback) {
let normalised = titleCase(needle);
let match = haystack.find(item => item[1].toLowerCase() === normalised.toLowerCase());
return match ? match[0] : fallback;
}
function mapGender(gender) {
let value = String(gender || '').toLowerCase();
if (['male', 'lelaki', 'l', 'm'].includes(value)) {
return 'Lelaki';
}
if (['female', 'perempuan', 'p', 'f'].includes(value)) {
return 'Perempuan';
}
return gender || '';
}
function formatDateOfBirth(dob) {
if (!dob) {
return '';
}
let hyphenated = String(dob).includes('-')
? String(dob)
: String(dob).replace(/(\d{4})(\d{2})(\d{2})/, '$1-$2-$3');
let [year, month, day] = hyphenated.split('-');
if (!year || !month || !day) {
return dob;
}
return day + '/' + month + '/' + year;
}
function getAgeFromDateOfBirth(dob) {
if (!dob) {
return '';
}
let hyphenated = String(dob).includes('-')
? String(dob)
: String(dob).replace(/(\d{4})(\d{2})(\d{2})/, '$1-$2-$3');
let birthDate = new Date(hyphenated);
if (Number.isNaN(birthDate.getTime())) {
return '';
}
let today = new Date();
let age = today.getFullYear() - birthDate.getFullYear();
let monthDiff = today.getMonth() - birthDate.getMonth();
if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) {
age -= 1;
}
return age;
}
function getBumiputeraStatus(race) {
const non_bumiputera_ethnicity = ['INDIA', 'CINA', 'INDIAN', 'CHINESE'];
return !non_bumiputera_ethnicity.includes(String(race || '').toUpperCase());
}
function joinAddress(data) {
return [data.address1, data.address2, data.address3]
.map(line => String(line || '').trim())
.filter(Boolean)
.join(', ');
}
function mapReaderErrorCode(rawMessage) {
const error_handler_dictionary = {
'0x80100069': 'Tiada MyKad dikesan, sila masukkan MyKad untuk dibaca',
'0x8010000C': 'Tiada MyKad dikesan, sila masukkan MyKad untuk dibaca',
'0x80100066': 'MyKad mungkin dimasukkan terbalik, sila masukkan dengan betul',
'0x80100067': 'MyKad tidak dapat dikesan. Sila cabut dan masukkan semula kad',
'0x80100016': 'Gagal membaca MyKad. Sila pastikan kad dimasukkan dengan betul dan cuba lagi',
'0x8010002E': 'Pembaca MyKad tidak dapat dikesan, sila periksa sambungan',
'0x80100017': 'Pembaca MyKad tidak dapat dikesan, sila periksa sambungan',
}; };
let error_data = sw12AndApiName.split(" "); let message = String(rawMessage || '');
let codes = message.match(/0x[0-9a-fA-F]+/g) || [];
let matched = codes.reverse().find(code => error_handler_dictionary[code]);
let error_code = error_data.filter(x => x.startsWith('0x')).splice(-1) return matched ? error_handler_dictionary[matched] : (message || null);
}
let error_message = error_handler_dictionary['default']; function getReaderErrorMessage(payload) {
if (!payload) {
let hasDefinedError = error_handler_dictionary.hasOwnProperty(error_code); return 'Pembaca MyKad tidak dapat membaca kad. Sila cuba lagi.';
if(hasDefinedError){
error_message = error_handler_dictionary[error_code];
} }
Swal.fire({ icon: 'error', title: error_message, }); let raw = payload.message || payload.error || payload.detail || '';
return mapReaderErrorCode(raw)
|| 'Pembaca MyKad tidak dapat membaca kad. Sila pastikan MyKad dimasukkan dengan betul.';
}
function getFetchErrorTitle(error) {
if (error && error.name === 'AbortError') {
return 'Masa menunggu pembaca MyKad telah tamat. Sila cuba lagi.';
} }
function callBackAfterReadMykad(data, respcode, sw12AndApiName) return 'Pembaca MyKad tidak dapat dikesan. Sila pastikan perkhidmatan pembaca MyKad sedang berjalan.';
{ }
if(respcode !== 0){
handleMyKadReaderError(sw12AndApiName); function getDisplayError(error) {
return if (!error) {
return getFetchErrorTitle(error);
} }
checkIfMyKadExists(data.IDNum) if (error.name === 'AbortError') {
return getFetchErrorTitle(error);
}
let message = String(error.message || '');
if (!message || message.startsWith('Failed to fetch') || message.includes('NetworkError')) {
return getFetchErrorTitle(error);
}
return message;
}
async function handleMyKadReader() {
showLoader();
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), MYKAD_READER_TIMEOUT_MS);
try {
const response = await fetch(getMyKadReaderEndpoint(), {
method: 'POST',
headers: {
'Accept': 'application/json',
},
signal: controller.signal,
});
const payload = await parseJsonResponse(response);
if (!response.ok) {
throw new Error(getReaderErrorMessage(payload));
}
if (!payload || payload.ok !== true || !payload.data) {
throw new Error(getReaderErrorMessage(payload));
}
await handleMyKadData(payload.data);
} catch (error) {
Swal.fire({
icon: 'error',
title: getDisplayError(error),
});
} finally {
clearTimeout(timeoutId);
hideLoader();
}
}
function handleMyKadData(data) {
return checkIfMyKadExists(data.nric)
.then(response => { .then(response => {
let isResponseArray = Array.isArray(response); let isResponseArray = Array.isArray(response);
resolved_response = isResponseArray ? response.pop() : response; let resolved_response = isResponseArray ? response.pop() : response;
if (resolved_response === 'no-data') { if (!resolved_response || resolved_response === 'no-data') {
populateDaftarPelangganModal(data, handleReadPhoto); populateDaftarPelangganModal(data);
} else { } else {
let input_search_noic = document.getElementById("noic"); let input_search_noic = document.querySelector("form[name='frmsearch'] input[name='noic']");
let form_search_noic = document.querySelector("form[name='frmsearch']"); let form_search_noic = document.querySelector("form[name='frmsearch']");
let getMyKadNumberWithoutHypens = myKadNumber => myKadNumber.replace(/-/g, ''); input_search_noic.value = getMyKadNumberWithoutHyphens(data.nric);
input_search_noic.value = getMyKadNumberWithoutHypens(data.IDNum);
form_search_noic.submit(); form_search_noic.submit();
} }
}) })
.catch(error => { .catch(() => {
Swal.fire({ populateDaftarPelangganModal(data);
icon: 'error',
title: error,
});
}); });
}
hideLoader(); function checkIfMyKadExists(myKadNumber) {
} let myKadNumberWithoutHyphens = getMyKadNumberWithoutHyphens(myKadNumber);
let lookupUrl = new URL(wakil_caricustomer_endpoint, window.location.origin);
lookupUrl.searchParams.set('icno', myKadNumberWithoutHyphens);
function checkIfMyKadExists(myKadNumber) { const csrfToken = document.querySelector('input[name="_token"]');
let myKadNumberWithoutHyphens = myKadNumber.replace(/-/g, '');
const url = wakil_caricustomer_endpoint + '/?icno=' + myKadNumberWithoutHyphens; return fetch(lookupUrl.toString(), {
method: 'GET',
const connectionConfig = {
headers: { headers: {
"Content-Type": "application/json",
"Accept": "application/json", "Accept": "application/json",
"X-Requested-With": "XMLHttpRequest", "X-Requested-With": "XMLHttpRequest",
"X-CSRF-Token": document.querySelector('input[name="_token"]').value "X-CSRF-Token": csrfToken ? csrfToken.value : ""
}, },
credentials: "same-origin", credentials: "same-origin",
}; }).then(async response => {
let text = await response.text();
let trimmed = (text || '').trim();
return fetch(url, connectionConfig) if (!trimmed || trimmed.charAt(0) === '<') {
.then(response => response.json()) return 'no-data';
} }
let populateDaftarPelangganModal = (data, callbackAfterPopulateDaftarPelanggan) => { try {
return JSON.parse(trimmed);
} catch (error) {
return 'no-data';
}
});
}
let populateDaftarPelangganModal = (data) => {
$('#daftar_pelanggan_mykad_reader').modal('show'); $('#daftar_pelanggan_mykad_reader').modal('show');
let modal_form = document.querySelector("#daftar_pelanggan_mykad_reader .modal-body .row.form-group"); let modal_form = document.querySelector("#daftar_pelanggan_mykad_reader .modal-body .row.form-group");
@@ -144,150 +305,99 @@ let original_photo_loader = document.querySelector("#photo-mykad").src;
['L', 'Lain-lain'], ['L', 'Lain-lain'],
]; ];
let getCodeFromValue = (needle, haystack) => {
let title_cased_text = needle.split(' ')
.map(w => w[0].toUpperCase() + w.substring(1).toLowerCase())
.join(' ');
return haystack.filter(x => x[1] == title_cased_text)[0][0];
};
let getMyKadNumberWithoutHypens = (IDNum) => {
return IDNum.replace(/-/g, '');
}
let getFormattedDateOfBirth = (date_of_birth) => {
let date_of_birth_hyphenated = date_of_birth.replace(/(\d{4})(\d{2})(\d{2})/, "$1-$2-$3");
const [year, month, day] = date_of_birth_hyphenated.split('-');
return day + '/' + month + '/' + year;
};
let getAgeFromDateOfBirth = (date_of_birth) => {
let date_of_birth_hyphenated = date_of_birth.replace(/(\d{4})(\d{2})(\d{2})/, "$1-$2-$3");
const [birth_year, birth_month, birth_day] = date_of_birth_hyphenated.split('-');
let formatBirthMonth = (birth_month) => {
let newbirthMonth = birth_month < 9 ? birth_month[1] : birth_month;
return newbirthMonth;
};
const count_leap_year = countLeapYears(birth_year, formatBirthMonth(birth_month), birth_day) - 1;
const start = new Date(date_of_birth_hyphenated),
end = new Date(),
diff = new Date(end - start),
days = ((diff / 1000 / 60 / 60 / 24) - count_leap_year) / 365;
return parseInt(days);
};
let getBumiputeraStatus = (race) => {
const non_bumiputera_ethnicity = ['INDIA', 'CINA'];
let isBumiputera = !non_bumiputera_ethnicity.includes(race);
return isBumiputera;
};
let disablePointerEventsOnClickableInputs = () => { let disablePointerEventsOnClickableInputs = () => {
modal_form.querySelector("input[name='bumiputera']").style.pointerEvents = 'none'; modal_form.querySelector("input[name='bumiputera']").style.pointerEvents = 'none';
modal_form.querySelector("input[name='warganegara']").style.pointerEvents = 'none'; modal_form.querySelector("input[name='warganegara']").style.pointerEvents = 'none';
modal_form.querySelector("select[name='bangsa']").style.pointerEvents = 'none'; modal_form.querySelector("select[name='bangsa']").style.pointerEvents = 'none';
modal_form.querySelector("select[name='agama']").style.pointerEvents = 'none'; modal_form.querySelector("select[name='agama']").style.pointerEvents = 'none';
} };
let mykad_modal = document.querySelector("#daftar_pelanggan_mykad_reader"); let mykad_modal = document.querySelector("#daftar_pelanggan_mykad_reader");
let photo_container = mykad_modal.querySelector("#photo-mykad-container"); let photo_container = mykad_modal.querySelector("#photo-mykad-container");
modal_form.querySelector("textarea[name='name']").value = data.OrgName;
modal_form.querySelector("input[name='noic']").value = getMyKadNumberWithoutHypens(data.IDNum);
modal_form.querySelector("input[name='tarikh_lahir']").value = getFormattedDateOfBirth(data.BirthDate);
modal_form.querySelector("input[name='negeri_lahir']").value = data.BirthPlace;
modal_form.querySelector("input[name='jantina']").value = data.Gender == 'LELAKI' ? 'Lelaki' : 'Perempuan';
modal_form.querySelector("input[name='warganegara']").checked = data.Citizenship === 'WARGANEGARA' ? true : false;
modal_form.querySelector("input[name='bumiputera']").checked = getBumiputeraStatus(data.Race);
modal_form.querySelector("select[name='agama']").value = getCodeFromValue(data.Religion, religions);
modal_form.querySelector("textarea[name='alamat']").value = [data.Address1, data.Address2, data.Address3].join(", ");
let getPostCode = (postCodeCity) => postCodeCity.replace(/\D/g, '');
modal_form.querySelector("input[name='poskod']").value = getPostCode(data.PostcodeCity);
let getCityName = (postCodeCity) => postCodeCity.replace(/\d/g, '');
modal_form.querySelector("input[name='daerah']").value = getCityName(data.PostcodeCity).trim();
modal_form.querySelector("input[name='negeri']").value = data.State;
modal_form.querySelector("input[name='age']").value = getAgeFromDateOfBirth(data.BirthDate);
modal_form.querySelector("select[name='bangsa']").value = getCodeFromValue(data.Race, races);
calculatePassportAge();
photo_container.classList.remove('d-none');
photo_container.classList.add('d-flex');
$('#daftar_pelanggan_mykad_reader').modal('show');
disablePointerEventsOnClickableInputs();
callbackAfterPopulateDaftarPelanggan();
}
function handleReadPhoto()
{
WebSocketManager.init("wss://localhost:5963/mykadwebsocket");
WebSocketManager.listReader(callbackDoneGetListCardReader2);
}
function callbackDoneGetListCardReader2(data, respcode)
{
if(respcode === -1){
console.log({'location': 'callbackDoneGetListCardReader', 'data' : data} );
}
let slots = data.toString().split(',');
WebSocketManager.open(doneOpenReaderConnection2, slots[0]);
}
function doneOpenReaderConnection2()
{
WebSocketManager.readPhoto(callbackAfterReadPhoto2);
}
function callbackAfterReadPhoto2(data, RespCode, RespSW12AndApiName)
{
let mykad_modal = document.querySelector("#daftar_pelanggan_mykad_reader");
let photo = mykad_modal.querySelector("#photo-mykad"); let photo = mykad_modal.querySelector("#photo-mykad");
let isDataEmpty = data modal_form.querySelector("textarea[name='name']").value = data.name || data.gmpc_name || '';
&& Object.keys(data).length === 0 modal_form.querySelector("input[name='noic']").value = getMyKadNumberWithoutHyphens(data.nric);
&& Object.getPrototypeOf(data) === Object.prototype; modal_form.querySelector("input[name='tarikh_lahir']").value = formatDateOfBirth(data.dob);
modal_form.querySelector("input[name='negeri_lahir']").value = data.birth_place || '';
modal_form.querySelector("input[name='jantina']").value = mapGender(data.gender);
modal_form.querySelector("input[name='warganegara']").checked = String(data.citizenship || '').toUpperCase() === 'WARGANEGARA';
modal_form.querySelector("input[name='bumiputera']").checked = getBumiputeraStatus(data.race);
modal_form.querySelector("select[name='agama']").value = getCodeFromValue(data.religion, religions, '05');
modal_form.querySelector("textarea[name='alamat']").value = joinAddress(data);
modal_form.querySelector("input[name='poskod']").value = data.postcode || '';
modal_form.querySelector("input[name='daerah']").value = data.city || '';
modal_form.querySelector("input[name='negeri']").value = data.state || '';
modal_form.querySelector("input[name='age']").value = data.age != null && data.age !== ''
? data.age
: getAgeFromDateOfBirth(data.dob);
modal_form.querySelector("select[name='bangsa']").value = getCodeFromValue(data.race, races, 'L');
if(isDataEmpty){ if (data.has_photo && data.photo_base64) {
let mime = data.photo_mime || 'image/jpeg';
photo.src = 'data:' + mime + ';base64,' + data.photo_base64;
photo_container.classList.remove('d-none');
photo_container.classList.add('d-flex');
setMykadPhotoFields(data.photo_base64, mime);
} else {
photo.src = original_photo_loader; photo.src = original_photo_loader;
return; photo_container.classList.add('d-none');
photo_container.classList.remove('d-flex');
setMykadPhotoFields('', '');
} }
photo.src = 'data:image/jpeg;base64, ' + data.Base64Photo; disablePointerEventsOnClickableInputs();
} $(modal_form.querySelector("input[name='poskod']")).trigger('change');
};
let hideMykadPhoto = () => { let hideMykadPhoto = () => {
WebSocketManager.close(() => console.log('Websocket closed'));
let photo = document.getElementById("photo-mykad"); let photo = document.getElementById("photo-mykad");
let photo_container = document.getElementById("photo-mykad-container"); let photo_container = document.getElementById("photo-mykad-container");
if (photo) {
photo.src = original_photo_loader; photo.src = original_photo_loader;
}
if (photo_container) {
photo_container.classList.add('d-none'); photo_container.classList.add('d-none');
photo_container.classList.remove('d-flex'); photo_container.classList.remove('d-flex');
}
setMykadPhotoFields('', '');
}; };
button_read_mykad.addEventListener("click", handleMyKadReader); function setMykadPhotoFields(base64, mime) {
button_close_modal.addEventListener("click", hideMykadPhoto); let form = document.querySelector("#daftar_pelanggan_mykad_reader form")
|| document.getElementById("form-mykad-reader");
let photoInput = (form && form.querySelector("input[name='photo_base64']"))
|| document.getElementById("photo_base64");
let mimeInput = (form && form.querySelector("input[name='photo_mime']"))
|| document.getElementById("photo_mime");
if (photoInput) {
photoInput.value = base64 || '';
}
if (mimeInput) {
mimeInput.value = mime || '';
}
}
function copyMykadPhotoFromImage() {
let photo = document.querySelector("#daftar_pelanggan_mykad_reader #photo-mykad");
if (!photo || !photo.src || photo.src.indexOf("base64,") === -1) {
return;
}
let parts = photo.src.split("base64,");
let mimeMatch = photo.src.match(/^data:([^;]+);/);
setMykadPhotoFields(parts[1] || "", mimeMatch ? mimeMatch[1] : "image/jpeg");
}
if (button_read_mykad) {
button_read_mykad.addEventListener("click", handleMyKadReader);
}
if (button_close_modal) {
button_close_modal.addEventListener("click", hideMykadPhoto);
}
+2 -2
View File
@@ -7,7 +7,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="icon" href="{{ asset('img/icon_pkb.png') }}"> <link rel="icon" href="{{ asset('img/icon_pkb.png') }}">
<title>arOS v2.2</title> <title>arOS {{ config('app.version') }}</title>
<style> <style>
@keyframes slide { @keyframes slide {
@@ -82,7 +82,7 @@
<div class="card-header pb-0 text-left"> <div class="card-header pb-0 text-left">
<div style="display: flex; justify-content:space-between; align-items:center"> <div style="display: flex; justify-content:space-between; align-items:center">
<h1 class="font-weight-bolder text-info text-gradient" style="word-break: break-word; white-space: nowrap;"> <h1 class="font-weight-bolder text-info text-gradient" style="word-break: break-word; white-space: nowrap;">
Kaunter arOS v2.2 Kaunter arOS {{ config('app.version') }}
</h1> </h1>
<img src="{{ asset('img/icon_pkb.png') }}" alt="logo-arrahn" style="width: 85px; margin-left: 30px;"> <img src="{{ asset('img/icon_pkb.png') }}" alt="logo-arrahn" style="width: 85px; margin-left: 30px;">
</div> </div>
@@ -0,0 +1,137 @@
@props([
'id',
'columns' => [],
'exportable' => true,
'filename' => '',
'skipExport' => ['Bil', 'Cetak'],
])
<style>
.erahn-datatable {
width: 100%;
max-width: 100%;
}
.erahn-datatable .dataTables_wrapper {
width: 100%;
overflow-x: auto;
}
.erahn-datatable table {
width: 100% !important;
margin: 0;
}
.erahn-datatable table th,
.erahn-datatable table td {
white-space: nowrap;
vertical-align: middle;
}
.erahn-datatable .dt-buttons {
margin: 0 0 8px 8px;
}
.erahn-datatable .dt-button {
border: 0;
border-radius: 4px;
padding: 6px 12px;
margin-left: 4px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
background: #17a2b8;
color: #fff;
}
.erahn-datatable .dt-button:hover {
opacity: 0.9;
}
.erahn-datatable .buttons-excel {
background: #28a745;
}
.erahn-datatable .buttons-csv {
background: #6c757d;
}
</style>
<script>
if (typeof window.initErahnDatatable !== 'function') {
window.initErahnDatatable = function (selector, options) {
var $table = $(selector);
var exportable = $table.data('exportable');
var filename = $table.data('export-filename') || $table.attr('id') || 'export';
var defaults = {
scrollX: true,
autoWidth: false,
language: {
emptyTable: 'Tiada rekod'
}
};
if ((exportable === undefined || exportable === 1 || exportable === true) &&
$.fn.dataTable && $.fn.dataTable.Buttons) {
defaults.dom = 'lBfrtip';
defaults.buttons = [
{
extend: 'excelHtml5',
text: 'Excel',
className: 'buttons-excel',
title: filename,
filename: filename,
exportOptions: {
columns: ':not(.no-export)',
format: {
body: function (data) {
return $('<div>').html(data).text().replace(/\s+/g, ' ').trim();
}
}
}
},
{
extend: 'csvHtml5',
text: 'CSV',
className: 'buttons-csv',
bom: true,
title: filename,
filename: filename,
exportOptions: {
columns: ':not(.no-export)',
format: {
body: function (data) {
return $('<div>').html(data).text().replace(/\s+/g, ' ').trim();
}
}
}
}
];
}
return $table.DataTable($.extend(true, {}, defaults, options || {}));
};
}
if (typeof window.adjustErahnDatatables !== 'function') {
window.adjustErahnDatatables = function () {
if (!$.fn.dataTable) {
return;
}
setTimeout(function () {
$.fn.dataTable.tables({ visible: true, api: true }).columns.adjust();
}, 50);
};
}
</script>
<div class="erahn-datatable">
<table
id="{{ $id }}"
{{ $attributes->merge(['class' => 'table table-striped table-hover nowrap']) }}
style="width:100%"
data-exportable="{{ $exportable ? 1 : 0 }}"
data-export-filename="{{ $filename !== '' ? $filename : $id }}"
>
<thead class="thead-dark">
<tr>
@foreach ($columns as $column)
<th @if(in_array($column, $skipExport, true)) class="no-export" @endif>{{ $column }}</th>
@endforeach
</tr>
</thead>
<tbody></tbody>
{{ $slot }}
</table>
</div>
@@ -0,0 +1,44 @@
@props([
'showCopies' => true,
])
<style>
.print-footer {
margin-top: 14px;
font-family: Arial, Helvetica, sans-serif;
color: #222;
}
.print-footer .copies {
margin-top: 10px;
font-size: 10px;
}
.print-footer .copies span {
display: inline-block;
border: 1px solid #111;
width: 11px;
height: 11px;
margin-right: 4px;
vertical-align: -1px;
}
.print-footer .notes {
margin-top: 10px;
font-size: 9.5px;
line-height: 1.45;
}
.print-footer .notes b { font-size: 10px; }
.print-footer .notes p { margin: 2px 0; }
</style>
<div {{ $attributes->merge(['class' => 'print-footer']) }}>
@if($showCopies)
<div class="copies">
Salinan: <span></span> Kad Pengenalan &nbsp;&nbsp; <span></span> Akaun Bank/ Slip/ Penyata Bank
</div>
@endif
@if(trim($slot) !== '')
<div class="notes">
{{ $slot }}
</div>
@endif
</div>
@@ -0,0 +1,101 @@
@props([
'cawangan' => '',
'kodcaw' => '',
'alamat1' => '',
'alamat2' => '',
'notel' => '',
'tarikh' => '',
'norujukan' => '',
'rujukanLabel' => 'No. Rujukan SAG',
'showDummyBadge' => false,
])
<style>
.print-header {
display: grid;
grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
align-items: start;
column-gap: 12px;
border-bottom: 2px solid #111;
padding-bottom: 8px;
font-family: Arial, Helvetica, sans-serif;
color: #111;
}
.print-header .org {
min-width: 0;
}
.print-header .org p {
margin: 0;
font-size: 10px;
line-height: 1.35;
}
.print-header .org .name {
font-size: 11px;
font-weight: bold;
}
.print-header .logos {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
padding-top: 2px;
}
.print-header .logos img {
display: block;
height: 40px;
width: auto;
max-width: 70px;
object-fit: contain;
}
.print-header .meta {
text-align: right;
font-size: 10px;
line-height: 1.4;
}
.print-header .meta .label { color: #444; }
.print-header .dummy-badge {
display: inline-block;
margin-top: 4px;
padding: 1px 6px;
border: 1px solid #c0392b;
color: #c0392b;
font-size: 9px;
font-weight: bold;
letter-spacing: 0.08em;
}
@media print {
.print-header .dummy-badge { display: none !important; }
}
</style>
<div {{ $attributes->merge(['class' => 'print-header']) }}>
<div class="org">
<p class="name">KOPERASI PERMODALAN KELANTAN BERHAD (D-5-0228)</p>
<p>Ar-Rahn {{ $cawangan }}</p>
<p>{{ $alamat1 }}</p>
<p>{{ $alamat2 }}</p>
<p>Tel: {{ $notel }}</p>
</div>
<div class="logos">
<img src="{{ asset('img/kopkb.png') }}" alt="KPKB">
<img src="{{ asset('img/logo-arrahn.jpeg') }}" alt="Ar-Rahn">
</div>
<div class="meta">
@if($kodcaw || $cawangan)
<div>
<span class="label">Cawangan</span><br>
<b>{{ trim(implode(' — ', array_filter([$kodcaw, $cawangan]))) }}</b>
</div>
@endif
@if($tarikh)
<div style="{{ ($kodcaw || $cawangan) ? 'margin-top:6px' : '' }}"><span class="label">Tarikh</span><br><b>{{ $tarikh }}</b></div>
@endif
@if($norujukan)
<div style="margin-top:6px"><span class="label">{{ $rujukanLabel }}</span><br><b>{{ $norujukan }}</b></div>
@endif
{{ $slot }}
@if($showDummyBadge)
<div class="dummy-badge">CONTOH SAHAJA</div>
@endif
</div>
</div>
@@ -0,0 +1,25 @@
<style>
.print-watermark {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
pointer-events: none;
z-index: 0;
}
.print-watermark img {
height: 160px;
max-width: 240px;
object-fit: contain;
opacity: 0.12;
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
</style>
<div class="print-watermark" aria-hidden="true">
<img src="{{ asset('img/logo_kad.jpeg') }}" alt="">
<img src="{{ asset('img/logo-arrahn.jpeg') }}" alt="">
</div>
+7 -4
View File
@@ -210,15 +210,17 @@
<div class="modal fade" id="daftar_pelanggan_mykad_reader" tabindex="-1" data-backdrop="static" aria-labelledby="ModalLabel" aria-hidden="true"> <div class="modal fade" id="daftar_pelanggan_mykad_reader" tabindex="-1" data-backdrop="static" aria-labelledby="ModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg"> <div class="modal-dialog modal-lg">
<div class="modal-content"> <div class="modal-content">
<form id="form-mykad-reader" action="{{ route('customer.store') }}" onsubmit="verifycustomerMyKadReader()" method="POST">
@csrf
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title" id="ModalLabel">Daftar Pelanggan</h5> <h5 class="modal-title" id="ModalLabel">Daftar Pelanggan</h5>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<form action="{{ route('customer.store') }}" onsubmit="verifycustomerMyKadReader()" method="POST">
@csrf
<div class="row form-group"> <div class="row form-group">
<div class="col-md-12 d-flex justify-content-center pb-3 d-none" id="photo-mykad-container"> <div class="col-md-12 d-flex justify-content-center pb-3 d-none" id="photo-mykad-container">
<img src="{{ asset('img/spinner-loader.gif') }}" alt="" id="photo-mykad" width="150" height="200" object-fit="cover"> <img src="{{ asset('img/spinner-loader.gif') }}" alt="" id="photo-mykad" width="150" height="200" object-fit="cover">
<input type="hidden" name="photo_base64" id="photo_base64" value="">
<input type="hidden" name="photo_mime" id="photo_mime" value="">
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<div class="row form-group"> <div class="row form-group">
@@ -811,6 +813,8 @@
function verifycustomerMyKadReader(){ function verifycustomerMyKadReader(){
copyMykadPhotoFromImage();
let myKadModal = $('#daftar_pelanggan_mykad_reader'); let myKadModal = $('#daftar_pelanggan_mykad_reader');
// Construct URLSearchParams object instance from current URL querystring. // Construct URLSearchParams object instance from current URL querystring.
@@ -1277,8 +1281,7 @@
let wakil_caricustomer_endpoint = "{{ route('wakil.caricustomer') }}"; let wakil_caricustomer_endpoint = "{{ route('wakil.caricustomer') }}";
let mykad_reader_driver_endpoint = "{{ route('mykadreader.driver') }}"; let mykad_reader_driver_endpoint = "{{ route('mykadreader.driver') }}";
</script> </script>
<script src="{{ asset('js/iris-mykad-websocket-client-api.js') }}"></script> <script src="{{ asset('js/mykad-reader-customer.js') }}?v=4"></script>
<script src="{{ asset('js/mykad-reader-customer.js') }}"></script>
<script src="{{ asset('js/postcode-concerns.js') }}"></script> <script src="{{ asset('js/postcode-concerns.js') }}"></script>
@endpush @endpush
@@ -21,6 +21,22 @@
</ol> </ol>
</nav> </nav>
@if(session()->has('message'))
<div class="alert alert-success">{{ session('message') }}</div>
@endif
@if(!empty($customer_info['photo_base64']))
<div class="form-group row col-md-12">
<div class="col-md-12 d-flex justify-content-center pb-3">
<img src="data:{{ $customer_info['photo_mime'] ?? 'image/jpeg' }};base64,{{ $customer_info['photo_base64'] }}"
alt="Gambar MyKad"
width="120"
height="160"
style="object-fit: cover;">
</div>
</div>
@endif
<div class="form-group row col-md-12"> <div class="form-group row col-md-12">
<div class="col-md-6"> <div class="col-md-6">
<div class="form-group row"> <div class="form-group row">
+12 -12
View File
@@ -229,15 +229,17 @@
<div class="modal fade" id="daftar_pelanggan_mykad_reader" tabindex="-1" data-backdrop="static" aria-labelledby="ModalLabel" aria-hidden="true"> <div class="modal fade" id="daftar_pelanggan_mykad_reader" tabindex="-1" data-backdrop="static" aria-labelledby="ModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg"> <div class="modal-dialog modal-lg">
<div class="modal-content"> <div class="modal-content">
<form id="form-mykad-reader" action="{{ route('customer.store') }}" onsubmit="verifycustomerMyKadReader()" method="POST">
@csrf
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title" id="ModalLabel">Daftar Pelanggan</h5> <h5 class="modal-title" id="ModalLabel">Daftar Pelanggan</h5>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<form action="{{ route('customer.store') }}" onsubmit="verifycustomerMyKadReader()" method="POST">
@csrf
<div class="row form-group"> <div class="row form-group">
<div class="col-md-12 d-flex justify-content-center pb-3 d-none" id="photo-mykad-container"> <div class="col-md-12 d-flex justify-content-center pb-3 d-none" id="photo-mykad-container">
<img src="{{ asset('img/spinner-loader.gif') }}" alt="" id="photo-mykad" width="150" height="200" object-fit="cover"> <img src="{{ asset('img/spinner-loader.gif') }}" alt="" id="photo-mykad" width="150" height="200" object-fit="cover">
<input type="hidden" name="photo_base64" id="photo_base64" value="">
<input type="hidden" name="photo_mime" id="photo_mime" value="">
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<div class="row form-group"> <div class="row form-group">
@@ -495,15 +497,14 @@
</div> </div>
<script> <script>
var api_url = "{{ api_url_for_browser() }}";
var search = "<?php echo $search ?>";
var type_id = "<?php echo $idtype ?>";
var blacklist_search = "<?php echo $blacklist_search ?>";
var isAnggota = @json($isAnggota ?? false); var isAnggota = @json($isAnggota ?? false);
var isPelanggan = @json($isPelanggan ?? false); var isPelanggan = @json($isPelanggan ?? false);
var showAssignAnggotaBtn = @json($showAssignAnggotaBtn ?? false); var showAssignAnggotaBtn = @json($showAssignAnggotaBtn ?? false);
var showDaftarPelangganBtn = @json($showDaftarPelangganBtn ?? false); var showDaftarPelangganBtn = @json($showDaftarPelangganBtn ?? false);
var api_url = "{{ api_url_for_browser() }}";
var search = "<?php echo $search ?>";
var type_id = "<?php echo $idtype ?>";
var blacklist_search = "<?php echo $blacklist_search ?>";
$(document).ready(function(){ $(document).ready(function(){
// Datepicker // Datepicker
$('#passport_tarikh_lahir').datepicker({ $('#passport_tarikh_lahir').datepicker({
@@ -525,7 +526,6 @@
}); });
// Logik amaran SweetAlert // Logik amaran SweetAlert
if("{{ auth()->user()->cawangan }}" == 'X0301'){
if (search === true) { if (search === true) {
if (blacklist_search === true) { if (blacklist_search === true) {
Swal.fire({ Swal.fire({
@@ -555,8 +555,7 @@
text: 'Sila Daftar Pelanggan.' text: 'Sila Daftar Pelanggan.'
}); });
} }
} }
}
}); });
@@ -893,6 +892,8 @@
function verifycustomerMyKadReader(){ function verifycustomerMyKadReader(){
copyMykadPhotoFromImage();
let myKadModal = $('#daftar_pelanggan_mykad_reader'); let myKadModal = $('#daftar_pelanggan_mykad_reader');
// Construct URLSearchParams object instance from current URL querystring. // Construct URLSearchParams object instance from current URL querystring.
@@ -1418,8 +1419,7 @@ document.getElementsByName('form-store')[0].addEventListener('keypress', functio
let wakil_caricustomer_endpoint = "{{ route('wakil.caricustomer') }}"; let wakil_caricustomer_endpoint = "{{ route('wakil.caricustomer') }}";
let mykad_reader_driver_endpoint = "{{ route('mykadreader.driver') }}"; let mykad_reader_driver_endpoint = "{{ route('mykadreader.driver') }}";
</script> </script>
<script src="{{ asset('js/iris-mykad-websocket-client-api.js') }}"></script> <script src="{{ asset('js/mykad-reader-customer.js') }}?v=4"></script>
<script src="{{ asset('js/mykad-reader-customer.js') }}"></script>
<script src="{{ asset('js/postcode-concerns.js') }}"></script> <script src="{{ asset('js/postcode-concerns.js') }}"></script>
@endpush @endpush
+139 -93
View File
@@ -22,6 +22,22 @@
</ol> </ol>
</nav> </nav>
@if(session()->has('message'))
<div class="alert alert-success">{{ session('message') }}</div>
@endif
@if(!empty($customer_info['photo_base64']))
<div class="form-group row col-md-12">
<div class="col-md-12 d-flex justify-content-center pb-3">
<img src="data:{{ $customer_info['photo_mime'] ?? 'image/jpeg' }};base64,{{ $customer_info['photo_base64'] }}"
alt="Gambar MyKad"
width="120"
height="160"
style="object-fit: cover;">
</div>
</div>
@endif
<div class="form-group row col-md-12"> <div class="form-group row col-md-12">
<div class="col-md-6"> <div class="col-md-6">
<div class="form-group row"> <div class="form-group row">
@@ -75,7 +91,8 @@
->json(); ->json();
@endphp @endphp
<input type="text" class="form-control" name="outstanding_semasa" <input type="text" class="form-control" name="outstanding_semasa"
id="outstanding_semasa" readonly value="{{ number_format($outstanding_semasa, 2) }}"> id="outstanding_semasa" readonly
value="{{ number_format($outstanding_semasa, 2) }}">
</div> </div>
</div> </div>
@@ -154,10 +171,15 @@
<i class="fas fa-info-circle me-1"></i> Maklumat Pelanggan <i class="fas fa-info-circle me-1"></i> Maklumat Pelanggan
</button> </button>
<button class="btn btn-info d-flex align-items-center" data-toggle="modal"
data-target="#export_maklumat_modal" style="margin-right: 12px;">
<i class="fas fa-print me-1"></i> Cetak Maklumat Pelanggan
</button>
<!-- Butang Assign Sebagai Anggota --> <!-- Butang Assign Sebagai Anggota -->
@if (isset($showAssignAnggotaBtn) && $showAssignAnggotaBtn && auth()->user()->cawangan == 'X0301') @if (isset($showAssignAnggotaBtn) && $showAssignAnggotaBtn)
<div class="m-0 p-2"> <div class="m-0 p-2">
<button type="button" class="btn btn-success d-flex align-items-center" <button type="button" class="btn btn-success d-flex align-items-center"
data-noic="{{ $noic }}" onclick="assignAnggota(this)"> data-noic="{{ $noic }}" onclick="assignAnggota(this)">
@@ -346,7 +368,7 @@
</div> </div>
<div class="nav nav-tabs" id="nav-tab" role="tablist"> <div class="nav nav-tabs" id="nav-tab" role="tablist">
@php @php
$activate_tab = request()->session()->pull('activate_tab', 'SAG_lama'); $activate_tab = request()->session()->pull('activate_tab', 'SAG_baru');
$activate_tab_SAG_baru = $activate_tab === 'SAG_baru' ? 'active' : ''; $activate_tab_SAG_baru = $activate_tab === 'SAG_baru' ? 'active' : '';
$aria_selected_SAG_baru = $activate_tab === 'SAG_baru' ? 'true' : 'false'; $aria_selected_SAG_baru = $activate_tab === 'SAG_baru' ? 'true' : 'false';
@@ -365,91 +387,41 @@
</nav> </nav>
<div class="tab-content" id="navtawarruqdannottawarruq"> <div class="tab-content" id="navtawarruqdannottawarruq">
<div class="tab-pane fade {{ $activate_pane_SAG_baru }}" id="nav-tawarruq" role="tabpanel" aria-labelledby="nav-tawarruq-tab"> <div class="tab-pane fade {{ $activate_pane_SAG_baru }}" id="nav-tawarruq" role="tabpanel" aria-labelledby="nav-tawarruq-tab">
<table class="table table-striped table-hover" id="myTable"> <x-datatable
<thead class="thead-dark"> id="myTable"
<tr> filename="SAG_Baru"
<th>Bil</th> :columns="['Bil', 'No Rujukan', 'Tarikh Pembiayaan', 'Tarikh Matang', 'Baki Harga Jualan', 'Baki Pembiayaan', 'Margin', 'Tempoh', 'Keuntungan', 'Lelong', 'Borang Online']"
<th>No Rujukan</th> />
<th>Tarikh Pembiayaan</th>
<th>Tarikh Matang</th>
<th>Baki Harga Jualan</th>
<th>Baki Pembiayaan</th>
<th>Margin</th>
<th>Tempoh</th>
<th>Keuntungan</th>
<th>Lelong</th>
</tr>
</thead>
</table>
</div> </div>
<div class="tab-pane fade {{ $activate_pane_SAG_lama }}" id="nav-xtawarruq" role="tabpanel" aria-labelledby="nav-xtawarruq-tab"> <div class="tab-pane fade {{ $activate_pane_SAG_lama }}" id="nav-xtawarruq" role="tabpanel" aria-labelledby="nav-xtawarruq-tab">
<table id="xtawarruq" class="table table-striped table-hover"> <x-datatable
<thead class="thead-dark"> id="xtawarruq"
{{-- <tr> filename="SAG_Lama"
<th>Bil</th> :columns="['Bil', 'No Rujukan', 'Tarikh Pembiayaan', 'Tarikh Matang', 'Baki Harga Jualan', 'Baki Pembiayaan', 'Margin', 'Tempoh', 'Keuntungan', 'Lelong', 'Borang Online']"
<th>No Rujukan</th> />
<th>Tarikh Gadai</th>
<th>Tarikh Matang</th>
<th>Pembiayaan</th>
<th>Margin</th>
<th>Tempoh</th>
<th>Lelong</th>
</tr> --}}
<tr>
<th>Bil</th>
<th>No Rujukan</th>
<th>Tarikh Pembiayaan</th>
<th>Tarikh Matang</th>
<th>Baki Harga Jualan</th>
<th>Baki Pembiayaan</th>
<th>Margin</th>
<th>Tempoh</th>
<th>Keuntungan</th>
<th>Lelong</th>
</tr>
</thead>
</table>
</div> </div>
</div> </div>
{{-- Sudah Tebus --}} {{-- Sudah Tebus --}}
<table id="dahtebusTable" class="table table-striped table-hover"> <x-datatable
<thead class="thead-dark"> id="dahtebusTable"
<tr> filename="SAG_Sudah_Tebus"
<th>Bil</th> :columns="['Bil', 'No Rujukan', 'Tarikh Penyelesaian Pembiayaan', 'Baki Pembiayaan', 'Margin', 'Tempoh']"
<th>No Rujukan</th> />
<th>Tarikh Penyelesaian Pembiayaan</th>
<th>Baki Pembiayaan</th>
<th>Margin</th>
<th>Tempoh</th>
</tr>
</thead>
</table>
<table id="lelongTable" class="table table-striped table-hover"> <x-datatable
<thead class="thead-dark"> id="lelongTable"
<tr> filename="SAG_Lelong"
<th>Bil</th> :columns="['Bil', 'No Rujukan', 'Tarikh Lelong', 'Tarikh Tuntut', 'Pembiayaan', 'Harga Jual', 'Jum Tuntut/Bayar', 'Keuntungan', 'Caj Lelong', 'Status Tuntutan', 'Cetak']"
<th>No Rujukan</th> />
<th>Tarikh Lelong</th>
<th>Tarikh Tuntut</th>
<th>Pembiayaan</th>
<th>Harga Jual</th>
<th>Jum Tuntut/Bayar</th>
<th>Keuntungan</th>
<th>Caj Lelong</th>
<th>Status Tuntutan</th>
<th>Cetak</th>
</tr>
</thead>
</table>
<div class="text-center"> <div class="text-center">
<button class="btn btn-primary" onclick="ubahfunc(this)" id="button-edit-gadaian">Ubah</button> <button class="btn btn-primary" onclick="ubahfunc(this)" id="button-edit-gadaian">Ubah</button>
<button class="btn btn-primary" onclick="hapusfunc()">Hapus</button> <button class="btn btn-primary" onclick="hapusfunc()">Hapus</button>
<button class="btn btn-primary" onclick="histfunc()">Rekod Pembiayaan</button> <button class="btn btn-primary" onclick="histfunc()">Rekod Pembiayaan</button>
<button class="btn btn-primary" onclick="removeansuran(this)" id="button-remove-ansuran">Hapus Ansuran</button> <button class="btn btn-primary" onclick="removeansuran(this)" id="button-remove-ansuran">Hapus Ansuran</button>
<button class="btn btn-info" type="button" onclick="cetakBorangOnlineSelected()">Cetak Borang Online</button>
</div> </div>
</div> </div>
@@ -668,6 +640,33 @@
</div> </div>
</div> </div>
<div class="modal fade" id="export_maklumat_modal" tabindex="-1" aria-labelledby="export_maklumat_modalLabel" aria-hidden="true">
<div class="modal-dialog modal-xl modal-dialog-centered" style="max-width: 920px;">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="export_maklumat_modalLabel">Eksport Maklumat Pelanggan</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body p-0" style="height: 80vh; background: #e8eaed;">
<iframe id="export_maklumat_iframe"
src="about:blank"
title="Maklumat Pelanggan"
style="width: 100%; height: 100%; border: 0;"></iframe>
</div>
</div>
</div>
</div>
<script>
$('#export_maklumat_modal').on('show.bs.modal', function () {
$('#export_maklumat_iframe').attr('src', '{{ route('customer.print.maklumat', ['noic' => $noic]) }}');
});
$('#export_maklumat_modal').on('hidden.bs.modal', function () {
$('#export_maklumat_iframe').attr('src', 'about:blank');
});
</script>
<div class="modal fade" id="opt_modal" tabindex="-1" aria-labelledby="opt_modalLabel" aria-hidden="true"> <div class="modal fade" id="opt_modal" tabindex="-1" aria-labelledby="opt_modalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-lg"> <div class="modal-dialog modal-dialog-centered modal-lg">
<div class="modal-content"> <div class="modal-content">
@@ -712,8 +711,8 @@
</div> </div>
</div> </div>
<script> <script>
var api_arrahnbid = "<?php echo config('app.api_arrahnbid'); ?>"; var api_arrahnbid = "<?php echo config('app.api_arrahnbid') ?>";
var api_url = "{{ api_url_for_browser() }}"; var api_url = "{{ api_url_for_browser() }}";
var kodcaw = "{{ $cawangan_info['kodcaw'] }}"; var kodcaw = "{{ $cawangan_info['kodcaw'] }}";
var tagarrahnbid = "{{ $cawangan_info['tagarrahnbid'] }}"; var tagarrahnbid = "{{ $cawangan_info['tagarrahnbid'] }}";
@@ -772,30 +771,34 @@
} }
}); });
} }
}); });
$('a[data-toggle="tab"]').on('shown.bs.tab', function(e) { $('a[data-toggle="tab"]').on('shown.bs.tab', function () {
// here is the new selected tab id if (typeof adjustErahnDatatables === 'function') {
var selectedTabId = e.target.id; adjustErahnDatatables();
}
}); });
var tablegadai = $('#myTable').DataTable({ var tablegadai = initErahnDatatable('#myTable', {
"scrollX": false, order: [[1, 'desc']],
order: [ columnDefs: [
[1, 'desc'] { targets: 0, orderable: false, width: '40px' },
{ targets: -1, orderable: false, searchable: false, width: '70px' }
] ]
}); });
var xtawarruq = $('#xtawarruq').DataTable({ var xtawarruq = initErahnDatatable('#xtawarruq', {
"scrollX": false columnDefs: [
{ targets: 0, orderable: false, width: '40px' }
]
}); });
var tabledahtebus = $('#dahtebusTable').DataTable({}); var tabledahtebus = initErahnDatatable('#dahtebusTable');
var tablelelong = $('#lelongTable').DataTable({ var tablelelong = initErahnDatatable('#lelongTable', {
"scrollX": false columnDefs: [
{ targets: -1, orderable: false, searchable: false, width: '80px' }
]
}); });
var historybayarandatatable = $('#tablehistorybayaran').DataTable({ var historybayarandatatable = $('#tablehistorybayaran').DataTable({
@@ -810,6 +813,41 @@
minimumFractionDigits: 2 minimumFractionDigits: 2
}); });
function urlBorangOnline(norujukan) {
return '{{ url("/gadai/borang-pengesahan-online") }}/' + encodeURIComponent(norujukan);
}
function cetakBorangOnlineCell(gadai_detail) {
var cara = String(gadai_detail['cara_terima'] || '').toUpperCase();
if (cara !== 'ONLINE') {
return '';
}
return '<a class="btn btn-sm btn-info" href="' + urlBorangOnline(gadai_detail['norujukan']) +
'" target="_blank" title="Cetak borang pengesahan online"><i class="fas fa-print"></i></a>';
}
function cetakBorangOnlineSelected() {
var selected = $('input[name="belum"]:checked');
if (!selected.length) {
Swal.fire({
icon: 'warning',
title: 'Sila pilih SAG',
text: 'Pilih pembiayaan untuk cetak borang pengesahan.'
});
return;
}
var cara = String(selected.data('cara-terima') || '').toUpperCase();
if (cara !== 'ONLINE') {
Swal.fire({
icon: 'info',
title: 'Bukan pembiayaan online',
text: 'Borang ini hanya untuk SAG yang terima pembiayaan secara online.'
});
return;
}
window.open(urlBorangOnline(selected.val()), '_blank');
}
function getGadaiByNorujukan(norujukan) { function getGadaiByNorujukan(norujukan) {
console.log(norujukan); console.log(norujukan);
let gadai_tawarruq = fetch(api_url + '/api/gadai/norujukan/' + norujukan); let gadai_tawarruq = fetch(api_url + '/api/gadai/norujukan/' + norujukan);
@@ -1012,7 +1050,8 @@
); );
alert alert
(xhr (xhr
.responseText); .responseText
);
} }
}); });
}); });
@@ -1210,8 +1249,8 @@
let table_norujukan = gadai_detail["norujukan"]; let table_norujukan = gadai_detail["norujukan"];
tablegadai.row.add([ tablegadai.row.add([
'<input type="radio" value=' + gadai_detail["norujukan"] + '<input type="radio" value="' + gadai_detail["norujukan"] +
' name="belum"/>', '" name="belum" data-cara-terima="' + (gadai_detail['cara_terima'] || '') + '"/>',
'<a href="#" onclick="checkserah(this)" data-pinjaman=' + '<a href="#" onclick="checkserah(this)" data-pinjaman=' +
gadai_detail['pinjaman'] + ' data-hargajualan=' + gadai_detail['pinjaman'] + ' data-hargajualan=' +
gadai_detail['hargajualan'] + ' data-norujukan=' + gadai_detail['hargajualan'] + ' data-norujukan=' +
@@ -1228,6 +1267,7 @@
'RM ' + formatter.format(upahsemasa['pendapatan']), 'RM ' + formatter.format(upahsemasa['pendapatan']),
'<div style="white-space: nowrap; color:red; font-weight: bolder">' + '<div style="white-space: nowrap; color:red; font-weight: bolder">' +
taglel + '</div>', taglel + '</div>',
cetakBorangOnlineCell(gadai_detail),
]).draw(); ]).draw();
} }
} else if (jenistebus == "sudah_tebus") { } else if (jenistebus == "sudah_tebus") {
@@ -1547,6 +1587,9 @@
success: function(data) { success: function(data) {
$('#myTable').parents('div.dataTables_wrapper').first().show(); $('#myTable').parents('div.dataTables_wrapper').first().show();
gadaiTable(data, "belum_tebus"); gadaiTable(data, "belum_tebus");
if (typeof adjustErahnDatatables === 'function') {
adjustErahnDatatables();
}
} }
}); });
@@ -2803,6 +2846,9 @@
url: api_url + "/api/customers/" + noic + "/gadai/belum_tebus/" + kodcaw, url: api_url + "/api/customers/" + noic + "/gadai/belum_tebus/" + kodcaw,
success: function(data) { success: function(data) {
gadaiTable(data, "belum_tebus"); gadaiTable(data, "belum_tebus");
if (typeof adjustErahnDatatables === 'function') {
adjustErahnDatatables();
}
} }
}); });
} }
@@ -0,0 +1,354 @@
<!DOCTYPE html>
<html lang="ms">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Dummy Borang Kelulusan Batch Pembiayaan Online</title>
<style>
* { box-sizing: border-box; }
body {
margin: 0;
background: #e8eaed;
font-family: Arial, Helvetica, sans-serif;
color: #111;
}
.toolbar {
position: sticky;
top: 0;
z-index: 10;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 10px 16px;
background: #1f3a5f;
color: #fff;
font-size: 13px;
}
.toolbar b { letter-spacing: 0.04em; }
.toolbar .hint { opacity: 0.85; }
.toolbar button {
border: 0;
background: #fff;
color: #1f3a5f;
font-weight: bold;
padding: 8px 16px;
cursor: pointer;
border-radius: 3px;
}
.sheet-wrap {
padding: 24px 0 48px;
}
.sheet {
width: 210mm;
min-height: 297mm;
margin: 0 auto;
background: #fff;
padding: 14mm 12mm 16mm;
box-shadow: 0 2px 12px rgba(0,0,0,.18);
}
.title {
text-align: center;
margin: 14px 0 4px;
}
.title h1 {
margin: 0;
font-size: 14px;
letter-spacing: 0.04em;
text-transform: uppercase;
}
.title p {
margin: 4px 0 0;
font-size: 10px;
color: #333;
}
.section {
margin-top: 12px;
}
.section-title {
font-size: 11px;
font-weight: bold;
background: #eef1f4;
border: 1px solid #111;
border-bottom: 0;
padding: 5px 8px;
text-transform: uppercase;
}
table.fields {
width: 100%;
border-collapse: collapse;
font-size: 11px;
}
table.fields th,
table.fields td {
border: 1px solid #111;
padding: 6px 8px;
vertical-align: top;
text-align: left;
}
table.fields th {
width: 22%;
font-weight: normal;
background: #fafbfc;
color: #222;
}
table.fields td.amt {
font-size: 14px;
font-weight: bold;
letter-spacing: 0.02em;
}
table.lines {
width: 100%;
border-collapse: collapse;
font-size: 9.5px;
}
table.lines th,
table.lines td {
border: 1px solid #111;
padding: 4px 5px;
vertical-align: top;
}
table.lines thead th {
background: #eef1f4;
font-weight: bold;
text-align: center;
text-transform: uppercase;
font-size: 8.5px;
letter-spacing: 0.02em;
}
table.lines td.num { text-align: center; }
table.lines td.amt { text-align: right; white-space: nowrap; }
table.lines tfoot td {
font-weight: bold;
background: #fafbfc;
}
.akuan {
font-size: 11px;
line-height: 1.55;
border: 1px solid #111;
padding: 10px 12px;
}
.akuan ol {
margin: 6px 0 0;
padding-left: 18px;
}
.akuan li { margin-bottom: 4px; }
.signs {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
margin-top: 16px;
}
.sign-box {
border: 1px solid #111;
min-height: 160px;
padding: 8px 10px;
font-size: 10px;
}
.sign-box .role {
font-weight: bold;
text-align: center;
text-transform: uppercase;
border-bottom: 1px solid #ccc;
padding-bottom: 4px;
margin-bottom: 8px;
}
.sign-line {
margin-top: 72px;
border-top: 1px solid #111;
padding-top: 6px;
}
.sign-line p { margin: 2px 0; }
@page {
size: A4;
margin: 10mm;
}
@media print {
body { background: #fff; }
.toolbar { display: none !important; }
.sheet-wrap { padding: 0; }
.sheet {
width: auto;
min-height: 0;
margin: 0;
padding: 0;
box-shadow: none;
}
}
</style>
</head>
<body>
@php
$cawangan = 'KOTA BHARU 2';
$kodcaw = 'KB2';
$alamat1 = 'Lot 123, Jalan Sultan Yahya Petra';
$alamat2 = '15200 Kota Bharu, Kelantan';
$notel = '09-123 4567';
$tarikh = '03/09/2026';
$batchNo = 'KB2-PO-20260903-B1-001';
$slot = 'BATCH1';
$status = 'SUBMITTED';
$dihantarOleh = 'NUR AINA BINTI HASSAN';
$dihantarPada = '03/09/2026 10:42';
$opsBy = 'MOHD FAIZ BIN ISMAIL';
$lines = [
['norujukan' => 'KB2-260903-0001', 'nama' => 'AHMAD BIN ABDULLAH', 'nokp' => '900101-03-1234', 'bank' => 'MAYBANK', 'noakaun' => '123456789012', 'nama_akaun' => 'AHMAD BIN ABDULLAH', 'pinjaman' => 15000.00],
['norujukan' => 'KB2-260903-0004', 'nama' => 'SITI MARIAM BINTI YUSOF', 'nokp' => '850215-03-5678', 'bank' => 'CIMB', 'noakaun' => '800123456789', 'nama_akaun' => 'SITI MARIAM BINTI YUSOF', 'pinjaman' => 8200.50],
['norujukan' => 'KB2-260903-0007', 'nama' => 'MOHD RAZAK BIN ALI', 'nokp' => '780930-03-1122', 'bank' => 'BANK ISLAM', 'noakaun' => '140234567890', 'nama_akaun' => 'MOHD RAZAK BIN ALI', 'pinjaman' => 5000.00],
['norujukan' => 'KB2-260903-0010', 'nama' => 'FATIMAH BINTI OMAR', 'nokp' => '920408-03-3344', 'bank' => 'RHB', 'noakaun' => '212345678901', 'nama_akaun' => 'FATIMAH BINTI OMAR', 'pinjaman' => 12500.00],
['norujukan' => 'KB2-260903-0012', 'nama' => 'HASSAN BIN IBRAHIM', 'nokp' => '700112-03-5566', 'bank' => 'MAYBANK', 'noakaun' => '156789012345', 'nama_akaun' => 'HASSAN BIN IBRAHIM', 'pinjaman' => 3400.00],
];
$jumlah = array_sum(array_column($lines, 'pinjaman'));
@endphp
<div class="toolbar">
<div>
<b>DUMMY FORM</b>
<span class="hint"> reka bentuk borang kelulusan batch pembiayaan online (Operasi). Data ini contoh sahaja.</span>
</div>
<button type="button" onclick="window.print()">Cetak / Pratinjau</button>
</div>
<div class="sheet-wrap">
<div class="sheet">
<x-print.header
:cawangan="$cawangan"
:kodcaw="$kodcaw"
:alamat1="$alamat1"
:alamat2="$alamat2"
:notel="$notel"
:tarikh="$tarikh"
:norujukan="$batchNo"
rujukan-label="No. Batch"
:show-dummy-badge="true"
/>
<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>
</div>
<div class="section">
<div class="section-title">A. Maklumat Batch</div>
<table class="fields">
<tr>
<th>No. Batch</th>
<td>{{ $batchNo }}</td>
<th>Cawangan</th>
<td>{{ $kodcaw }} {{ $cawangan }}</td>
</tr>
<tr>
<th>Tarikh / Slot</th>
<td>{{ $tarikh }} &nbsp;·&nbsp; {{ $slot }}</td>
<th>Status Batch</th>
<td>{{ $status }}</td>
</tr>
<tr>
<th>Dihantar Oleh (PC)</th>
<td>{{ $dihantarOleh }}</td>
<th>Dihantar Pada</th>
<td>{{ $dihantarPada }}</td>
</tr>
<tr>
<th>Bilangan Rekod</th>
<td>{{ count($lines) }}</td>
<th>Jumlah Keseluruhan</th>
<td class="amt">RM {{ number_format($jumlah, 2) }}</td>
</tr>
</table>
</div>
<div class="section">
<div class="section-title">B. Senarai Penerima Dalam Batch</div>
<table class="lines">
<thead>
<tr>
<th style="width:4%">Bil</th>
<th style="width:14%">No. SAG</th>
<th>Pelanggan</th>
<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 style="width:12%">Pinjaman (RM)</th>
</tr>
</thead>
<tbody>
@foreach($lines as $i => $line)
<tr>
<td class="num">{{ $i + 1 }}</td>
<td class="num">{{ $line['norujukan'] }}</td>
<td>{{ $line['nama'] }}</td>
<td class="num">{{ $line['nokp'] }}</td>
<td>{{ $line['bank'] }}</td>
<td class="num">{{ $line['noakaun'] }}</td>
<td>{{ $line['nama_akaun'] }}</td>
<td class="amt">{{ number_format($line['pinjaman'], 2) }}</td>
</tr>
@endforeach
</tbody>
<tfoot>
<tr>
<td colspan="7" style="text-align:right">JUMLAH</td>
<td class="amt">{{ number_format($jumlah, 2) }}</td>
</tr>
</tfoot>
</table>
</div>
<div class="section">
<div class="section-title">C. Pengesahan Operasi</div>
<div class="akuan">
Dengan ini disahkan bahawa senarai di Bahagian B telah disemak dan amaun pembiayaan
sebanyak <b>RM {{ number_format($jumlah, 2) }}</b> ({{ count($lines) }} rekod)
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>Sebarang ralat akaun yang dikesan selepas kelulusan hendaklah dilaporkan serta-merta.</li>
</ol>
</div>
</div>
<div class="signs">
<div class="sign-box">
<div class="role">Tandatangan Operasi</div>
<div class="sign-line">
<p>Nama: {{ $opsBy }}</p>
<p>Jawatan: Operasi</p>
<p>Tarikh: {{ $tarikh }}</p>
</div>
</div>
<div class="sign-box">
<div class="role">Pengurus Besar</div>
<div class="sign-line">
<p>Nama:</p>
<p>Cop / Paraf:</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>
</html>
@@ -0,0 +1,322 @@
<!DOCTYPE html>
<html lang="ms">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Dummy Borang Pengesahan Pembiayaan Online</title>
<style>
* { box-sizing: border-box; }
body {
margin: 0;
background: #e8eaed;
font-family: Arial, Helvetica, sans-serif;
color: #111;
}
.toolbar {
position: sticky;
top: 0;
z-index: 10;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 10px 16px;
background: #1f3a5f;
color: #fff;
font-size: 13px;
}
.toolbar b { letter-spacing: 0.04em; }
.toolbar .hint { opacity: 0.85; }
.toolbar button {
border: 0;
background: #fff;
color: #1f3a5f;
font-weight: bold;
padding: 8px 16px;
cursor: pointer;
border-radius: 3px;
}
.sheet-wrap {
padding: 24px 0 48px;
}
.sheet {
width: 210mm;
min-height: 297mm;
margin: 0 auto;
background: #fff;
padding: 14mm 14mm 16mm;
box-shadow: 0 2px 12px rgba(0,0,0,.18);
}
.title {
text-align: center;
margin: 14px 0 4px;
}
.title h1 {
margin: 0;
font-size: 14px;
letter-spacing: 0.04em;
text-transform: uppercase;
}
.title p {
margin: 4px 0 0;
font-size: 10px;
color: #333;
}
.section {
margin-top: 12px;
}
.section-title {
font-size: 11px;
font-weight: bold;
background: #eef1f4;
border: 1px solid #111;
border-bottom: 0;
padding: 5px 8px;
text-transform: uppercase;
}
table.fields {
width: 100%;
border-collapse: collapse;
font-size: 11px;
}
table.fields th,
table.fields td {
border: 1px solid #111;
padding: 6px 8px;
vertical-align: top;
text-align: left;
}
table.fields th {
width: 28%;
font-weight: normal;
background: #fafbfc;
color: #222;
}
table.fields td.amt {
font-size: 16px;
font-weight: bold;
letter-spacing: 0.02em;
}
.akuan {
font-size: 11px;
line-height: 1.55;
border: 1px solid #111;
padding: 10px 12px;
}
.akuan ol {
margin: 6px 0 0;
padding-left: 18px;
}
.akuan li { margin-bottom: 4px; }
.signs {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 10px;
margin-top: 16px;
}
.sign-box {
border: 1px solid #111;
min-height: 150px;
padding: 8px;
font-size: 10px;
}
.sign-box .role {
font-weight: bold;
text-align: center;
text-transform: uppercase;
border-bottom: 1px solid #ccc;
padding-bottom: 4px;
margin-bottom: 8px;
}
.sign-line {
margin-top: 62px;
border-top: 1px solid #111;
padding-top: 6px;
}
.sign-line p { margin: 2px 0; }
@page {
size: A4;
margin: 10mm;
}
@media print {
body { background: #fff; }
.toolbar { display: none !important; }
.sheet-wrap { padding: 0; }
.sheet {
width: auto;
min-height: 0;
margin: 0;
padding: 0;
box-shadow: none;
}
}
</style>
</head>
<body>
@php
// Dummy data — replace later with real gadai / customer / bank values
$cawangan = 'KOTA BHARU 2';
$kodcaw = 'KB2';
$alamat1 = 'Lot 123, Jalan Sultan Yahya Petra';
$alamat2 = '15200 Kota Bharu, Kelantan';
$notel = '09-123 4567';
$norujukan = 'KB2-260903-0001';
$tarikh = '03/09/2026';
$teller = 'SITI AMINAH BINTI ALI';
$nama = 'AHMAD BIN ABDULLAH';
$nokp = '900101-03-1234';
$nopelanggan = 'KB2001234';
$telefon = '012-3456789';
$pinjaman = '15,000.00';
$bank = 'Malayan Banking Berhad (MAYBANK)';
$noakaun = '123456789012';
$nama_akaun = 'AHMAD BIN ABDULLAH';
@endphp
<div class="toolbar">
<div>
<b>DUMMY FORM</b>
<span class="hint"> reka bentuk borang pengesahan pembiayaan online. Data ini contoh sahaja.</span>
</div>
<button type="button" onclick="window.print()">Cetak / Pratinjau</button>
</div>
<div class="sheet-wrap">
<div class="sheet">
<x-print.header
:cawangan="$cawangan"
:kodcaw="$kodcaw"
:alamat1="$alamat1"
:alamat2="$alamat2"
:notel="$notel"
:tarikh="$tarikh"
:show-dummy-badge="true"
/>
<div class="title">
<h1>Borang Pengesahan Penerimaan Pembiayaan<br>Secara Online (Akaun Bank)</h1>
<p>Untuk diisi dan ditandatangani oleh pelanggan semasa transaksi gadai.</p>
</div>
<div class="section">
<div class="section-title">A. Maklumat Pelanggan</div>
<table class="fields">
<tr>
<th>Nama Pelanggan</th>
<td colspan="3">{{ $nama }}</td>
</tr>
<tr>
<th>No. MyKad / Passport</th>
<td>{{ $nokp }}</td>
<th style="width:22%">No. Pelanggan</th>
<td>{{ $nopelanggan }}</td>
</tr>
<tr>
<th>No. Telefon</th>
<td>{{ $telefon }}</td>
<th>Cawangan</th>
<td>{{ $cawangan }}</td>
</tr>
</table>
</div>
<div class="section">
<div class="section-title">B. Maklumat Pembiayaan</div>
<table class="fields">
<tr>
<th>No. Surat Akuan Gadai</th>
<td>{{ $norujukan }}</td>
</tr>
<tr>
<th>Amaun Pembiayaan</th>
<td class="amt">RM {{ $pinjaman }}</td>
</tr>
<tr>
<th>Cara Terima Pembiayaan</th>
<td>Online (kredit ke akaun bank) bukan tunai</td>
</tr>
<tr>
<th>Teller</th>
<td>{{ $teller }}</td>
</tr>
</table>
</div>
<div class="section">
<div class="section-title">C. Maklumat Akaun Bank Penerima</div>
<table class="fields">
<tr>
<th>Nama Bank</th>
<td>{{ $bank }}</td>
</tr>
<tr>
<th>No. Akaun</th>
<td>{{ $noakaun }}</td>
</tr>
<tr>
<th>Nama Pemegang Akaun</th>
<td>{{ $nama_akaun }}</td>
</tr>
</table>
</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>
</ol>
</div>
</div>
<div class="signs">
<div class="sign-box">
<div class="role">Tandatangan Pelanggan</div>
<div class="sign-line">
<p>Nama: {{ $nama }}</p>
<p>No. KP: {{ $nokp }}</p>
<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>
<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>
</x-print.footer>
</div>
</div>
</body>
</html>
@@ -0,0 +1,292 @@
<!DOCTYPE html>
<html lang="ms">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Dummy Maklumat Pelanggan</title>
<style>
* { box-sizing: border-box; }
body {
margin: 0;
background: #e8eaed;
font-family: Arial, Helvetica, sans-serif;
color: #111;
}
.toolbar {
position: sticky;
top: 0;
z-index: 10;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 10px 16px;
background: #1f3a5f;
color: #fff;
font-size: 13px;
}
.toolbar b { letter-spacing: 0.04em; }
.toolbar .hint { opacity: 0.85; }
.toolbar button {
border: 0;
background: #fff;
color: #1f3a5f;
font-weight: bold;
padding: 8px 16px;
cursor: pointer;
border-radius: 3px;
}
.sheet-wrap {
padding: 24px 0 48px;
}
.sheet {
width: 210mm;
min-height: 297mm;
margin: 0 auto;
background: #fff;
padding: 14mm 14mm 16mm;
box-shadow: 0 2px 12px rgba(0,0,0,.18);
}
.title {
text-align: center;
margin: 14px 0 4px;
}
.title h1 {
margin: 0;
font-size: 14px;
letter-spacing: 0.04em;
text-transform: uppercase;
}
.title p {
margin: 4px 0 0;
font-size: 10px;
color: #333;
}
.identity {
display: grid;
grid-template-columns: minmax(0, 1fr) 32mm;
gap: 12px;
align-items: start;
margin-top: 12px;
}
.photo-box {
width: 32mm;
text-align: center;
}
.photo-box img {
display: block;
width: 32mm;
height: 42mm;
object-fit: cover;
border: 1px solid #111;
background: #eef1f4;
}
.photo-box .caption {
margin-top: 4px;
font-size: 8.5px;
color: #444;
letter-spacing: 0.04em;
text-transform: uppercase;
}
.section {
margin-top: 12px;
}
.section-title {
font-size: 11px;
font-weight: bold;
background: #eef1f4;
border: 1px solid #111;
border-bottom: 0;
padding: 5px 8px;
text-transform: uppercase;
}
table.fields {
width: 100%;
border-collapse: collapse;
font-size: 11px;
}
table.fields th,
table.fields td {
border: 1px solid #111;
padding: 6px 8px;
vertical-align: top;
text-align: left;
}
table.fields th {
width: 28%;
font-weight: normal;
background: #fafbfc;
color: #222;
}
@page {
size: A4;
margin: 10mm;
}
@media print {
body { background: #fff; }
.toolbar { display: none !important; }
.sheet-wrap { padding: 0; }
.sheet {
width: auto;
min-height: 0;
margin: 0;
padding: 0;
box-shadow: none;
}
}
</style>
</head>
<body>
@php
$cawangan = 'KOTA BHARU 2';
$kodcaw = 'KB2';
$alamat1 = 'Lot 123, Jalan Sultan Yahya Petra';
$alamat2 = '15200 Kota Bharu, Kelantan';
$notel = '09-123 4567';
$tarikh = now()->format('d/m/Y');
$nama = 'AHMAD BIN ABDULLAH';
$nokp = '900101-03-1234';
$nopelanggan = 'KB2001234';
$telefon = '012-3456789';
$telefon2 = '09-765 4321';
$alamat = 'Lot 45, Kampung Sireh';
$poskod = '15050';
$daerah = 'Kota Bharu';
$negeri = 'Kelantan';
$bank = 'Malayan Banking Berhad (MAYBANK)';
$noakaun = '123456789012';
$pekerjaan = 'Kakitangan Kerajaan';
$nota = 'Pelanggan tetap cawangan KB2.';
$status = 'Anggota Koperasi';
$photo_svg = '<svg xmlns="http://www.w3.org/2000/svg" width="240" height="320" viewBox="0 0 240 320">'
. '<rect fill="#d9dee5" width="240" height="320"/>'
. '<circle cx="120" cy="118" r="52" fill="#8b949e"/>'
. '<ellipse cx="120" cy="286" rx="90" ry="92" fill="#8b949e"/>'
. '</svg>';
$photo_src = 'data:image/svg+xml;base64,' . base64_encode($photo_svg);
@endphp
<div class="toolbar">
<div>
<b>DUMMY FORM</b>
<span class="hint"> reka bentuk cetakan A4 maklumat pelanggan. Data ini contoh sahaja.</span>
</div>
<button type="button" onclick="window.print()">Cetak / Pratinjau</button>
</div>
<div class="sheet-wrap">
<div class="sheet">
<x-print.header
:cawangan="$cawangan"
:kodcaw="$kodcaw"
:alamat1="$alamat1"
:alamat2="$alamat2"
:notel="$notel"
:tarikh="$tarikh"
:norujukan="$nopelanggan"
rujukan-label="No. Pelanggan"
:show-dummy-badge="true"
/>
<div class="title">
<h1>Maklumat Pelanggan</h1>
<p>Salinan maklumat pengenalan diri pelanggan untuk rujukan cawangan.</p>
</div>
<div class="identity">
<div>
<div class="section-title">A. Pengenalan Diri</div>
<table class="fields">
<tr>
<th>Nama</th>
<td>{{ $nama }}</td>
</tr>
<tr>
<th>No. Kad Pengenalan</th>
<td>{{ $nokp }}</td>
</tr>
<tr>
<th>No. Pelanggan</th>
<td>{{ $nopelanggan }}</td>
</tr>
<tr>
<th>Status</th>
<td>{{ $status }}</td>
</tr>
</table>
</div>
<div class="photo-box">
<img src="{{ $photo_src }}" alt="Gambar MyKad">
<div class="caption">Gambar MyKad</div>
</div>
</div>
<div class="section">
<div class="section-title">B. Maklumat Hubungan &amp; Alamat</div>
<table class="fields">
<tr>
<th>No. Telefon 1</th>
<td>{{ $telefon }}</td>
<th style="width:22%">No. Telefon 2</th>
<td>{{ $telefon2 }}</td>
</tr>
<tr>
<th>Alamat</th>
<td colspan="3">{{ $alamat }}</td>
</tr>
<tr>
<th>Poskod</th>
<td>{{ $poskod }}</td>
<th>Daerah</th>
<td>{{ $daerah }}</td>
</tr>
<tr>
<th>Negeri</th>
<td colspan="3">{{ $negeri }}</td>
</tr>
</table>
</div>
<div class="section">
<div class="section-title">C. Bank &amp; Pekerjaan</div>
<table class="fields">
<tr>
<th>Bank</th>
<td colspan="3">{{ $bank }}</td>
</tr>
<tr>
<th>No. Akaun</th>
<td colspan="3">{{ $noakaun }}</td>
</tr>
<tr>
<th>Pekerjaan</th>
<td colspan="3">{{ $pekerjaan }}</td>
</tr>
<tr>
<th>Nota</th>
<td colspan="3">{{ $nota }}</td>
</tr>
</table>
</div>
<x-print.footer :show-copies="false">
<b>Nota:</b>
<p>1. Borang ini dicetak dari skrin Maklumat Pelanggan untuk rujukan cawangan.</p>
<p>2. Gambar diambil daripada rekod MyKad pelanggan dalam sistem.</p>
</x-print.footer>
</div>
</div>
</body>
</html>
+45 -1
View File
@@ -29,6 +29,7 @@
<div class="form-group row col-12"> <div class="form-group row col-12">
<input type="hidden" name="kodcaw" id="kodcaw" value="{{$cawangan_info['kodcaw']}}"> <input type="hidden" name="kodcaw" id="kodcaw" value="{{$cawangan_info['kodcaw']}}">
<input type="hidden" name="nokp" id="nokp" value="{{ $customer_info['kpbaru'] }}"> <input type="hidden" name="nokp" id="nokp" value="{{ $customer_info['kpbaru'] }}">
<input type="hidden" name="tag_anggota" id="tag_anggota" value="{{ $customer_info['tag_anggota'] ?? 0 }}">
</div> </div>
<div class="col-12 mb-2"> <div class="col-12 mb-2">
@@ -289,6 +290,7 @@
<input type="text" name="nokp" id="nokp" value="{{ !empty($customer_info['kpbaru']) ? $customer_info['kpbaru'] : ($customer_info['kplama'] ?? '') }}" hidden> <input type="text" name="nokp" id="nokp" value="{{ !empty($customer_info['kpbaru']) ? $customer_info['kpbaru'] : ($customer_info['kplama'] ?? '') }}" hidden>
<a href="{{ route('gadai.batal',['norujukan'=>$norujukan]) }}" class="float-right btn btn-danger mr-2">Batal</a> <a href="{{ route('gadai.batal',['norujukan'=>$norujukan]) }}" class="float-right btn btn-danger mr-2">Batal</a>
<input type="submit" name="terima_gadai" id="terima_gadai" class="float-right btn btn-primary mr-2" value="Terima"> <input type="submit" name="terima_gadai" id="terima_gadai" class="float-right btn btn-primary mr-2" value="Terima">
<button type="button" id="cetak_borang_online" class="float-right btn btn-info mr-2" style="display:none;">Cetak Borang</button>
</div> </div>
</div> </div>
</form> </form>
@@ -702,8 +704,13 @@
}, },
success:function(data){ success:function(data){
kadarupah = data; kadarupah = data;
// Jika anggota (tag_anggota = 1), kadar ujrah dipaksa 0.00
if(Number($('#tag_anggota').val()) === 1){
kadarupah = 0;
} }
}); }
});
$('#total_nilai_marhun').val(formatter.format(total_nilai_marhun)); $('#total_nilai_marhun').val(formatter.format(total_nilai_marhun));
$('#marhun_detail').val(marhun_details); $('#marhun_detail').val(marhun_details);
@@ -1265,6 +1272,7 @@
} else { } else {
$('#cara_terima_section').hide(); $('#cara_terima_section').hide();
$('#bank_online_section').hide(); $('#bank_online_section').hide();
$('#cetak_borang_online').hide();
$('#cara_terima_tunai').prop('checked', true); $('#cara_terima_tunai').prop('checked', true);
} }
} }
@@ -1272,11 +1280,13 @@
function toggleBankOnlineSection() { function toggleBankOnlineSection() {
if ($('input[name="cara_terima"]:checked').val() === 'ONLINE') { if ($('input[name="cara_terima"]:checked').val() === 'ONLINE') {
$('#bank_online_section').show(); $('#bank_online_section').show();
$('#cetak_borang_online').show();
var nama = $('#gadai_kodbank option:selected').data('nama') || ''; var nama = $('#gadai_kodbank option:selected').data('nama') || '';
$('#gadai_nama_bank').val(nama); $('#gadai_nama_bank').val(nama);
refreshModalOnlineStatus(); refreshModalOnlineStatus();
} else { } else {
$('#bank_online_section').hide(); $('#bank_online_section').hide();
$('#cetak_borang_online').hide();
} }
} }
@@ -1288,6 +1298,40 @@
$('#gadai_nama_bank').val($('#gadai_kodbank option:selected').data('nama') || ''); $('#gadai_nama_bank').val($('#gadai_kodbank option:selected').data('nama') || '');
}); });
$(document).on('click', '#cetak_borang_online', function () {
var kodbank = $('#gadai_kodbank').val();
var noakaun = ($('#gadai_noakaun').val() || '').trim();
var namaAkaun = ($('#gadai_nama_akaun').val() || '').trim();
var namaBank = $('#gadai_kodbank option:selected').data('nama') || $('#gadai_nama_bank').val() || '';
var pinjaman = String($('#pinjaman').val() || '').replace(/,/g, '');
if (!kodbank || !noakaun) {
Swal.fire({
icon: 'warning',
title: 'Maklumat bank tidak lengkap',
text: 'Sila lengkapkan bank dan no. akaun sebelum cetak borang. Anda boleh betulkan dan cetak semula tanpa tekan Terima.'
});
return;
}
var form = $('<form>', {
method: 'POST',
action: '{{ route("gadai.print.borang.online.preview") }}',
target: '_blank'
});
form.append($('<input>', { type: 'hidden', name: '_token', value: '{{ csrf_token() }}' }));
form.append($('<input>', { type: 'hidden', name: 'norujukan', value: $('#norujukan_gadai').val() || $('#norujukan').val() || '' }));
form.append($('<input>', { type: 'hidden', name: 'nokp', value: $('#nokp').val() || '' }));
form.append($('<input>', { type: 'hidden', name: 'nopelanggan', value: $('#nopelanggan').val() || '' }));
form.append($('<input>', { type: 'hidden', name: 'nama_pelanggan', value: $('input[name="nama_pelanggan"]').val() || '' }));
form.append($('<input>', { type: 'hidden', name: 'pinjaman', value: pinjaman }));
form.append($('<input>', { type: 'hidden', name: 'kodbank', value: kodbank }));
form.append($('<input>', { type: 'hidden', name: 'nama_bank', value: namaBank }));
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();
});
async function getModalOnline() { async function getModalOnline() {
let kodcaw = $('#kodcaw').val(); let kodcaw = $('#kodcaw').val();
let apiUrl = api_url + '/api/modal-online/' + kodcaw; let apiUrl = api_url + '/api/modal-online/' + kodcaw;
@@ -43,9 +43,6 @@
<b>Cawangan</b> <b>Cawangan</b>
</div> </div>
<div class='col-md-7'> <div class='col-md-7'>
@php
$cawangan_all = Http::withOptions(['verify' => config('app.api_verify')])->get($api_url . '/api/cawangan')->json();
@endphp
<select class="form-control" name="cawangan" data-placeholder="Pilih Cawangan" id="cawangan"> <select class="form-control" name="cawangan" data-placeholder="Pilih Cawangan" id="cawangan">
<option disabled selected value> -- Pilih Cawangan -- </option> <option disabled selected value> -- Pilih Cawangan -- </option>
<option value="operasi">Operasi</option> <option value="operasi">Operasi</option>
@@ -106,81 +103,12 @@
<div class="card-body text-center"> <div class="card-body text-center">
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
<table id="rekod_kakitangan" class="table table-striped table-hover"> <x-datatable
<thead class="thead-dark"> id="rekod_kakitangan"
<tr> filename="Senarai_Kakitangan"
<th>Bil</th> :columns="['Bil', 'Tindakan', 'Nama Staff', 'Jawatan', 'Role Harian', 'Role Asal', 'Cawangan']"
<th>Nama Staff</th> :skipExport="['Bil', 'Tindakan']"
<th>Jawatan</th> />
<th>Cawangan</th>
<th>Tindakan</th>
</tr>
</thead>
<tbody>
@foreach($kakitangan as $index=>$staff)
@if($staff->roles != 'admin')
<tr>
<td>{{ $loop->iteration }}</td>
<td>{{ $staff->name }}</td>
<td>{{ strtoupper($staff->roles) }}</td>
<td>
@if($staff->cawangan == 'operasi' || $staff->cawangan == 'callcenter' || $staff->cawangan == 'admin')
{{ ucwords($staff->cawangan) }}
@else
@php
$cawangan_staff = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url') . '/api/cawangan/'. $staff->cawangan)->json();
echo (isset($cawangan_staff['details_caw'])) ? ucwords($cawangan_staff['details_caw']) : 'test';
@endphp
@endif
</td>
<td>
@if($auth_user->roles == 'operasi')
<a href="{{ route('kakitangan.tukartaraf',['id'=>$staff->id]) }}" class="btn btn-warning" title="Tukar Taraf"><i class="material-icons">edit</i></a>
<a href="{{ route('kakitangan.tukarcawangan',['id'=>$staff->id]) }}" class="btn btn-info" title="Tukar Cawangan"><i class="material-icons">work</i></a>
<a href="{{ route('operasi.kakitangan.edit',['id'=>$staff->id]) }}" class="btn btn-success" title="Edit Maklumat"><i class="material-icons">settings</i></a>
@if(
app()->environment('local')
&&
in_array($auth_user->roles, ['admin'])
&& $staff->roles != 'admin'
&& $staff->id != $auth_user->id
&& !(Auth::check() && method_exists(Auth::user(), 'isImpersonated') && Auth::user()->isImpersonated())
)
<form method="post" action="{{ route('impersonate.start', ['id' => $staff->id]) }}" style="display:inline;">
@csrf
<button type="button" class="btn btn-secondary btn-impersonate" data-staff-name="{{ $staff->name }}" title="Impersonate"><i class="material-icons">supervised_user_circle</i></button>
</form>
@endif
<button type="button" class="btn btn-danger btn-delete" data-roles="{{ $auth_user->roles }}" data-staff-id="{{ $staff->id }}" data-token="{{ csrf_token() }}" title="Delete Kakitangan"><i class="material-icons">delete</i></button>
@elseif($auth_user->roles == 'admin')
{{-- <form method="post" action="{{ route('admin.kakitangan.delete') }}">
{{ @csrf_field() }} --}}
<input type="hidden" name="staff_id" value="{{ $staff->id }}"/>
<a href="{{ route('admin.kakitangan.tukartaraf',['id'=>$staff->id]) }}" class="btn btn-warning" title="Tukar Taraf"><i class="material-icons">edit</i></a>
<a href="{{ route('admin.kakitangan.tukarcawangan',['id'=>$staff->id]) }}" class="btn btn-info" title="Tukar Cawangan"><i class="material-icons">work</i></a>
<a href="{{ route('admin.kakitangan.edit',['id'=>$staff->id]) }}" class="btn btn-success" title="Edit Maklumat"><i class="material-icons">settings</i></a>
<a href="{{ route('admin.kakitangan.resetcredentials',['id'=>$staff->id]) }}" class="btn btn-primary" title="Reset Password"><i class="material-icons">restore</i></a>
@if(
app()->environment('local')
&&
$staff->roles != 'admin'
&& $staff->id != $auth_user->id
&& !(Auth::check() && method_exists(Auth::user(), 'isImpersonated') && Auth::user()->isImpersonated())
)
<form method="post" action="{{ route('impersonate.start', ['id' => $staff->id]) }}" style="display:inline;">
@csrf
<button type="button" class="btn btn-secondary btn-impersonate" data-staff-name="{{ $staff->name }}" title="Impersonate"><i class="material-icons">supervised_user_circle</i></button>
</form>
@endif
<button type="button" class="btn btn-danger btn-delete" data-staff-id="{{ $staff->id }}" data-token="{{ csrf_token() }}" title="Delete Kakitangan"><i class="material-icons">delete</i></button>
{{-- </form> --}}
@endif
</td>
</tr>
@endif
@endforeach
</tbody>
</table>
</div> </div>
</div> </div>
</div> </div>
@@ -189,8 +117,72 @@
</div> </div>
</div> </div>
<script> <script>
@php
$isOperasi = $auth_user->roles == 'operasi';
$canImpersonate = app()->environment(['local', 'staging'])
&& $auth_user->roles === 'admin'
&& !(Auth::check() && method_exists(Auth::user(), 'isImpersonated') && Auth::user()->isImpersonated());
@endphp
var tablerekod = $('#rekod_kakitangan').DataTable(); function escapeHtml(value) {
return $('<div>').text(value == null ? '' : String(value)).html();
}
function staffTindakanHtml(staff) {
var id = staff.id;
var html = '';
html += '<a href="' + staffRoutes.tukarTaraf.replace('__ID__', id) + '" class="btn btn-warning" title="Tukar Taraf"><i class="material-icons">edit</i></a> ';
html += '<a href="' + staffRoutes.tukarCawangan.replace('__ID__', id) + '" class="btn btn-info" title="Tukar Cawangan"><i class="material-icons">work</i></a> ';
html += '<a href="' + staffRoutes.edit.replace('__ID__', id) + '" class="btn btn-success" title="Edit Maklumat"><i class="material-icons">settings</i></a> ';
if (authRole === 'admin') {
html += '<a href="' + staffRoutes.reset.replace('__ID__', id) + '" class="btn btn-primary" title="Reset Password"><i class="material-icons">restore</i></a> ';
}
if (canImpersonate && id !== authUserId) {
html += '<form method="post" action="' + staffRoutes.impersonate.replace('__ID__', id) + '" style="display:inline;">' +
csrfField +
'<button type="button" class="btn btn-secondary btn-impersonate" data-staff-name="' + escapeHtml(staff.name) + '" title="Impersonate"><i class="material-icons">supervised_user_circle</i></button>' +
'</form> ';
}
html += '<button type="button" class="btn btn-danger btn-delete" data-roles="' + authRole + '" data-staff-id="' + id + '" title="Delete Kakitangan"><i class="material-icons">delete</i></button>';
return html;
}
var authRole = @json($auth_user->roles);
var authUserId = @json($auth_user->id);
var canImpersonate = @json($canImpersonate);
var csrfField = '<input type="hidden" name="_token" value="' + @json(csrf_token()) + '">';
var staffRoutes = {
tukarTaraf: @json($isOperasi ? route('kakitangan.tukartaraf', ['id' => '__ID__']) : route('admin.kakitangan.tukartaraf', ['id' => '__ID__'])),
tukarCawangan: @json($isOperasi ? route('kakitangan.tukarcawangan', ['id' => '__ID__']) : route('admin.kakitangan.tukarcawangan', ['id' => '__ID__'])),
edit: @json($isOperasi ? route('operasi.kakitangan.edit', ['id' => '__ID__']) : route('admin.kakitangan.edit', ['id' => '__ID__'])),
reset: @json(route('admin.kakitangan.resetcredentials', ['id' => '__ID__'])),
impersonate: @json(route('impersonate.start', ['id' => '__ID__']))
};
var tablerekod = initErahnDatatable('#rekod_kakitangan', {
columnDefs: [
{ targets: 0, orderable: false, width: '40px' },
{ targets: 1, orderable: false, searchable: false }
]
});
$.each(@json($kakitangan_rows), function (index, staff) {
tablerekod.row.add([
index + 1,
staffTindakanHtml(staff),
escapeHtml(staff.name),
escapeHtml(staff.roles),
escapeHtml(staff.role_harian),
escapeHtml(staff.role_asal),
escapeHtml(staff.cawangan)
]);
});
tablerekod.draw();
$("#rekod_kakitangan").on('click', ".btn-delete", function(){ $("#rekod_kakitangan").on('click', ".btn-delete", function(){
//$('.btn-delete').on('click', function(){ //$('.btn-delete').on('click', function(){
@@ -253,7 +245,7 @@
var staffName = $(this).data('staff-name'); var staffName = $(this).data('staff-name');
Swal.fire({ Swal.fire({
title: 'Menyamar sebagai Kakitangan?', title: 'Menyamar sebagai ' + staffName + '?',
text: "Anda akan log masuk sebagai " + staffName + ".", text: "Anda akan log masuk sebagai " + staffName + ".",
type: 'warning', type: 'warning',
showCancelButton: true, showCancelButton: true,
@@ -3,7 +3,7 @@
@php @php
$pendingCount = collect($lines)->whereIn('status', ['IN_BATCH', 'OPS_REVIEW'])->count(); $pendingCount = collect($lines)->whereIn('status', ['IN_BATCH', 'OPS_REVIEW'])->count();
$approvedCount = collect($lines)->where('status', 'APPROVED')->count(); $approvedCount = collect($lines)->where('status', 'APPROVED')->count();
$cawName = $cawangan_info['nama'] ?? $cawangan_info['namacaw'] ?? $kodcaw; $cawName = $cawangan_info['details_caw'] ?? $cawangan_info['namacaw'] ?? $kodcaw;
@endphp @endphp
<div class="container-fluid"> <div class="container-fluid">
<div class="row justify-content-center"> <div class="row justify-content-center">
@@ -45,6 +45,11 @@
<div class="card-header d-flex justify-content-between align-items-center flex-wrap"> <div class="card-header d-flex justify-content-between align-items-center flex-wrap">
<h5 class="mb-0">Senarai Dalam Batch</h5> <h5 class="mb-0">Senarai Dalam Batch</h5>
<div class="mt-2 mt-md-0"> <div class="mt-2 mt-md-0">
<a href="{{ route('pembiayaan.online.operasi.batch.print', ['kodcaw' => $kodcaw, 'batchId' => $batch['id']]) }}"
class="btn btn-outline-dark btn-sm"
target="_blank">
Cetak Borang
</a>
<a href="{{ route('pembiayaan.online.operasi') }}" class="btn btn-secondary btn-sm">Kembali</a> <a href="{{ route('pembiayaan.online.operasi') }}" class="btn btn-secondary btn-sm">Kembali</a>
@if($pendingCount > 0) @if($pendingCount > 0)
<form method="post" <form method="post"
@@ -0,0 +1,336 @@
<!DOCTYPE html>
<html lang="ms">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Borang Kelulusan Batch {{ $batchNo }}</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>
$(document).ready(function () {
window.print();
});
</script>
<style>
* { box-sizing: border-box; }
body {
margin: 0;
background: #e8eaed;
font-family: Arial, Helvetica, sans-serif;
color: #111;
}
.toolbar {
position: sticky;
top: 0;
z-index: 10;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 10px 16px;
background: #1f3a5f;
color: #fff;
font-size: 13px;
}
.toolbar button {
border: 0;
background: #fff;
color: #1f3a5f;
font-weight: bold;
padding: 8px 16px;
cursor: pointer;
border-radius: 3px;
}
.sheet-wrap {
padding: 24px 0 48px;
}
.sheet {
width: 210mm;
min-height: 297mm;
margin: 0 auto;
background: #fff;
padding: 14mm 12mm 16mm;
box-shadow: 0 2px 12px rgba(0,0,0,.18);
}
.title {
text-align: center;
margin: 14px 0 4px;
}
.title h1 {
margin: 0;
font-size: 14px;
letter-spacing: 0.04em;
text-transform: uppercase;
}
.title p {
margin: 4px 0 0;
font-size: 10px;
color: #333;
}
.section {
margin-top: 12px;
}
.section-title {
font-size: 11px;
font-weight: bold;
background: #eef1f4;
border: 1px solid #111;
border-bottom: 0;
padding: 5px 8px;
text-transform: uppercase;
}
table.fields {
width: 100%;
border-collapse: collapse;
font-size: 11px;
}
table.fields th,
table.fields td {
border: 1px solid #111;
padding: 6px 8px;
vertical-align: top;
text-align: left;
}
table.fields th {
width: 22%;
font-weight: normal;
background: #fafbfc;
color: #222;
}
table.fields td.amt {
font-size: 14px;
font-weight: bold;
letter-spacing: 0.02em;
}
table.lines {
width: 100%;
border-collapse: collapse;
font-size: 9.5px;
}
table.lines th,
table.lines td {
border: 1px solid #111;
padding: 4px 5px;
vertical-align: top;
}
table.lines thead th {
background: #eef1f4;
font-weight: bold;
text-align: center;
text-transform: uppercase;
font-size: 8.5px;
letter-spacing: 0.02em;
}
table.lines td.num { text-align: center; }
table.lines td.amt { text-align: right; white-space: nowrap; }
table.lines tfoot td {
font-weight: bold;
background: #fafbfc;
}
.akuan {
font-size: 11px;
line-height: 1.55;
border: 1px solid #111;
padding: 10px 12px;
}
.akuan ol {
margin: 6px 0 0;
padding-left: 18px;
}
.akuan li { margin-bottom: 4px; }
.signs {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
margin-top: 16px;
}
.sign-box {
border: 1px solid #111;
min-height: 160px;
padding: 8px 10px;
font-size: 10px;
}
.sign-box .role {
font-weight: bold;
text-align: center;
text-transform: uppercase;
border-bottom: 1px solid #ccc;
padding-bottom: 4px;
margin-bottom: 8px;
}
.sign-line {
margin-top: 72px;
border-top: 1px solid #111;
padding-top: 6px;
}
.sign-line p { margin: 2px 0; }
@page {
size: A4;
margin: 10mm;
}
@media print {
body { background: #fff; }
.toolbar { display: none !important; }
.sheet-wrap { padding: 0; }
.sheet {
width: auto;
min-height: 0;
margin: 0;
padding: 0;
box-shadow: none;
}
table.lines thead { display: table-header-group; }
table.lines tfoot { display: table-footer-group; }
}
</style>
</head>
<body>
<div class="toolbar">
<div><b>Borang Kelulusan Batch Pembiayaan Online</b></div>
<button type="button" onclick="window.print()">Cetak</button>
</div>
<div class="sheet-wrap">
<div class="sheet">
<x-print.header
:cawangan="$cawangan"
:kodcaw="$kodcaw"
:alamat1="$alamat1"
:alamat2="$alamat2"
:notel="$notel"
:tarikh="$tarikh"
:norujukan="$batchNo"
rujukan-label="No. Batch"
/>
<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>
</div>
<div class="section">
<div class="section-title">A. Maklumat Batch</div>
<table class="fields">
<tr>
<th>No. Batch</th>
<td>{{ $batchNo }}</td>
<th>Cawangan</th>
<td>{{ $kodcaw }} {{ $cawangan }}</td>
</tr>
<tr>
<th>Tarikh / Slot</th>
<td>{{ $tarikh }} &nbsp;·&nbsp; {{ $slot }}</td>
<th>Status Batch</th>
<td>{{ $status }}</td>
</tr>
<tr>
<th>Dihantar Oleh (PC)</th>
<td>{{ $dihantarOleh }}</td>
<th>Dihantar Pada</th>
<td>{{ $dihantarPada }}</td>
</tr>
<tr>
<th>Bilangan Rekod</th>
<td>{{ count($lines) }}</td>
<th>Jumlah Keseluruhan</th>
<td class="amt">RM {{ number_format((float) $jumlah, 2) }}</td>
</tr>
</table>
</div>
<div class="section">
<div class="section-title">B. Senarai Penerima Dalam Batch</div>
<table class="lines">
<thead>
<tr>
<th style="width:4%">Bil</th>
<th style="width:14%">No. SAG</th>
<th>Pelanggan</th>
<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 style="width:12%">Pinjaman (RM)</th>
</tr>
</thead>
<tbody>
@forelse($lines as $i => $line)
<tr>
<td class="num">{{ $i + 1 }}</td>
<td class="num">{{ $line['norujukan'] ?? '-' }}</td>
<td>{{ $line['nama_pelanggan'] ?? '-' }}</td>
<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 class="amt">{{ number_format((float) ($line['pinjaman'] ?? 0), 2) }}</td>
</tr>
@empty
<tr>
<td colspan="8" class="num">Tiada rekod dalam batch.</td>
</tr>
@endforelse
</tbody>
<tfoot>
<tr>
<td colspan="7" style="text-align:right">JUMLAH</td>
<td class="amt">{{ number_format((float) $jumlah, 2) }}</td>
</tr>
</tfoot>
</table>
</div>
<div class="section">
<div class="section-title">C. Pengesahan Operasi</div>
<div class="akuan">
Dengan ini disahkan bahawa senarai di Bahagian B telah disemak dan amaun pembiayaan
sebanyak <b>RM {{ number_format((float) $jumlah, 2) }}</b> ({{ count($lines) }} rekod)
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>Sebarang ralat akaun yang dikesan selepas kelulusan hendaklah dilaporkan serta-merta.</li>
</ol>
</div>
</div>
<div class="signs">
<div class="sign-box">
<div class="role">Tandatangan Operasi</div>
<div class="sign-line">
<p>Nama: {{ $opsBy }}</p>
<p>Jawatan: Operasi</p>
<p>Tarikh: {{ $tarikh }}</p>
</div>
</div>
<div class="sign-box">
<div class="role">Pengurus Besar</div>
<div class="sign-line">
<p>Nama:</p>
<p>Cop / Paraf:</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>
</html>
@@ -0,0 +1,302 @@
<!DOCTYPE html>
<html lang="ms">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Borang Pengesahan Online {{ $norujukan }}</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>
$(document).ready(function () {
window.print();
});
</script>
<style>
* { box-sizing: border-box; }
body {
margin: 0;
background: #e8eaed;
font-family: Arial, Helvetica, sans-serif;
color: #111;
}
.toolbar {
position: sticky;
top: 0;
z-index: 10;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 10px 16px;
background: #1f3a5f;
color: #fff;
font-size: 13px;
}
.toolbar button {
border: 0;
background: #fff;
color: #1f3a5f;
font-weight: bold;
padding: 8px 16px;
cursor: pointer;
border-radius: 3px;
}
.sheet-wrap {
padding: 24px 0 48px;
}
.sheet {
width: 210mm;
min-height: 297mm;
margin: 0 auto;
background: #fff;
padding: 14mm 14mm 16mm;
box-shadow: 0 2px 12px rgba(0,0,0,.18);
}
.title {
text-align: center;
margin: 14px 0 4px;
}
.title h1 {
margin: 0;
font-size: 14px;
letter-spacing: 0.04em;
text-transform: uppercase;
}
.title p {
margin: 4px 0 0;
font-size: 10px;
color: #333;
}
.section {
margin-top: 12px;
}
.section-title {
font-size: 11px;
font-weight: bold;
background: #eef1f4;
border: 1px solid #111;
border-bottom: 0;
padding: 5px 8px;
text-transform: uppercase;
}
table.fields {
width: 100%;
border-collapse: collapse;
font-size: 11px;
}
table.fields th,
table.fields td {
border: 1px solid #111;
padding: 6px 8px;
vertical-align: top;
text-align: left;
}
table.fields th {
width: 28%;
font-weight: normal;
background: #fafbfc;
color: #222;
}
table.fields td.amt {
font-size: 16px;
font-weight: bold;
letter-spacing: 0.02em;
}
.akuan {
font-size: 11px;
line-height: 1.55;
border: 1px solid #111;
padding: 10px 12px;
}
.akuan ol {
margin: 6px 0 0;
padding-left: 18px;
}
.akuan li { margin-bottom: 4px; }
.signs {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 10px;
margin-top: 16px;
}
.sign-box {
border: 1px solid #111;
min-height: 150px;
padding: 8px;
font-size: 10px;
}
.sign-box .role {
font-weight: bold;
text-align: center;
text-transform: uppercase;
border-bottom: 1px solid #ccc;
padding-bottom: 4px;
margin-bottom: 8px;
}
.sign-line {
margin-top: 62px;
border-top: 1px solid #111;
padding-top: 6px;
}
.sign-line p { margin: 2px 0; }
@page {
size: A4;
margin: 10mm;
}
@media print {
body { background: #fff; }
.toolbar { display: none !important; }
.sheet-wrap { padding: 0; }
.sheet {
width: auto;
min-height: 0;
margin: 0;
padding: 0;
box-shadow: none;
}
}
</style>
</head>
<body>
<div class="toolbar">
<div><b>Borang Pengesahan Pembiayaan Online</b></div>
<button type="button" onclick="window.print()">Cetak</button>
</div>
<div class="sheet-wrap">
<div class="sheet">
<x-print.header
:cawangan="$cawangan"
:kodcaw="$kodcaw"
:alamat1="$alamat1"
:alamat2="$alamat2"
:notel="$notel"
:tarikh="$tarikh"
/>
<div class="title">
<h1>Borang Pengesahan Penerimaan Pembiayaan<br>Secara Online (Akaun Bank)</h1>
<p>Untuk diisi dan ditandatangani oleh pelanggan semasa transaksi gadai.</p>
</div>
<div class="section">
<div class="section-title">A. Maklumat Pelanggan</div>
<table class="fields">
<tr>
<th>Nama Pelanggan</th>
<td colspan="3">{{ $nama }}</td>
</tr>
<tr>
<th>No. MyKad / Passport</th>
<td>{{ $nokp }}</td>
<th style="width:22%">No. Pelanggan</th>
<td>{{ $nopelanggan }}</td>
</tr>
<tr>
<th>No. Telefon</th>
<td>{{ $telefon }}</td>
<th>Cawangan</th>
<td>{{ $cawangan }}</td>
</tr>
</table>
</div>
<div class="section">
<div class="section-title">B. Maklumat Pembiayaan</div>
<table class="fields">
<tr>
<th>No. Surat Akuan Gadai</th>
<td>{{ $norujukan }}</td>
</tr>
<tr>
<th>Amaun Pembiayaan</th>
<td class="amt">RM {{ $pinjaman }}</td>
</tr>
<tr>
<th>Cara Terima Pembiayaan</th>
<td>Online (kredit ke akaun bank) bukan tunai</td>
</tr>
<tr>
<th>Teller</th>
<td>{{ $teller }}</td>
</tr>
</table>
</div>
<div class="section">
<div class="section-title">C. Maklumat Akaun Bank Penerima</div>
<table class="fields">
<tr>
<th>Nama Bank</th>
<td>{{ $bank }}</td>
</tr>
<tr>
<th>No. Akaun</th>
<td>{{ $noakaun }}</td>
</tr>
<tr>
<th>Nama Pemegang Akaun</th>
<td>{{ $nama_akaun }}</td>
</tr>
</table>
</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>
</ol>
</div>
</div>
<div class="signs">
<div class="sign-box">
<div class="role">Tandatangan Pelanggan</div>
<div class="sign-line">
<p>Nama: {{ $nama }}</p>
<p>No. KP: {{ $nokp }}</p>
<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>
<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>
</x-print.footer>
</div>
</div>
</body>
</html>
@@ -0,0 +1,260 @@
<!DOCTYPE html>
<html lang="ms">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Maklumat Pelanggan {{ $nopelanggan }}</title>
<style>
* { box-sizing: border-box; }
body {
margin: 0;
background: #e8eaed;
font-family: Arial, Helvetica, sans-serif;
color: #111;
}
.toolbar {
position: sticky;
top: 0;
z-index: 10;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 10px 16px;
background: #1f3a5f;
color: #fff;
font-size: 13px;
}
.toolbar b { letter-spacing: 0.04em; }
.toolbar .hint { opacity: 0.85; }
.toolbar button {
border: 0;
background: #fff;
color: #1f3a5f;
font-weight: bold;
padding: 8px 16px;
cursor: pointer;
border-radius: 3px;
}
.sheet-wrap {
padding: 24px 0 48px;
}
.sheet {
width: 210mm;
min-height: 297mm;
margin: 0 auto;
background: #fff;
padding: 14mm 14mm 16mm;
box-shadow: 0 2px 12px rgba(0,0,0,.18);
}
.title {
text-align: center;
margin: 14px 0 4px;
}
.title h1 {
margin: 0;
font-size: 14px;
letter-spacing: 0.04em;
text-transform: uppercase;
}
.title p {
margin: 4px 0 0;
font-size: 10px;
color: #333;
}
.identity {
display: grid;
grid-template-columns: minmax(0, 1fr) 32mm;
gap: 12px;
align-items: start;
margin-top: 12px;
}
.photo-box {
width: 32mm;
text-align: center;
}
.photo-box img {
display: block;
width: 32mm;
height: 42mm;
object-fit: cover;
border: 1px solid #111;
background: #eef1f4;
}
.photo-box .caption {
margin-top: 4px;
font-size: 8.5px;
color: #444;
letter-spacing: 0.04em;
text-transform: uppercase;
}
.section {
margin-top: 12px;
}
.section-title {
font-size: 11px;
font-weight: bold;
background: #eef1f4;
border: 1px solid #111;
border-bottom: 0;
padding: 5px 8px;
text-transform: uppercase;
}
table.fields {
width: 100%;
border-collapse: collapse;
font-size: 11px;
}
table.fields th,
table.fields td {
border: 1px solid #111;
padding: 6px 8px;
vertical-align: top;
text-align: left;
}
table.fields th {
width: 28%;
font-weight: normal;
background: #fafbfc;
color: #222;
}
@page {
size: A4;
margin: 10mm;
}
@media print {
body { background: #fff; }
.toolbar { display: none !important; }
.sheet-wrap { padding: 0; }
.sheet {
width: auto;
min-height: 0;
margin: 0;
padding: 0;
box-shadow: none;
}
}
</style>
</head>
<body>
<div class="toolbar">
<div>
<b>Maklumat Pelanggan</b>
<span class="hint"> {{ $nama }}</span>
</div>
<button type="button" onclick="window.print()">Cetak / Pratinjau</button>
</div>
<div class="sheet-wrap">
<div class="sheet">
<x-print.header
:cawangan="$cawangan"
:kodcaw="$kodcaw"
:alamat1="$alamat1"
:alamat2="$alamat2"
:notel="$notel"
:tarikh="$tarikh"
:norujukan="$nopelanggan"
rujukan-label="No. Pelanggan"
/>
<div class="title">
<h1>Maklumat Pelanggan</h1>
<p>Salinan maklumat pengenalan diri pelanggan untuk rujukan cawangan.</p>
</div>
<div class="identity">
<div>
<div class="section-title">A. Pengenalan Diri</div>
<table class="fields">
<tr>
<th>Nama</th>
<td>{{ $nama }}</td>
</tr>
<tr>
<th>No. Kad Pengenalan</th>
<td>{{ $nokp }}</td>
</tr>
<tr>
<th>No. Pelanggan</th>
<td>{{ $nopelanggan }}</td>
</tr>
<tr>
<th>Status</th>
<td>{{ $status }}</td>
</tr>
</table>
</div>
<div class="photo-box">
<img src="{{ $photo_src }}" alt="Gambar MyKad">
<div class="caption">Gambar MyKad</div>
</div>
</div>
<div class="section">
<div class="section-title">B. Maklumat Hubungan &amp; Alamat</div>
<table class="fields">
<tr>
<th>No. Telefon 1</th>
<td>{{ $telefon }}</td>
<th style="width:22%">No. Telefon 2</th>
<td>{{ $telefon2 }}</td>
</tr>
<tr>
<th>Alamat</th>
<td colspan="3">{{ $alamat }}</td>
</tr>
<tr>
<th>Poskod</th>
<td>{{ $poskod }}</td>
<th>Daerah</th>
<td>{{ $daerah }}</td>
</tr>
<tr>
<th>Negeri</th>
<td colspan="3">{{ $negeri }}</td>
</tr>
</table>
</div>
<div class="section">
<div class="section-title">C. Bank &amp; Pekerjaan</div>
<table class="fields">
<tr>
<th>Bank</th>
<td colspan="3">{{ $bank }}</td>
</tr>
<tr>
<th>No. Akaun</th>
<td colspan="3">{{ $noakaun }}</td>
</tr>
<tr>
<th>Pekerjaan</th>
<td colspan="3">{{ $pekerjaan }}</td>
</tr>
<tr>
<th>Nota</th>
<td colspan="3">{{ $nota }}</td>
</tr>
</table>
</div>
<x-print.footer :show-copies="false">
<b>Nota:</b>
<p>1. Borang ini dicetak dari skrin Maklumat Pelanggan untuk rujukan cawangan.</p>
<p>2. Gambar diambil daripada rekod MyKad pelanggan dalam sistem.</p>
</x-print.footer>
</div>
</div>
</body>
</html>
@@ -634,7 +634,7 @@
<tr> <tr>
<td style="width:10px">(-) Rebat Untung</td> <td style="width:10px">(-) Rebat Untung</td>
<td style="width:10px">: RM <td style="width:10px">: RM
{{ number_format($totalrebate['onepercent'], 2) }} {{ number_format($keuntungan_rebate,2) }}
</td> </td>
</tr> </tr>
<tr> <tr>
@@ -644,7 +644,7 @@
<tr> <tr>
<td style="width:10px">(-) Rebat Lain-Lain Caj</td> <td style="width:10px">(-) Rebat Lain-Lain Caj</td>
<td style="width:10px">: RM <td style="width:10px">: RM
{{ number_format($totalrebate['ujrah'], 2) }} </td> {{ number_format($ujrah_rebate, 2) }} </td>
</tr> </tr>
<tr> <tr>
<td style="width:10px">Jumlah Keuntungan + Lain-Lain Caj</td> <td style="width:10px">Jumlah Keuntungan + Lain-Lain Caj</td>
@@ -867,7 +867,7 @@
<tr> <tr>
<td style="width:10px">(-) Rebat Untung</td> <td style="width:10px">(-) Rebat Untung</td>
<td style="width:10px">: RM <td style="width:10px">: RM
{{ number_format($totalrebate['onepercent'], 2) }} {{ number_format($keuntungan_rebate, 2) }}
</td> </td>
</tr> </tr>
<tr> <tr>
@@ -877,7 +877,7 @@
<tr> <tr>
<td style="width:10px">(-) Rebat Lain-Lain Caj</td> <td style="width:10px">(-) Rebat Lain-Lain Caj</td>
<td style="width:10px">: RM <td style="width:10px">: RM
{{ number_format($totalrebate['ujrah'], 2) }} </td> {{ number_format($ujrah_rebate, 2) }} </td>
</tr> </tr>
<tr> <tr>
<td style="width:10px">Jumlah Keuntungan + Lain-Lain Caj</td> <td style="width:10px">Jumlah Keuntungan + Lain-Lain Caj</td>
+73 -29
View File
@@ -655,7 +655,7 @@
</table> </table>
<table class="main-table text-center" style="border-top: none !important"> <table class="main-table text-center" style="border-top: none !important">
<tr> <tr>
<th colspan="5">JUMLAH KEUNTUNGAN DAN LAIN-LAIN CAJ BERDASARKAN TEMPOH PEMBIAYAAN</th> <th colspan="5">JUMLAH KEUNTUNGAN BERDASARKAN TEMPOH PEMBIAYAAN</th>
</tr> </tr>
<tr> <tr>
<th>TEMPOH PEMBIAYAAN</th> <th>TEMPOH PEMBIAYAAN</th>
@@ -670,13 +670,13 @@
<td>{{ $formatted_t_matang_1 }}</td> <td>{{ $formatted_t_matang_1 }}</td>
</tr> </tr>
<tr> <tr>
<th class="text-left">JUMLAH KEUNTUNGAN (1%) DAN LAIN-LAIN CAJ YANG PERLU DIBAYAR</th> <th class="text-left">JUMLAH KEUNTUNGAN YANG PERLU DIBAYAR</th>
<td style="font-size:12px"><b>RM{{ number_format($gadai_detail['bakiupah'] / 6, 2) }}</b></td> <td style="font-size:12px"><b>RM{{ number_format($gadai_detail['bakiupah'] / 6, 2) }}</b></td>
<td style="font-size:12px"><b>RM{{ number_format($gadai_detail['bakiupah'], 2) }}</b></td> <td style="font-size:12px"><b>RM{{ number_format($gadai_detail['bakiupah'], 2) }}</b></td>
<td>RM{{ number_format($gadai_detail['bakiupah'], 2) }}</td> <td>RM{{ number_format($gadai_detail['bakiupah'], 2) }}</td>
</tr> </tr>
<tr> <tr>
<th class="text-left">JUMLAH KEUNTUNGAN (1%) DAN LAIN-LAIN CAJ TERKUMPUL<br> SEPANJANG TEMPOH <th class="text-left">JUMLAH KEUNTUNGAN TERKUMPUL<br> SEPANJANG TEMPOH
PEMBIAYAAN PEMBIAYAAN
</th> </th>
<td>-</td> <td>-</td>
@@ -717,7 +717,7 @@
<tr> <tr>
<td colspan="4"> <td colspan="4">
Pelanggan bersetuju dan dengan ini menggadaikan Marhun sebagai cagaran kepada Pelanggan bersetuju dan dengan ini menggadaikan Marhun sebagai cagaran kepada
Jumlah Pembiayaan termasuk Jumlah Keuntungan dan Lain-Lain Caj Terkumpul Jumlah Pembiayaan termasuk Jumlah Keuntungan Terkumpul
mengikut mengikut
syarat-syarat pembiayaan dalam SAG ini. KoPKB dengan ini menerima gadaian Marhun syarat-syarat pembiayaan dalam SAG ini. KoPKB dengan ini menerima gadaian Marhun
tersebut. tersebut.
@@ -731,14 +731,12 @@
<td style="vertical-align: top"> <td style="vertical-align: top">
<table class="akad-table"> <table class="akad-table">
<tr> <tr>
<td colspan="4"><b><u>AKAD (SIMPAN MARHUN)</u></b></td> <td colspan="4"><b><u>KADAR KEUNTUNGAN BULANAN</u></b></td>
</tr> </tr>
<tr> <tr>
<td colspan="4"> <td colspan="4">
Pelanggan dengan ini bersetuju marhun disimpan oleh KoPKB dan bersetuju membayar Kadar keuntungan Pembiayaan Ar-Rahn Tawarruq adalah berdasarkan jumlah
lain-lain caj pembiayaan seperti berikut :
sebagaimana jadual dibawah bagi setiap
RM100.00 Jumlah Marhun Keseluruhan:
</td> </td>
</tr> </tr>
</table> </table>
@@ -746,35 +744,35 @@
<table class="main-table text-center" <table class="main-table text-center"
style="font-size: 9px; width: 100%; margin-top: 5px;"> style="font-size: 9px; width: 100%; margin-top: 5px;">
<tr> <tr>
<th>KADAR LAIN-LAIN CAJ / RM100</th> <th>KADAR KEUNTUNGAN SEBULAN (%)</th>
</tr> </tr>
<tr> <tr>
<td>RM{{ $kadar_ujrah[0]['kadarujrah'] }}/sebulan</td> <td>1.00</td>
</tr> </tr>
</table> </table>
@else @else
<table class="main-table text-center" <table class="main-table text-center"
style="font-size: 9px; width: 100%; margin-top: 5px;"> style="font-size: 9px; width: 100%; margin-top: 5px;">
<tr> <tr>
<th>NILAI MARHUN KESELURUHAN</th> <th>JUMLAH PEMBIAYAAN</th>
<th></th> <th></th>
<th>KADAR LAIN-LAIN CAJ / RM100</th> <th>KADAR KEUNTUNGAN SEBULAN (%)</th>
</tr> </tr>
<tr> <tr>
<td> <td>
< RM400.99</td> < RM400.99</td>
<td>-</td> <td>-</td>
<td>RM{{ $kadar_ujrah[0]['kadarujrah'] }}/sebulan</td> <td>{{ $kadar_ujrah[0]['kadarupdah'] }}</td>
</tr> </tr>
<tr> <tr>
<td>RM401.00 - RM2000.99</td> <td>RM401.00 - RM2000.99</td>
<td>-</td> <td>-</td>
<td>RM{{ $kadar_ujrah[1]['kadarujrah'] }}/sebulan</td> <td>{{ $kadar_ujrah[1]['kadarupdah'] }}</td>
</tr> </tr>
<tr> <tr>
<td>> RM2001.00</td> <td>> RM2001.00</td>
<td>-</td> <td>-</td>
<td>RM{{ $kadar_ujrah[2]['kadarujrah'] }}/sebulan</td> <td>{{ $kadar_ujrah[2]['kadarupdah'] }}</td>
</tr> </tr>
</table> </table>
@endif @endif
@@ -829,15 +827,15 @@
<tr> <tr>
<td></td> <td></td>
<td>e.</td> <td>e.</td>
<td colspan="2">Membayar Jumlah Pembiayaan termasuk Jumlah Keuntungan dan <td colspan="2">Membayar Jumlah Pembiayaan termasuk Jumlah Keuntungan yang diberi
Lain-Lain Caj Terkumpul yang diberi mengikut dalam tempoh seperti di SAG ini; mengikut dalam tempoh seperti di SAG ini;
</td> </td>
</tr> </tr>
<tr> <tr>
<td></td> <td></td>
<td>f.</td> <td>f.</td>
<td colspan="2">Tertakluk kepada pembayaran penuh Jumlah Pembiayaan termasuk <td colspan="2">Tertakluk kepada pembayaran penuh Jumlah Pembiayaan termasuk
Jumlah Keuntungan dan Lain-Lain Caj Terkumpul, Pelanggan perlu hadir sendiri Jumlah Keuntungan Terkumpul, Pelanggan perlu hadir sendiri
menyelesaikan Pembiayaan;</td> menyelesaikan Pembiayaan;</td>
</tr> </tr>
<tr> <tr>
@@ -1066,8 +1064,7 @@
<td colspan="1"></td> <td colspan="1"></td>
<td colspan="1">o.</td> <td colspan="1">o.</td>
<td colspan="2">KoPKB berhak menuntut Jumlah Pembiayaan termasuk Jumlah Keuntungan <td colspan="2">KoPKB berhak menuntut Jumlah Pembiayaan termasuk Jumlah Keuntungan
dan setelah tamat tempoh yang dinyatakan pada notis berkaitan;
Lain-Lain Caj Terkumpul setelah tamat tempoh yang dinyatakan pada notis berkaitan;
</td> </td>
</tr> </tr>
<tr> <tr>
@@ -1212,8 +1209,7 @@
<td colspan="1">a.</td> <td colspan="1">a.</td>
<td colspan="2">Marhun akan dilelong atau dijual. KoPKB akan membatalkan <td colspan="2">Marhun akan dilelong atau dijual. KoPKB akan membatalkan
lelongan Marhun sekiranya Pelanggan membayar tunggakan Jumlah Pembiayaan lelongan Marhun sekiranya Pelanggan membayar tunggakan Jumlah Pembiayaan
termasuk Jumlah Keuntungan dan termasuk Jumlah Keuntungan
Lain-Lain Caj
sepenuhnya tiga (3) hari sebelum tarikh lelongan;</td> sepenuhnya tiga (3) hari sebelum tarikh lelongan;</td>
</tr> </tr>
<tr> <tr>
@@ -1233,7 +1229,7 @@
<td colspan="1">c.</td> <td colspan="1">c.</td>
<td colspan="2">Sebarang lebihan hasil lelongan atau jualan (jika ada) setelah <td colspan="2">Sebarang lebihan hasil lelongan atau jualan (jika ada) setelah
ditolak ditolak
Jumlah Pembiayaan termasuk Jumlah Keuntungan dan Lain-Lain Caj Terkumpul Jumlah Pembiayaan termasuk Jumlah Keuntungan Terkumpul
yang tertunggak dan kos-kos berkaitan akan yang tertunggak dan kos-kos berkaitan akan
dikembalikan dikembalikan
kepada Pelanggan dan;</td> kepada Pelanggan dan;</td>
@@ -1264,7 +1260,7 @@
<td>=</td> <td>=</td>
<td><b>BAKI TEMPOH PEMBIAYAAN <br></td> <td><b>BAKI TEMPOH PEMBIAYAAN <br></td>
<td>X</td> <td>X</td>
<td><b>JUMLAH KEUNTUNGAN 1% DAN LAIN-LAIN CAJ YANG PERLU DIBAYAR</b></td> <td><b>JUMLAH KEUNTUNGAN YANG PERLU DIBAYAR</b></td>
</tr> </tr>
</table> </table>
<br> <br>
@@ -1280,7 +1276,7 @@
<td colspan="1"></td> <td colspan="1"></td>
<td colspan="1">a.</td> <td colspan="1">a.</td>
<td colspan="2">Pelanggan gagal dan/ingkar menjelaskan Jumlah Pembiayaan, <td colspan="2">Pelanggan gagal dan/ingkar menjelaskan Jumlah Pembiayaan,
Jumlah Keuntungan dan Lain-Lain Caj Terkumpul pada tarikh yang ditetapkan; Jumlah Keuntungan Terkumpul pada tarikh yang ditetapkan;
</td> </td>
</tr> </tr>
<tr> <tr>
@@ -1306,6 +1302,45 @@
<tr> <tr>
<td colspan="1"></td> <td colspan="1"></td>
<td colspan="1"><b>6.</b></td> <td colspan="1"><b>6.</b></td>
<td colspan="2"><b>Pelanggan dengan ini bersetuju, memberi kuasa dan melantik
Ar-Rahn sebagai wakil (wakil di bawah prinsip Wakalah) untuk menguruskan baki
hasil lelongan yang menjadi hak Pelanggan,
sekiranya ada, yang bernilai RM10.00 (Ringgit Malaysia: Sepuluh) atau kurang,
dan tidak dituntut oleh Pelanggan dalam tempoh tuntutan yang ditetapkan oleh
Ar-Rahn setelah usaha munasabah telah diambil untuk memaklumkan kepada
Pelanggan.</b></td>
</tr>
<tr>
<td colspan="1"></td>
<td colspan="1"><b>7.</b></td>
<td colspan="2">
<b>
Pelanggan selanjutnya bersetuju bahawa baki hasil lelongan tersebut boleh
disalurkan oleh Ar-Rahn, bagi pihak Pelanggan, sebagai infaq kepada mana-mana
institusi agama Islam, badan kebajikan atau institusi lain yang diluluskan
oleh Jawatankuasa Syariah dan/atau pihak berkuasa yang berkaitan, tanpa
sebarang liabiliti ke atas Ar-Rahn selepas penyaluran tersebut dibuat dengan
jumlah tidak melebihi RM10.00.
</b>
<div style="margin-top: 8px;">
<span
style="
display: inline-block;
width: 13px;
height: 13px;
border: 1px solid #000;
margin-right: 6px;
vertical-align: middle;
"></span>
<b>Tandakan (✓) jika tidak bersetuju.</b>
</div>
</td>
</tr>
<tr>
<td colspan="1"></td>
<td colspan="1"><b>8.</b></td>
<td colspan="2"><b>Sekiranya Pelanggan mempunyai sebarang pertanyaan atau <td colspan="2"><b>Sekiranya Pelanggan mempunyai sebarang pertanyaan atau
aduan, Pelanggan boleh menghubungi KoPKB sebagaimana berikut:</b></td> aduan, Pelanggan boleh menghubungi KoPKB sebagaimana berikut:</b></td>
</tr> </tr>
@@ -1713,6 +1748,7 @@
{{-- MARHUN DETAIL SECTION --}} {{-- MARHUN DETAIL SECTION --}}
<div class="main-marhun"> <div class="main-marhun">
<table class="main-table text-center" style="width: 75%"> <table class="main-table text-center" style="width: 75%">
<tr> <tr>
<th>BIL</th> <th>BIL</th>
<th>JENIS MARHUN</th> <th>JENIS MARHUN</th>
@@ -1846,7 +1882,7 @@
</table> </table>
<table class="main-table text-center" style="border-top: none !important"> <table class="main-table text-center" style="border-top: none !important">
<tr> <tr>
<th colspan="5">JUMLAH KEUNTUNGAN DAN LAIN-LAIN CAJ BERDASARKAN TEMPOH PEMBIAYAAN</th> <th colspan="5">JUMLAH KEUNTUNGAN BERDASARKAN TEMPOH PEMBIAYAAN</th>
</tr> </tr>
<tr> <tr>
<th>TEMPOH PEMBIAYAAN</th> <th>TEMPOH PEMBIAYAAN</th>
@@ -1861,19 +1897,27 @@
<td>{{ $formatted_t_matang_1 }}</td> <td>{{ $formatted_t_matang_1 }}</td>
</tr> </tr>
<tr> <tr>
<th class="text-left">JUMLAH KEUNTUNGAN DAN LAIN-LAIN CAJ YANG PERLU DIBAYAR BULANAN</th> <th class="text-left">JUMLAH KEUNTUNGAN YANG PERLU DIBAYAR BULANAN</th>
<td style="font-size:12px"><b>RM{{ number_format($gadai_detail['bakiupah'] / 6, 2) }}</b></td> <td style="font-size:12px"><b>RM{{ number_format($gadai_detail['bakiupah'] / 6, 2) }}</b></td>
<td style="font-size:12px"><b>RM{{ number_format($gadai_detail['bakiupah'], 2) }}</b></td> <td style="font-size:12px"><b>RM{{ number_format($gadai_detail['bakiupah'], 2) }}</b></td>
<td>RM {{ number_format($gadai_detail['bakiupah'], 2) }}</td> <td>RM {{ number_format($gadai_detail['bakiupah'], 2) }}</td>
</tr> </tr>
<tr> <tr>
<th class="text-left">JUMLAH KEUNTUNGAN DAN LAIN-LAIN CAJ TERKUMPUL <br> SEPANJANG TEMPOH <th class="text-left">JUMLAH KEUNTUNGAN TERKUMPUL <br> SEPANJANG TEMPOH
PEMBIAYAAN</th> PEMBIAYAAN</th>
<td>-</td> <td>-</td>
<td>RM{{ number_format($gadai_detail['bakiupah'], 2) }}</td> <td>RM{{ number_format($gadai_detail['bakiupah'], 2) }}</td>
<td>RM{{ number_format($gadai_detail['upah12'], 2) }}</td> <td>RM{{ number_format($gadai_detail['upah12'], 2) }}</td>
</tr> </tr>
</table> </table>
<div style="color: black; font-weight: bold; margin-top: 0px; width: 100%;">
<span>PENGURUSAN HASIL LELONGAN YANG TIDAK DITUNTUT</span>
<span>
<input type="checkbox" name="hasil_lelongan_tidak_dituntut">
</span>
</div>
{{-- SIGNATURE SECTION --}} {{-- SIGNATURE SECTION --}}
<table class="main-table mt-10"> <table class="main-table mt-10">
<tr> <tr>
+21
View File
@@ -15,6 +15,23 @@ use Illuminate\Support\Facades\Storage;
| |
*/ */
Route::get('/health', function () {
return response()->json(['status' => 'ok']);
});
// Dummy print form — no auth, for layout design only
Route::get('/dummy/borang-pengesahan-online', function () {
return view('dummy.borang_pengesahan_online');
});
Route::get('/dummy/borang-batch-pembiayaan-online', function () {
return view('dummy.borang_batch_pembiayaan_online');
});
Route::get('/dummy/maklumat-pelanggan', function () {
return view('dummy.maklumat_pelanggan');
});
Route::get('/',['as'=>'login','uses'=>'HomeController@index']); Route::get('/',['as'=>'login','uses'=>'HomeController@index']);
Route::get('/testtry',function(ImbangdugaServices $imbangduga){ Route::get('/testtry',function(ImbangdugaServices $imbangduga){
@@ -89,6 +106,7 @@ Route::get('/home', 'HomeController@index')->name('home');
//Teller //Teller
Route::get('/pelanggan','CustomersController@index')->middleware('auth','teller'); Route::get('/pelanggan','CustomersController@index')->middleware('auth','teller');
Route::get('/customer_info',['as'=>'customer_info','uses'=>'CustomersController@info'])->middleware('auth','teller'); Route::get('/customer_info',['as'=>'customer_info','uses'=>'CustomersController@info'])->middleware('auth','teller');
Route::get('/customer/maklumat/cetak',['as'=>'customer.print.maklumat','uses'=>'CustomersController@printMaklumat'])->middleware('auth','teller');
Route::get('/gadaian',['as'=>'gadaian','uses'=>'GadaianController@index'])->middleware('auth','teller'); Route::get('/gadaian',['as'=>'gadaian','uses'=>'GadaianController@index'])->middleware('auth','teller');
Route::get('/daftar_marhun',['as'=>'daftar_marhun','uses'=>'MarhunController@index'])->middleware('auth','teller'); Route::get('/daftar_marhun',['as'=>'daftar_marhun','uses'=>'MarhunController@index'])->middleware('auth','teller');
Route::get('/penebusan/{norujukan}',['as'=>'penebusan','uses' => 'PenebusanController@index'])->middleware('auth'); Route::get('/penebusan/{norujukan}',['as'=>'penebusan','uses' => 'PenebusanController@index'])->middleware('auth');
@@ -109,6 +127,8 @@ Route::post('/profile/edit/store',['as'=>'profile.editstore','uses'=>'UserContro
Route::get('/customer/search',['as'=>'customer.search','uses'=>'CustomersController@search'])->middleware('auth','teller'); Route::get('/customer/search',['as'=>'customer.search','uses'=>'CustomersController@search'])->middleware('auth','teller');
Route::post('/customer/store',['as'=>'customer.store','uses'=>'CustomersController@store'])->middleware('auth','teller'); Route::post('/customer/store',['as'=>'customer.store','uses'=>'CustomersController@store'])->middleware('auth','teller');
Route::post('/gadai/store',['as'=>'gadai.store','uses'=>'GadaianController@store'])->middleware('auth','teller'); Route::post('/gadai/store',['as'=>'gadai.store','uses'=>'GadaianController@store'])->middleware('auth','teller');
Route::get('/gadai/borang-pengesahan-online/{norujukan}',['as'=>'gadai.print.borang.online','uses'=>'GadaianController@printBorangPengesahanOnline'])->middleware('auth','teller');
Route::post('/gadai/borang-pengesahan-online/preview',['as'=>'gadai.print.borang.online.preview','uses'=>'GadaianController@previewBorangPengesahanOnline'])->middleware('auth','teller');
Route::get('/gadai/batal',['as'=>'gadai.batal','uses'=>'GadaianController@batal'])->middleware('auth','teller'); Route::get('/gadai/batal',['as'=>'gadai.batal','uses'=>'GadaianController@batal'])->middleware('auth','teller');
Route::get('/customer/otp',['as'=>'customer.otp','uses'=>'CustomersController@getOTP'])->middleware('auth','teller'); Route::get('/customer/otp',['as'=>'customer.otp','uses'=>'CustomersController@getOTP'])->middleware('auth','teller');
Route::get('/getwakil',['as'=>'wakil.search','uses'=>'PenebusanController@getWakil'])->middleware('auth','teller'); Route::get('/getwakil',['as'=>'wakil.search','uses'=>'PenebusanController@getWakil'])->middleware('auth','teller');
@@ -190,6 +210,7 @@ Route::get('/pembiayaan-online/operasi/eod',['as'=>'pembiayaan.online.operasi.eo
Route::post('/pembiayaan-online/operasi/eod/close-all',['as'=>'pembiayaan.online.operasi.eod.closeall','uses'=>'PembiayaanOnlineController@operasiCloseAllModal'])->middleware('auth','operasi'); Route::post('/pembiayaan-online/operasi/eod/close-all',['as'=>'pembiayaan.online.operasi.eod.closeall','uses'=>'PembiayaanOnlineController@operasiCloseAllModal'])->middleware('auth','operasi');
Route::post('/pembiayaan-online/operasi/eod/{kodcaw}/close',['as'=>'pembiayaan.online.operasi.eod.close','uses'=>'PembiayaanOnlineController@operasiCloseModal'])->middleware('auth','operasi'); Route::post('/pembiayaan-online/operasi/eod/{kodcaw}/close',['as'=>'pembiayaan.online.operasi.eod.close','uses'=>'PembiayaanOnlineController@operasiCloseModal'])->middleware('auth','operasi');
Route::get('/pembiayaan-online/operasi/{kodcaw}/batches/{batchId}',['as'=>'pembiayaan.online.operasi.batch','uses'=>'PembiayaanOnlineController@operasiShowBatch'])->middleware('auth','operasi'); Route::get('/pembiayaan-online/operasi/{kodcaw}/batches/{batchId}',['as'=>'pembiayaan.online.operasi.batch','uses'=>'PembiayaanOnlineController@operasiShowBatch'])->middleware('auth','operasi');
Route::get('/pembiayaan-online/operasi/{kodcaw}/batches/{batchId}/print',['as'=>'pembiayaan.online.operasi.batch.print','uses'=>'PembiayaanOnlineController@operasiPrintBatch'])->middleware('auth','operasi');
Route::post('/pembiayaan-online/operasi/{kodcaw}/lines/{id}/approve',['as'=>'pembiayaan.online.operasi.approve','uses'=>'PembiayaanOnlineController@operasiApproveLine'])->middleware('auth','operasi'); Route::post('/pembiayaan-online/operasi/{kodcaw}/lines/{id}/approve',['as'=>'pembiayaan.online.operasi.approve','uses'=>'PembiayaanOnlineController@operasiApproveLine'])->middleware('auth','operasi');
Route::post('/pembiayaan-online/operasi/{kodcaw}/lines/{id}/reject',['as'=>'pembiayaan.online.operasi.reject','uses'=>'PembiayaanOnlineController@operasiRejectLine'])->middleware('auth','operasi'); Route::post('/pembiayaan-online/operasi/{kodcaw}/lines/{id}/reject',['as'=>'pembiayaan.online.operasi.reject','uses'=>'PembiayaanOnlineController@operasiRejectLine'])->middleware('auth','operasi');
Route::post('/pembiayaan-online/operasi/{kodcaw}/batches/{batchId}/approve',['as'=>'pembiayaan.online.operasi.approve.batch','uses'=>'PembiayaanOnlineController@operasiApproveBatch'])->middleware('auth','operasi'); Route::post('/pembiayaan-online/operasi/{kodcaw}/batches/{batchId}/approve',['as'=>'pembiayaan.online.operasi.approve.batch','uses'=>'PembiayaanOnlineController@operasiApproveBatch'])->middleware('auth','operasi');