62 Commits

Author SHA1 Message Date
nurafrinaalimi16 ca2a02e354 hotfix: betulkan lajur tarikh untuk semakan ubah dan hapus tebus
Build Docker Image / build-frontend (push) Successful in 40s
2026-09-27 15:10:29 +08:00
nurafrinaalimi16 76176ed453 hotfix:ubah hapus gadai
Build Docker Image / build-frontend (push) Successful in 40s
2026-09-27 13:05:54 +08:00
ISMAIL MASSERAN 17a5bfafdd DONE: update env.staging
Build Docker Image / build-frontend (push) Successful in 41s
2026-09-26 18:51:36 +08:00
ismailmasseran 5f29f064a0 Merge pull request 'feat(khazanah): papar tarikh ansuran terkini dalam senarai gadaian' (#25) from feat/khazanah-tarikh-ansuran-terkini into main
Reviewed-on: #25
2026-09-24 16:16:16 +08:00
nurafrinaalimi16 9dae32a462 feat(khazanah): papar tarikh ansuran terkini dalam senarai gadaian 2026-09-24 16:15:20 +08:00
ismailmasseran c437a3f504 Feature/whatsapp blast (#24)
Build Docker Image / build-frontend (push) Successful in 53s
Co-authored-by: ISMAIL MASSERAN <topaz@Mac.dlinkrouter.local>
Co-authored-by: ISMAIL MASSERAN <topaz@ISMAILs-Macbook.local>
Reviewed-on: #24
2026-09-24 11:28:05 +08:00
ismailmasseran b41234e10b Merge pull request 'DONE: fix duplicate url when print aliran tunai report' (#23) from hotfix/aliran-tunai into main
Reviewed-on: #23
2026-09-22 10:30:49 +08:00
ismailmasseran b7d217948c Merge branch 'main' into hotfix/aliran-tunai 2026-09-22 10:24:11 +08:00
ISMAIL MASSERAN 2d282897fc DONE: fix duplicate url when print aliran tunai report
Build Docker Image / build-frontend (push) Successful in 41s
2026-09-22 10:14:52 +08:00
ismailmasseran 94f8f6e855 Merge pull request 'fix: selaraskan tarikh lelong khazanah dan tambah pengesahan baki lelong sebelum daftar pembiayaan' (#22) from fix/tarikh-lelong-khazanah into main
Reviewed-on: #22
2026-09-21 16:22:54 +08:00
nurafrinaalimi16 72108d3f87 fix: selaraskan tarikh lelong khazanah dan tambah pengesahan baki lelong sebelum daftar pembiayaan 2026-09-21 14:57:28 +08:00
ISMAIL MASSERAN 3a64cc2400 DONE: drop column cara terima on report 2026-09-20 16:37:30 +08:00
ismailmasseran 1d71a31078 Merge pull request 'DONE: use constant for min and max pinjaman' (#21) from bugfix/limit-error-cara-terima into main
Reviewed-on: #21
2026-09-20 16:28:38 +08:00
ISMAIL MASSERAN 8e19d0488a DONE: use constant for min and max pinjaman 2026-09-20 16:28:01 +08:00
ismailmasseran 918502b504 Experiment/merge annur (#20)
Build Docker Image / build-frontend (push) Successful in 55s
Co-authored-by: ISMAIL MASSERAN <topaz@Mac.dlinkrouter.local>
Reviewed-on: #20
2026-09-20 12:49:39 +08:00
ismailmasseran 8a2bae7a88 Merge pull request 'tagging cara terima on sag and reports' (#19) from feature/tagging-cara-terima into main
Reviewed-on: #19
2026-09-20 09:38:02 +08:00
nurafrinaalimi16 67af37a1cd tagging cara terima on sag and reports 2026-09-17 16:26:00 +08:00
ismailmasseran 3f6fb8d985 Merge pull request 'feature/modal-online-pendahuluan' (#18) from feature/modal-online-pendahuluan into main
Reviewed-on: #18
2026-09-17 07:59:00 +08:00
nurafrinaalimi16 371e4d8168 feature/modal-online-pendahuluan 2026-09-14 15:22:28 +08:00
nurafrinaalimi16 5d421760af fix resit tebus
Build Docker Image / build-frontend (push) Failing after 21s
2026-09-10 09:58:32 +08:00
ismailmasseran 44b1d847bc Explore/ismail (#17)
Build Docker Image / build-frontend (push) Successful in 14s
Co-authored-by: ISMAIL MASSERAN <topaz@ISMAILs-Macbook.local>
Co-authored-by: nurafrinaalimi16 <nurafrinaalimi16@gmail.com>
Co-authored-by: ISMAIL MASSERAN <topaz@Mac.dlinkrouter.local>
Reviewed-on: #17
Co-authored-by: ismailmasseran <ismail@koppkb.com>
Co-committed-by: ismailmasseran <ismail@koppkb.com>
2026-09-09 14:46:15 +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 11s
Co-authored-by: ISMAIL MASSERAN <topaz@ISMAILs-Macbook.local>
Reviewed-on: #1
2026-08-26 13:36:18 +08:00
nurafrinaalimi16 a825b0b467 add new line for address on notis tuntut baki 2026-07-28 16:46:31 +08:00
nurafrinaalimi16 b0792cceb0 Merge remote-tracking branch 'origin/production' into rebate-anggota 2026-06-15 11:17:24 +08:00
nurafrinaalimi16 82f31baa6a :new logo koperasi 2026-06-15 10:22:51 +08:00
Muhd hafifie 3d3a7f35f9 Merged in feature/margin-hargaemas-v2 (pull request #376)
Feature/margin hargaemas v2
2026-04-19 07:27:21 +00:00
Hafifie PKB 46c7cc08a7 1. added homepage for laporan audit in operasi.
2. bug fix when table 0 marhun must cannot press papar margin.
3. bug fix when in papar margin display must cannot press daftar marhun.
4. change bersih to bersih in editmarhun
2026-04-19 15:07:48 +08:00
Hafifie PKB 79ee2be97e added audit logs in marginmutu operasi 2026-04-19 15:07:48 +08:00
Hafifie PKB 55168f350a 1. add button papar margin that change to shows information about margin.
2. fix the nilai marhun not same with the calculation in javascript. we use all from PHP
2026-04-19 15:07:47 +08:00
Hafifie PKB 0a19fa3d4b block scientific notation from input type number include [e,+,0,-] 2026-04-19 15:06:07 +08:00
Hafifie PKB fbec4b82fd Block Margin Mutu for operasi edit.
2. popup error for each error logs
2026-04-19 15:06:07 +08:00
Hafifie PKB 8190c7361f add view marhun & make table for details of marhun and margins 2026-04-19 15:06:04 +08:00
Hafifie PKB 73db8b97d7 1.added page that shows margin mutu harga emas.
2. change in gadaian page that have static 75% and 80% max pinjam turn to manipulate value of max pinjam based on mutu emas
2026-04-19 15:04:37 +08:00
Ain Izazi Aziz 66b8f7f195 Merged in hotfix/removekodcawkaunterv2 (pull request #359)
remove kodcaw
2026-01-06 14:00:13 +00:00
Ain Izazi b81fb5157c remove kodcaw 2026-01-06 21:57:28 +08:00
Ain Izazi Aziz f66198c206 Merged in bugfix/blockEnterarosV2 (pull request #347)
add block enter after add new customer
2025-12-03 05:16:32 +00:00
Ain Izazi 7e9dc3b72a add block enter after add new customer 2025-12-03 13:15:35 +08:00
Muhd hafifie 5de4632ed5 Merged in bugfix/resit-ujrah (pull request #345)
Bugfix/resit ujrah
2025-12-02 12:12:47 +00:00
Cyclone 0156712742 remove totalrebate 2025-12-02 20:09:29 +08:00
Cyclone 94abeef80b fix the defined variable ujrah_rebate and keuntungan_rebate 2025-11-28 23:15:31 +08:00
Cyclone 98ad8a585e resit ujrah align with keuntungan 2025-11-23 20:49:23 +08:00
Aiman Mazlan 5b3bd0ea5a Merged in Hotfix/aiman/block-tebus-refer-t_lelong-gadai (pull request #337)
Fix the width of column pembiayaan belum selesai and lelong at role khazanah
2025-11-23 08:51:52 +00:00
aiman 6d5bb3183b Fix the width of column pembiayaan belum selesai and lelong at role khazanah 2025-11-23 13:00:33 +08:00
Aiman Mazlan 7986e6750e Merged in Hotfix/aiman/block-tebus-refer-t_lelong-gadai (pull request #334)
Block SAG refer to t_lelong table gadai instead of table lelong
2025-11-19 08:54:25 +00:00
aiman c50dbe6013 Block SAG refer to t_lelong table gadai instead of table lelong 2025-11-13 11:27:25 +08:00
Aiman Mazlan da06e1ff74 Merged in bugfix/block-tuntutbaki (pull request #330)
Bugfix/block tuntutbaki
2025-10-30 00:35:06 +00:00
Aiman Mazlan baf9975a87 Merged in feature/aiman/laporan-aliran-tunai-teller-v2 (pull request #327)
Feature/aiman/laporan aliran tunai teller v2

Approved-by: Muhd hafifie
2025-10-27 07:55:33 +00:00
aiman 42493bd265 Change the status from 403 to 200 2025-10-26 16:27:28 +08:00
nur amirah zulkipli 22c0325cba Merged in bugfix/laporan-kawalan-stok (pull request #326)
Fix calculation for column baki on Laporan Kawalan Stock report
2025-10-22 05:19:29 +00:00
Amirah c41db872b1 Fix calculation for column baki on Laporan Kawalan Stock report 2025-10-22 13:18:45 +08:00
aiman 1720537632 Filter bakiLelong with kodlaluan and re-adjust the laporan aliran tunai teller 2025-10-15 14:52:08 +08:00
aiman 1d903a70ba Adjust the tarikh for laporan aliran tunai 2025-10-14 16:40:20 +08:00
aiman 1114aadbef Enable datepicker, new route for laporan aliran tunai teller 2025-10-14 15:51:40 +08:00
aiman e6c60a8d83 Update Laporan Aliran Tunai Teller 2025-10-12 12:48:43 +08:00
Hafifie PKB e90886130c block tuntutbaki 2025-10-10 20:32:37 +08:00
Afiq Hamzah e9f9ebad30 Remove unneeded document 2025-10-05 16:36:17 +08:00
Afiq Hamzah cab39e6604 Merge with feature/aiman/pampasan/sempornaV2 2025-10-05 16:34:16 +08:00
Ain Izazi Aziz 04ed49af8a Merged in hotfix/editsuratgadai (pull request #321)
add no tel di sag
2025-09-23 02:24:47 +00:00
Ain Izazi f1a950fb51 add no tel di sag 2025-09-23 10:23:34 +08:00
192 changed files with 15617 additions and 2703 deletions
+16
View File
@@ -0,0 +1,16 @@
.git
.gitea
node_modules
vendor
.env
.env.*
!.env.example
storage/logs
storage/framework/cache
storage/framework/sessions
storage/framework/views
storage/debugbar
npm-debug.log
Dockerfile
docker-compose.yml
docker-compose.staging.yml
+50
View File
@@ -0,0 +1,50 @@
APP_NAME=Laravel
APP_ENV=production
APP_KEY=base64:FFRogE6mJj8FaDSSHl/KpXpzqi2M+P4plxVSRgVM8JI=
APP_DEBUG=false
APP_URL=https://annurv2.erahn.com.my
API_URL=https://apiannurv2.erahn.com.my
API_ARRAHNBID=http://lelong.test/api/
LOG_CHANNEL=stack
DB_CONNECTION=mysql
DB_HOST=192.168.1.216
DB_PORT=3306
DB_DATABASE=kaunter_annur
DB_USERNAME=spider
DB_PASSWORD=arrahn@123
BROADCAST_DRIVER=pusher
CACHE_DRIVER=file
QUEUE_CONNECTION=sync
SESSION_DRIVER=file
SESSION_LIFETIME=120
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
PUSHER_APP_ID=1132718
PUSHER_APP_KEY=26b0ec2f297f94b7aa9b
PUSHER_APP_SECRET=421b61fb5a8ecce477a6
MAIL_MAILER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS=null
MAIL_FROM_NAME="${APP_NAME}"
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
MYKAD_READER_PORT=8085
MYKAD_READER_ENABLED_CAWANGAN='' # IF NO CAWANGAN STATED, ALL CAWANGAN IS ENABLED
+56
View File
@@ -0,0 +1,56 @@
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
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
INTERNAL_NOTIFICATION_SECRET=erahn-local-notification-secret
+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
+5 -3
View File
@@ -1,10 +1,10 @@
APP_NAME=Laravel
APP_ENV=local
APP_VERSION=v3.3
APP_KEY=base64:FFRogE6mJj8FaDSSHl/KpXpzqi2M+P4plxVSRgVM8JI=
APP_DEBUG=true
APP_URL=http://localhost
API_URL=http://localhost:8000
API_ARRAHNBID=http://lelong.test/api/
LOG_CHANNEL=stack
@@ -46,5 +46,7 @@ AWS_BUCKET=
MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
MYKAD_READER_PORT=8085
MYKAD_READER_ENABLED_CAWANGAN='' # IF NO CAWANGAN STATED, ALL CAWANGAN IS ENABLED
MYKAD_READER_PORT=17321
MYKAD_READER_ENABLED_CAWANGAN=''
INTERNAL_NOTIFICATION_SECRET=
+52
View File
@@ -0,0 +1,52 @@
APP_NAME=Laravel
APP_ENV=production
APP_VERSION=v3.3
APP_KEY=base64:WRTHcFTq5YARKVdA8YDUzsh9Ue8vD63ivgZgXNNBiGo=
APP_DEBUG=false
APP_URL=https://ujrah.erahn.com.my
API_URL=https://apiujrah.erahn.com.my
LOG_CHANNEL=stack
DB_CONNECTION=mysql
DB_HOST=192.168.1.216
DB_PORT=3306
DB_DATABASE=kaunter_erahn
DB_USERNAME=spider
DB_PASSWORD=arrahn@123
BROADCAST_DRIVER=pusher
CACHE_DRIVER=file
QUEUE_CONNECTION=sync
SESSION_DRIVER=file
SESSION_LIFETIME=120
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
PUSHER_APP_ID=1132718
PUSHER_APP_KEY=26b0ec2f297f94b7aa9b
PUSHER_APP_SECRET=421b61fb5a8ecce477a6
MAIL_MAILER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS=null
MAIL_FROM_NAME="${APP_NAME}"
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
MYKAD_READER_PORT=17321
MYKAD_READER_ENABLED_CAWANGAN=''
INTERNAL_NOTIFICATION_SECRET="nIDTe(u@6[)VLJyd"
+60
View File
@@ -0,0 +1,60 @@
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=''
INTERNAL_NOTIFICATION_SECRET=staging_noti_secret
+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"
+1 -1
View File
@@ -11,5 +11,5 @@ Homestead.yaml
npm-debug.log
yarn-error.log
/.vscode
docker-compose.yml
docker_src/
**/.DS_Store
+44
View File
@@ -1 +1,45 @@
If you does't know what is this you probably in wrong project
```sql
--- for all ip
CREATE USER 'kaunteruser'@'%' IDENTIFIED BY 'your_password';
--- for specific ip
CREATE USER 'kaunteruser'@'localhost' IDENTIFIED BY 'your_password';
GRANT ALL PRIVILEGES ON kaunter_erahn.* TO 'kaunteruser'@'%';
FLUSH PRIVILEGES;
```
# System Flowchart
## Proses Gadaian
1) Pelanggan datang bawa marhun
2) Teller daftar maklumat pelanggan jika pelanggan baharu
3) Serah marhun ke penilai untuk timbang
4) Teller daftar marhun di sistem berdasarkan maklumat dari penilai
5) Penilai print SAG dan serah marhun ke Khazanah (PC/PPC) - __khazanah can still scan still scan though__
6) Khazanah scan barcode dekat SAG untuk masuk ke dalam peti besi
* table involved - gadai.semakbarcode (default is blank, after is Lulus)
7) DONE
## Setup new branch command:
1. Duplicate previous branch table - use erahn_{{name}}
```sql
SET FOREIGN_KEY_CHECKS = 0;
SET SESSION group_concat_max_len = 1000000;
SELECT GROUP_CONCAT(
CONCAT('TRUNCATE TABLE `', table_name, '`')
SEPARATOR '; '
) INTO @sql
FROM information_schema.tables
WHERE table_schema = DATABASE();
SELECT @sql; --- copy the output and paste it
SET FOREIGN_KEY_CHECKS = 1;
```
2. Add new cawangan kod on master_erahn.arrahn_master_db and and kaunter_erahn.cawangan
3. Register new cawangan detail in erahn_{{name}}.cawangan
+67 -4
View File
@@ -1,20 +1,83 @@
<?php
namespace App;
use DateTime;
class Helper
{
public static function formatNumber($amount)
{
$formattedAmount = number_format($amount, 2, '.', ',');
return $formattedAmount;
return number_format($amount, 2, '.', ',');
}
public static function formatDate($date_string)
{
return (new DateTime($date_string))->format("d-m-Y");
}
public static function terbilangBm(int $number): string
{
$units = [
0 => 'kosong',
1 => 'satu',
2 => 'dua',
3 => 'tiga',
4 => 'empat',
5 => 'lima',
6 => 'enam',
7 => 'tujuh',
8 => 'lapan',
9 => 'sembilan',
10 => 'sepuluh',
11 => 'sebelas'
];
if ($number < 0) return 'negatif ' . self::terbilangBm(abs($number));
if ($number < 12) return $units[$number];
if ($number < 20) return self::terbilangBm($number - 10) . ' belas';
if ($number < 100) {
$tens = intdiv($number, 10);
$rest = $number % 10;
return self::terbilangBm($tens) . ' puluh' . ($rest ? ' ' . self::terbilangBm($rest) : '');
}
if ($number < 200) return 'seratus' . ($number > 100 ? ' ' . self::terbilangBm($number - 100) : '');
if ($number < 1000) {
$hundreds = intdiv($number, 100);
$rest = $number % 100;
return self::terbilangBm($hundreds) . ' ratus' . ($rest ? ' ' . self::terbilangBm($rest) : '');
}
if ($number < 2000) return 'seribu' . ($number > 1000 ? ' ' . self::terbilangBm($number - 1000) : '');
if ($number < 1000000) {
$thousands = intdiv($number, 1000);
$rest = $number % 1000;
return self::terbilangBm($thousands) . ' ribu' . ($rest ? ' ' . self::terbilangBm($rest) : '');
}
if ($number < 1000000000) {
$millions = intdiv($number, 1000000);
$rest = $number % 1000000;
return self::terbilangBm($millions) . ' juta' . ($rest ? ' ' . self::terbilangBm($rest) : '');
}
return 'terlalu besar';
}
public static function ringgitTerbilang($amount, bool $tambahSahaja = true): string
{
$formatted = number_format((float)$amount, 2, '.', '');
[$ringgitStr, $senStr] = explode('.', $formatted);
$ringgit = (int)$ringgitStr;
$sen = (int)$senStr;
$parts = [];
$parts[] = self::terbilangBm($ringgit) . ' ringgit';
if ($sen > 0) {
$parts[] = 'dan ' . self::terbilangBm($sen) . ' sen';
} elseif ($tambahSahaja) {
$parts[] = 'sahaja';
}
return trim(preg_replace('/\s+/', ' ', implode(' ', $parts)));
}
}
@@ -0,0 +1,70 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
class AdminFeatureController extends Controller
{
public function __construct()
{
$this->middleware(['auth', 'admin']);
}
protected function api()
{
return Http::withOptions(['verify' => config('app.api_verify')]);
}
public function index()
{
$api_url = config('api.url');
$metaRes = $this->api()->get($api_url . '/api/features');
$meta = $metaRes->successful() ? $metaRes->json() : [];
$keys = is_array($meta) && !empty($meta['keys']) ? $meta['keys'] : ['pembiayaan_online', 'rebet_annur'];
$labels = is_array($meta) && !empty($meta['labels']) ? $meta['labels'] : [
'pembiayaan_online' => 'Pembiayaan Online',
'rebet_annur' => 'Rebet / diskaun Annur',
];
$listRes = $this->api()->get($api_url . '/api/features/cawangan');
$cawangan = ($listRes->successful() && is_array($listRes->json())) ? $listRes->json() : [];
if (!$listRes->successful()) {
session()->now('error', 'Gagal muat senarai ciri cawangan.');
}
return view('admin.features', compact('api_url', 'keys', 'labels', 'cawangan'));
}
public function update(Request $request)
{
$kodcaw = trim((string) $request->input('kodcaw', ''));
$feature = trim((string) $request->input('feature', ''));
$enabled = (int) $request->input('enabled') ? 1 : 0;
if ($kodcaw === '' || $feature === '') {
return redirect()->route('admin.features')->with('error', 'Kod cawangan dan ciri diperlukan.');
}
$res = $this->api()
->put(config('api.url') . '/api/features/' . $kodcaw . '/' . $feature, [
'enabled' => $enabled,
]);
if (!$res->successful()) {
$body = $res->json();
$msg = is_array($body) && isset($body['message'])
? $body['message']
: 'Gagal kemaskini ciri cawangan.';
return redirect()->route('admin.features')->with('error', $msg);
}
$label = $enabled ? 'diaktifkan' : 'dinyahaktifkan';
return redirect()
->route('admin.features')
->with('message', $feature . ' ' . $label . ' untuk ' . $kodcaw . '.');
}
}
+67 -7
View File
@@ -2,12 +2,14 @@
namespace App\Http\Controllers;
use App\User;
use App\Cawangan;
use Carbon\Carbon;
use Illuminate\Http\Request;
use App\Services\CustomerService;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Auth;
use Carbon\Carbon;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Session;
class AliranTunaiController extends Controller
@@ -599,7 +601,7 @@ class AliranTunaiController extends Controller
return response($updateTunai);
}
public function LaporanAliranTunai($teller)
public function LaporanAliranTunai($teller, Request $request)
{
$auth_user = Auth::user();
$api_url = config('api.url');
@@ -611,8 +613,6 @@ class AliranTunaiController extends Controller
$cawangan=Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url').'/api/cawangan/detail/'.$auth_user['cawangan'])->json();
$cawangan_detail=$cawangan[0];
$tarikh_cetak = $current->toDateString();
$cawangan=Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url').'/api/cawangan/detail/'.$auth_user['cawangan'])->json();
$alirantunai = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url') . '/api/GetCurrentTunai/' . $auth_user['cawangan'],['kodlaluan' => $auth_user['name']])->json();
@@ -621,7 +621,67 @@ class AliranTunaiController extends Controller
$teller = $auth_user['name'];
return view('aliran_tunai.laporan.laporanalirantunai',compact('tarikh_cetak','cawangan_info','cawangan','api_url','cawangan_detail','addtunai','alirantunai','teller'));
$bakiLelong = Http::withOptions(['verify' =>config('app.api_verify')])->get(config('api.url') . '/api/lelong/bakilelongbyteller/'.$auth_user['cawangan'],[
'tarikh' => $request->datetimepicker1
])->json();
return view('aliran_tunai.laporan.laporanalirantunai',compact('tarikh_cetak','cawangan_info','cawangan','api_url','cawangan_detail','addtunai','alirantunai','teller', 'bakiLelong'));
}
public function laporanAliranTunaiTeller()
{
$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('khazanah.laporan.alirantunai', compact('api_url', 'cawangan_info'));
}
public function cetakLaporanAliranTunaiTeller(Request $request)
{
$api_url = config('api_url');
$auth_user = Auth::user();
$current = Carbon::now();
$current = new Carbon();
$tarikh_cetak = $current->toDateString();
if($request->datetimepicker1 == null){
$tarikh = $current->toDateString();
}else{
$split_date = explode('-',$request->datetimepicker1);
$tarikh = $split_date[2] . '-' . $split_date[1] . '-' . $split_date[0];
}
$cawangan_info = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url') . '/api/cawangan/'. $auth_user['cawangan'])->json();
$cawangan=Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url').'/api/cawangan/detail/'.$auth_user['cawangan'])->json();
$cawangan_detail=$cawangan[0];
$addtunai = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url') . '/api/getAddTunai/' . $auth_user['cawangan'] . '/kodlaluan/',['kodlaluan' => $auth_user['name']])->json();
$teller = $auth_user['name'];
$bakiLelong = Http::withOptions(['verify' =>config('app.api_verify')])->get(config('api.url') . '/api/lelong/bakilelongbyteller/'.$auth_user['cawangan'],[
'tarikh' => $tarikh,
'kodlaluan' => $auth_user['name']
])->json();
$tunai = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url') . '/api/getTunaiByTarikh/',[
'kodlaluan' => $auth_user['name'],
'kodcaw' => $auth_user['cawangan'],
'tarikh' => $tarikh
])->json();
if(!$tunai)
{
return redirect()->back()->with('errormessage', 'Tiada data pada tarikh tersebut.');
}
$jumlahTambah = $tunai['tambah'] + $tunai['masuk'] + $bakiLelong['rugi_lelong'];
$jumlahTolak = $tunai['kurang'] + $tunai['keluar'] + $bakiLelong['baki_lelong'];
return view('aliran_tunai.laporan.laporanalirantunai',compact('tarikh_cetak','cawangan_info','cawangan','api_url','cawangan_detail','addtunai','tunai','teller', 'bakiLelong', 'jumlahTambah', 'jumlahTolak'));
}
public function LaporanTunaiTeller(Request $request){
@@ -0,0 +1,52 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\User;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Auth;
class ImpersonateController extends Controller
{
public function __construct()
{
$this->middleware('auth');
}
public function impersonate(Request $request, $id): RedirectResponse
{
Gate::authorize('impersonate');
$impersonator = $request->user();
$id = (int) $id;
$target = User::findOrFail($id);
if ($target->id === $request->user()->id) {
return back()->with('error', 'Tidak boleh impersonate diri sendiri.');
}
if (method_exists($target, 'canBeImpersonated') && !$target->canBeImpersonated()) {
return back()->with('error', 'Akaun ini tidak boleh di-impersonate.');
}
$request->user()->impersonate($target);
return redirect()->route('home');
}
public function leave(Request $request): RedirectResponse
{
if ($request->user() && method_exists($request->user(), 'isImpersonated') && $request->user()->isImpersonated()) {
$impersonated = $request->user();
$impersonator = method_exists($impersonated, 'impersonator') ? $impersonated->impersonator() : null;
$request->user()->leaveImpersonation();
}
return redirect()->route('home');
}
}
+40 -6
View File
@@ -60,9 +60,7 @@ class LoginController extends Controller
'password' => 'required|string',
]);
$credentials = $request->only('email', 'password');
if (Auth::guard('web')->attempt($credentials)) {
if ($this->attemptWebLogin($request)) {
$auth_user = Auth::guard('web')->user();
$api_url = config('api.url');
@@ -103,9 +101,7 @@ class LoginController extends Controller
'password' => 'required|string',
]);
$credentials = $request->only('email', 'password');
if (Auth::guard('dashboard')->attempt($credentials,true)) {
if ($this->attemptDashboardLogin($request)) {
$auth_user = Auth::guard('dashboard')->user();
$user_login['nama'] = $auth_user['name'];
@@ -187,4 +183,42 @@ class LoginController extends Controller
return response()->json($kakitangan,200);
}
/**
* In local environment, log in by email only (password ignored).
*/
private function attemptWebLogin(Request $request): bool
{
if (app()->environment('local')) {
$user = User::where('email', $request->email)->first();
if ($user) {
Auth::guard('web')->login($user);
return true;
}
return false;
}
return Auth::guard('web')->attempt($request->only('email', 'password'));
}
/**
* In local environment, log in by email only (password ignored).
*/
private function attemptDashboardLogin(Request $request): bool
{
if (app()->environment('local')) {
$user = Dashboard::where('email', $request->email)->first();
if ($user) {
Auth::guard('dashboard')->login($user, true);
return true;
}
return false;
}
return Auth::guard('dashboard')->attempt($request->only('email', 'password'), true);
}
}
+141 -2
View File
@@ -289,6 +289,29 @@ class CustomersController extends Controller
return response(["no-data"], 200);
}
/**
* Store MyKad photo for an existing customer when gambar_mykad is still empty.
*/
public function storeMykadPhotoIfMissing(Request $request)
{
$icno = preg_replace('/[^0-9A-Za-z]/', '', (string) $request->input('icno'));
if ($icno === '') {
return response()->json(['error' => 'IC number is required.', 'stored' => false], 422);
}
if (!$request->filled('photo_base64')) {
return response()->json(['error' => 'Photo is required.', 'stored' => false], 422);
}
$response = Http::withOptions(['verify' => config('app.api_verify')])
->post(config('api.url') . '/api/customers/' . $icno . '/mykad-photo', [
'photo_base64' => $request->input('photo_base64'),
'photo_mime' => $request->input('photo_mime') ?: 'image/jpeg',
]);
return response()->json($response->json() ?? ['stored' => false], $response->status());
}
/**
* Show the form for creating a new resource.
*
@@ -358,7 +381,7 @@ class CustomersController extends Controller
])->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'],
'nopelanggan' => $no_pelanggan,
'nokpbaru' => $nokpbaru,
@@ -385,7 +408,14 @@ class CustomersController extends Controller
'nota_pekerjaan' => $request->nota_pekerjaan,
'notaarcc' => $request->notaarcc,
'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('noic', $request->noic);
@@ -574,4 +604,113 @@ class CustomersController extends Controller
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 '';
}
}
+301 -4
View File
@@ -11,6 +11,7 @@ use App\Events\SerahanPenilai;
use Illuminate\Http\Request;
use App\Services\CustomerService;
use App\Services\NotificationService;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Auth;
@@ -22,6 +23,9 @@ use Illuminate\Support\Facades\Log;
class GadaianController extends Controller
{
const MIN_PINJAMAN = 5.00;
const MAX_PINJAMAN = 50000.00;
public $AliranTunaiController;
public $customerService;
@@ -32,6 +36,122 @@ class GadaianController extends Controller
$this->AliranTunaiController = new AliranTunaiController();
}
protected function isFeatureEnabled($kodcaw, $feature)
{
$cawangan_info = Http::withOptions(['verify' => config('app.api_verify')])
->get(config('api.url') . '/api/cawangan/' . $kodcaw)
->json();
$features = is_array($cawangan_info) ? ($cawangan_info['features'] ?? []) : [];
return !empty($features[$feature]);
}
protected function isPembiayaanOnlineEnabled($kodcaw)
{
return $this->isFeatureEnabled($kodcaw, 'pembiayaan_online');
}
/**
* Inbox row for PC at this branch. Failures must not block gadai.
*/
protected function notifyPcPembiayaanOnlinePending($auth_user, Request $request, $pinjamanAmt)
{
try {
app(NotificationService::class)->notifyRoleAtCawangan('pc', $auth_user['cawangan'], [
'type' => NotificationService::TYPE_PEMBIAYAAN_PENDING_PC,
'title' => 'Pembiayaan online menunggu semakan',
'body' => sprintf(
'%s — %s (RM %s)',
$request->nama_pelanggan,
$request->norujukan_gadai,
number_format((float) $pinjamanAmt, 2)
),
'url' => route('pembiayaan.online.pc'),
'data' => [
'norujukan' => $request->norujukan_gadai,
'nama_pelanggan' => $request->nama_pelanggan,
'pinjaman' => $pinjamanAmt,
'created_by' => $auth_user['name'],
],
]);
} catch (\Throwable $e) {
Log::warning('Pembiayaan online PC notification failed: '.$e->getMessage(), [
'norujukan' => $request->norujukan_gadai,
'cawangan' => $auth_user['cawangan'],
]);
}
}
protected function parseMoney($value)
{
if ($value === null || $value === '') {
return 0.0;
}
$cleaned = preg_replace('/[^0-9.\-]/', '', str_replace([',', '%', ' '], '', (string) $value));
return is_numeric($cleaned) ? (float) $cleaned : 0.0;
}
protected function normalizeBorangOnlineFields($norujukan, $pinjaman, $kodbank, $noakaun, $namaAkaun)
{
return [
'norujukan' => trim((string) $norujukan),
'pinjaman' => number_format((float) $this->parseMoney($pinjaman), 2, '.', ''),
'kodbank' => strtoupper(trim((string) $kodbank)),
'noakaun' => preg_replace('/\s+/', '', (string) $noakaun),
'nama_akaun' => strtoupper(trim((string) $namaAkaun)),
];
}
protected function borangOnlineFingerprint(array $fields)
{
return sha1(implode('|', [
$fields['norujukan'],
$fields['pinjaman'],
$fields['kodbank'],
$fields['noakaun'],
$fields['nama_akaun'],
]));
}
protected function markBorangOnlinePrinted(array $fields)
{
Session::put('borang_online_printed', array_merge($fields, [
'fingerprint' => $this->borangOnlineFingerprint($fields),
]));
}
protected function hasMatchingBorangOnlinePrint(Request $request)
{
$printed = $request->session()->get('borang_online_printed');
if (!is_array($printed)) {
return false;
}
$norujukan = (string) $request->input('norujukan_gadai', $request->input('norujukan'));
$fields = $this->normalizeBorangOnlineFields(
$norujukan,
$request->input('pinjaman'),
$request->input('kodbank'),
$request->input('noakaun'),
$request->input('nama_akaun') ?: $request->input('nama_pelanggan')
);
return ($printed['norujukan'] ?? '') === $fields['norujukan']
&& ($printed['fingerprint'] ?? '') === $this->borangOnlineFingerprint($fields);
}
protected function borangOnlinePrintedFor($norujukan)
{
$printed = Session::get('borang_online_printed');
if (!is_array($printed) || ($printed['norujukan'] ?? '') !== (string) $norujukan) {
return null;
}
return $printed;
}
public function index(Request $request)
{
@@ -59,6 +179,7 @@ class GadaianController extends Controller
Session::put('nokp', $noic);
Session::put('nopelanggan', $customer_info['nopelanggan']);
Session::put('page_reload', true);
Session::forget('borang_online_printed');
$gadai = Http::withOptions(['verify' => config('app.api_verify')])->post(config('api.url') . '/api/gadai/' . $auth_user['cawangan'], [
'norujukan' => $norujukan,
'nopelanggan' => $customer_info['nopelanggan'],
@@ -83,6 +204,7 @@ class GadaianController extends Controller
Session::put('nokp', $nokp);
Session::put('nopelanggan', $nopelanggan);
Session::put('page_reload', true);
Session::forget('borang_online_printed');
$gadai_baru = Http::withOptions(['verify' => config('app.api_verify')])->post(config('api.url') . '/api/gadai/' . $auth_user['cawangan'], [
'norujukan' => $norujukan_baru,
'nopelanggan' => $nopelanggan,
@@ -95,7 +217,8 @@ class GadaianController extends Controller
$cawangan_info = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url') . '/api/cawangan/' . $auth_user['cawangan'])->json();
return view('gadaian.' . $cawangan_info['version'] . '.index', compact('customer_info', 'limit_pinjaman_semasa', 'cawangan_info', 'norujukan', 'api_url', 'current'));
$borang_online_printed = $this->borangOnlinePrintedFor($norujukan);
return view('gadaian.' . $cawangan_info['version'] . '.index', compact('customer_info', 'limit_pinjaman_semasa', 'cawangan_info', 'norujukan', 'api_url', 'current', 'borang_online_printed'));
}
public function edit(Request $request, $norujukan)
@@ -162,6 +285,22 @@ class GadaianController extends Controller
public function store(Request $request)
{
$auth_user = Auth::user();
$caraTerima = strtoupper((string) $request->input('cara_terima', 'TUNAI'));
$pinjamanAmt = round((float) $request->pinjaman, 2);
// Online only within min/max pinjaman; otherwise force cash path.
if ($caraTerima === 'ONLINE' && ($pinjamanAmt < self::MIN_PINJAMAN || $pinjamanAmt > self::MAX_PINJAMAN)) {
$caraTerima = 'TUNAI';
}
if ($caraTerima === 'ONLINE' && !$this->isPembiayaanOnlineEnabled($auth_user['cawangan'])) {
$caraTerima = 'TUNAI';
}
if ($caraTerima === 'ONLINE' && !$this->hasMatchingBorangOnlinePrint($request)) {
return redirect()->back()->with('error', 'Sila cetak borang pengesahan pelanggan sebelum tekan Terima.');
}
//$total_nilai_marhun = $request->total_nilai_marhun;
//need to get total nilai marhun by api get from table marhun
@@ -201,8 +340,8 @@ class GadaianController extends Controller
'onepercent' => $onepercent,
'margin_semasa' => $margin_pinjaman,
'nokp' => $request->nokp,
'kadar_rebate' => $request->kadar_rebate,
'rebate' => $request->rebate
'kadar_rebate' => $this->parseMoney($request->kadar_rebate),
'rebate' => $this->parseMoney($request->rebate),
])->json();
@@ -213,17 +352,172 @@ class GadaianController extends Controller
'teller' => $auth_user['name']
]);
$this->AliranTunaiController->alirantunaiGadai($request, $auth_user['name']);
if ($caraTerima === 'ONLINE') {
$online = Http::withOptions(['verify' => config('app.api_verify')])
->asForm()
->post(config('api.url') . '/api/pembiayaan-online/' . $auth_user['cawangan'], [
'norujukan' => $request->norujukan_gadai,
'nokp' => $request->nokp,
'pinjaman' => $pinjamanAmt,
'kodbank' => $request->kodbank,
'noakaun' => $request->noakaun,
'nama_bank' => $request->nama_bank,
'nama_akaun' => $request->nama_akaun ?: $request->nama_pelanggan,
'nama_pelanggan' => $request->nama_pelanggan,
'nopelanggan' => $request->nopelanggan,
'created_by' => $auth_user['name'],
'update_customer_bank' => true,
]);
if (!$online->successful()) {
$body = $online->json();
$msg = is_array($body) && isset($body['message'])
? $body['message']
: 'Gagal daftar pembiayaan online. Sila cuba lagi.';
return redirect()->back()->with('error', $msg);
}
$this->notifyPcPembiayaanOnlinePending($auth_user, $request, $pinjamanAmt);
} else {
$this->AliranTunaiController->alirantunaiGadai($request, $auth_user['name']);
}
event(new GadaiNotify('lulus', $auth_user['cawangan']));
$request->session()->forget('page_reload');
$request->session()->forget('norujukan');
$request->session()->forget('borang_online_printed');
$request->session()->flash('activate_tab', 'SAG_baru');
if ($caraTerima === 'ONLINE') {
$request->session()->flash('message', 'Gadai berjaya. Pembiayaan online menunggu semakan PC.');
}
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.');
}
$this->markBorangOnlinePrinted($this->normalizeBorangOnlineFields(
$line['norujukan'],
$line['pinjaman'],
$line['kodbank'],
$line['noakaun'],
$line['nama_akaun'] ?: $line['nama_pelanggan']
));
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)
{
$auth_user = Auth::user();
@@ -259,6 +553,8 @@ class GadaianController extends Controller
'ujrah' => $ujrah,
'onepercent' => $onepercent,
'margin_semasa' => $margin_pinjaman,
'kadar_rebate' => $this->parseMoney($request->kadar_rebate),
'rebate' => $this->parseMoney($request->rebate),
])->json();
$update_komuditi = Http::withOptions(['verify' => config('app.api_verify')])->put(config('api.url') . '/api/komuditi/update', [
'unit_komuditi' => round($request->pinjaman, 2),
@@ -280,6 +576,7 @@ class GadaianController extends Controller
Http::withOptions(['verify' => config('app.api_verify')])->delete(config('api.url') . '/api/gadai/batal/' . $auth_user['cawangan'] . '/' . $norujukan);
$request->session()->forget('page_reload');
$request->session()->forget('norujukan');
$request->session()->forget('borang_online_printed');
return redirect()->route('customer_info');
}
-23
View File
@@ -5,9 +5,7 @@ namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use App\Services\CustomerService;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Hash;
use Carbon\Carbon;
class HomeController extends Controller
@@ -41,17 +39,6 @@ class HomeController extends Controller
if(session()->has('iklan') && session()->get('iklan') == 0)
{
session()->put('iklan', 1);
}else{
if (Hash::check(config('app.reset_password'), $auth_user['password']) || Hash::check($auth_user['no_kp'], $auth_user['password'])) {
session()->put('iklan', 0);
}
}
if($auth_user['roles'] == 'teller'){
if($auth_user['role_harian'] == 'teller'){
$cawangan_info = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url') . '/api/cawangan/'. $auth_user['cawangan'])->json();
@@ -117,16 +104,6 @@ class HomeController extends Controller
]);
if(session()->has('iklan') && session()->get('iklan') == 0)
{
session()->put('iklan', 1);
}else{
if (Hash::check(config('app.reset_password'), $auth_user['password']) || Hash::check($auth_user['no_kp'], $auth_user['password'])) {
session()->put('iklan', 0);
}
}
if($auth_user['roles'] == 'teller'){
if($auth_user['role_harian'] == 'teller'){
$cawangan_info = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url') . '/api/cawangan/'. $auth_user['cawangan'])->json();
@@ -0,0 +1,32 @@
<?php
namespace App\Http\Controllers;
use App\Services\NotificationService;
use Illuminate\Http\Request;
class InternalNotificationController extends Controller
{
public function store(Request $request, NotificationService $notifications)
{
$expected = (string) config('notifications.internal_secret');
$provided = (string) $request->header('X-Internal-Token', '');
if ($expected === '' || !hash_equals($expected, $provided)) {
return response()->json(['message' => 'Unauthorized'], 401);
}
$audience = $request->input('audience', []);
$role = is_array($audience) && !empty($audience['role']) ? $audience['role'] : null;
if (!$role) {
return response()->json(['message' => 'audience.role diperlukan'], 422);
}
$created = $notifications->dispatch($request->all());
return response()->json([
'created' => $created->count(),
], 201);
}
}
+36 -3
View File
@@ -20,10 +20,43 @@ class KakitanganController extends Controller
public function daftarKakitangan(){
$api_url = config('api.url');
$auth_user = Auth::user();
$cawangan_info = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url') . '/api/cawangan/'. $auth_user['cawangan'])->json();
$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();
$kakitangan = User::all();
return view('operasi.daftar_kakitangan',compact('cawangan_info','api_url','role','kakitangan','auth_user'));
$kakitangan_rows = User::where('roles', '!=', 'admin')
->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){
+453 -106
View File
@@ -29,6 +29,7 @@ use Carbon\Carbon;
use PDF;
use DateTime;
use App\Helper;
use App\Services\NotificationService;
use Log;
class KhazanahController extends Controller
@@ -505,6 +506,9 @@ class KhazanahController extends Controller
public function mintakatalaluansemula(Request $request)
{
if (app()->environment('local')) {
return "True";
}
if(Hash::check($request->passsemula, Auth::user()->password))
{
@@ -665,6 +669,23 @@ class KhazanahController extends Controller
"namakhazanah" => $request->namakhazanah
])->json();
// Start 30s Operasi auto-approve clock only after Khazanah escalates (10k–45k).
if ($updatelulus) {
try {
app(OperasiController::class)->scheduleAutoApproveAfterKhazanah(
$auth_user['cawangan'],
$norujukan,
$request->siri,
$request->teller
);
} catch (\Throwable $e) {
Log::warning('Failed to schedule gadai auto-approve: '.$e->getMessage(), [
'norujukan' => $norujukan,
'cawangan' => $auth_user['cawangan'],
]);
}
}
}else{
$updatelulus = Http::withOptions(['verify' => config('app.api_verify')])->put(config('api.url') . '/api/UpdateKelulusan/' . $auth_user['cawangan'] . '/' . $norujukan,[
@@ -1081,6 +1102,7 @@ class KhazanahController extends Controller
if($test == 'success'){
event(new PendahuluanSerahan("Lulus","pendahuluan",$auth_user['cawangan'],$request->teller));
$this->notifyTellerAliranTunai('Lulus', 'pendahuluan', $auth_user['cawangan'], $request->teller, $amount);
return redirect()->route('khazanah.pendahulu');
}else{
return redirect()->route('khazanah.pendahulu')->with('error','Permohonan tidak berjaya! Permohonan Semula perlu melebihi 1 minit.');
@@ -1106,6 +1128,7 @@ class KhazanahController extends Controller
if($test == 'success'){
event(new PendahuluanSerahan("Lulus","serahan",$auth_user['cawangan'],$request->teller));
$this->notifyTellerAliranTunai('Lulus', 'serahan', $auth_user['cawangan'], $request->teller, $request->amt);
return redirect()->route('khazanah.serahan');
}else{
return redirect()->route('khazanah.serahan')->with('error','Permohonan tidak berjaya! Permohonan Semula perlu melebihi 1 minit.');
@@ -2459,7 +2482,6 @@ class KhazanahController extends Controller
$penghutangkaki = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url').'/api/PenghutangKakitangan/' . $auth_user['cawangan'],['tarikh' => $tarikh_imbangan2])->json();
$untungrugi = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url').'/api/UntungRugiCalculation/' . $auth_user['cawangan'],['tarikh' => $tarikh_imbangan2])->json();
$bayaranpendahuluan = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url').'/api/BayaranPendahuluan/' . $auth_user['cawangan'],['tarikh' => $tarikh_imbangan2])->json();
$rebetpromo = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url').'/api/RebetPromoCalculation/' . $auth_user['cawangan'],['tarikh' => $tarikh_imbangan2])->json();
//Tanggungan Semasa
$panjar = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url').'/api/CalculationPanjar/' . $auth_user['cawangan'],['tarikh' => $tarikh_imbangan2])->json();
@@ -2470,8 +2492,14 @@ class KhazanahController extends Controller
$cajperkhidmatan = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url').'/api/cajperkhidmatanterkumpul/' . $auth_user['cawangan'],['tarikh' => $tarikh_imbangan2])->json();
$hapuskira = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url').'/api/CalculationHapusKira/' . $auth_user['cawangan'],['tarikh' => $tarikh_imbangan2])->json();
$xtra = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url').'/api/Xtra/' . $auth_user['cawangan'],['tarikh' => $tarikh_imbangan2])->json();
$rebetpromo = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url').'/api/RebetPromoCalculation/' . $auth_user['cawangan'],['tarikh' => $tarikh_imbangan2])->json();
$pelarasanrebate = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url').'/api/PelarasanRebate/' . $auth_user['cawangan'],['tarikh' => $tarikh_imbangan2])->json();
$rebetAnnur = !empty(($cawangan_info['features'] ?? [])['rebet_annur']);
$rebetpromo = 0;
$pelarasanrebate = 0;
if ($rebetAnnur) {
$rebetpromo = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url').'/api/RebetPromoCalculation/' . $auth_user['cawangan'],['tarikh' => $tarikh_imbangan2])->json();
$pelarasanrebate = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url').'/api/PelarasanRebate/' . $auth_user['cawangan'],['tarikh' => $tarikh_imbangan2])->json();
}
//TunaiDitangan Sementara Upah
@@ -2525,7 +2553,6 @@ class KhazanahController extends Controller
if($belian1stop > 0) array_push($hartasemasa,['glcode' => '8000/501','descpt' => 'Belian 1 Stop', 'amaun' => $belian1stop]);
if($rebetpromo > 0) array_push($hartasemasa,['glcode' => '5000/242','descpt' => 'Diskaun Promosi', 'amaun' => ($rebetpromo * -1)]);
$totalhartasemasa = 0;
foreach($hartasemasa as $index => $hs)
{
@@ -2590,108 +2617,114 @@ class KhazanahController extends Controller
}
//print lejer_AM
public function cetak_lejer_am(Request $request)
{
public function cetak_lejer_am(Request $request)
{
$auth_user = Auth::user();
$api_url = config('api.url');
$auth_user = Auth::user();
$api_url = config('api.url');
$cawangan_info = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url') . '/api/cawangan/'. $auth_user['cawangan'])->json();
$cawangan = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url').'/api/cawangan/detail/'.$auth_user['cawangan'])->json();
$cawangan_detail = $cawangan[0];
$cawangan_info = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url') . '/api/cawangan/'. $auth_user['cawangan'])->json();
$cawangan=Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url').'/api/cawangan/detail/'.$auth_user['cawangan'])->json();
$cawangan_detail=$cawangan[0];
$num = $request->num;
$current = new Carbon();
$tarikh_cetak = $current->toDateString();
$auth_user = Auth::user();
$api_url = config('api.url');
// fix: bagi default value supaya $tarikh_mula / $tarikh_akhir tak undefined
$tarikh_mula = $tarikh_cetak;
$tarikh_akhir = $tarikh_cetak;
$num=$request->num;
$current = Carbon::now();
$current = new Carbon();
$tarikh_cetak = $current->toDateString();
if($request->datetimepicker1 == null){
$tarikh_cetak = $current->toDateString();
}else {
$split_date = explode('-',$request->datetimepicker1);
$tarikh_mula = $split_date[2] . '-' . $split_date[1] . '-' . $split_date[0];
}
if($request->datetimepicker2 == null){
$tarikh_cetak = $current->toDateString();
}
else{
$split_date = explode('-',$request->datetimepicker2);
$tarikh_akhir = $split_date[2] . '-' . $split_date[1] . '-' . $split_date[0];
}
$lejeramdata = Lejeram::orderBy('glcode')->get();
$lejeram = array();
$len = count($lejeramdata);
$totalkredit = 0;
$totaldebit = 0;
foreach($lejeramdata as $index => $lejer)
{
if($lejer->api_redirect != null)
{
${$lejer['variable']} = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url'). $lejer['api_redirect'] . $auth_user['cawangan'],['mula' => $tarikh_mula, 'hingga' => $tarikh_akhir])->json();
if($lejer->namadebit == null)
{
$debit = null;
}else{
if($lejer->oneVar == 1)
{
$debit = ${$lejer['variable']};
}else{
$debit = ${$lejer['variable']}[$lejer->namadebit];
}
$totaldebit += floatval($debit);
}
if($lejer->namakredit == null)
{
$kredit = null;
}else{
if($lejer->oneVar == 1)
{
$kredit = ${$lejer['variable']};
}else{
$kredit = ${$lejer['variable']}[$lejer->namakredit];
}
$totalkredit += floatval($kredit);
}
if(!($kredit == null && $debit == null))
{
array_push($lejeram,['glcode' => $lejer->glcode, 'descpt' => $lejer->descpt,'debit' => ($debit) ? $debit : 0,'kredit' => ($kredit) ? $kredit : 0]);
}
}else{
array_push($lejeram,['glcode' => $lejer->glcode, 'descpt' => $lejer->descpt,'debit' => 0,'kredit' => 0]);
}
if($index == $len-1)
{
$lejeram[0]['debit'] = $totalkredit;
$lejeram[0]['kredit'] = $totaldebit;
}
}
$totalsamadebit = 0;
$totalsamakredit = 0;
foreach($lejeram as $index=>$lejer)
{
$totalsamadebit += floatval($lejer['debit']);
$totalsamakredit += floatval($lejer['kredit']);
}
return view('print.laporan_lejer_am',compact('cawangan','tarikh_cetak','cawangan_info', 'api_url','cawangan_detail','tarikh_akhir','tarikh_mula','lejeram','totalsamadebit','totalsamakredit'));
if($request->datetimepicker1 != null){
$split_date = explode('-', $request->datetimepicker1);
$tarikh_mula = $split_date[2] . '-' . $split_date[1] . '-' . $split_date[0];
}
if($request->datetimepicker2 != null){
$split_date = explode('-', $request->datetimepicker2);
$tarikh_akhir = $split_date[2] . '-' . $split_date[1] . '-' . $split_date[0];
}
// ambil 'had' daripada modal_online melalui API (VaultController@CalculationModalOnlineLejer)
$had_response = Http::withOptions(['verify' => config('app.api_verify')])
->get($api_url . '/api/ModalOnlineLejer/' . $auth_user['cawangan'], [
'mula' => $tarikh_mula,
'hingga' => $tarikh_akhir,
])->json();
$had_modal_online = $had_response['had'] ?? 0;
$lejeramdata = Lejeram::orderBy('glcode')->get();
$lejeram = array();
$len = count($lejeramdata);
$totalkredit = 0;
$totaldebit = 0;
foreach($lejeramdata as $index => $lejer)
{
if($lejer->api_redirect != null)
{
${$lejer['variable']} = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url'). $lejer['api_redirect'] . $auth_user['cawangan'], ['mula' => $tarikh_mula, 'hingga' => $tarikh_akhir])->json();
if($lejer->namadebit == null)
{
$debit = null;
}else{
if($lejer->oneVar == 1)
{
$debit = ${$lejer['variable']};
}else{
$debit = ${$lejer['variable']}[$lejer->namadebit];
}
$totaldebit += floatval($debit);
}
if($lejer->namakredit == null)
{
$kredit = null;
}else{
if($lejer->oneVar == 1)
{
$kredit = ${$lejer['variable']};
}else{
$kredit = ${$lejer['variable']}[$lejer->namakredit];
}
// tambah 'had' modal_online khusus untuk Pendahuluan Daripada HQ
if($lejer->glcode == '7000/102')
{
$kredit = floatval($kredit) + $had_modal_online;
}
$totalkredit += floatval($kredit);
}
if(!($kredit == null && $debit == null))
{
array_push($lejeram, ['glcode' => $lejer->glcode, 'descpt' => $lejer->descpt, 'debit' => ($debit) ? $debit : 0, 'kredit' => ($kredit) ? $kredit : 0]);
}
}else{
array_push($lejeram, ['glcode' => $lejer->glcode, 'descpt' => $lejer->descpt, 'debit' => 0, 'kredit' => 0]);
}
if($index == $len-1)
{
$lejeram[0]['debit'] = $totalkredit;
$lejeram[0]['kredit'] = $totaldebit;
}
}
$totalsamadebit = 0;
$totalsamakredit = 0;
foreach($lejeram as $index => $lejer)
{
$totalsamadebit += floatval($lejer['debit']);
$totalsamakredit += floatval($lejer['kredit']);
}
return view('print.laporan_lejer_am', compact('cawangan','tarikh_cetak','cawangan_info', 'api_url','cawangan_detail','tarikh_akhir','tarikh_mula','lejeram','totalsamadebit','totalsamakredit'));
}
public function laporan_tuntutbaki(Request $request)
{
$auth_user = Auth::user();
@@ -2835,10 +2868,43 @@ class KhazanahController extends Controller
]);
event(new PendahuluanSerahan("Batal","all",$auth_user['cawangan'],$kodlaluan));
$this->notifyTellerAliranTunai('Batal', 'all', $kodcaw, $kodlaluan);
return response('Berjaya');
}
/**
* Inbox for the teller waiting on aliran tunai. Failures must not block approval.
*
* @param string $pengesahan
* @param string $jenis
* @param string $kodcaw
* @param string $teller
* @param mixed $amaun
* @return void
*/
protected function notifyTellerAliranTunai($pengesahan, $jenis, $kodcaw, $teller, $amaun = 0)
{
try {
$raw = str_replace(',', '', (string) $amaun);
$amount = is_numeric($raw) ? (float) $raw : 0;
app(NotificationService::class)->notifyAliranTunaiDecided($kodcaw, $teller, [
'pengesahan' => $pengesahan,
'jenis' => $jenis,
'amaun' => $amount,
'kodlaluan' => $teller,
'khazanah' => Auth::user() ? Auth::user()->name : '',
]);
} catch (\Throwable $e) {
Log::warning('Aliran tunai teller notification failed: '.$e->getMessage(), [
'kodcaw' => $kodcaw,
'teller' => $teller,
'jenis' => $jenis,
]);
}
}
public function SuratAkuanGadaian(Request $request)
{
$auth_user = Auth::user();
@@ -3994,7 +4060,7 @@ class KhazanahController extends Controller
}
public function cetakLaporanGadaiUjrah(Request $request){
public function cetakLaporanGadaiUjrah(Request $request){
$api_url = config('api.url');
$auth_user = Auth::user();
@@ -4016,12 +4082,22 @@ class KhazanahController extends Controller
$hingga_date = $split_date[2] . '-' . $split_date[1] . '-' . $split_date[0];
}
$gadai = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url'). '/api/gadailaporanRangeDate/'. $auth_user['cawangan'] ,['mula' => $mula_date,'akhir' => $hingga_date])->json();
$reportGadai = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url'). '/api/gadailaporanRangeDate/'. $auth_user['cawangan'] ,['mula' => $mula_date,'akhir' => $hingga_date])->json();
$gadai = $reportGadai['gadai'];
$statistik =$reportGadai['statistik'];
$reportPembiayaan= $statistik['reportPembiayaan'];
$reportLoanAmount=$statistik['reportLoanAmount'];
$reportMarhunValue=$statistik['reportMarhunValue'];
$reportMarhunMargin=$statistik['reportMarhunMargin'];
$reportCustomerJob=$statistik['reportCustomerJob'];
$reportCustomerRace=$statistik['reportCustomerRace'];
// dd($reportCustomerRace);
$cawangan=Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url').'/api/cawangan/detail/'.$auth_user['cawangan'])->json();
$cawangan_detail=$cawangan[0];
$cawangan_info = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url') . '/api/cawangan/' . $auth_user['cawangan'])->json();
return view('print.laporan_gadaian_ujrah', compact('gadai','cawangan_detail','tarikh_cetak','mula_date','api_url','hingga_date'));
return view('print.laporan_gadaian_ujrah', compact('gadai','cawangan_detail','cawangan_info','tarikh_cetak','mula_date','api_url','hingga_date','statistik','reportPembiayaan','reportLoanAmount','reportMarhunValue','reportMarhunMargin','reportCustomerJob','reportCustomerRace','reportCustomerRace'));
}
@@ -4064,7 +4140,8 @@ class KhazanahController extends Controller
$cawangan=Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url').'/api/cawangan/detail/'.$auth_user['cawangan'])->json();
$cawangan_detail=$cawangan[0];
return view('print.laporan_tebus_ujrah', compact('tebus','cawangan_detail','tarikh_cetak','mula_date','hingga_date','tebuspengasingan','api_url','upahlama','ansuran','totalkeuntunganrebet'));
$cawangan_info = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url') . '/api/cawangan/'. $auth_user['cawangan'])->json();
return view('print.laporan_tebus_ujrah', compact('tebus','cawangan_detail','cawangan_info','tarikh_cetak','mula_date','hingga_date','tebuspengasingan','api_url','upahlama','ansuran','totalkeuntunganrebet'));
}
public function laporanAnsuranUjrah(){
@@ -4424,4 +4501,274 @@ class KhazanahController extends Controller
}
}
public function pampasanDetail($id)
{
$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();
$gadai = Http::withOptions(['verify' => config('app.api_verify')])
->get(config('api.url') . '/api/gadai/norujukan/' . '011237' . '/' . $id)
->json();
$gadai_info = is_array($gadai) && isset($gadai[0]) ? $gadai[0] : $gadai;
$customer_info = null;
if(isset($gadai_info['nokp'])) {
$customer = Http::withOptions(['verify' => config('app.api_verify')])
->get(config('api.url') . '/api/customers/' . $gadai_info['nokp'])
->json();
$customer_info = null;
if (isset($gadai_info['nokp'])) {
$customer = Http::withOptions(['verify' => config('app.api_verify')])
->get(config('api.url') . '/api/customers/' . $gadai_info['nokp'])
->json();
$customer_info = is_array($customer) && isset($customer[0]) ? $customer[0] : $customer;
}
$barang_kes = Http::withOptions(['verify' => config('app.api_verify')])
->get(config('api.url') . '/api/palsu/laporan/', ['kodcaw' => $auth_user['cawangan']])
->json();
// You may need to adjust the array index if the API returns an array of results
$gadai_info = is_array($gadai) && isset($gadai[0]) ? $gadai[0] : $gadai;
// $pampasan_info = is_array($pampasan) && isset($pampasan[0]) ? $pampasan[0] : $pampasan;
$barang_kes_collection = collect($barang_kes);
$barang_kes_info = $barang_kes_collection->where('norujukan', '=', $id)->first();
$customer_info = null;
if (isset($gadai_info['nokp'])) {
$customer = Http::withOptions(['verify' => config('app.api_verify')])
->get(config('api.url') . '/api/customers/' . $gadai_info['nokp'])
->json();
$customer_info = is_array($customer) && isset($customer[0]) ? $customer[0] : $customer;
}
$denominasi = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url') . '/api/DenominasiController/' . $auth_user['cawangan'])->json();
if (sizeof($denominasi) == 0) {
$denominasi = Http::withOptions(['verify' => config('app.api_verify')])->post(config('api.url') . '/api/DenominasiCreate/' . $auth_user['cawangan'])->json();
} else {
$denominasi = $denominasi[0];
}
$pampasan = Http::withOptions(['verify' => config('app.api_verify')])
->get(config('api.url') . '/api/pampasan', ['kodcaw' => '011237', 'norujukan' => $id])
->json()['data'][0];
$baucerno = $pampasan['baucerno'];
$nilaimarhun = $barang_kes_info['nilaimarhun'] ?? 0;
$pembiayaan = $barang_kes_info['pembiayaan'] ?? 0;
$untung = $barang_kes_info['untung'] ?? 0;
$jumlah_tebus = Http::withOptions(['verify' => config('app.api_verify')])
->get(config('api.url') . '/api/tebus/norujukan/' . $auth_user['cawangan'] . '/' . $id)
->json()[0]['bakipjm'];
$jumlah_pampasan = $nilaimarhun - $pembiayaan - $untung;
$jumlahPanjar = $jumlah_tebus + $jumlah_pampasan;
return view('khazanah.pampasan_detail', compact('gadai_info', 'barang_kes_info', 'cawangan_info', 'customer_info', 'baucerno', 'denominasi', 'jumlah_pampasan', 'jumlah_tebus', 'jumlahPanjar'));
}
}
public function initiatePampasan(Request $request)
{
$norujukan = $request->input('norujukan');
$teller = auth()->user()->name;
$kodcaw = auth()->user()->cawangan;
$baucerno = $this->generateBaucer($kodcaw);
$response = Http::post(config('api.url') . '/api/pampasan/initiatePampasan', [
'norujukan' => $norujukan,
'kodcaw' => $kodcaw,
'teller' => $teller,
'baucerno' => $baucerno,
]);
if ($response->failed()) {
throw new \Exception("HTTP error! status: " . $response->status(), 1);
}
$result = $response->json();
// $jumlah_pampasan = $nilaimarhun - $pembiayaan - $untung;
// $jumlahTebus = $pembiayaan + $untung;
// $jumlahPanjar = $jumlah_pampasan + $jumlahTebus;
//return view('khazanah.pampasan_detail', compact('gadai_info', 'pampasan_info', 'cawangan_info', 'customer_info','baucerno', 'denominasi','jumlah_pampasan', 'jumlahTebus', 'jumlahPanjar') );
return response()->json($result);
}
public function generateBaucer($kodcaw)
{
$data = Http::withOptions(['verify' => config('app.api_verify')])
->get(config('api.url').'/api/SenaraiPanjar/list/' . $kodcaw)
->json();
$today = now();
$dd = $today->format('d');
$mm = $today->format('m');
$yy = $today->format('y');
$recno = $data['recno'] ?? 0;
$baucerno = 'B' . $yy . $mm . $dd . '-' . str_pad($recno, 4, '0', STR_PAD_LEFT);
return $baucerno;
}
public function terimaPampasan(Request $request)
{
$responsePampasan = Http::withOptions(['verify' => config('app.api_verify')])
->post(config('api.url') . '/api/pampasan/terimaPampasan',[
"norujukan" => $request->norujukan,
"kodcaw" => auth()->user()->cawangan,
"jumlah_pampasan" => $request->jumlahpampasan,
"teller" => auth()->user()->name,
"denominasi" => $request->denominasi,
]
)->json();
$jumlahTebus = Http::withOptions(['verify' => config('app.api_verify')])
->get(config('api.url') . '/api/tebus/norujukan/' . auth()->user()->cawangan .'/'.$request->norujukan)->json()[0]['bakipjm'];
$bayaranPanjar = $request->jumlahpampasan + $jumlahTebus;
$responsePanjar = Http::withOptions(['verify' => config('app.api_verify')])
->post(
config('api.url') . '/api/panjar/createpanjar/' . auth()->user()->cawangan,
array_merge(
[
'tarikhbil' => now()->format('Y-m-d'),
'jenismodal' => 'belanjaam',
'deskripsi' => 'BAYARAN PAMPASAN ' . '(' . $request->norujukan . ')',
'bayaran' => $bayaranPanjar,
'kodlaluan' => auth()->user()->name,
'baucer' => $responsePampasan['data']['baucerno'],
'tuntutan' => auth()->user()->name
],
$request->denominasi
)
)->json();
if ($responsePampasan['success'] == true) {
return redirect()->route('pampasan.resit', ["pampasan" => $responsePampasan['data']]);
} else
return redirect()->route('khazanah.panjar')->with('pampasaninformation', ['status' => 'error', 'message' => 'Baki perubatan tidak mencukupi.']);
}
public function resitPampasan(Request $request)
{
$pampasan = $request->pampasan;
$kodcaw = $pampasan['kodcaw'];
$norujukan = $pampasan['norujukan'] ?? null;
if ($norujukan) {
$gadai_info = Http::withOptions(['verify' => config('app.api_verify')])
->get(config('api.url') . '/api/gadai/norujukan/' . $kodcaw . '/' . $norujukan)
->json();
$gadai_info = is_array($gadai_info) && isset($gadai_info[0]) ? $gadai_info[0] : $gadai_info;
$customer_info = null;
if (isset($gadai_info['nokp'])) {
$customer = Http::withOptions(['verify' => config('app.api_verify')])
->get(config('api.url') . '/api/customers/' . $gadai_info['nokp'])
->json();
$customer_info = is_array($customer) && isset($customer[0]) ? $customer[0] : $customer;
}
$pampasan_info = Http::withOptions(['verify' => config('app.api_verify')])
->get(config('api.url') . '/api/palsu/laporan/', ['kodcaw' => $kodcaw])
->json();
$norujukan = $norujukan;
$pampasan_info = collect($pampasan_info)->firstWhere('norujukan', $norujukan);
$nilaimarhun = $pampasan_info['nilaimarhun'] ?? 0;
$pembiayaan = $pampasan_info['pembiayaan'] ?? 0;
$untung = $pampasan_info['untung'] ?? 0;
$jumlah_pampasan = $nilaimarhun - $pembiayaan - $untung;
$status = Http::withOptions(['verify' => config('app.api_verify')])
->put(config('api.url') . '/api/pampasan/updateStatusPampasan', ['kodcaw' => $kodcaw, 'norujukan' => $norujukan])
->json();
// dd($pampasan_info, $nilaimarhun, $pembiayaan, $untung, $jumlah_pampasan);
// dd($norujukan);
return view('print.resit.resitpampasan', compact(
'pampasan',
'gadai_info',
'customer_info',
'pampasan_info',
'nilaimarhun',
'pembiayaan',
'untung',
'jumlah_pampasan',
'status'
));
}
}
public function suratPampasan(Request $request, $norujukan)
{
$auth_user = Auth::user();
$cawangan_info = Http::withOptions(['verify' => config('app.api_verify')])
->get(config('api.url') . '/api/cawangan/'.$auth_user['cawangan'])->json();
$gadai = Http::withOptions(['verify' => config('app.api_verify')])
->get(config('api.url') . '/api/gadai/norujukan/' . '011237' . '/' . $norujukan)
->json();
$gadai_info = is_array($gadai) && isset($gadai[0]) ? $gadai[0] : $gadai;
$customer_info = null;
if(isset($gadai_info['nokp'])) {
$customer = Http::withOptions(['verify' => config('app.api_verify')])
->get(config('api.url') . '/api/customers/' . $gadai_info['nokp'])
->json();
$customer_info = is_array($customer) && isset($customer[0]) ? $customer[0] : $customer;
}
$pampasan = Http::withOptions(['verify' => config('app.api_verify')])
->get(config('api.url') . '/api/palsu/laporan/' , ['kodcaw'=>'011237'])
->json();
$pampasan_collection = collect($pampasan);
$pampasan_info = $pampasan_collection->where('norujukan', '=', $norujukan)->first();
if (!$pampasan_info) {
abort(404, 'Maklumat pampasan tidak dijumpai untuk no rujukan tersebut.');
}
$nilaimarhun = $pampasan_info['nilaimarhun'] ?? 0;
$pembiayaan = $pampasan_info['pembiayaan'] ?? 0;
$untung = $pampasan_info['untung'] ?? 0;
$jumlah_pampasan = $nilaimarhun - $pembiayaan - $untung;
$jumlah = $pampasan_info['jumlah_pampasan'] ?? 0;
$jumlah_pampasan_terbilang = Helper::ringgitTerbilang($jumlah_pampasan);
return view('print.surat_pampasan', compact('cawangan_info', 'gadai', 'gadai_info', 'customer_info', 'pampasan', 'pampasan_info','jumlah_pampasan_terbilang', 'jumlah_pampasan'));
}
}
+21 -8
View File
@@ -99,6 +99,15 @@ class LelongController extends Controller
public function transaksituntutbaki(Request $request){
$auth_user = Auth::user();
$aliranTunaiController = new AliranTunaiController();
$checkCashBox = $aliranTunaiController->periksaAliranTunai($request);
if($checkCashBox->getContent() !== 'sama')
{
return response('kemaskini', 200);
}
$lelong = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url').'/api/TuntutBaki/resit/' . $auth_user['cawangan'] . '/' . $request->norujukan)->json();
if($lelong)
@@ -106,6 +115,18 @@ class LelongController extends Controller
return response('Berjaya');
}
$getTunai2=Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url').'/api/GetCurrentTunai/'.$auth_user['cawangan'],['kodlaluan' => $request->teller])->json();
$keluar = $getTunai2['keluar'];
$baki = $getTunai2['baki'];
$masuk = $getTunai2['masuk'];
$bakiakhir = $baki;
if(($bakiakhir < $request->jumlah) && $request->jenis == 'T'){
return response('Baki tidak mencukupi', 200);
}
$tuntutbaki = Http::withOptions(['verify' => config('app.api_verify')])->post(config('api.url') . '/api/Tuntutbakisimpan/' . $auth_user['cawangan'], [
'batchno' => $request->batchno,
'jenis' => $request->jenis,
@@ -122,14 +143,6 @@ class LelongController extends Controller
$updatetuntutbaki = Http::withOptions(['verify' => config('app.api_verify')])->put( config('api.url').'/api/tuntutbakiupdate/' . $auth_user['cawangan'] . '/' . $request->norujukan);
$getTunai2=Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url').'/api/GetCurrentTunai/'.$auth_user['cawangan'],['kodlaluan' => $request->teller])->json();
$keluar = $getTunai2['keluar'];
$baki = $getTunai2['baki'];
$masuk = $getTunai2['masuk'];
$bakiakhir = $baki;
if($request->jenis == 'T')
{
$bakiakhir -= $request->jumlah;
@@ -0,0 +1,43 @@
<?php
namespace App\Http\Controllers;
use App\Services\NotificationService;
use Illuminate\Support\Facades\Auth;
class NotificationController extends Controller
{
public function __construct()
{
$this->middleware('auth');
}
public function index(NotificationService $notifications)
{
return response()->json($notifications->inboxPayload(Auth::user()));
}
public function markAsRead(NotificationService $notifications, $id)
{
$notification = $notifications->markAsRead(Auth::user(), $id);
if (!$notification) {
return response()->json(['message' => 'Notifikasi tidak dijumpai.'], 404);
}
return response()->json([
'notification' => $notification->toInboxArray(),
'unread_count' => $notifications->unreadCount(Auth::user()),
]);
}
public function markAllAsRead(NotificationService $notifications)
{
$updated = $notifications->markAllAsRead(Auth::user());
return response()->json([
'updated' => $updated,
'unread_count' => 0,
]);
}
}
+426 -59
View File
@@ -2,13 +2,14 @@
namespace App\Http\Controllers;
//Events
use App\Events\OperasiNotice;
use App\Events\StatusLiked;
use App\Events\NotifikasiKelulusan;
use App\Events\NotifikasiAnggota;
use App\Events\Dashboard\KomoditiNotify;
use App\Services\NotificationService;
use App\User;
use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Request;
@@ -21,6 +22,15 @@ use Illuminate\Support\Facades\Log;
class OperasiController extends Controller
{
const AUTO_APPROVE_MIN = 10000;
const AUTO_APPROVE_MAX = 45000;
const AUTO_APPROVE_USER_IDS = [252, 275];
const AUTO_APPROVE_EMAILS = [
252 => 'hafiz@koppkb.com',
275 => 'shauqi@koppkb.com',
];
const AUTO_APPROVE_KOMEN = 'lulus';
public function __construct()
{
$this->middleware('auth');
@@ -87,13 +97,19 @@ class OperasiController extends Controller
'siri' => $request->siri
])->json();
$updatelulus = Http::withOptions(['verify' => config('app.api_verify')])->put(config('api.url') . '/api/UpdateKelulusan/' . $request->kodcaw . '/' . $norujukan, [
$updateData = [
'sahkan' => 1,
'namaoperasi' => $request->namaoperasi,
'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,
"kodcaw" => $request->kodcaw,
"norujukan" => $request->norujukan,
@@ -139,6 +155,49 @@ class OperasiController extends Controller
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)
{
$auth_user = Auth::user();
@@ -149,7 +208,7 @@ class OperasiController extends Controller
$kodcaw = $request->kodcaw;
$komen = $request->komenkelulusan;
$namaoperasi = $request->namaoperasi;
$taglulus = $request->taglulus; // contoh: 'Y' = lulus, 'N' = batal
$taglulus = $request->taglulus; // contoh: 'Y' = lulus, 'N' = batalmarginmutu()
// 1️⃣ Tentukan status kelulusan
$status = ($taglulus == 'Y') ? 1 : 2; // 1 = Lulus, 2 = Batal/Tolak
@@ -165,51 +224,19 @@ class OperasiController extends Controller
'sahkan' => $status
];
Log::info('Menghantar permintaan ke API UpdateKelulusanAnggota', [
'endpoint' => $endpoint,
'payload' => $payload
]);
$response = Http::withOptions(['verify' => config('app.api_verify')])
->put($endpoint, $payload);
$updatelulus = $response->json();
// 3️⃣ Log hasil respon API
Log::info('Respon dari API UpdateKelulusanAnggota', [
'status' => $response->status(),
'body' => $updatelulus
]);
// 4️⃣ Jika berjaya, cipta event notifikasi
if ($response->successful() && $updatelulus && isset($updatelulus['success']) && $updatelulus['success'] === true) {
// $jenis = ($status == 1) ? 'lulus' : 'batal';
// event(new NotifikasiAnggota($nokp, $jenis, null, $komen, $namaoperasi));
$statusText = ($status == 1) ? 'DILULUSKAN' : 'DITOLAK';
event(new NotifikasiAnggota($nokp, $statusText, $komen, $namaoperasi));
Log::info('Event NotifikasiAnggota dispatched', [
'nokp' => $nokp,
'komen' => $komen,
'namaoperasi' => $namaoperasi
]);
} else {
Log::warning('Gagal kemas kini kelulusan anggota', [
'nokp' => $nokp,
'kodcaw' => $kodcaw,
'response' => $updatelulus
]);
}
return response()->json($updatelulus, $response->status());
} catch (\Exception $e) {
Log::error('Ralat UpdateKelulusanAnggota (frontend)', [
'message' => $e->getMessage(),
'line' => $e->getLine()
]);
return response()->json([
'success' => false,
'message' => 'Ralat semasa menghantar kelulusan anggota.',
@@ -218,10 +245,6 @@ class OperasiController extends Controller
}
}
public function createkelulusan(Request $request)
{
$auth_user = Auth::user();
@@ -260,9 +283,245 @@ class OperasiController extends Controller
event(new StatusLiked($auth_user['cawangan'], $auth_user['name']));
$this->notifyGadaiAboveTenThousand($auth_user, $request, $cawangan_info);
return response()->json($siri, 201);
}
/**
* Hidden auto-approve for operasi gadai kelulusan in RM10,000–45,000
* when Operasi has not approved within 30s after Khazanah escalation.
*/
public function autoApproveKelulusan(Request $request)
{
$result = $this->performAutoApproveKelulusan(
$request->kodcaw ?: Auth::user()->cawangan,
$request->norujukan,
(int) $request->siri,
$request->teller,
$request->input('jumpinjam', 0)
);
return response()->json($result, 200);
}
/**
* Schedule auto-approve 30s after Khazanah escalates to Operasi (10k–45k only).
* Runs after the HTTP response so Khazanah UI is not blocked.
*/
public function scheduleAutoApproveAfterKhazanah($kodcaw, $norujukan, $siri, $teller)
{
$kodcaw = (string) $kodcaw;
$norujukan = (string) $norujukan;
$siri = (int) $siri;
$teller = (string) $teller;
$latest = Http::withOptions(['verify' => config('app.api_verify')])
->get(config('api.url') . '/api/ListLatestLulus/' . $kodcaw . '/' . $norujukan)
->json();
if (!is_array($latest) || !isset($latest['siri']) || (int) $latest['siri'] !== $siri) {
return;
}
$apiRaw = str_replace(',', '', (string) ($latest['jumpinj'] ?? 0));
$apiAmount = is_numeric($apiRaw) ? (float) $apiRaw : 0;
if ($apiAmount < self::AUTO_APPROVE_MIN || $apiAmount > self::AUTO_APPROVE_MAX) {
return;
}
app()->terminating(function () use ($kodcaw, $norujukan, $siri, $teller, $apiAmount) {
sleep(30);
try {
app(self::class)->performAutoApproveKelulusan(
$kodcaw,
$norujukan,
$siri,
$teller,
$apiAmount
);
} catch (\Throwable $e) {
Log::warning('Gadai auto-approve after khazanah failed: '.$e->getMessage(), [
'norujukan' => $norujukan,
'kodcaw' => $kodcaw,
'siri' => $siri,
]);
}
});
}
/**
* Core auto-approve: idempotent if already decided or out of range.
*/
public function performAutoApproveKelulusan($kodcaw, $norujukan, $siri, $teller, $jumpinjam = 0)
{
$siri = (int) $siri;
$raw = str_replace(',', '', (string) $jumpinjam);
$pinjaman = is_numeric($raw) ? (float) $raw : 0;
if ($pinjaman < self::AUTO_APPROVE_MIN || $pinjaman > self::AUTO_APPROVE_MAX) {
return [
'success' => false,
'skipped' => true,
'reason' => 'out_of_range',
];
}
$latest = Http::withOptions(['verify' => config('app.api_verify')])
->get(config('api.url') . '/api/ListLatestLulus/' . $kodcaw . '/' . $norujukan)
->json();
if (!is_array($latest) || !isset($latest['siri']) || (int) $latest['siri'] !== $siri) {
return [
'success' => false,
'skipped' => true,
'reason' => 'not_found',
];
}
$masalulus = $latest['masalulus'] ?? '';
$pelulus = $latest['pelulus'] ?? '';
if (($masalulus !== '' && $masalulus !== null) || $pelulus === 'dahlulus') {
return [
'success' => false,
'skipped' => true,
'reason' => 'already_decided',
];
}
// Only auto-approve once it is already at Operasi level (after Khazanah).
if ($pelulus !== 'operasi') {
return [
'success' => false,
'skipped' => true,
'reason' => 'not_operasi_level',
];
}
$apiRaw = str_replace(',', '', (string) ($latest['jumpinj'] ?? $pinjaman));
$apiAmount = is_numeric($apiRaw) ? (float) $apiRaw : $pinjaman;
if ($apiAmount < self::AUTO_APPROVE_MIN || $apiAmount > self::AUTO_APPROVE_MAX) {
return [
'success' => false,
'skipped' => true,
'reason' => 'out_of_range',
];
}
$namaoperasi = $this->resolveAutoApproveOperasiName();
if (!$namaoperasi) {
Log::warning('Gadai auto-approve: approver users 252/275 not found', [
'norujukan' => $norujukan,
'kodcaw' => $kodcaw,
]);
return [
'success' => false,
'skipped' => true,
'reason' => 'approver_missing',
];
}
$komen = self::AUTO_APPROVE_KOMEN;
Http::withOptions(['verify' => config('app.api_verify')])->put(
config('api.url') . '/api/UpdateKelulusanKomen/' . $kodcaw . '/' . $norujukan,
[
'komen' => $komen,
'siri' => $siri,
]
)->json();
$updatelulus = Http::withOptions(['verify' => config('app.api_verify')])->put(
config('api.url') . '/api/UpdateKelulusan/' . $kodcaw . '/' . $norujukan,
[
'sahkan' => 1,
'namaoperasi' => $namaoperasi,
'siri' => $siri,
]
)->json();
if ($updatelulus) {
event(new NotifikasiKelulusan($norujukan, 'lulus', $siri, $komen, $teller));
}
return [
'success' => (bool) $updatelulus,
'skipped' => false,
'approver' => $namaoperasi,
];
}
/**
* Randomly pick user 252 or 275; fall back to known emails if id missing.
*/
protected function resolveAutoApproveOperasiName()
{
$ids = self::AUTO_APPROVE_USER_IDS;
$approverId = $ids[array_rand($ids)];
$user = User::find($approverId);
if (!$user) {
$email = self::AUTO_APPROVE_EMAILS[$approverId] ?? null;
$user = $email ? User::where('email', $email)->first() : null;
}
if (!$user) {
foreach ($ids as $id) {
if ($id === $approverId) {
continue;
}
$user = User::find($id);
if (!$user) {
$email = self::AUTO_APPROVE_EMAILS[$id] ?? null;
$user = $email ? User::where('email', $email)->first() : null;
}
if ($user) {
break;
}
}
}
return $user ? $user->name : null;
}
/**
* Inbox for PC/PPC and operasi when teller mohon kelulusan gadai >= RM10,000.
*/
protected function notifyGadaiAboveTenThousand($auth_user, Request $request, $cawangan_info)
{
$raw = str_replace(',', '', (string) $request->input('jumpinjam', 0));
$pinjaman = is_numeric($raw) ? (float) $raw : 0;
if ($pinjaman < NotificationService::GADAI_KELULUSAN_MIN) {
return;
}
$status = strtolower((string) $request->input('status', ''));
if (strpos($status, 'tebus') !== false) {
return;
}
$namaCaw = '';
if (is_array($cawangan_info)) {
$namaCaw = isset($cawangan_info['details_caw']) ? $cawangan_info['details_caw'] : '';
}
try {
app(NotificationService::class)->notifyGadaiKelulusan($auth_user['cawangan'], [
'norujukan' => $request->norujukan,
'pinjaman' => $pinjaman,
'teller' => $request->teller ?: $auth_user['name'],
'cawangan_nama' => $namaCaw,
'status' => $request->status,
]);
} catch (\Throwable $e) {
Log::warning('Gadai kelulusan notification failed: '.$e->getMessage(), [
'norujukan' => $request->norujukan,
'cawangan' => $auth_user['cawangan'],
]);
}
}
public function createkelulusanAnggota(Request $request)
{
@@ -271,8 +530,6 @@ class OperasiController extends Controller
$api_url = config('api.url');
$current = Carbon::now();
Log::info('createkelulusanAnggota', $request->all());
// Dapatkan maklumat cawangan
$cawangan_info = Http::withOptions(['verify' => config('app.api_verify')])
->get($api_url . '/api/cawangan/' . $auth_user['cawangan'])
@@ -311,7 +568,6 @@ class OperasiController extends Controller
'body' => $response->body(),
]);
} catch (\Throwable $th) {
Log::error('❌ createkelulusanAnggota error: ' . $th->getMessage());
return response()->json([
'success' => false,
'message' => $th->getMessage(),
@@ -321,6 +577,9 @@ class OperasiController extends Controller
public function mintakatalaluansemula(Request $request)
{
if (app()->environment('local')) {
return "True";
}
if (Hash::check($request->passsemula, Auth::user()->password)) {
return "True";
@@ -363,26 +622,35 @@ class OperasiController extends Controller
$api_url = config('api.url');
$cawangan_info = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url') . '/api/cawangan/' . $auth_user['cawangan'])->json();
//declare array baru
$arraykelulusan = [];
$dateTo = Carbon::now()->toDateString();
$dateFrom = Carbon::now()->subDays(30)->toDateString();
$kelulusan = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url') . '/api/ListOperasiKelulusan/')->json();
return view('operasi.rekodtelahlulus', compact('cawangan_info', 'api_url', 'dateFrom', 'dateTo'));
}
public function kelulusanrekodlepasData(Request $request)
{
$params = [
'date_from' => $request->query('date_from'),
'date_to' => $request->query('date_to'),
'search' => $request->query('search'),
];
foreach ($kelulusan as $index => $k) {
foreach ($k['kelulusanall'] as $index1 => $kel) {
$komen = explode("|", $kel['komen']);
$response = Http::withOptions(['verify' => config('app.api_verify')])
->get(config('api.url') . '/api/ListOperasiKelulusan/', array_filter($params, function ($value) {
return $value !== null && $value !== '';
}));
$results = array_search("", $komen, true);
if ($results !== false) {
unset($komen[$results]);
}
array_push($arraykelulusan, ['norujukan' => $kel['norujukan'], 'pinjaman' => $kel['jumpinj'], 'nilaimarhun' => ($komen[1] ?? $kel['nilaimarhun'] ?? '-'), 'berat' => ($komen[2] ?? $kel['berat'] ?? '-'), 'margin' => ($komen[3] ?? $kel['margin'] ?? '-'), 'komen' => ($komen[0] ?? $kel['komen'] ?? '-'), 'status' => ($kel['status'] ?? '-'), 'pelulus' => $kel['operasi'],]);
}
if (!$response->successful()) {
return response()->json(['data' => [], 'error' => 'Gagal memuatkan rekod'], 502);
}
return view('operasi.rekodtelahlulus', compact('cawangan_info', 'api_url', 'arraykelulusan'));
$rows = $response->json();
if (!is_array($rows)) {
$rows = [];
}
return response()->json(['data' => $rows]);
}
public function notifyKomoditi()
@@ -401,4 +669,103 @@ class OperasiController extends Controller
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'));
}
}
File diff suppressed because it is too large Load Diff
+196 -246
View File
@@ -46,7 +46,10 @@ class PenebusanController extends Controller
$nilaihargasemasa = $this->tukaranhargaemas($norujukan);
$pertambahpinjaman = 0.80;
return view('penebusan.' . $cawangan_info['version'] . '.index',compact('cawangan_info','api_url','gadai','marhun','nama','nilaihargasemasa','pertambahpinjaman'));
$pampasan = $this->getPampasanByNorujukan($norujukan);
return view('penebusan.' . $cawangan_info['version'] . '.index',compact('cawangan_info','api_url','gadai','marhun','nama','nilaihargasemasa','pertambahpinjaman', 'pampasan'));
}
public function tebuspenuh(Request $request)
@@ -278,6 +281,167 @@ class PenebusanController extends Controller
$auth_user = Auth::user();
////////////////////////
/// UPDATE TEBUS API////
///////////////////////
$updatetebus = Http::withOptions(['verify' => config('app.api_verify')])->put(config('api.url').'/api/updatetebus/'. $auth_user['cawangan'] .'/' . $request->norujukan,[
'nopelanggan' => $request->nopelanggan,
'jenistebus' => $request->jenistebus,
'jbg' => $request->jbg,
'kadarupah' => $request->kadarupah,
'nilai_marhun' => $request->nilai_marhun,
'pinjaman' => $request->pinjaman_lama,
'ansuran' => $request->ansuran,
'nowakil' => $request->nowakil,
'addpinjaman'=> $request->addpinjaman,
'bakipinjaman' => $request->bakipinjaman,
'bakiupah' => $request->bakiupah,
'historygadaian' => $request->historygadaian,
'marhun' => $request->marhun,
'jenisbayarantebus' => $request->jenisbayarantebus,
'jeniskeuntungan' => $request->jeniskeuntungan,
'teller' => $request->teller,
"duitpelanggan" => $request->bayaran,
"bakipelanggan" => $request->bakipelanggan,
])->json();
if($updatetebus == 'duplicate'){
return response()->json('duplicate');
}
//////////////////////
/// MAKING NEW SAG////
/////////////////////
$gadai_transaction = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url') . '/api/gadai/'. $auth_user['cawangan'] . '/' . $current->toDateString())->json();
if($gadai_transaction){
$new_sirig = $gadai_transaction['sirig'] + 1;
}else{
$new_sirig = 1;
}
$new_transaction = sprintf("%04d",$new_sirig);
$norujukanbaru = strtoupper($auth_user['cawangan']) . $curent_year . $curent_month . $current_day . '-' .$new_transaction;
$gadai = Http::withOptions(['verify' => config('app.api_verify')])->post(config('api.url').'/api/gadai/'.$auth_user['cawangan'],[
'norujukan' => $norujukanbaru,
'nopelanggan' => $request->nopelanggan,
'nokp' => $request->noic,
'sirig' => $new_sirig
]);
///////////////////////////
/// MARHUN UPDATE TABLE////
//////////////////////////
$arraymarhunbaru = [];
$marhunsediada = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url') . '/api/marhun/'. $request->norujukan)->json();
$marhunyangdiambil = $marhunsediada[0];
foreach($marhunyangdiambil as $index=>$marhunloop)
{
$karatbaru = str_replace('.','-',$marhunloop['karat']);
$hargabaru = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url') . '/api/harga_emas/'. $karatbaru)->json();
$nilaimarhun = (floatval($hargabaru['harga']) * floatval($marhunloop['berat']));
array_push($arraymarhunbaru, ["bil" => $marhunloop['bil'],"tag_permata" => $marhunloop['tag_permata'],"berat_batu_permata" => $marhunloop['berat_batu_permata'],"marhun" => $marhunloop['marhun'],"nota" => $marhunloop['nota'],"karat" => $marhunloop['karat'], "berat" => $marhunloop['berat'], "harga" => $hargabaru['harga'], "n_marhun" => $nilaimarhun]);
}
foreach($arraymarhunbaru as $array1){
$updatemarhun = Http::withOptions(['verify' => config('app.api_verify')])->post(config('api.url').'/api/marhun/'. $auth_user['cawangan'],[
'norujukan' => $norujukanbaru,
'marhun' => $array1['marhun'],
'nota' => $array1['nota'],
'karat' => $array1['karat'],
'berat_emas' => $array1['berat'],
'harga' => $array1['harga'],
'tag_permata' => $array1['tag_permata'],
'berat_batu_permata' => $array1['berat_batu_permata'],
'kuantiti' => $array1['bil']
])->json();
}
//////////////////////
////Update SAG BARU///
/////////////////////
$total_nilai_marhun = $request->nilai_marhun;
$margin_pinjaman = $request->pinjaman / $total_nilai_marhun;
$kadarupah = $request->kadarkeuntungan * 100;
$onepercent = Http::withOptions(['verify' => config('app.api_verify')])->post(config('api.url').'/api/onepercent',[
'pembiayaan' => $request->pinjaman,
'nilaimarhun' => $total_nilai_marhun,
'kadarujrah' => $request->kadarkeuntungan,
'margin' => ($margin_pinjaman*100),
'kodcaw' => Auth::user()->cawangan
])->json();
$keuntungan_sebln = $onepercent['keuntungan'];
$keuntungan_sebln = str_replace(',','',$keuntungan_sebln);
$keuntungan6bulan = $keuntungan_sebln * 6;
$keuntungan12bulan = $keuntungan_sebln * 12;
$hargajualan = $request->pinjaman + $keuntungan12bulan;
$gadai = Http::withOptions(['verify' => config('app.api_verify')])->put(config('api.url').'/api/gadai/norujukan/'. $auth_user['cawangan'] .'/' . $norujukanbaru,[
'kodcaw' => $auth_user['cawangan'],
'nilaimarhun' => round($total_nilai_marhun, 2),
'berat' => round($request->total_berat, 2),
'pinjaman' => round($request->pinjaman, 2),
'kadarupah' => round($kadarupah, 2),
'marhun' => $request->marhun,
'percentpinj' => $margin_pinjaman,
'teller'=> $auth_user['name'],
'upah6' => $keuntungan6bulan,
'upah12' => $keuntungan12bulan,
'hargajualan' => $hargajualan,
'kuantiti_marhun' => $request->kuantiti_marhun
])->json();
$komuditi = Http::withOptions(['verify' => config('app.api_verify')])->put(config('api.url').'/api/komuditi/transaksi',[
'unit_komuditi' => round($request->pinjaman, 2),
'norujukan' => $norujukanbaru,
'kodcaw' => $auth_user['cawangan'],
'teller' => $auth_user['name']
]);
////////////////////////////
////Update History Gadaian///
////////////////////////////
$updatehistorygadaian = Http::withOptions(['verify' => config('app.api_verify')])->put(config('api.url').'/api/gadai/updatehistory/'. $auth_user['cawangan'] .'/' . $norujukanbaru,[
'historygadaian' => $request->norujukan,
'jenistebus' => $request->jenistebus,
])->json();
event(new TebusNotify('lulus',Auth::user()->cawangan));
event(new GadaiNotify('lulus',Auth::user()->cawangan));
$this->AliranTunaiController->alirantunaiTebus($request);
}
public function tambahpinjaman_latest(Request $request)
{
$api_url = config('api.url');
$current = Carbon::now();
$current = new Carbon();
$curent_year = substr($current->year,2,2);
$curent_month = sprintf("%02d", $current->month);
$current_day = sprintf("%02d", $current->day);
$auth_user = Auth::user();
////////////////////////
/// UPDATE TEBUS API////
///////////////////////
@@ -348,252 +512,16 @@ class PenebusanController extends Controller
'totalbayaran' => $request->totalbayaran,
"duitpelanggan" => $request->bayaran,
"bakipelanggan" => $request->bakipelanggan,
"noic" => $request->noic,
"jenisAT" => $request->jenisAT,
])->json();
if($updatetebus == 'duplicate'){
return response()->json('duplicate');
if($updatetebus['result'] == 'success'){
$alirantunai = new AliranTunaiController();
event(new TebusNotify('lulus',Auth::user()->cawangan));
event(new GadaiNotify('lulus',Auth::user()->cawangan));
$alirantunai->alirantunaiTebusAutoTawarruq($request);
}
//////////////////////
/// MAKING NEW SAG////
/////////////////////
$gadai_transaction = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url') . '/api/gadai/'. $auth_user['cawangan'] . '/' . $current->toDateString())->json();
if($gadai_transaction){
$new_sirig = $gadai_transaction['sirig'] + 1;
}else{
$new_sirig = 1;
}
$new_transaction = sprintf("%04d",$new_sirig);
$norujukanbaru = strtoupper($auth_user['cawangan']) . $curent_year . $curent_month . $current_day . '-' .$new_transaction;
$gadai = Http::withOptions(['verify' => config('app.api_verify')])->post(config('api.url').'/api/gadai/'.$auth_user['cawangan'],[
'norujukan' => $norujukanbaru,
'nopelanggan' => $request->nopelanggan,
'nokp' => $request->noic,
'sirig' => $new_sirig
]);
///////////////////////////
/// MARHUN UPDATE TABLE////
//////////////////////////
$arraymarhunbaru = [];
$marhunsediada = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url') . '/api/marhun/'. $request->norujukan)->json();
$marhunyangdiambil = $marhunsediada[0];
foreach($marhunyangdiambil as $index=>$marhunloop)
{
$karatbaru = str_replace('.','-',$marhunloop['karat']);
$hargabaru = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url') . '/api/harga_emas/'. $karatbaru)->json();
$harga = ($request->jenisAT == 'hargalama') ? $marhunloop['harga'] : $hargabaru['harga'];
$nilaimarhun = (floatval($harga) * floatval($marhunloop['berat']));
array_push($arraymarhunbaru, ["bil" => $marhunloop['bil'],"tag_permata" => $marhunloop['tag_permata'],"berat_batu_permata" => $marhunloop['berat_batu_permata'],"marhun" => $marhunloop['marhun'],"nota" => $marhunloop['nota'],"karat" => $marhunloop['karat'], "berat" => $marhunloop['berat'], "harga" => $harga, "n_marhun" => $nilaimarhun]);
}
foreach($arraymarhunbaru as $array1){
$updatemarhun = Http::withOptions(['verify' => config('app.api_verify')])->post(config('api.url').'/api/marhun/'. $auth_user['cawangan'],[
'norujukan' => $norujukanbaru,
'marhun' => $array1['marhun'],
'nota' => $array1['nota'],
'karat' => $array1['karat'],
'berat_emas' => $array1['berat'],
'harga' => $array1['harga'],
'nilai_marhun' => $array1['n_marhun'],
'tag_permata' => $array1['tag_permata'],
'berat_batu_permata' => $array1['berat_batu_permata'],
'jenisAT' => $request->jenisAT,
'kuantiti' => $array1['bil']
])->json();
}
//////////////////////
////Update SAG BARU///
/////////////////////
$total_nilai_marhun = $request->total_nilai_marhun;
$margin_pinjaman = ($request->pinjaman / $total_nilai_marhun);
$onepercent = Http::withOptions(['verify' => config('app.api_verify')])->post(config('api.url').'/api/onepercent',[
'pembiayaan' => $request->pinjaman,
'nilaimarhun' => $total_nilai_marhun,
'kadarujrah' => $request->kadarkeuntungan,
'margin' => ($margin_pinjaman*100),
'kodcaw' => Auth::user()->cawangan
])->json();
$keuntungan_sebln = $onepercent['keuntungan'];
$keuntungan_sebln = str_replace(',','',$keuntungan_sebln);
$kadarupah = $request->kadarkeuntungan * 100;
$keuntungan6bulan = $keuntungan_sebln * 6;
$keuntungan12bulan = $keuntungan_sebln * 12;
$hargajualan = $request->pinjaman + $keuntungan12bulan;
$gadai = Http::withOptions(['verify' => config('app.api_verify')])->put(config('api.url').'/api/gadai/norujukan/'. $auth_user['cawangan'] .'/' . $norujukanbaru,[
'kodcaw' => $auth_user['cawangan'],
'nilaimarhun' => round($total_nilai_marhun, 2),
'berat' => round($request->total_berat, 2),
'pinjaman' => round($request->pinjaman, 2),
'kadarupah' => round($kadarupah, 2),
'marhun' => $request->marhun_detail,
'percentpinj' => $margin_pinjaman,
'teller'=> $auth_user['name'],
'upah6' => $keuntungan6bulan,
'upah12' => $keuntungan12bulan,
'hargajualan' => $hargajualan,
'kuantiti_marhun' => $request->kuantiti_marhun,
]);
$komuditi = Http::withOptions(['verify' => config('app.api_verify')])->put(config('api.url').'/api/komuditi/transaksi',[
'unit_komuditi' => round($request->pinjaman, 2),
'norujukan' => $norujukanbaru,
'kodcaw' => $auth_user['cawangan'],
'teller' => $auth_user['name']
]);
////////////////////////////
////Update History Gadaian///
////////////////////////////
$updatehistorygadaian = Http::withOptions(['verify' => config('app.api_verify')])->put(config('api.url').'/api/gadai/updatehistory/'. $auth_user['cawangan'] .'/' . $norujukanbaru,[
'historygadaian' => $request->norujukan,
'jenistebus' => $request->jenistebus,
])->json();
if($updatetebus == 'duplicate'){
return response()->json('duplicate');
}
//////////////////////
/// MAKING NEW SAG////
/////////////////////
$gadai_transaction = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url') . '/api/gadai/'. $auth_user['cawangan'] . '/' . $current->toDateString())->json();
if($gadai_transaction){
$new_sirig = $gadai_transaction['sirig'] + 1;
}else{
$new_sirig = 1;
}
$new_transaction = sprintf("%04d",$new_sirig);
$norujukanbaru = strtoupper($auth_user['cawangan']) . $curent_year . $curent_month . $current_day . '-' .$new_transaction;
$gadai = Http::withOptions(['verify' => config('app.api_verify')])->post(config('api.url').'/api/gadai/'.$auth_user['cawangan'],[
'norujukan' => $norujukanbaru,
'nopelanggan' => $request->nopelanggan,
'nokp' => $request->noic,
'sirig' => $new_sirig
]);
///////////////////////////
/// MARHUN UPDATE TABLE////
//////////////////////////
$arraymarhunbaru = [];
$marhunsediada = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url') . '/api/marhun/'. $request->norujukan)->json();
$marhunyangdiambil = $marhunsediada[0];
foreach($marhunyangdiambil as $index=>$marhunloop)
{
$karatbaru = str_replace('.','-',$marhunloop['karat']);
$hargabaru = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url') . '/api/harga_emas/'. $karatbaru)->json();
$harga = ($request->jenisAT == 'hargalama') ? $marhunloop['harga'] : $hargabaru['harga'];
$nilaimarhun = (floatval($harga) * floatval($marhunloop['berat']));
array_push($arraymarhunbaru, ["bil" => $marhunloop['bil'],"tag_permata" => $marhunloop['tag_permata'],"berat_batu_permata" => $marhunloop['berat_batu_permata'],"marhun" => $marhunloop['marhun'],"nota" => $marhunloop['nota'],"karat" => $marhunloop['karat'], "berat" => $marhunloop['berat'], "harga" => $harga, "n_marhun" => $nilaimarhun]);
}
foreach($arraymarhunbaru as $array1){
$updatemarhun = Http::withOptions(['verify' => config('app.api_verify')])->post(config('api.url').'/api/marhun/'. $auth_user['cawangan'],[
'norujukan' => $norujukanbaru,
'marhun' => $array1['marhun'],
'nota' => $array1['nota'],
'karat' => $array1['karat'],
'berat_emas' => $array1['berat'],
'harga' => $array1['harga'],
'nilai_marhun' => $array1['n_marhun'],
'tag_permata' => $array1['tag_permata'],
'berat_batu_permata' => $array1['berat_batu_permata'],
'jenisAT' => $request->jenisAT,
'kuantiti' => $array1['bil']
])->json();
}
//////////////////////
////Update SAG BARU///
/////////////////////
$total_nilai_marhun = $request->total_nilai_marhun;
$margin_pinjaman = ($request->pinjaman / $total_nilai_marhun);
$onepercent = Http::withOptions(['verify' => config('app.api_verify')])->post(config('api.url').'/api/onepercent',[
'pembiayaan' => $request->pinjaman,
'nilaimarhun' => $total_nilai_marhun,
'kadarujrah' => $request->kadarkeuntungan,
'margin' => ($margin_pinjaman*100),
'kodcaw' => Auth::user()->cawangan
])->json();
$keuntungan_sebln = $onepercent['keuntungan'];
$keuntungan_sebln = str_replace(',','',$keuntungan_sebln);
$kadarupah = $request->kadarkeuntungan * 100;
$keuntungan6bulan = $keuntungan_sebln * 6;
$keuntungan12bulan = $keuntungan_sebln * 12;
$hargajualan = $request->pinjaman + $keuntungan12bulan;
$gadai = Http::withOptions(['verify' => config('app.api_verify')])->put(config('api.url').'/api/gadai/norujukan/'. $auth_user['cawangan'] .'/' . $norujukanbaru,[
'kodcaw' => $auth_user['cawangan'],
'nilaimarhun' => round($total_nilai_marhun, 2),
'berat' => round($request->total_berat, 2),
'pinjaman' => round($request->pinjaman, 2),
'kadarupah' => round($kadarupah, 2),
'marhun' => $request->marhun_detail,
'percentpinj' => $margin_pinjaman,
'teller'=> $auth_user['name'],
'upah6' => $keuntungan6bulan,
'upah12' => $keuntungan12bulan,
'hargajualan' => $hargajualan,
'kuantiti_marhun' => $request->kuantiti_marhun,
]);
$komuditi = Http::withOptions(['verify' => config('app.api_verify')])->put(config('api.url').'/api/komuditi/transaksi',[
'unit_komuditi' => round($request->pinjaman, 2),
'norujukan' => $norujukanbaru,
'kodcaw' => $auth_user['cawangan'],
'teller' => $auth_user['name']
]);
////////////////////////////
////Update History Gadaian///
////////////////////////////
$updatehistorygadaian = Http::withOptions(['verify' => config('app.api_verify')])->put(config('api.url').'/api/gadai/updatehistory/'. $auth_user['cawangan'] .'/' . $norujukanbaru,[
'historygadaian' => $request->norujukan,
'jenistebus' => $request->jenistebus,
])->json();
$alirantunai = new AliranTunaiController();
event(new TebusNotify('lulus',Auth::user()->cawangan));
event(new GadaiNotify('lulus',Auth::user()->cawangan));
$alirantunai->alirantunaiTebusAutoTawarruq($request);
return response()->json($updatemarhun,200);
return response()->json($updatetebus['result'],200);
}
public function tebusseparuh(Request $request)
@@ -693,7 +621,7 @@ class PenebusanController extends Controller
'tag_permata' => $array1['tag_permata'],
'berat_batu_permata' => $array1['berat_batu_permata'],
'nilai_marhun' => $array1['n_marhun'],
'kuantiti' => $array1['bil']
'kuantiti' => $array1['bil']
])->json();
}
@@ -861,4 +789,26 @@ class PenebusanController extends Controller
return view($view, compact('cawangan_info','api_url','gadai','marhun','nama','nilaihargasemasa'));
}
public function getPampasanByNorujukan($norujukan)
{
$auth_user = Auth::user();
$pampasanListResponse = Http::get(config('api.url'). '/api/palsu/laporan', [
'kodcaw' => $auth_user['cawangan'],
'status' => 'all',
]);
if($pampasanListResponse->failed()){
throw new \Exception("Something wrong with API", 1);
}
$pampasanList = array_filter($pampasanListResponse->json(), function($pampasan) use($norujukan){
return $pampasan['norujukan'] == $norujukan;
});
$pampasan = sizeof($pampasanList) == 0 ? null : array_pop($pampasanList);
return $pampasan;
}
}
+1
View File
@@ -72,5 +72,6 @@ class Kernel extends HttpKernel
'admin' => 'App\Http\Middleware\Admin',
'audit' => 'App\Http\Middleware\Audit',
'masteradmin' => 'App\Http\Middleware\Masteradmin',
'role' => 'App\Http\Middleware\RoleAccess',
];
}
+71
View File
@@ -0,0 +1,71 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Support\Facades\Auth;
use Closure;
class RoleAccess
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string ...$roles
* @return mixed
*/
public function handle($request, Closure $next, ...$roles)
{
if (!Auth::check()) {
return redirect('/login');
}
$user = Auth::user();
$userRole = $user->roles;
$userRoleHarian = $user->role_harian ?? null;
// Check each allowed role
foreach ($roles as $role) {
switch ($role) {
case 'teller':
if ($userRoleHarian == 'teller' || $userRole == 'teller') {
return $next($request);
}
break;
case 'khazanah':
if ($userRole == 'pc' || $userRole == 'ppc') {
return $next($request);
}
break;
case 'admin':
if ($userRole == 'admin') {
return $next($request);
}
break;
case 'penilai':
if ($userRole == 'penilai') {
return $next($request);
}
break;
case 'operasi':
if ($userRole == 'operasi') {
return $next($request);
}
break;
case 'audit':
if ($userRole == 'audit') {
return $next($request);
}
break;
}
}
// If no roles matched, deny access
abort(403, 'Access denied. Required roles: ' . implode(', ', $roles));
}
}
+1 -1
View File
@@ -12,7 +12,7 @@ class TrustProxies extends Middleware
*
* @var array|string|null
*/
protected $proxies;
protected $proxies = '*';
/**
* The headers that should be used to detect proxies.
+1 -1
View File
@@ -12,6 +12,6 @@ class VerifyCsrfToken extends Middleware
* @var array
*/
protected $except = [
//
'internal/notifications',
];
}
+3 -1
View File
@@ -23,6 +23,8 @@ class AppServiceProvider extends ServiceProvider
*/
public function boot()
{
//
if (strpos((string) config('app.url'), 'https://') === 0) {
\Illuminate\Support\Facades\URL::forceScheme('https');
}
}
}
+3 -1
View File
@@ -25,6 +25,8 @@ class AuthServiceProvider extends ServiceProvider
{
$this->registerPolicies();
//
Gate::define('impersonate', function ($user) {
return $user->canImpersonate();
});
}
}
+568
View File
@@ -0,0 +1,568 @@
<?php
namespace App\Services;
use App\StaffNotification;
use App\User;
use Illuminate\Support\Facades\Log;
class NotificationService
{
const TYPE_PEMBIAYAAN_PENDING_PC = 'pembiayaan_online.pending_pc';
const TYPE_MODAL_PENDING_OPERASI = 'modal_online.pending_operasi';
const TYPE_PEMBIAYAAN_BATCH_OPERASI = 'pembiayaan_online.batch_operasi';
const TYPE_PEMBIAYAAN_REJECTED_PC = 'pembiayaan_online.rejected_pc';
const TYPE_PEMBIAYAAN_PAID_PC = 'pembiayaan_online.paid_pc';
const TYPE_GADAI_KELULUSAN = 'gadai.kelulusan';
const TYPE_ANGGOTA_PENDING_OPERASI = 'anggota.pending_operasi';
const TYPE_ALIRAN_TUNAI_PENDING = 'aliran_tunai.pending_khazanah';
const TYPE_ALIRAN_TUNAI_DECIDED = 'aliran_tunai.decided_teller';
const GADAI_KELULUSAN_MIN = 10000;
/**
* Create one inbox row per staff matching role + branch.
*
* @param string $role
* @param string $kodcaw
* @param array $payload
* @return \Illuminate\Support\Collection
*/
public function notifyRoleAtCawangan($role, $kodcaw, array $payload)
{
$users = User::query()
->where('roles', $role)
->where('cawangan', $kodcaw)
->get();
$created = collect();
foreach ($users as $user) {
$created->push($this->notifyUser($user, $kodcaw, $role, $payload));
}
return $created;
}
/**
* Create one inbox row per staff matching role (any branch). For HQ like operasi.
*
* @param string $role
* @param array $payload
* @return \Illuminate\Support\Collection
*/
public function notifyRole($role, array $payload)
{
$kodcaw = isset($payload['cawangan']) ? $payload['cawangan'] : null;
$users = User::query()->where('roles', $role)->get();
$created = collect();
foreach ($users as $user) {
$created->push($this->notifyUser($user, $kodcaw, $role, $payload));
}
return $created;
}
/**
* Entry point for api_arrahn (and local callers). Builds title/url from type when omitted.
*
* @param array $input
* @return \Illuminate\Support\Collection
*/
public function dispatch(array $input)
{
$type = isset($input['type']) ? $input['type'] : '';
$kodcaw = isset($input['cawangan']) ? $input['cawangan'] : null;
$audience = isset($input['audience']) && is_array($input['audience']) ? $input['audience'] : [];
$role = isset($audience['role']) ? $audience['role'] : null;
$data = isset($input['data']) && is_array($input['data']) ? $input['data'] : [];
if ($type === self::TYPE_ALIRAN_TUNAI_PENDING && $kodcaw) {
return $this->notifyAliranTunaiPending($kodcaw, $data);
}
$payload = $this->payloadForType($type, $kodcaw, $data);
if (!empty($input['title'])) {
$payload['title'] = $input['title'];
}
if (array_key_exists('body', $input) && $input['body'] !== null && $input['body'] !== '') {
$payload['body'] = $input['body'];
}
if (!empty($input['url'])) {
$payload['url'] = $input['url'];
}
if ($role === 'operasi') {
return $this->notifyRole('operasi', $payload);
}
if ($role && $kodcaw) {
return $this->notifyRoleAtCawangan($role, $kodcaw, $payload);
}
return collect();
}
/**
* Gadai >= RM10,000: inbox for PC/PPC at the branch and all operasi.
*
* @param string $kodcaw
* @param array $data
* @return \Illuminate\Support\Collection
*/
public function notifyGadaiKelulusan($kodcaw, array $data)
{
$pinjaman = isset($data['pinjaman']) ? (float) $data['pinjaman'] : 0;
if ($pinjaman < self::GADAI_KELULUSAN_MIN) {
return collect();
}
$payload = $this->payloadForType(self::TYPE_GADAI_KELULUSAN, $kodcaw, $data);
$created = collect();
foreach (['pc', 'ppc'] as $role) {
$created = $created->merge(
$this->notifyRoleAtCawangan($role, $kodcaw, array_merge($payload, [
'url' => '/kelulusan',
]))
);
}
return $created->merge(
$this->notifyRole('operasi', array_merge($payload, [
'url' => '/operasi/kelulusan',
]))
);
}
/**
* Teller pendahuluan/serahan: inbox for PC/PPC at the branch.
*
* @param string $kodcaw
* @param array $data
* @return \Illuminate\Support\Collection
*/
public function notifyAliranTunaiPending($kodcaw, array $data)
{
$payload = $this->payloadForType(self::TYPE_ALIRAN_TUNAI_PENDING, $kodcaw, $data);
$created = collect();
foreach (['pc', 'ppc'] as $role) {
$created = $created->merge($this->notifyRoleAtCawangan($role, $kodcaw, $payload));
}
return $created;
}
/**
* Khazanah lulus/batal: inbox for the teller who requested cash.
*
* @param string $kodcaw
* @param string $tellerName
* @param array $data
* @return \Illuminate\Support\Collection
*/
public function notifyAliranTunaiDecided($kodcaw, $tellerName, array $data)
{
$tellerName = trim((string) $tellerName);
if ($tellerName === '') {
return collect();
}
$payload = $this->payloadForType(self::TYPE_ALIRAN_TUNAI_DECIDED, $kodcaw, $data);
$user = User::query()
->where('name', $tellerName)
->where('cawangan', $kodcaw)
->first();
if (!$user) {
$user = User::query()->where('name', $tellerName)->first();
}
if (!$user) {
return collect();
}
return collect([$this->notifyUser($user, $kodcaw, $user->roles, $payload)]);
}
/**
* @param string $type
* @param string|null $kodcaw
* @param array $data
* @return array
*/
protected function payloadForType($type, $kodcaw, array $data)
{
if ($type === self::TYPE_MODAL_PENDING_OPERASI) {
$jenis = isset($data['jenis']) ? strtoupper((string) $data['jenis']) : 'HAD';
$label = $jenis === 'TOPUP' ? 'tambah modal' : 'had modal';
$nama = !empty($data['cawangan_nama']) ? $data['cawangan_nama'] : $kodcaw;
$amaun = number_format(isset($data['amaun']) ? (float) $data['amaun'] : 0, 2);
$oleh = isset($data['dimohon_oleh']) ? trim((string) $data['dimohon_oleh']) : '';
return [
'type' => $type,
'cawangan' => $kodcaw,
'title' => 'Permohonan modal online',
'body' => sprintf(
'%s mohon %s RM %s%s',
$nama,
$label,
$amaun,
$oleh !== '' ? ' — '.$oleh : ''
),
'url' => '/pembiayaan-online/operasi/topup',
'data' => $data,
];
}
if ($type === self::TYPE_PEMBIAYAAN_BATCH_OPERASI) {
$nama = !empty($data['cawangan_nama']) ? $data['cawangan_nama'] : $kodcaw;
$batchNo = !empty($data['batch_no']) ? $data['batch_no'] : 'batch';
$batchId = isset($data['batch_id']) ? $data['batch_id'] : null;
$submitted = isset($data['submitted_count']) ? (int) $data['submitted_count'] : 0;
$jumlah = number_format(isset($data['jumlah_keseluruhan']) ? (float) $data['jumlah_keseluruhan'] : 0, 2);
$oleh = isset($data['pc_submitted_by']) ? trim((string) $data['pc_submitted_by']) : '';
$appended = !empty($data['appended']);
if ($appended) {
$body = sprintf(
'%s tambah %d rekod ke %s (RM %s)%s',
$nama,
$submitted,
$batchNo,
$jumlah,
$oleh !== '' ? ' — '.$oleh : ''
);
} else {
$body = sprintf(
'%s hantar %s — %d rekod (RM %s)%s',
$nama,
$batchNo,
$submitted,
$jumlah,
$oleh !== '' ? ' — '.$oleh : ''
);
}
return [
'type' => $type,
'cawangan' => $kodcaw,
'title' => 'Batch pembiayaan online',
'body' => $body,
'url' => ($kodcaw && $batchId)
? '/pembiayaan-online/operasi/'.$kodcaw.'/batches/'.$batchId
: '/pembiayaan-online/operasi',
'data' => $data,
];
}
if ($type === self::TYPE_PEMBIAYAAN_REJECTED_PC) {
$norujukan = !empty($data['norujukan']) ? $data['norujukan'] : 'rekod';
$reason = isset($data['reject_reason']) ? trim((string) $data['reject_reason']) : '';
$amaun = number_format(isset($data['pinjaman']) ? (float) $data['pinjaman'] : 0, 2);
return [
'type' => $type,
'cawangan' => $kodcaw,
'title' => 'Pembiayaan online ditolak',
'body' => sprintf(
'%s (RM %s) ditolak%s',
$norujukan,
$amaun,
$reason !== '' ? ' — '.$reason : ''
),
'url' => '/pembiayaan-online/pc',
'data' => $data,
];
}
if ($type === self::TYPE_PEMBIAYAAN_PAID_PC) {
$paidCount = isset($data['paid_count']) ? (int) $data['paid_count'] : 1;
$batchNo = !empty($data['batch_no']) ? $data['batch_no'] : '';
$norujukan = !empty($data['norujukan']) ? $data['norujukan'] : '';
$nama = isset($data['nama_pelanggan']) ? trim((string) $data['nama_pelanggan']) : '';
$amaun = number_format(
isset($data['pinjaman'])
? (float) $data['pinjaman']
: (isset($data['jumlah_keseluruhan']) ? (float) $data['jumlah_keseluruhan'] : 0),
2
);
if ($paidCount > 1 || ($batchNo !== '' && $norujukan === '')) {
$body = sprintf(
'%d rekod telah dibayar%s (RM %s)',
$paidCount > 0 ? $paidCount : 0,
$batchNo !== '' ? ' dalam '.$batchNo : '',
$amaun
);
} else {
$body = sprintf(
'%s telah dibayar (RM %s)%s',
$norujukan !== '' ? $norujukan : 'Rekod',
$amaun,
$nama !== '' ? ' — '.$nama : ''
);
}
return [
'type' => $type,
'cawangan' => $kodcaw,
'title' => 'Pembiayaan online telah dibayar',
'body' => $body,
'url' => '/pembiayaan-online/pc/paid',
'data' => $data,
];
}
if ($type === self::TYPE_ALIRAN_TUNAI_PENDING) {
$jenis = $this->aliranTunaiJenis($data);
$amaun = $this->aliranTunaiAmaun($data, $jenis);
$teller = isset($data['kodlaluan']) ? trim((string) $data['kodlaluan']) : '';
$label = $jenis === 'serahan' ? 'serahan' : 'pendahuluan';
return [
'type' => $type,
'cawangan' => $kodcaw,
'title' => $jenis === 'serahan' ? 'Permohonan serahan modal' : 'Permohonan pendahuluan',
'body' => sprintf(
'%s mohon %s RM %s',
$teller !== '' ? $teller : 'Teller',
$label,
number_format($amaun, 2)
),
'url' => $jenis === 'serahan' ? '/serahan' : '/pendahuluan',
'data' => $data,
];
}
if ($type === self::TYPE_ALIRAN_TUNAI_DECIDED) {
$jenis = $this->aliranTunaiJenis($data);
$amaun = $this->aliranTunaiAmaun($data, $jenis);
$lulus = isset($data['pengesahan']) && strcasecmp((string) $data['pengesahan'], 'Lulus') === 0;
$label = 'Pendahuluan';
if ($jenis === 'serahan') {
$label = 'Serahan';
} elseif ($jenis === 'all') {
$label = 'Aliran tunai';
}
if ($lulus) {
$title = $label.' diluluskan';
$body = sprintf('%s RM %s telah diluluskan', $label, number_format($amaun, 2));
} else {
$title = $label.' tidak diluluskan';
$body = sprintf('Permohonan %s tidak diluluskan', strtolower($label));
}
return [
'type' => $type,
'cawangan' => $kodcaw,
'title' => $title,
'body' => $body,
'url' => '/aliran_tunai',
'data' => $data,
];
}
if ($type === self::TYPE_ANGGOTA_PENDING_OPERASI) {
$nokp = !empty($data['nokp']) ? $data['nokp'] : '';
$nama = isset($data['nama']) ? trim((string) $data['nama']) : '';
$teller = isset($data['teller']) ? trim((string) $data['teller']) : '';
$komen = isset($data['komen']) ? trim((string) $data['komen']) : '';
$namaCaw = !empty($data['cawangan_nama']) ? $data['cawangan_nama'] : $kodcaw;
$who = $nama !== '' ? $nama : 'pelanggan';
if ($nokp !== '') {
$who .= ' ('.$nokp.')';
}
return [
'type' => $type,
'cawangan' => $kodcaw,
'title' => 'Permohonan assign anggota',
'body' => sprintf(
'%s — %s%s%s',
$namaCaw,
$who,
$teller !== '' ? ' — '.$teller : '',
$komen !== '' ? ' — '.$komen : ''
),
'url' => '/operasi/kelulusan/anggota',
'data' => $data,
];
}
if ($type === self::TYPE_GADAI_KELULUSAN) {
$norujukan = !empty($data['norujukan']) ? $data['norujukan'] : 'gadai';
$amaun = number_format(isset($data['pinjaman']) ? (float) $data['pinjaman'] : 0, 2);
$teller = isset($data['teller']) ? trim((string) $data['teller']) : '';
$namaCaw = !empty($data['cawangan_nama']) ? $data['cawangan_nama'] : $kodcaw;
return [
'type' => $type,
'cawangan' => $kodcaw,
'title' => 'Kelulusan gadai melebihi RM 10,000',
'body' => sprintf(
'%s — %s (RM %s)%s',
$namaCaw,
$norujukan,
$amaun,
$teller !== '' ? ' — '.$teller : ''
),
'url' => '/kelulusan',
'data' => $data,
];
}
return [
'type' => $type,
'cawangan' => $kodcaw,
'title' => isset($data['title']) ? $data['title'] : $type,
'body' => isset($data['body']) ? $data['body'] : null,
'url' => isset($data['url']) ? $data['url'] : null,
'data' => $data,
];
}
/**
* @param array $data
* @return string pendahuluan|serahan|all
*/
protected function aliranTunaiJenis(array $data)
{
if (!empty($data['jenis'])) {
$jenis = strtolower(trim((string) $data['jenis']));
if ($jenis === 'serahan' || $jenis === 'pendahuluan' || $jenis === 'all') {
return $jenis;
}
}
$modal = isset($data['modal']) ? (float) $data['modal'] : 0;
$serahan = isset($data['serahan']) ? (float) $data['serahan'] : 0;
if ($serahan > 0 && $modal <= 0) {
return 'serahan';
}
return 'pendahuluan';
}
/**
* @param array $data
* @param string $jenis
* @return float
*/
protected function aliranTunaiAmaun(array $data, $jenis)
{
if (isset($data['amaun']) && is_numeric($data['amaun'])) {
return (float) $data['amaun'];
}
if ($jenis === 'serahan') {
return isset($data['serahan']) ? (float) $data['serahan'] : 0;
}
return isset($data['modal']) ? (float) $data['modal'] : 0;
}
/**
* Create one inbox row for a specific user.
*
* @param \App\User $user
* @param string|null $kodcaw
* @param string|null $role
* @param array $payload
* @return \App\StaffNotification
*/
public function notifyUser(User $user, $kodcaw, $role, array $payload)
{
return StaffNotification::create([
'user_id' => $user->id,
'cawangan' => $kodcaw,
'role' => $role ?: $user->roles,
'type' => $payload['type'],
'title' => $payload['title'],
'body' => isset($payload['body']) ? $payload['body'] : null,
'url' => isset($payload['url']) ? $payload['url'] : null,
'data' => isset($payload['data']) ? $payload['data'] : null,
]);
}
/**
* @param \App\User $user
* @param int $limit
* @return \Illuminate\Support\Collection
*/
public function inboxFor(User $user, $limit = 20)
{
return StaffNotification::query()
->where('user_id', $user->id)
->orderByDesc('id')
->limit($limit)
->get();
}
/**
* @param \App\User $user
* @return int
*/
public function unreadCount(User $user)
{
return StaffNotification::query()
->where('user_id', $user->id)
->unread()
->count();
}
/**
* @param \App\User $user
* @param int $id
* @return \App\StaffNotification|null
*/
public function markAsRead(User $user, $id)
{
$notification = StaffNotification::query()
->where('user_id', $user->id)
->where('id', $id)
->first();
if (!$notification) {
return null;
}
if ($notification->read_at === null) {
$notification->read_at = now();
$notification->save();
}
return $notification;
}
/**
* @param \App\User $user
* @return int
*/
public function markAllAsRead(User $user)
{
return StaffNotification::query()
->where('user_id', $user->id)
->unread()
->update(['read_at' => now()]);
}
/**
* @param \App\User $user
* @param int $limit
* @return array
*/
public function inboxPayload(User $user, $limit = 20)
{
return [
'unread_count' => $this->unreadCount($user),
'notifications' => $this->inboxFor($user, $limit)->map(function (StaffNotification $notification) {
return $notification->toInboxArray();
})->values(),
];
}
}
+56
View File
@@ -0,0 +1,56 @@
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class StaffNotification extends Model
{
protected $table = 'staff_notifications';
protected $fillable = [
'user_id',
'cawangan',
'role',
'type',
'title',
'body',
'url',
'data',
'read_at',
];
protected $casts = [
'data' => 'array',
'read_at' => 'datetime',
];
public function user()
{
return $this->belongsTo(User::class);
}
public function isRead()
{
return $this->read_at !== null;
}
public function scopeUnread($query)
{
return $query->whereNull('read_at');
}
public function toInboxArray()
{
return [
'id' => $this->id,
'type' => $this->type,
'title' => $this->title,
'body' => $this->body,
'url' => $this->url,
'read_at' => $this->read_at ? $this->read_at->toIso8601String() : null,
'created_at' => $this->created_at ? $this->created_at->toIso8601String() : null,
'created_at_human' => $this->created_at ? $this->created_at->diffForHumans() : null,
];
}
}
+17
View File
@@ -6,9 +6,11 @@ use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Lab404\Impersonate\Models\Impersonate;
class User extends Authenticatable
{
use Impersonate;
use Notifiable;
protected $guard = 'web';
@@ -43,4 +45,19 @@ class User extends Authenticatable
{
return $this->hasOne(Cawangan::class, 'kodcaw', 'cawangan');
}
public function staffNotifications()
{
return $this->hasMany(StaffNotification::class);
}
public function canImpersonate(): bool
{
return in_array($this->roles ?? '', ['admin'], true);
}
public function canBeImpersonated(): bool
{
return ($this->roles ?? '') !== 'admin';
}
}
+15
View File
@@ -0,0 +1,15 @@
<?php
if (!function_exists('api_url_for_browser')) {
/**
* Base URL for JavaScript in the browser, derived from API_URL (config api.url).
* Docker dev often uses host.docker.internal for PHP→host; browsers may not resolve it.
* Same host-mapped port is reachable at 127.0.0.1. Production URLs are unchanged.
*/
function api_url_for_browser(): string
{
$url = rtrim((string) config('api.url'), '/');
return str_ireplace('host.docker.internal', '127.0.0.1', $url);
}
}
+4
View File
@@ -14,6 +14,7 @@
"fruitcake/laravel-cors": "^2.0",
"guzzlehttp/guzzle": "^6.3",
"jeroennoten/laravel-adminlte": "^3.5",
"lab404/laravel-impersonate": "^1.7",
"laravel/framework": "^7.24",
"laravel/tinker": "^2.0",
"laravel/ui": "^2.4",
@@ -51,6 +52,9 @@
"classmap": [
"database/seeds",
"database/factories"
],
"files": [
"app/helpers.php"
]
},
"autoload-dev": {
Generated
+71 -4
View File
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "bfd66ab0246126c4f2cc7bbeeacca4ec",
"content-hash": "fa66321247398a94dc8b7f8099bb184e",
"packages": [
{
"name": "almasaeed2010/adminlte",
@@ -1293,6 +1293,73 @@
},
"time": "2022-05-18T14:12:47+00:00"
},
{
"name": "lab404/laravel-impersonate",
"version": "1.7.8",
"source": {
"type": "git",
"url": "https://github.com/404labfr/laravel-impersonate.git",
"reference": "0008a39da8914cc946b6a5ed211230708ee736b3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/404labfr/laravel-impersonate/zipball/0008a39da8914cc946b6a5ed211230708ee736b3",
"reference": "0008a39da8914cc946b6a5ed211230708ee736b3",
"shasum": ""
},
"require": {
"laravel/framework": "^6.0 | ^7.0 | ^8.0 | ^9.0 | ^10.0 | ^11.0 | ^12.0 | ^13.0",
"php": "^7.2 | ^8.0"
},
"require-dev": {
"mockery/mockery": "^1.3.3",
"orchestra/testbench": "^4.0 | ^5.0 | ^6.0 | ^7.0 | ^8.0 | ^9.0 | ^10.0 | ^11.0",
"phpunit/phpunit": "^7.5 | ^8.0 | ^9.0 | ^10.0 | ^11.0"
},
"type": "library",
"extra": {
"laravel": {
"providers": [
"Lab404\\Impersonate\\ImpersonateServiceProvider"
]
}
},
"autoload": {
"files": [
"src/helpers.php"
],
"psr-4": {
"Lab404\\Impersonate\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Marceau Casals",
"email": "marceau@casals.fr"
}
],
"description": "Laravel Impersonate is a plugin that allows to you to authenticate as your users.",
"keywords": [
"auth",
"impersonate",
"impersonation",
"laravel",
"laravel-package",
"laravel-plugin",
"package",
"plugin",
"user"
],
"support": {
"issues": "https://github.com/404labfr/laravel-impersonate/issues",
"source": "https://github.com/404labfr/laravel-impersonate/tree/1.7.8"
},
"time": "2026-03-17T15:24:14+00:00"
},
{
"name": "laravel/framework",
"version": "v7.30.6",
@@ -9013,12 +9080,12 @@
],
"aliases": [],
"minimum-stability": "dev",
"stability-flags": [],
"stability-flags": {},
"prefer-stable": true,
"prefer-lowest": false,
"platform": {
"php": "^7.2.5"
},
"platform-dev": [],
"plugin-api-version": "2.3.0"
"platform-dev": {},
"plugin-api-version": "2.9.0"
}
+2
View File
@@ -15,6 +15,8 @@ return [
'name' => env('APP_NAME', 'Laravel'),
'version' => env('APP_VERSION', ''),
/*
|--------------------------------------------------------------------------
| Application Environment
+2 -2
View File
@@ -1,7 +1,7 @@
<?php
$default_port = '8085';
$getReaderEndpoint = fn($x) => 'http://localhost:' . $x . '/?MYKADJSON';
$default_port = '17321';
$getReaderEndpoint = fn($x) => 'http://localhost:' . $x . '/read';
return[
'endpoint' => env('MYKAD_READER_ENDPOINT', $getReaderEndpoint(env('MYKAD_READER_PORT', $default_port))),
+5
View File
@@ -0,0 +1,5 @@
<?php
return [
'internal_secret' => env('INTERNAL_NOTIFICATION_SECRET', ''),
];
@@ -0,0 +1,43 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateStaffNotificationsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('staff_notifications', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('user_id');
$table->string('cawangan', 20)->nullable();
$table->string('role', 30)->nullable();
$table->string('type', 80);
$table->string('title');
$table->text('body')->nullable();
$table->string('url', 500)->nullable();
$table->text('data')->nullable();
$table->timestamp('read_at')->nullable();
$table->timestamps();
$table->index(['user_id', 'read_at']);
$table->index(['cawangan', 'type']);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('staff_notifications');
}
}
+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:
+43
View File
@@ -0,0 +1,43 @@
services:
app:
build:
context: .
dockerfile: docker/php/Dockerfile
target: development
working_dir: /var/www/html
volumes:
- ./:/var/www/html:cached
environment:
APP_ENV: local
APP_DEBUG: "true"
# Lets PHP resolve host.docker.internal to the host machine (Linux Docker; Desktop already provides this).
extra_hosts:
- "host.docker.internal:host-gateway"
depends_on:
- mysql
web:
image: nginx:1.25-alpine
ports:
- "8081:80"
volumes:
- ./:/var/www/html:cached
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
depends_on:
- app
mysql:
image: mysql:8.0
command: --default-authentication-plugin=mysql_native_password
ports:
- "3307:3306"
environment:
MYSQL_DATABASE: kaunter_erahn
MYSQL_USER: kaunteruser
MYSQL_PASSWORD: password
MYSQL_ROOT_PASSWORD: root
volumes:
- mysql_data:/var/lib/mysql
volumes:
mysql_data:
+39
View File
@@ -0,0 +1,39 @@
server {
listen 80;
server_name _;
root /var/www/html/public;
index index.php index.html;
charset utf-8;
client_max_body_size 50M;
location / {
# 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$ {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_pass app:9000;
fastcgi_index index.php;
fastcgi_read_timeout 120s;
}
location ~ /\.(?!well-known).* {
deny all;
}
}
+51
View File
@@ -0,0 +1,51 @@
FROM php:7.4-fpm AS base
WORKDIR /var/www/html
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
git \
pkg-config \
unzip \
libonig-dev \
libzip-dev \
libpng-dev \
libjpeg62-turbo-dev \
libfreetype6-dev \
&& docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install -j"$(nproc)" \
pdo_mysql \
mbstring \
bcmath \
zip \
exif \
gd \
&& rm -rf /var/lib/apt/lists/*
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/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 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 "$@"
+4
View File
@@ -0,0 +1,4 @@
memory_limit=512M
upload_max_filesize=50M
post_max_size=50M
max_execution_time=120
+3
View File
@@ -0,0 +1,3 @@
[www]
; Pass container env vars (from compose env_file) through to PHP workers.
clear_env = no
Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

+411 -247
View File
@@ -1,293 +1,457 @@
let button_read_mykad = document.getElementById("button_read_mykad");
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 = () => {
let pageLoader = document.getElementById('page_loader');
pageLoader.removeAttribute('class');
pageLoader.classList.add('loading');
const MYKAD_READER_TIMEOUT_MS = 45000;
const MYKAD_READER_DEFAULT_ENDPOINT = "http://localhost:17321/read";
let showLoader = () => {
let pageLoader = document.getElementById('page_loader');
if (!pageLoader) {
return;
}
pageLoader.removeAttribute('class');
pageLoader.classList.add('loading');
};
let hideLoader = () => {
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;
}
return endpoint;
}
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.');
}
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.');
}
try {
return JSON.parse(trimmed);
} catch (error) {
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 hideLoader = () => {
let pageLoader = document.getElementById('page_loader');
pageLoader.removeAttribute('class');
};
let message = String(rawMessage || '');
let codes = message.match(/0x[0-9a-fA-F]+/g) || [];
let matched = codes.reverse().find(code => error_handler_dictionary[code]);
function handleMyKadReader()
{
showLoader();
WebSocketManager.init("wss://localhost:5963/mykadwebsocket");
WebSocketManager.listReader(callbackDoneGetListCardReader);
return matched ? error_handler_dictionary[matched] : (message || null);
}
function getReaderErrorMessage(payload) {
if (!payload) {
return 'Pembaca MyKad tidak dapat membaca kad. Sila cuba lagi.';
}
function callbackDoneGetListCardReader(data, respcode)
{
console.log('done get list card reader');
let raw = payload.message || payload.error || payload.detail || '';
return mapReaderErrorCode(raw)
|| 'Pembaca MyKad tidak dapat membaca kad. Sila pastikan MyKad dimasukkan dengan betul.';
}
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;
}
let slots = data.toString().split(',');
WebSocketManager.open(doneOpenReaderConnection, slots[0]);
}
function doneOpenReaderConnection()
{
WebSocketManager.readAllText(callBackAfterReadMykad);
function getFetchErrorTitle(error) {
if (error && error.name === 'AbortError') {
return 'Masa menunggu pembaca MyKad telah tamat. Sila cuba lagi.';
}
function handleMyKadReaderError(sw12AndApiName)
{
hideLoader();
return 'Pembaca MyKad tidak dapat dikesan. Sila pastikan perkhidmatan pembaca MyKad sedang berjalan.';
}
let error_handler_dictionary = {
'0x80100069' : 'Tiada MyKad dikesan, sila masukkan Mykad untuk dibaca',
'0x80100066' : 'MyKad mungkin dimasukkan terbalik, sila masukkan dengan betul',
'default' : sw12AndApiName,
};
let error_data = sw12AndApiName.split(" ");
let error_code = error_data.filter(x => x.startsWith('0x')).splice(-1)
let error_message = error_handler_dictionary['default'];
let hasDefinedError = error_handler_dictionary.hasOwnProperty(error_code);
if(hasDefinedError){
error_message = error_handler_dictionary[error_code];
}
Swal.fire({ icon: 'error', title: error_message, });
function getDisplayError(error) {
if (!error) {
return getFetchErrorTitle(error);
}
function callBackAfterReadMykad(data, respcode, sw12AndApiName)
{
if(respcode !== 0){
handleMyKadReaderError(sw12AndApiName);
return
}
if (error.name === 'AbortError') {
return getFetchErrorTitle(error);
}
checkIfMyKadExists(data.IDNum)
.then(response => {
let isResponseArray = Array.isArray(response);
resolved_response = isResponseArray ? response.pop() : response;
let message = String(error.message || '');
if (!message || message.startsWith('Failed to fetch') || message.includes('NetworkError')) {
return getFetchErrorTitle(error);
}
if (resolved_response === 'no-data') {
populateDaftarPelangganModal(data, handleReadPhoto);
} else {
let input_search_noic = document.getElementById("noic");
let form_search_noic = document.querySelector("form[name='frmsearch']");
return message;
}
let getMyKadNumberWithoutHypens = myKadNumber => myKadNumber.replace(/-/g, '');
input_search_noic.value = getMyKadNumberWithoutHypens(data.IDNum);
form_search_noic.submit();
}
})
.catch(error => {
Swal.fire({
icon: 'error',
title: error,
});
});
async function handleMyKadReader() {
showLoader();
hideLoader();
}
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), MYKAD_READER_TIMEOUT_MS);
function checkIfMyKadExists(myKadNumber) {
let myKadNumberWithoutHyphens = myKadNumber.replace(/-/g, '');
const url = wakil_caricustomer_endpoint + '/?icno=' + myKadNumberWithoutHyphens;
const connectionConfig = {
try {
const response = await fetch(getMyKadReaderEndpoint(), {
method: 'POST',
headers: {
"Content-Type": "application/json",
"Accept": "application/json",
"X-Requested-With": "XMLHttpRequest",
"X-CSRF-Token": document.querySelector('input[name="_token"]').value
'Accept': 'application/json',
},
credentials: "same-origin",
};
signal: controller.signal,
});
return fetch(url, connectionConfig)
.then(response => response.json())
}
const payload = await parseJsonResponse(response);
let populateDaftarPelangganModal = (data, callbackAfterPopulateDaftarPelanggan) => {
$('#daftar_pelanggan_mykad_reader').modal('show');
let modal_form = document.querySelector("#daftar_pelanggan_mykad_reader .modal-body .row.form-group");
const religions = [
['01', 'Islam'],
['02', 'Hindu'],
['03', 'Buddha'],
['04', 'Kristian'],
['05', 'Lain-lain'],
];
const races = [
['M', 'Melayu'],
['C', 'Cina'],
['I', 'India'],
['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, '');
if (!response.ok) {
throw new Error(getReaderErrorMessage(payload));
}
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 = () => {
modal_form.querySelector("input[name='bumiputera']").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='agama']").style.pointerEvents = 'none';
if (!payload || payload.ok !== true || !payload.data) {
throw new Error(getReaderErrorMessage(payload));
}
let mykad_modal = document.querySelector("#daftar_pelanggan_mykad_reader");
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();
await handleMyKadData(payload.data);
} catch (error) {
Swal.fire({
icon: 'error',
title: getDisplayError(error),
});
} finally {
clearTimeout(timeoutId);
hideLoader();
}
}
function handleReadPhoto()
{
WebSocketManager.init("wss://localhost:5963/mykadwebsocket");
WebSocketManager.listReader(callbackDoneGetListCardReader2);
}
function callbackDoneGetListCardReader2(data, respcode)
{
if(respcode === -1){
console.log({'location': 'callbackDoneGetListCardReader', 'data' : data} );
function customerNeedsMykadPhoto(customer) {
if (!customer || typeof customer !== 'object') {
return false;
}
let slots = data.toString().split(',');
WebSocketManager.open(doneOpenReaderConnection2, slots[0]);
return !customer.gambar_mykad && !customer.photo_base64;
}
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 isDataEmpty = data
&& Object.keys(data).length === 0
&& Object.getPrototypeOf(data) === Object.prototype;
if(isDataEmpty){
photo.src = original_photo_loader;
async function storeMykadPhotoForExistingCustomerIfMissing(customer, readerData) {
if (!customerNeedsMykadPhoto(customer)) {
return;
}
photo.src = 'data:image/jpeg;base64, ' + data.Base64Photo;
if (!readerData || !readerData.has_photo || !readerData.photo_base64) {
return;
}
if (typeof store_mykad_photo_endpoint === 'undefined' || !store_mykad_photo_endpoint) {
return;
}
const csrfToken = document.querySelector('input[name="_token"]');
const nric = getMyKadNumberWithoutHyphens(
customer.kpbaru || customer.kplama || readerData.nric || ''
);
if (!nric) {
return;
}
try {
await fetch(store_mykad_photo_endpoint, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest',
'X-CSRF-TOKEN': csrfToken ? csrfToken.value : '',
},
credentials: 'same-origin',
body: JSON.stringify({
icno: nric,
photo_base64: readerData.photo_base64,
photo_mime: readerData.photo_mime || 'image/jpeg',
}),
});
} catch (error) {
// Do not block customer search if photo backfill fails.
console.warn('Failed to store MyKad photo for existing customer', error);
}
}
function handleMyKadData(data) {
return checkIfMyKadExists(data.nric)
.then(async response => {
let isResponseArray = Array.isArray(response);
let resolved_response = isResponseArray ? response.pop() : response;
if (!resolved_response || resolved_response === 'no-data') {
populateDaftarPelangganModal(data);
} else {
await storeMykadPhotoForExistingCustomerIfMissing(resolved_response, data);
let input_search_noic = document.querySelector("form[name='frmsearch'] input[name='noic']");
let form_search_noic = document.querySelector("form[name='frmsearch']");
input_search_noic.value = getMyKadNumberWithoutHyphens(data.nric);
form_search_noic.submit();
}
})
.catch(() => {
populateDaftarPelangganModal(data);
});
}
function checkIfMyKadExists(myKadNumber) {
let myKadNumberWithoutHyphens = getMyKadNumberWithoutHyphens(myKadNumber);
let lookupUrl = new URL(wakil_caricustomer_endpoint, window.location.origin);
lookupUrl.searchParams.set('icno', myKadNumberWithoutHyphens);
const csrfToken = document.querySelector('input[name="_token"]');
return fetch(lookupUrl.toString(), {
method: 'GET',
headers: {
"Accept": "application/json",
"X-Requested-With": "XMLHttpRequest",
"X-CSRF-Token": csrfToken ? csrfToken.value : ""
},
credentials: "same-origin",
}).then(async response => {
let text = await response.text();
let trimmed = (text || '').trim();
if (!trimmed || trimmed.charAt(0) === '<') {
return 'no-data';
}
try {
return JSON.parse(trimmed);
} catch (error) {
return 'no-data';
}
});
}
let populateDaftarPelangganModal = (data) => {
$('#daftar_pelanggan_mykad_reader').modal('show');
let modal_form = document.querySelector("#daftar_pelanggan_mykad_reader .modal-body .row.form-group");
const religions = [
['01', 'Islam'],
['02', 'Hindu'],
['03', 'Buddha'],
['04', 'Kristian'],
['05', 'Lain-lain'],
];
const races = [
['M', 'Melayu'],
['C', 'Cina'],
['I', 'India'],
['L', 'Lain-lain'],
];
let disablePointerEventsOnClickableInputs = () => {
modal_form.querySelector("input[name='bumiputera']").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='agama']").style.pointerEvents = 'none';
};
let mykad_modal = document.querySelector("#daftar_pelanggan_mykad_reader");
let photo_container = mykad_modal.querySelector("#photo-mykad-container");
let photo = mykad_modal.querySelector("#photo-mykad");
modal_form.querySelector("textarea[name='name']").value = data.name || data.gmpc_name || '';
modal_form.querySelector("input[name='noic']").value = getMyKadNumberWithoutHyphens(data.nric);
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 (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_container.classList.add('d-none');
photo_container.classList.remove('d-flex');
setMykadPhotoFields('', '');
}
disablePointerEventsOnClickableInputs();
$(modal_form.querySelector("input[name='poskod']")).trigger('change');
};
let hideMykadPhoto = () => {
WebSocketManager.close(() => console.log('Websocket closed'));
let photo = document.getElementById("photo-mykad");
let photo_container = document.getElementById("photo-mykad-container");
photo.src = original_photo_loader;
if (photo) {
photo.src = original_photo_loader;
}
photo_container.classList.add('d-none');
photo_container.classList.remove('d-flex');
if (photo_container) {
photo_container.classList.add('d-none');
photo_container.classList.remove('d-flex');
}
setMykadPhotoFields('', '');
};
button_read_mykad.addEventListener("click", handleMyKadReader);
button_close_modal.addEventListener("click", hideMykadPhoto);
function setMykadPhotoFields(base64, mime) {
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);
}
+30
View File
@@ -0,0 +1,30 @@
/**
* First we will load all of this project's JavaScript dependencies which
* includes Vue and other libraries. It is a great starting point when
* building robust, powerful web applications using Vue and Laravel.
*/
require('./bootstrap');
window.Vue = require('vue');
/**
* The following block of code may be used to automatically register your
* Vue components. It will recursively scan this directory for the Vue
* components and automatically register them with their "basename".
*
* Eg. ./components/ExampleComponent.vue -> <example-component></example-component>
*/
// const files = require.context('./', true, /\.vue$/i)
// files.keys().map(key => Vue.component(key.split('/').pop().split('.')[0], files(key).default))
/**
* Next, we will create a fresh Vue application instance and attach it to
* the page. Then, you may begin adding components to this application
* or customize the JavaScript scaffolding to fit your unique needs.
*/
const app = new Vue({
el: '#app',
});
+3 -3
View File
@@ -35,7 +35,7 @@ window.Pusher = require('pusher-js');
window.Echo = new Echo({
broadcaster: 'pusher',
key: '26b0ec2f297f94b7aa9b',
cluster: 'ap1',
forceTLS: true
key: process.env.MIX_PUSHER_APP_KEY,
cluster: process.env.MIX_PUSHER_APP_CLUSTER,
forceTLS: false
});
@@ -306,7 +306,7 @@
</div>
</div>
<script>
var api_url = "<?php echo $api_url ?>";
var api_url = "{{ api_url_for_browser() }}";
var search = "<?php echo $search ?>";
var type_id = "<?php echo $idtype ?>";
$(document).ready(function(){
@@ -157,7 +157,7 @@
</div>
<script>
var api_url = "<?php echo $api_url ?>";
var api_url = "{{ api_url_for_browser() }}";
var ppc = "{{ Auth::user()->name }}";
var ic_asal = "{{ $customer_info['kpbaru'] != null ? $customer_info['kpbaru'] : $customer_info['kplama'] }}"
+88
View File
@@ -0,0 +1,88 @@
@extends('layouts.app_operasi')
@section('content')
<div class="container-fluid">
<div class="row justify-content-center">
<div class="col-md-10">
<nav aria-label="breadcrumb">
<ol class="breadcrumb pb-3">
<li class="breadcrumb-item"><a href="{{ route('home') }}">Home</a></li>
<li class="breadcrumb-item active" aria-current="page">Ciri Cawangan</li>
</ol>
</nav>
@if(session()->has('message'))
<div class="alert alert-success">{{ session('message') }}</div>
@endif
@if(session()->has('error'))
<div class="alert alert-danger">{{ session('error') }}</div>
@endif
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="mb-0">Ciri Cawangan</h5>
<span class="text-muted">{{ count($cawangan) }} cawangan</span>
</div>
<div class="card-body table-responsive">
<table id="featureTable" class="table table-striped table-hover table-sm">
<thead class="thead-dark">
<tr>
<th>Kod</th>
<th>Cawangan</th>
@foreach($keys as $key)
<th class="text-center">{{ $labels[$key] ?? $key }}</th>
@endforeach
</tr>
</thead>
<tbody>
@forelse($cawangan as $row)
<tr>
<td>{{ $row['kodcaw'] ?? '-' }}</td>
<td>{{ $row['details_caw'] ?? '-' }}</td>
@foreach($keys as $key)
@php
$on = !empty(($row['features'] ?? [])[$key]);
@endphp
<td class="text-center">
<form method="post" action="{{ route('admin.features.update') }}" class="mb-0 d-inline"
@if($on) onsubmit="return confirm('Nyahaktifkan {{ $labels[$key] ?? $key }} untuk {{ $row['kodcaw'] }}?');" @endif>
@csrf
<input type="hidden" name="kodcaw" value="{{ $row['kodcaw'] }}">
<input type="hidden" name="feature" value="{{ $key }}">
<input type="hidden" name="enabled" value="{{ $on ? 0 : 1 }}">
<button type="submit" class="btn btn-sm {{ $on ? 'btn-success' : 'btn-secondary' }}">
{{ $on ? 'Aktif' : 'Tidak aktif' }}
</button>
</form>
</td>
@endforeach
</tr>
@empty
<tr>
<td colspan="{{ 2 + count($keys) }}" class="text-center text-muted">Tiada cawangan.</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<script>
$(function () {
if ($.fn.DataTable && $('#featureTable tbody tr').length && !$('#featureTable tbody td[colspan]').length) {
$('#featureTable').DataTable({
pageLength: 25,
order: [[0, 'asc']],
language: {
search: 'Cari:',
lengthMenu: 'Papar _MENU_',
info: '_START_–_END_ daripada _TOTAL_',
paginate: { previous: 'Sebelum', next: 'Seterusnya' }
}
});
}
});
</script>
@endsection
+1 -1
View File
@@ -48,6 +48,6 @@
</div>
</div>
<script>
var api_url = "<?php echo $api_url ?>";
var api_url = "{{ api_url_for_browser() }}";
</script>
@endsection
+5 -9
View File
@@ -26,7 +26,7 @@
<div class="container">
<div class="row justify-content-center">
@php $auth_user = Auth::user();@endphp
<input type="hidden" id="api" value="{{ $api_url }}"/>
<input type="hidden" id="api" value="{{ api_url_for_browser() }}"/>
<div class="col-md-12">
@@ -348,7 +348,7 @@
</div>
<script>
var api_url = "<?php echo $api_url ?>";
var api_url = "{{ api_url_for_browser() }}";
var kodcaw = "<?php echo $cawangan_info['kodcaw'] ?>";
var tableaddtunai = $('#tableaddtunai').DataTable({
@@ -558,15 +558,11 @@
function cetaklaporanalirantunai()
{
let teller = "{{ Auth::user()->name }}";
let urlroute = '{{ route("laporan.alirantunai", ":id") }}';
urlroute = urlroute.replace(':id', teller);
window.open(urlroute, '_blank');
let urlroute = '{{ route("laporan.alirantunai") }}';
window.location.href = urlroute;
}
function cetakserahankeseluruhan()
{
let urlroute = '{{ route("laporan.serahanteller") }}';
@@ -1,211 +1,298 @@
<html>
<head>
<style>
.header{
font-size:12px;
font-weight: bold;
margin-left:40px;
}
body {
background: white;
}
page {
background: white;
display: block;
margin: 0 auto;
margin-bottom: 0.5cm;
/* box-shadow: 0 0 0.5cm rgba(0,0,0,0.5); */
}
page[size="A4"] {
width: 21cm;
/* height: 29.7cm; */
}
page[size="A4"][layout="landscape"] {
width: 29.7cm;
height: 21cm;
}
@media print {
body, page {
margin: 0;
box-shadow: 0;
}
}
.middleheader{
text-align: center;
}
.container{
font-family: Arial, Helvetica, sans-serif;
width: 25%;
border: 2px solid gray;
border-radius: 15px;
padding: 5px 10px 10px 30px;
margin: 0 auto;
}
#header {
font-family: Arial, Helvetica, sans-serif;
border-collapse: collapse;
width: 100%;
margin-left:40px;
}
#header td, #customer th {
padding:4px;
font-size:10px;
}
#header tr:nth-child(even){
background-color: #ffffff;
}
/* #header tr:hover {
background-color: #ddd;
} */
#header th {
padding-top: 12px;
padding-bottom: 12px;
text-align: left;
padding:4px;
color:black;
}
.statements{
font-family: Arial, Helvetica, sans-serif;
border-collapse: collapse;
width: 95%;
margin: 0 auto;
}
.statements th{
font-size:14px;
background-color: #f9ca24;
border:1px solid gray;
color:black;
padding:4px 10px 4px 10px;
}
.statements td{
border:1px solid gray;
padding:4px 4px 4px 4px;
}
.center{
position: fixed;
left: 0;
bottom: 0;
width: 100%;
color: white;
text-align: center;
}
</style>
</head>
<body onload="window.print()">
<page size="A4">
<h6 class="header">SULIT DAN PERSENDIRIAN</h6>
<table id="header">
<tr>
<th rowspan="4" style="width:50px"><br><img src="{{ asset('img/icon_pkb.png') }}" style="width:70px"></img></th>
<th colspan="1">KOPERASI PERMODALAN KELANTAN BERHAD</th>
</tr>
<tr>
<td>{{ $cawangan_detail['namacaw'] }}</td>
<td></td>
</tr>
<tr>
<td>{{ $cawangan_detail['alamat1'] }}</td>
<td></td>
</tr>
<tr>
<td>{{ $cawangan_detail['alamat2'].' No Tel : '. $cawangan_detail['notel'].' No Faks : '. $cawangan_detail['nofaks'] }}</td>
<td></td>
</tr>
</table>
<h6 class="middleheader">PENYATA ALIRAN TUNAI : {{ $teller }}</h6>
<div class="row" style="display: flex; justify-content: start;">
<div class="container" style="flex: 1; margin-left: 20px;">
<h3>Baki Awal</h3>
<span>RM {{ number_format($alirantunai['bakiawal'], 2) }}</span>
</div>
<div class="container" style="flex: 1; margin-left: 10px;">
<h3>Pendahuluan</h3>
<span>RM {{ number_format($alirantunai['tambah'], 2) }}</span>
</div>
<div class="container" style="flex: 1; margin-left: 10px;">
<h3>Penyelesaian Pem.</h3>
<span>RM {{ number_format($alirantunai['masuk'], 2) }}</span>
</div>
</div>
<div class="row" style="display: flex; justify-content: start; margin-top: 20px;">
<div class="container" style="flex: 1; margin-left: 20px;">
<h3>Baki Tunai Semasa</h3>
<span>RM {{ number_format($alirantunai['baki'], 2) }}</span>
</div>
<div class="container" style="flex: 1; margin-left: 10px;">
<h3>Serahan</h3>
<span>RM {{ number_format($alirantunai['kurang'], 2) }}</span>
</div>
<div class="container" style="flex: 1; margin-left: 10px;">
<h3>Pembiayaan</h3>
<span>RM {{ number_format($alirantunai['keluar'], 2) }}</span>
</div>
</div>
<h6 class="middleheader" style="margin-bottom:-5px;">KEMASUKAN TUNAI</h6>
<br>
<table class="statements">
<tr>
<th>TARIKH</th>
<th>DEBIT (RM)</th>
<th>KREDIT (RM)</th>
<th>NOTA</th>
</tr>
@php $totaltambah = 0; $totalkurang = 0; @endphp
@foreach($addtunai as $index=>$ad)
@php $totaltambah += $ad['tambah']; $totalkurang += $ad['kurang']; @endphp
<tr style="text-align:center;">
<td>{{ $ad['tarikh'] }}</td>
<td>{{ number_format($ad['tambah'],2) }}</td>
<td>{{ number_format($ad['kurang'],2) }}</td>
<td>{{ $ad['nota'] }}</td>
</tr>
@endforeach
<tr style="text-align:center;">
<td style="font-weight:bold;">JUMLAH</td>
<td>{{ number_format($totaltambah,2) }}</td>
<td>{{ number_format($totalkurang,2) }}</td>
<td></td>
</tr>
</table>
</page>
<script>
<style>
#header {
font-family: Arial, Helvetica, sans-serif;
border-collapse: collapse;
width: 100%;
}
#header td, #customers th {
padding:4px;
font-size:10px;
}
#header tr:nth-child(even){background-color: #ffffff;}
#header tr:hover {background-color: #ddd;}
#header th {
padding-top: 12px;
padding-bottom: 12px;
text-align: left;
padding:4px;
color:black;
}
</style>
<style>
#scan {
font-family: Arial, Helvetica, sans-serif;
border-collapse: collapse;
width: 100%;
}
#scan td, #scan th {
padding: 8px;
font-size:11px;
}
#scan tr:nth-child(even){background-color: #ffffff;}
#scan tr:hover {background-color: #ddd;}
#scan th {
padding-top: 12px;
padding-bottom: 12px;
text-align: left;
padding:4px;
color:black;
}
</style>
<style>
#scan {
font-family: Arial, Helvetica, sans-serif;
border-collapse: collapse;
width: 100%;
}
#scan td, #scan th {
border-collapse: collapse;
padding: 8px;
font-size:11px;
}
#scan tr:nth-child(even){background-color: #ffffff;}
#scan tr:hover {background-color: #ddd;}
#scan th {
border-collapse: collapse;
padding-top: 12px;
padding-bottom: 12px;
text-align: left;
padding:4px;
color:black;
}
</style>
<style>
#details {
font-family: Arial, Helvetica, sans-serif;
border-collapse: collapse;
width: 50%;
}
#details td, #details th {
padding: 8px;
font-size:11px;
}
#details tr:nth-child(even){background-color: #ffffff;}
#details tr:hover {background-color: #ddd;}
#details th {
padding-top: 12px;
font-style:bold;
padding-bottom: 12px;
text-align: left;
padding:4px;
color:black;
}
</style>
<style>
#sign {
font-family: Arial, Helvetica, sans-serif;
border-collapse: collapse;
width: 100%;
}
#sign td, #sign th {
padding: 8px;
font-size:11px;
text-align: center;
}
#sign tr:nth-child(even){background-color: #ffffff;}
#sign tr:hover {background-color: #ddd;}
#sign th {
padding-top: 12px;
padding-bottom: 12px;
text-align: left;
padding:4px;
color:black;
}
</style>
<table id="header">
<tr>
<th rowspan="4" style="width:50px"><br><img src="{{ asset('img/ARRAHN1.png') }}" style="width:70px"></img></th>
<th colspan="1">KOPERASI PERMODALAN KELANTAN BERHAD</th>
</tr>
<tr>
<td>{{ $cawangan_detail['namacaw'] }}</td>
<td></td>
</tr>
<tr>
<td>{{ $cawangan_detail['alamat1'] }}</td>
<td></td>
</tr>
<tr>
<td>{{ $cawangan_detail['alamat2'].' No Tel : '. $cawangan_detail['notel'].' No Faks : '. $cawangan_detail['nofaks'] }}</td>
<td></td>
</tr>
</table>
<br>
<table id="scan">
<tr>
<th colspan="3" style="
text-align: left;
font-size: 16px;
font-weight: bold;
text-transform: uppercase;
padding-top: 10px;
padding-bottom: 10px;
letter-spacing: 0.5px;
">
LAPORAN ALIRAN TUNAI TELLER
</th>
</tr>
<tr>
<td style="width:100px"><b>TARIKH:</b></td>
<td><b>
@php
$tarikhTeller = DateTime::createFromFormat('Y-m-d', $tunai['tarikh']);
echo $tarikhTeller->format('d-m-Y');
@endphp
</b></td>
</tr>
<tr>
<td><b>TARIKH CETAK:</b></td>
<td><b>
@php
$myDateTime = DateTime::createFromFormat('Y-m-d', $tarikh_cetak);
$formattedweddingdate = $myDateTime->format('d-m-Y');
echo $formattedweddingdate;
@endphp
</b></td>
</tr>
</table>
<br>
<table id="details">
<tr>
<th>BAKI AWAL</th>
</tr>
<tr>
<td>Baki Awal</td>
<td>:</td>
<td>RM {{ number_format($tunai['bakiawal'],2) }}</td>
</tr>
<tr>
<th>TAMBAH</th>
</tr>
<tr>
<td>Pendahuluan</td>
<td>:</td>
<td>RM {{ number_format($tunai['tambah'],2) }}</td>
</tr>
<tr>
<td>Pen. Pembiayaan / Keuntungan</td>
<td>:</td>
<td>RM {{ number_format($tunai['masuk'],2) }}</td>
</tr>
<tr>
<td>Kerugian Baki Lelong</td>
<td>:</td>
<td>RM {{ number_format($bakiLelong['rugi_lelong'],2) }}</td>
</tr>
<tr>
<td>Jumlah</td>
<td>:</td>
<td>RM {{ number_format($jumlahTambah,2) }}</td>
</tr>
<tr>
<th>TOLAK</th>
</tr>
<tr>
<td>Serahan</td>
<td>:</td>
<td>RM {{ number_format($tunai['kurang'],2) }}</td>
</tr>
<tr>
<td>Pembiayaan</td>
<td>:</td>
<td>RM {{ number_format($tunai['keluar'],2) }}</td>
</tr>
<tr>
<td>Baki Lelong</td>
<td>:</td>
<td>RM {{ number_format($bakiLelong['baki_lelong'],2) }}</td>
</tr>
<tr>
<td>Jumlah</td>
<td>:</td>
<td>RM {{ number_format($jumlahTolak,2) }}</td>
</tr>
<tr><th>BAKI AKHIR</th></tr>
<tr>
<td colspan="3" style="
border: 2px solid black;
text-align: center;
background-color: #f8f8f8;
font-weight: bold;
font-size: 13px;
padding: 10px;
">
BAKI AKHIR TELLER: RM {{ number_format($tunai['bakiawal'] + $jumlahTambah - $jumlahTolak,2) }}
</td>
</tr>
</table>
<br>
<br>
<br>
<br>
<table id="sign">
<tr>
<td></td>
<td colspan="2"> .................................................................. </td>
<td> </td>
<td colspan="2">.................................................................. </td>
<td> </td>
</tr>
<tr>
<td></td>
<td colspan="2"><B> DI SEMAK</B> </td>
<td> </td>
<td colspan="2"><B>DI SAHKAN </B></td>
<td> </td>
</tr>
</table>
</script>
</body>
</html>
+2 -2
View File
@@ -7,7 +7,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="icon" href="{{ asset('img/icon_pkb.png') }}">
<title>arOS v2.2</title>
<title>arOS {{ config('app.version') }}</title>
<style>
@keyframes slide {
@@ -82,7 +82,7 @@
<div class="card-header pb-0 text-left">
<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;">
Kaunter arOS v2.2
Kaunter arOS {{ config('app.version') }}
</h1>
<img src="{{ asset('img/icon_pkb.png') }}" alt="logo-arrahn" style="width: 85px; margin-left: 30px;">
</div>
@@ -228,7 +228,7 @@
<script>
var api_url = "{{ $api_url }}";
var api_url = "{{ api_url_for_browser() }}";
// var kodcaw = "{{ $kodcaw }}";
// var notisid = "{{ $notisid }}";
//var nokp = "{{ $noic }}";
+1 -1
View File
@@ -389,7 +389,7 @@
</script>
<script>
var api_url = "<?php echo $api_url ?>";
var api_url = "{{ api_url_for_browser() }}";
var search = "<?php echo $search ?>";
var type_id = "<?php echo $idtype ?>";
$(document).ready(function(){
@@ -387,7 +387,7 @@
</script>
<script>
var api_url = "<?php echo $api_url ?>";
var api_url = "{{ api_url_for_browser() }}";
var search = "<?php echo $search ?>";
var type_id = "<?php echo $idtype ?>";
$(document).ready(function(){
+2 -2
View File
@@ -412,8 +412,8 @@
</div>
<script>
var api_arrahnbid = "<?php echo config('app.api_arrahnbid') ?>";
var api_url = "<?php echo $api_url ?>";
var api_arrahnbid = @json($cawangan_info['api_arrahnbid'] ?? config('app.api_arrahnbid'));
var api_url = "{{ api_url_for_browser() }}";
var kodcaw = "{{ $cawangan_info['kodcaw'] }}";
var tagarrahnbid = "{{ $cawangan_info['tagarrahnbid'] }}";
@@ -1099,7 +1099,7 @@
let pertambahpinjaman = "{{ $pertambahpinjaman }}";
var api = '<?php echo $api_url ?>';
var api = "{{ api_url_for_browser() }}";
let nw = 0;
let jenisAT = 'none';
+2 -2
View File
@@ -541,8 +541,8 @@
</div>
<script>
var api_arrahnbid = "<?php echo config('app.api_arrahnbid') ?>";
var api_url = "<?php echo $api_url ?>";
var api_arrahnbid = @json($cawangan_info['api_arrahnbid'] ?? config('app.api_arrahnbid'));
var api_url = "{{ api_url_for_browser() }}";
var kodcaw = "{{ $cawangan_info['kodcaw'] }}";
var tagarrahnbid = "{{ $cawangan_info['tagarrahnbid'] }}";
@@ -0,0 +1,138 @@
@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>
{{ $slot }}
</tbody>
</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,104 @@
@props([
'cawangan' => '',
'kodcaw' => '',
'alamat1' => '',
'alamat2' => '',
'notel' => '',
'tarikh' => '',
'norujukan' => '',
'rujukanLabel' => 'No. Rujukan SAG',
'showDummyBadge' => false,
])
<style>
.print-header {
display: grid;
grid-template-columns: minmax(0, 1.75fr) auto minmax(0, 1fr);
align-items: start;
column-gap: 10px;
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: 10px;
font-weight: bold;
}
.print-header .org .name .reg {
white-space: nowrap;
}
.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 <span class="reg">(D-5-0228)</span></p>
<p>{{ $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>
+9 -5
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-dialog modal-lg">
<div class="modal-content">
<form id="form-mykad-reader" action="{{ route('customer.store') }}" onsubmit="verifycustomerMyKadReader()" method="POST">
@csrf
<div class="modal-header">
<h5 class="modal-title" id="ModalLabel">Daftar Pelanggan</h5>
</div>
<div class="modal-body">
<form action="{{ route('customer.store') }}" onsubmit="verifycustomerMyKadReader()" method="POST">
@csrf
<div class="row form-group">
<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">
<input type="hidden" name="photo_base64" id="photo_base64" value="">
<input type="hidden" name="photo_mime" id="photo_mime" value="">
</div>
<div class="col-md-6">
<div class="row form-group">
@@ -473,7 +475,7 @@
</div>
</div>
<script>
var api_url = "<?php echo $api_url ?>";
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 ?>";
@@ -811,6 +813,8 @@
function verifycustomerMyKadReader(){
copyMykadPhotoFromImage();
let myKadModal = $('#daftar_pelanggan_mykad_reader');
// Construct URLSearchParams object instance from current URL querystring.
@@ -1275,10 +1279,10 @@
// mycard-read endpoint initialization
let mykad_reader_endpoint = "{{ config('mykad-reader.endpoint') }}";
let wakil_caricustomer_endpoint = "{{ route('wakil.caricustomer') }}";
let store_mykad_photo_endpoint = "{{ route('customer.storeMykadPhoto') }}";
let mykad_reader_driver_endpoint = "{{ route('mykadreader.driver') }}";
</script>
<script src="{{ asset('js/iris-mykad-websocket-client-api.js') }}"></script>
<script src="{{ asset('js/mykad-reader-customer.js') }}"></script>
<script src="{{ asset('js/mykad-reader-customer.js') }}?v=5"></script>
<script src="{{ asset('js/postcode-concerns.js') }}"></script>
@endpush
+18 -2
View File
@@ -21,6 +21,22 @@
</ol>
</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="col-md-6">
<div class="form-group row">
@@ -421,8 +437,8 @@
</div>
<script>
var api_arrahnbid = "<?php echo config('app.api_arrahnbid') ?>";
var api_url = "<?php echo $api_url ?>";
var api_arrahnbid = @json($cawangan_info['api_arrahnbid'] ?? config('app.api_arrahnbid'));
var api_url = "{{ api_url_for_browser() }}";
var kodcaw = "{{ $cawangan_info['kodcaw'] }}";
var tagarrahnbid = "{{ $cawangan_info['tagarrahnbid'] }}";
var min_komoditi = "{{ $min_komoditi }}";
+29 -11
View File
@@ -102,7 +102,7 @@
<h5 class="modal-title" id="ModalLabel">Daftar Pelanggan</h5>
</div>
<div class="modal-body">
<form action="{{ route('customer.store') }}" onsubmit="verifycustomer()" method="POST">
<form name="form-store" action="{{ route('customer.store') }}" onsubmit="verifycustomer()" method="POST">
@csrf
<div class="row form-group">
<div class="col-md-6">
@@ -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-dialog modal-lg">
<div class="modal-content">
<form id="form-mykad-reader" action="{{ route('customer.store') }}" onsubmit="verifycustomerMyKadReader()" method="POST">
@csrf
<div class="modal-header">
<h5 class="modal-title" id="ModalLabel">Daftar Pelanggan</h5>
</div>
<div class="modal-body">
<form action="{{ route('customer.store') }}" onsubmit="verifycustomerMyKadReader()" method="POST">
@csrf
<div class="row form-group">
<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">
<input type="hidden" name="photo_base64" id="photo_base64" value="">
<input type="hidden" name="photo_mime" id="photo_mime" value="">
</div>
<div class="col-md-6">
<div class="row form-group">
@@ -373,7 +375,7 @@
<h5 class="modal-title" id="ModalLabel">Daftar Pelanggan</h5>
</div>
<div class="modal-body">
<form action="{{ route('customer.store') }}" onsubmit="verifycustomerpassport()" method="POST">
<form name="form-store" action="{{ route('customer.store') }}" onsubmit="verifycustomerpassport()" method="POST">
@csrf
<div class="row form-group">
<div class="col-md-6">
@@ -495,15 +497,14 @@
</div>
<script>
var api_url = @json($api_url ?? '');
var search = @json($search ?? false);
var type_id = @json($idtype ?? '');
var isAnggota = @json($isAnggota ?? false);
var isPelanggan = @json($isPelanggan ?? false);
var blacklist_search = @json($blacklist_search ?? false);
var showAssignAnggotaBtn = @json($showAssignAnggotaBtn ?? 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(){
// Datepicker
$('#passport_tarikh_lahir').datepicker({
@@ -891,6 +892,8 @@ var api_url = @json($api_url ?? '');
function verifycustomerMyKadReader(){
copyMykadPhotoFromImage();
let myKadModal = $('#daftar_pelanggan_mykad_reader');
// Construct URLSearchParams object instance from current URL querystring.
@@ -1392,6 +1395,21 @@ var api_url = @json($api_url ?? '');
$('#pekerjaan-lain').val('');
lainContainer.hide();
}
});
document.getElementsByName('form-store')[0].addEventListener('keypress', function(e) {
// Jika tekan Enter
if (e.key === 'Enter') {
// Jika bukan butang submit dan bukan textarea
const tag = e.target.tagName.toLowerCase();
const type = e.target.type ? e.target.type.toLowerCase() : '';
if (tag !== 'textarea' && type !== 'submit') {
e.preventDefault();
return false;
}
}
});
</script>
@push('additional_scripts')
@@ -1399,10 +1417,10 @@ var api_url = @json($api_url ?? '');
// mycard-read endpoint initialization
let mykad_reader_endpoint = "{{ config('mykad-reader.endpoint') }}";
let wakil_caricustomer_endpoint = "{{ route('wakil.caricustomer') }}";
let store_mykad_photo_endpoint = "{{ route('customer.storeMykadPhoto') }}";
let mykad_reader_driver_endpoint = "{{ route('mykadreader.driver') }}";
</script>
<script src="{{ asset('js/iris-mykad-websocket-client-api.js') }}"></script>
<script src="{{ asset('js/mykad-reader-customer.js') }}"></script>
<script src="{{ asset('js/mykad-reader-customer.js') }}?v=5"></script>
<script src="{{ asset('js/postcode-concerns.js') }}"></script>
@endpush
+222 -144
View File
@@ -22,6 +22,22 @@
</ol>
</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="col-md-6">
<div class="form-group row">
@@ -155,6 +171,11 @@
<i class="fas fa-info-circle me-1"></i> Maklumat Pelanggan
</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 -->
@@ -231,7 +252,6 @@
// 4️⃣ Hantar permohonan ke backend (Laravel)
const response = await fetch("{{ route('operasi.createkelulusan-anggota') }}", {
method: 'POST',
headers: {
"Content-Type": "application/json",
@@ -239,15 +259,14 @@
},
body: JSON.stringify({
mohon: "Assign Anggota",
noic: noic,
nokp: noic,
norujukan: norujukan,
komen: komen,
teller: "{{ Auth::user()->name }}",
tagoperasi: 1 // 1 = tunggu kelulusan operasi
})
console.log(`Response: ${response}`);
});
console.log(`Response: ${response}`);
const result = await response.json();
@@ -349,7 +368,7 @@
</div>
<div class="nav nav-tabs" id="nav-tab" role="tablist">
@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' : '';
$aria_selected_SAG_baru = $activate_tab === 'SAG_baru' ? 'true' : 'false';
@@ -368,91 +387,41 @@
</nav>
<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">
<table class="table table-striped table-hover" id="myTable">
<thead class="thead-dark">
<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>
<x-datatable
id="myTable"
filename="SAG_Baru"
:columns="['Bil', 'No Rujukan', 'Cara Terima', 'Tarikh Pembiayaan', 'Tarikh Matang', 'Baki Harga Jualan', 'Baki Pembiayaan', 'Margin', 'Tempoh', 'Keuntungan', 'Lelong', 'Borang Online']"
/>
</div>
<div class="tab-pane fade {{ $activate_pane_SAG_lama }}" id="nav-xtawarruq" role="tabpanel" aria-labelledby="nav-xtawarruq-tab">
<table id="xtawarruq" class="table table-striped table-hover">
<thead class="thead-dark">
{{-- <tr>
<th>Bil</th>
<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>
<x-datatable
id="xtawarruq"
filename="SAG_Lama"
:columns="['Bil', 'No Rujukan', 'Cara Terima', 'Tarikh Pembiayaan', 'Tarikh Matang', 'Baki Harga Jualan', 'Baki Pembiayaan', 'Margin', 'Tempoh', 'Keuntungan', 'Lelong', 'Borang Online']"
/>
</div>
</div>
{{-- Sudah Tebus --}}
<table id="dahtebusTable" class="table table-striped table-hover">
<thead class="thead-dark">
<tr>
<th>Bil</th>
<th>No Rujukan</th>
<th>Tarikh Penyelesaian Pembiayaan</th>
<th>Baki Pembiayaan</th>
<th>Margin</th>
<th>Tempoh</th>
</tr>
</thead>
</table>
<x-datatable
id="dahtebusTable"
filename="SAG_Sudah_Tebus"
:columns="['Bil', 'No Rujukan', 'Tarikh Penyelesaian Pembiayaan', 'Baki Pembiayaan', 'Margin', 'Tempoh']"
/>
<table id="lelongTable" class="table table-striped table-hover">
<thead class="thead-dark">
<tr>
<th>Bil</th>
<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>
<x-datatable
id="lelongTable"
filename="SAG_Lelong"
:columns="['Bil', 'No Rujukan', 'Tarikh Lelong', 'Tarikh Tuntut', 'Pembiayaan', 'Harga Jual', 'Jum Tuntut/Bayar', 'Keuntungan', 'Caj Lelong', 'Status Tuntutan', 'Cetak']"
/>
<div class="text-center">
<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="histfunc()">Rekod Pembiayaan</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>
@@ -671,6 +640,33 @@
</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-dialog modal-dialog-centered modal-lg">
<div class="modal-content">
@@ -715,90 +711,116 @@
</div>
</div>
<script>
var api_arrahnbid = "<?php echo config('app.api_arrahnbid'); ?>";
var api_url = "<?php echo $api_url; ?>";
var kodcaw = "{{ $cawangan_info['kodcaw'] }}";
var tagarrahnbid = "{{ $cawangan_info['tagarrahnbid'] }}";
var min_komoditi = "{{ $min_komoditi }}";
$('#daftar_gadai').on('click', function() {
$("#daftar_gadai").prop('disabled', true);
var noic = $('#noic').val();
var iclama = "{{ $customer_info['kplama'] }}";
var icbaru = "{{ $customer_info['kpbaru'] }}";
var outstanding_semasa = "{{ $outstanding_semasa }}";
event.preventDefault();
if (outstanding_semasa >= 300000) {
Swal.fire({
icon: 'error',
title: 'Had Pembiayaan',
text: 'Outstanding Pembiayaan Pelanggan Telah Melebihi Had'
});
<script>
var api_arrahnbid = @json($cawangan_info['api_arrahnbid'] ?? config('app.api_arrahnbid'));
var api_url = "{{ api_url_for_browser() }}";
var kodcaw = "{{ $cawangan_info['kodcaw'] }}";
var tagarrahnbid = "{{ $cawangan_info['tagarrahnbid'] }}";
var min_komoditi = "{{ $min_komoditi }}";
console.log('test api_arrahnbid: ', api_arrahnbid);
$('#daftar_gadai').on('click', async function(event) {
event.preventDefault();
if ($(this).prop('disabled')) {
return;
}
$("#daftar_gadai").prop('disabled', true);
var bakiLelong = Number(@json((float) $bakilelong));
if (bakiLelong > 0) {
var confirmation = await Swal.fire({
icon: 'warning',
title: 'Baki Lelong',
text: 'Pelanggan mempunyai baki lelong, teruskan gadai?',
showCancelButton: true,
confirmButtonText: 'Teruskan',
cancelButtonText: 'Batal',
focusCancel: true
});
if (!(confirmation.isConfirmed || confirmation.value === true)) {
$("#daftar_gadai").prop('disabled', false);
event.preventDefault();
return false;
} else {
$.ajax({
method: 'get',
url: api_url + '/api/komuditi/check',
success: function(data) {
var check_komuditi = Object.keys(data).length;
if (check_komuditi == 0) {
return;
}
}
var noic = $('#noic').val();
var iclama = "{{ $customer_info['kplama'] }}";
var icbaru = "{{ $customer_info['kpbaru'] }}";
var outstanding_semasa = "{{ $outstanding_semasa }}";
event.preventDefault();
if (outstanding_semasa >= 300000) {
Swal.fire({
icon: 'error',
title: 'Had Pembiayaan',
text: 'Outstanding Pembiayaan Pelanggan Telah Melebihi Had'
});
$("#daftar_gadai").prop('disabled', false);
event.preventDefault();
return false;
} else {
$.ajax({
method: 'get',
url: api_url + '/api/komuditi/check',
success: function(data) {
var check_komuditi = Object.keys(data).length;
if (check_komuditi == 0) {
Swal.fire({
icon: 'error',
title: 'Unit Komoditi Berbaki Minima',
text: 'Sila Hubungi Operasi Untuk Membuat Pembelian Komoditi'
});
$("#daftar_gadai").prop('disabled', false);
event.preventDefault();
return false;
} else {
if (Number(data['unit_komuditi']) <= min_komoditi) {
$("#daftar_gadai").prop('disabled', false);
Swal.fire({
icon: 'error',
title: 'Unit Komoditi Berbaki Minima',
text: 'Sila Hubungi Operasi Untuk Membuat Pembelian Komoditi'
});
$("#daftar_gadai").prop('disabled', false);
event.preventDefault();
return false;
} else {
if (Number(data['unit_komuditi']) <= min_komoditi) {
$("#daftar_gadai").prop('disabled', false);
Swal.fire({
icon: 'error',
title: 'Unit Komoditi Berbaki Minima',
text: 'Sila Hubungi Operasi Untuk Membuat Pembelian Komoditi'
});
event.preventDefault();
return false;
if (icbaru.length > 0) {
window.location.href =
"{{ route('gadaian', ['noic' => $customer_info['kpbaru']]) }}";
} else {
if (icbaru.length > 0) {
window.location.href =
"{{ route('gadaian', ['noic' => $customer_info['kpbaru']]) }}";
} else {
window.location.href =
"{{ route('gadaian', ['noic' => $customer_info['kplama']]) }}";
}
window.location.href =
"{{ route('gadaian', ['noic' => $customer_info['kplama']]) }}";
}
}
}
});
}
});
}
});
$('a[data-toggle="tab"]').on('shown.bs.tab', function () {
if (typeof adjustErahnDatatables === 'function') {
adjustErahnDatatables();
}
});
$('a[data-toggle="tab"]').on('shown.bs.tab', function(e) {
// here is the new selected tab id
var selectedTabId = e.target.id;
});
var tablegadai = $('#myTable').DataTable({
"scrollX": false,
order: [
[1, 'desc']
var tablegadai = initErahnDatatable('#myTable', {
order: [[1, 'desc']],
columnDefs: [
{ targets: 0, orderable: false, width: '40px' },
{ targets: -1, orderable: false, searchable: false, width: '70px' }
]
});
var xtawarruq = $('#xtawarruq').DataTable({
"scrollX": false
var xtawarruq = initErahnDatatable('#xtawarruq', {
columnDefs: [
{ targets: 0, orderable: false, width: '40px' }
]
});
var tabledahtebus = $('#dahtebusTable').DataTable({});
var tabledahtebus = initErahnDatatable('#dahtebusTable');
var tablelelong = $('#lelongTable').DataTable({
"scrollX": false
var tablelelong = initErahnDatatable('#lelongTable', {
columnDefs: [
{ targets: -1, orderable: false, searchable: false, width: '80px' }
]
});
var historybayarandatatable = $('#tablehistorybayaran').DataTable({
@@ -813,6 +835,45 @@
minimumFractionDigits: 2
});
function urlBorangOnline(norujukan) {
return '{{ url("/gadai/borang-pengesahan-online") }}/' + encodeURIComponent(norujukan);
}
function formatCaraTerima(value) {
return String(value || 'TUNAI').toUpperCase() === 'ONLINE' ? 'Online' : 'Tunai';
}
function cetakBorangOnlineCell(gadai_detail) {
var cara = String(gadai_detail['cara_terima'] || '').toUpperCase();
if (cara !== 'ONLINE') {
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) {
console.log(norujukan);
let gadai_tawarruq = fetch(api_url + '/api/gadai/norujukan/' + norujukan);
@@ -1098,6 +1159,13 @@
text: 'Pembiayaan Sedang Dilelong..'
});
}
},
error: function() {
Swal.fire({
icon: 'error',
title: 'Amaran',
text: 'Tidak dapat semak status lelong. Sila cuba semula.'
});
}
});
@@ -1163,13 +1231,14 @@
let table_norujukan = gadai_detail["norujukan"];
if (gadai_detail["percentpinj"] != 0)
xtawarruq.row.add([
'<input type="radio" value=' + gadai_detail["norujukan"] +
' name="belum"/>',
'<input type="radio" value="' + gadai_detail["norujukan"] +
'" name="belum" data-cara-terima="' + (gadai_detail['cara_terima'] || '') + '"/>',
'<a href="#" onclick="checkserah(this)" data-pinjaman=' +
gadai_detail['pinjaman'] + ' data-hargajualan=' +
gadai_detail['hargajualan'] + ' data-norujukan=' +
gadai_detail['norujukan'] + ' data-url=' + urlroute + '>' +
gadai_detail['norujukan'] + '</a>',
formatCaraTerima(gadai_detail['cara_terima']),
gadai_detail['t_gadai'],
tarikh_matang,
'RM ' + formatter.format(gadai_detail['bakihargajualan']),
@@ -1181,6 +1250,7 @@
'RM ' + formatter.format(upahsemasa),
'<div style="white-space: nowrap; color:red; font-weight: bolder">' +
taglel + '</div>',
cetakBorangOnlineCell(gadai_detail),
]).draw();
} else {
var taglel = '';
@@ -1214,13 +1284,14 @@
let table_norujukan = gadai_detail["norujukan"];
tablegadai.row.add([
'<input type="radio" value=' + gadai_detail["norujukan"] +
' name="belum"/>',
'<input type="radio" value="' + gadai_detail["norujukan"] +
'" name="belum" data-cara-terima="' + (gadai_detail['cara_terima'] || '') + '"/>',
'<a href="#" onclick="checkserah(this)" data-pinjaman=' +
gadai_detail['pinjaman'] + ' data-hargajualan=' +
gadai_detail['hargajualan'] + ' data-norujukan=' +
gadai_detail['norujukan'] + ' data-url=' + urlroute + '>' +
gadai_detail['norujukan'] + '</a>',
formatCaraTerima(gadai_detail['cara_terima']),
gadai_detail['t_gadai'],
tarikh_matang,
'RM ' + formatter.format(gadai_detail['bakihargajualan']),
@@ -1232,6 +1303,7 @@
'RM ' + formatter.format(upahsemasa['pendapatan']),
'<div style="white-space: nowrap; color:red; font-weight: bolder">' +
taglel + '</div>',
cetakBorangOnlineCell(gadai_detail),
]).draw();
}
} else if (jenistebus == "sudah_tebus") {
@@ -1551,6 +1623,9 @@
success: function(data) {
$('#myTable').parents('div.dataTables_wrapper').first().show();
gadaiTable(data, "belum_tebus");
if (typeof adjustErahnDatatables === 'function') {
adjustErahnDatatables();
}
}
});
@@ -1992,7 +2067,7 @@
harini.setHours(0, 0, 0, 0);
if ($("input[name='options']:checked").val() == 'belum_tebus') {
let test1 = $('input[name="belum"]:checked').closest('tr').children('td:eq(2)').text();
let test1 = $('input[name="belum"]:checked').closest('tr').children('td:eq(3)').text();
let tarikhgadai2 = new Date(test1);
tarikhgadai2.setHours(0, 0, 0, 0);
if (+harini !== +tarikhgadai2) {
@@ -2409,7 +2484,7 @@
}
if ($("input[name='options']:checked").val() == 'belum_tebus') {
let test1 = $('input[name="belum"]:checked').closest('tr').children('td:eq(2)').text();
let test1 = $('input[name="belum"]:checked').closest('tr').children('td:eq(3)').text();
let tarikhgadai2 = new Date(test1);
tarikhgadai2.setHours(0, 0, 0, 0);
@@ -2807,6 +2882,9 @@
url: api_url + "/api/customers/" + noic + "/gadai/belum_tebus/" + kodcaw,
success: function(data) {
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 Atas Talian (Online)</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>Atas Talian (Online)</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>
+1 -1
View File
@@ -34,7 +34,7 @@
getPusherResponse();
async function getPusherResponse(){
let api_url = "<?php echo $api_url ?>";
let api_url = "{{ api_url_for_browser() }}";
const jsonData = {
norujukan: "{{ $errorapproval['norujukan'] }}",
kodcaw: "{{ Auth::user()->cawangan }}",
+1 -1
View File
@@ -215,7 +215,7 @@
</div>
</div>
<script>
var api_url = "<?php echo $api_url ?>";
var api_url = "{{ api_url_for_browser() }}";
$('form input').keydown(function (e) {
if (e.keyCode == 13) {
e.preventDefault();
+1 -1
View File
@@ -242,7 +242,7 @@
@endphp
@endif
<script>
var api_url = "<?php echo $api_url ?>";
var api_url = "{{ api_url_for_browser() }}";
$('form input').keydown(function (e) {
if (e.keyCode == 13) {
e.preventDefault();
+309 -11
View File
@@ -24,6 +24,9 @@
</div>
<div class="col-12 text-right mb-2">
<button type="button" style="margin:auto" class="btn btn-primary" data-toggle="modal" data-target="#daftar_marhun">Daftar Marhun</button>
<button type="button" id="toggleTableBtn" class="btn btn-info ml-2">
<i class="fas fa-exchange-alt"></i> Papar Margin
</button>
</div>
<div class="card">
<div class="card-body">
@@ -53,6 +56,33 @@
<th colspan="5"></th>
</tfoot>
</table>
<!-- Margin Summary Table (Hidden by default) -->
<table class="table table-striped table-hover text-center" id="marginTable" style="display: none;">
<thead class="thead-dark">
<tr>
<th>Bil</th>
<th>Karat</th>
<th>Nilai Marhun</th>
<th>Margin Teller (%)</th>
<th>Margin PC (%)</th>
<th>Nilai Marhun Teller</th>
<th>Nilai Marhun PC</th>
</tr>
</thead>
<tbody id="marginTableBody">
<!-- Dynamic content will be inserted here -->
</tbody>
<tfoot class="thead-success">
<tr>
<th colspan="2" class="text-center">Jumlah</th>
<th id="totalNilaiMarhunMargin"></th>
<th colspan="2"></th>
<th id="totalNilaiTellerMargin"></th>
<th id="totalNilaiPCMargin"></th>
</tr>
</tfoot>
</table>
</div>
</div>
<div class="card">
@@ -296,7 +326,7 @@
</div>
<script src="{{ asset('js/rounded-methods.js') }}"></script>
<script>
var api_url = "<?php echo $api_url ?>";
var api_url = "{{ api_url_for_browser() }}";
var kodcaw_ujrah = "<?php echo $cawangan_info['kodcaw'] ?>";
$('form input').keydown(function (e) {
@@ -511,7 +541,21 @@
"visible": false,
"searchable": false
}
]
],
"language": {
"lengthMenu": "Papar _MENU_ rekod setiap halaman",
"zeroRecords": "Tiada rekod dijumpai",
"info": "Memapar halaman _PAGE_ dari _PAGES_",
"infoEmpty": "Tiada rekod tersedia",
"infoFiltered": "(ditapis dari _MAX_ jumlah rekod)",
"search": "Cari:",
"paginate": {
"first": "Pertama",
"last": "Akhir",
"next": "Seterusnya",
"previous": "Sebelumnya"
}
}
});
$(".select2").select2({
@@ -567,6 +611,7 @@
total_berat = 0;
total_kuantiti = 0;
var marhun_details = '';
var array_mutu = [];
tablemarhun.clear().draw();
$.each(data,function(index,marhun){
$.each(marhun,function(index,marhun_detail){
@@ -576,6 +621,7 @@
jumlah_nilai_marhun += Number(marhun_detail['n_marhun']);
status_tag_permata = marhun_detail['tag_permata'] == 1 ? 'Ada' : '';
total_kuantiti += Number(marhun_detail['bil']);
array_mutu.push([marhun_detail['karat'], marhun_detail['n_marhun']]);
tablemarhun.row.add([
counter,
marhun_detail['recno'],
@@ -588,7 +634,7 @@
marhun_detail['harga'],
status_tag_permata,
marhun_detail['berat_batu_permata'],
"<div class='buttonpunya text-center'><button onclick='editmarhun(this)' class='btn btn-primary'><i class='fas fa-edit'></i></button> <button onclick='deletemarhun(this)' class='btn btn-danger'><i class='fas fa-trash-alt'></i></button></div>"
"<div class='buttonpunya text-center'><button onclick='viewmarhun(this)' class='btn btn-info'><i class='fas fa-eye'></i></button> <button onclick='editmarhun(this)' class='btn btn-primary'><i class='fas fa-edit'></i></button> <button onclick='deletemarhun(this)' class='btn btn-danger'><i class='fas fa-trash-alt'></i></button></div>"
]).draw();
count++;
counter = counter + 1;
@@ -601,7 +647,7 @@
$('#bilangan_jumlah_kuantiti').val(formatter.format(total_kuantiti));
$('#jumlah_nilai_marhun').val(formatter.format(jumlah_nilai_marhun));
if (count == 1){
$('.buttonpunya').html("<button onclick='editmarhun(this)' class='btn btn-primary'><i class='fas fa-edit'></i></button>");
$('.buttonpunya').html("<button onclick='viewmarhun(this)' class='btn btn-info'><i class='fas fa-eye'></i></button> <button onclick='editmarhun(this)' class='btn btn-primary'><i class='fas fa-edit'></i></button>");
}
// if(total_nilai_marhun > 0 && total_nilai_marhun < 401){
@@ -656,25 +702,32 @@
$('#keuntungan_sebulan').val(formatter.format(keuntungan_sebulan.toFixed(2)));
$('#keuntungan_6bln').val(formatter.format(keuntungan_6bln.toFixed(2)));
var max_teller = 0;
var max_pelulus = 0;
$.ajax({
method:'GET',
url: api_url + '/api/maxpinjaman',
data: {
'mutu': array_mutu,
'total_nilai_marhun': total_nilai_marhun,
},
success:function(data){
$.each(data,function(index,pinjaman){
if(pinjaman['user_type'] == 'teller'){
if (pinjaman['user_type'] == 'teller') {
pinjaman_teller = Number(pinjaman['max']);
max_teller = pinjaman['teller_pinjaman'];
}
if(pinjaman['user_type'] == 'khazanah'){
if (pinjaman['user_type'] == 'khazanah') {
pinjaman_pelulus = Number(pinjaman['max']);
max_pelulus = pinjaman['pc_pinjaman'];
}
});
$('.span-pinjaman-max-pelulus').text(formatter.format(pinjaman_pelulus) + ' %');
$('.span-pinjaman-max').text(formatter.format(pinjaman_teller) + ' %');
var max_teller = total_nilai_marhun * (pinjaman_teller/100);
var max_pelulus = total_nilai_marhun * (pinjaman_pelulus/100);
$('#pembiayaan_max_teller').val(formatter.format(max_teller));
$('#pembiyaan_max_pelulus').val(formatter.format(max_pelulus.toFixed(2)));
$('#pembiyaan_max_pelulus').val(formatter.format(max_pelulus));
}
});
}
@@ -701,6 +754,103 @@
});
}
function viewmarhun(e){
let selectedrow = $(e).closest('tr');
let recno = tablemarhun.row(selectedrow).data();
let nm = recno[3];
let karat = recno[4];
let berat = recno[5];
let harga = recno[8];
let nota = recno[7];
let kuantiti = recno[6];
let jenismarhun = recno[2];
let beratpermata = recno[10];
// Call API to get specific MaxMutuEmas data by karat
$.ajax({
url: '{{ api_url_for_browser() }}/api/maxmutuemas/specific',
type: 'GET',
data: { karat: karat, nilaimarhun: nm },
success: function(response) {
let marginInfo = '';
let marginTable = '';
if (response && response.karat) {
marginTable = `
<table class="table table-bordered table-sm mt-3">
<thead class="thead-light">
<tr><th colspan="2" class="text-center"><strong>Maklumat Margin</strong></th></tr>
</thead>
<tbody>
<tr><td><strong>Margin Teller</strong></td><td>${response.teller}%</td></tr>
<tr><td><strong>Margin PC</strong></td><td>${response.pc}%</td></tr>
<tr><td><strong>Nilai Marhun Teller</strong></td><td>RM${parseFloat(response.maxteller).toLocaleString('en-MY', {minimumFractionDigits: 2, maximumFractionDigits: 2})}</td></tr>
<tr><td><strong>Nilai Marhun PC</strong></td><td>RM${parseFloat(response.maxpc).toLocaleString('en-MY', {minimumFractionDigits: 2, maximumFractionDigits: 2})}</td></tr>
</tbody>
</table>
`;
}
let details = `
<table class="table table-bordered table-sm">
<thead class="thead-light">
<tr><th colspan="2" class="text-center"><strong>Maklumat Marhun</strong></th></tr>
</thead>
<tbody>
<tr><td><strong>Jenis Marhun</strong></td><td>${jenismarhun}</td></tr>
<tr><td><strong>Nilai Marhun Asal</strong></td><td>${nm}</td></tr>
<tr><td><strong>Karat</strong></td><td>${karat}</td></tr>
<tr><td><strong>Berat</strong></td><td>${berat}g</td></tr>
<tr><td><strong>Kuantiti</strong></td><td>${kuantiti}</td></tr>
<tr><td><strong>Harga</strong></td><td>RM${harga}</td></tr>
<tr><td><strong>Nota</strong></td><td>${nota}</td></tr>
<tr><td><strong>Berat Batu Permata</strong></td><td>${beratpermata}g</td></tr>
</tbody>
</table>
${marginTable}
`;
Swal.fire({
title: 'Maklumat Marhun & Margin',
html: details,
icon: 'info',
confirmButtonText: 'OK',
width: '600px'
});
},
error: function(xhr) {
// If API call fails, show basic marhun info only
let details = `
<table class="table table-bordered table-sm">
<thead class="thead-light">
<tr><th colspan="2" class="text-center"><strong>Maklumat Marhun</strong></th></tr>
</thead>
<tbody>
<tr><td><strong>Jenis Marhun</strong></td><td>${jenismarhun}</td></tr>
<tr><td><strong>Nama</strong></td><td>${nm}</td></tr>
<tr><td><strong>Karat</strong></td><td>${karat}</td></tr>
<tr><td><strong>Berat</strong></td><td>${berat}g</td></tr>
<tr><td><strong>Kuantiti</strong></td><td>${kuantiti}</td></tr>
<tr><td><strong>Harga</strong></td><td>RM${harga}</td></tr>
<tr><td><strong>Nota</strong></td><td>${nota}</td></tr>
<tr><td><strong>Berat Batu Permata</strong></td><td>${beratpermata}g</td></tr>
</tbody>
</table>
<div class="alert alert-warning text-center">
<em>Maklumat margin tidak tersedia</em>
</div>
`;
Swal.fire({
title: 'Maklumat Marhun',
html: details,
icon: 'info',
confirmButtonText: 'OK',
width: '600px'
});
}
});
}
function editmarhun(e){
let norujuk = $('#norujukan').val();
let selectedrow = $(e).closest('tr');
@@ -1502,7 +1652,7 @@
swalWithBootstrapButtons.fire({
title: 'Dapatkan Pengesahan Daripada Khazanah?',
text: "Tetapan margin melebihi" + pinjaman_teller + "% untuk Khazanah!",
text: "Tetapan Margin Melebihi Limit Maksima Teller!",
icon: 'info',
showCancelButton: true,
confirmButtonText: 'Hantar',
@@ -1514,7 +1664,7 @@
let norujukan = $('#norujukan').val();
let nm = parseFloat($('#total_nilai_marhun').val().replace(/[^0-9.-]+/g,""));let ber = $('#total_berat').val();let marg = $('.span-pinjaman').text();
// let komen = "Pembiayaan Melebihi Margin 70%|" + nm + "|" + ber + "|" + marg + "|";
let status = "Pembiayaan Melebihi Margin " + pinjaman_teller + "%";
let status = "Tetapan Margin Melebihi Limit Maksima Teller: " + pinjaman_teller + "%";
let jumpinjam = $('#pinjaman').val();
let teller = "{{ Auth::user()->name }}";
@@ -1618,5 +1768,153 @@
}
}
}
// Toggle Table Functionality
let isMarginTableVisible = false;
$('#toggleTableBtn').on('click', function() {
if (!isMarginTableVisible) {
// Show margin table, hide main table
$('#myTable_wrapper').hide();
$('#marginTable').show();
$(this).html('<i class="fas fa-exchange-alt"></i> Papar Marhun');
loadMarginData();
isMarginTableVisible = true;
} else {
// Show main table, hide margin table
$('#marginTable_wrapper').hide();
$('#myTable_wrapper').show();
$(this).html('<i class="fas fa-exchange-alt"></i> Papar Margin');
isMarginTableVisible = false;
}
});
function loadMarginData() {
let marginData = [];
let totalNilaiMarhun = 0;
let totalNilaiTeller = 0;
let totalNilaiPC = 0;
// Process each individual row from the main table
let rowsData = [];
tablemarhun.rows().data().each(function(row, index) {
let recno = row[1]; // Recno column
let karat = row[4]; // Karat column
let nilaiMarhun = parseFloat(row[3].replace(/[^0-9.-]+/g, "")); // Nilai Marhun column
rowsData.push({
index: index,
recno: recno,
karat: karat,
nilaiMarhun: nilaiMarhun
});
});
let bil = 1;
let processedCount = 0;
let totalEntries = rowsData.length;
rowsData.forEach((rowData) => {
// Call API to get margin data for each individual item
$.ajax({
url: '{{ api_url_for_browser() }}/api/maxmutuemas/specific',
type: 'GET',
data: { karat: rowData.karat, nilaimarhun: rowData.nilaiMarhun },
success: function(response) {
if (response && response.karat) {
let nilaiTeller = parseFloat(response.maxteller) || 0;
let nilaiPC = parseFloat(response.maxpc) || 0;
marginData.push({
index: rowData.index,
bil: rowData.index + 1,
recno: rowData.recno,
karat: rowData.karat,
nilaiMarhun: rowData.nilaiMarhun,
marginTeller: response.teller,
marginPC: response.pc,
nilaiTeller: nilaiTeller,
nilaiPC: nilaiPC
});
totalNilaiMarhun += rowData.nilaiMarhun;
totalNilaiTeller += nilaiTeller;
totalNilaiPC += nilaiPC;
}
processedCount++;
if (processedCount === totalEntries) {
// Sort by original index to maintain order
marginData.sort((a, b) => a.index - b.index);
populateMarginTable(marginData, totalNilaiMarhun, totalNilaiTeller, totalNilaiPC);
}
},
error: function() {
processedCount++;
if (processedCount === totalEntries) {
// Sort by original index to maintain order
marginData.sort((a, b) => a.index - b.index);
populateMarginTable(marginData, totalNilaiMarhun, totalNilaiTeller, totalNilaiPC);
}
}
});
});
}
let marginDataTable = null;
function populateMarginTable(data, totalMarhun, totalTeller, totalPC) {
// If DataTable already exists, destroy it first
if (marginDataTable !== null) {
marginDataTable.destroy();
marginDataTable = null;
}
let tbody = $('#marginTableBody');
tbody.empty();
data.forEach(function(item) {
let row = `
<tr>
<td>${item.bil}</td>
<td>${item.karat}</td>
<td>RM${item.nilaiMarhun.toLocaleString('en-MY', {minimumFractionDigits: 2, maximumFractionDigits: 2})}</td>
<td>${item.marginTeller}%</td>
<td>${item.marginPC}%</td>
<td>RM${item.nilaiTeller.toLocaleString('en-MY', {minimumFractionDigits: 2, maximumFractionDigits: 2})}</td>
<td>RM${item.nilaiPC.toLocaleString('en-MY', {minimumFractionDigits: 2, maximumFractionDigits: 2})}</td>
</tr>
`;
tbody.append(row);
});
// Initialize DataTable
marginDataTable = $('#marginTable').DataTable({
"pageLength": 10,
"ordering": false,
"order": [[ 0, "asc" ]],
"responsive": true,
"language": {
"lengthMenu": "Papar _MENU_ rekod setiap halaman",
"zeroRecords": "Tiada rekod dijumpai",
"info": "Memapar halaman _PAGE_ dari _PAGES_",
"infoEmpty": "Tiada rekod tersedia",
"infoFiltered": "(ditapis dari _MAX_ jumlah rekod)",
"search": "Cari:",
"paginate": {
"first": "Pertama",
"last": "Akhir",
"next": "Seterusnya",
"previous": "Sebelumnya"
}
},
"footerCallback": function (row, data, start, end, display) {
// Update footer totals
$('#totalNilaiMarhunMargin').text('RM' + totalMarhun.toLocaleString('en-MY', {minimumFractionDigits: 2, maximumFractionDigits: 2}));
$('#totalNilaiTellerMargin').text('RM' + totalTeller.toLocaleString('en-MY', {minimumFractionDigits: 2, maximumFractionDigits: 2}));
$('#totalNilaiPCMargin').text('RM' + totalPC.toLocaleString('en-MY', {minimumFractionDigits: 2, maximumFractionDigits: 2}));
}
});
}
</script>
@endsection
File diff suppressed because it is too large Load Diff
@@ -16,6 +16,7 @@
<div class="card-body text-center">
<a href="{{ route('admin.daftar.kakitangan') }}" class="btn btn-info btn-lg">Kakitangan</a>
<a href="{{ route('admin.pelanggan') }}" class="btn btn-info btn-lg">Pelanggan</a>
<a href="{{ route('admin.features') }}" class="btn btn-info btn-lg">Ciri Cawangan</a>
<!-- <a href="{{ route('admin.poskod') }}" class="btn btn-info btn-lg">Poskod</a> -->
</div>
</div>
@@ -219,72 +219,70 @@
<div class="card-body text-center">
<div class="row form-group justify-content-center">
<div class="col-3 mx-1">
<div class="row">
<a href="{{ url('dashboard') }}" disabled
class="btn btn-info btn-lg btn-block mb-3">Dashboard</a>
</div>
<div class="row">
<a href="{{ url('kakitangan') }}"
class="btn btn-info btn-lg btn-block mb-3">Kakitangan</a>
</div>
<div class="row">
<a href="{{ url('kelulusan') }}" id="btnkelulusan"
class="btn btn-info btn-lg btn-block mb-3">Kelulusan</a>
</div>
{{-- <div class="row">
<a href="{{ url('permohonan') }}" class="btn btn-danger btn-lg btn-block mb-3">Permohonan</a>
</div> --}}
<div class="row">
<a href="#" data-target="#denominasiShowModal" data-backdrop="static" data-keyboard="false"
data-toggle="modal" class="btn btn-info btn-lg btn-block mb-3">Denominasi</a>
<div class="col-4 px-2">
<a href="{{ url('dashboard') }}" disabled
class="btn btn-info btn-lg btn-block mb-3">Dashboard</a>
</div>
<div class="row">
<a href="{{ route('khazanah.harga_emas') }}"
class="btn btn-info btn-lg btn-block mb-3">Harga Emas</a>
</div>
</div>
<div class="col-3 mx-1">
<div class="row">
<div class="col-4 px-2">
<a href="{{ route('khazanah.pelanggan') }}"
class="btn btn-info btn-lg btn-block mb-3">Pelanggan</a>
</div>
<div class="row">
<div class="col-4 px-2">
<a href="{{ route('lelong') }}" class="btn btn-info btn-lg btn-block mb-3">Lelong</a>
</div>
<div class="col-4 px-2">
<a href="{{ url('kakitangan') }}"
class="btn btn-info btn-lg btn-block mb-3">Kakitangan</a>
</div>
<div class="col-4 px-2">
<a href="{{ route('laporan') }}" class="btn btn-info btn-lg btn-block mb-3">Laporan</a>
</div>
<div class="row">
<div class="col-4 px-2">
<a href="{{ url('panjar') }}" class="btn btn-info btn-lg btn-block mb-3">Perbelanjaan</a>
</div>
<div class="col-4 px-2">
<a href="{{ url('kelulusan') }}" id="btnkelulusan"
class="btn btn-info btn-lg btn-block mb-3">Kelulusan</a>
</div>
<div class="col-4 px-2">
<a href="{{ url('menu') }}"
id="btn-khazanah"
class="btn {{ $countkhazanah > 0 ? 'btn-danger' : 'btn-info' }} btn-lg btn-block mb-3">Khazanah</a>
</div>
<div class="row">
<div class="col-4 px-2">
<a href="{{ url('alirantunai') }}" class="btn btn-info btn-lg btn-block mb-3">Aliran
Tunai</a>
</div>
<div class="col-4 px-2">
<a href="#" data-target="#denominasiShowModal" data-backdrop="static" data-keyboard="false"
data-toggle="modal" class="btn btn-info btn-lg btn-block mb-3">Denominasi</a>
</div>
<div class="col-4 px-2">
<a href="{{ url('pendahuluanserahan') }}"
class="btn btn-info btn-lg btn-block mb-3">Pendahuluan - Serahan</a>
</div>
<div class="row">
<div class="col-4 px-2">
<a href="{{ route('khazanah.bajet') }}" class="btn btn-info btn-lg btn-block mb-3">Bajet</a>
</div>
<div class="col-4 px-2">
<a href="{{ route('khazanah.harga_emas') }}"
class="btn btn-info btn-lg btn-block mb-3">Harga Emas</a>
</div>
@if(!empty(($cawangan_info['features'] ?? [])['pembiayaan_online']))
<div class="col-4 px-2">
<a href="{{ route('pembiayaan.online.pc') }}"
class="btn btn-info btn-lg btn-block mb-3">Pembiayaan Online</a>
</div>
<div class="col-4 px-2">
<a href="{{ route('pembiayaan.online.pc.topup') }}"
class="btn btn-info btn-lg btn-block mb-3">Had / Modal Online</a>
</div>
@endif
<div class="col-4 px-2">
<a href="{{ route('khazanah.indexwakil') }}"
class="btn btn-info btn-lg btn-block mb-3">Surat Wakil</a>
</div>
</div>
<div class="col-3 mx-1">
{{-- <div class="row">
<a href="#" class="btn btn-danger btn-lg btn-block mb-3">Capaian</a>
</div> --}}
<div class="row">
<a href="{{ route('lelong') }}" class="btn btn-info btn-lg btn-block mb-3">Lelong</a>
</div>
<div class="row">
<a href="{{ url('panjar') }}" class="btn btn-info btn-lg btn-block mb-3">Perbelanjaan</a>
</div>
<div class="row">
<a href="{{ url('alirantunai') }}" class="btn btn-info btn-lg btn-block mb-3">Aliran
Tunai</a>
</div>
<div class="row">
<a href="{{ route('khazanah.bajet') }}" class="btn btn-info btn-lg btn-block mb-3">Bajet</a>
</div>
</div>
</div>
</div>
@@ -320,7 +318,7 @@
});
let api = "{{ config('api.url') }}";
let api = "{{ api_url_for_browser() }}";
let kodcaw = "{{ Auth::user()->cawangan }}";
const initialambilan = [
@@ -14,13 +14,18 @@
</nav>
<div class="card-body text-center">
<a href="{{ route('operasi.kelulusananggota') }}" class="btn btn-info btn-lg">Kelulusan Anggota</a>
<a href="{{ route('operasi.kelulusan') }}" class="btn btn-info btn-lg">Kelulusan</a>
<a href="{{ route('daftar.kakitangan') }}" class="btn btn-info btn-lg">Kakitangan</a>
<a href="{{ route('parameter') }}" class="btn btn-info btn-lg">Parameter</a>
<a href="{{ route('komuditi') }}" class="btn btn-info btn-lg">Komoditi</a>
<a href="{{ route('perubatan') }}" class="btn btn-info btn-lg">Perubatan</a>
<a href="{{ route('operasi.kelulusananggota') }}" class="btn btn-info btn-lg d-inline-block m-1">Kelulusan Anggota</a>
<a href="{{ route('operasi.kelulusan') }}" class="btn btn-info btn-lg d-inline-block m-1">Kelulusan</a>
<a href="{{ route('pembiayaan.online.operasi') }}" class="btn btn-info btn-lg d-inline-block m-1">Pembiayaan Online</a>
<a href="{{ route('pembiayaan.online.operasi.topup') }}" class="btn btn-info btn-lg d-inline-block m-1">Modal Online</a>
<a href="{{ route('pembiayaan.online.operasi.eod') }}" class="btn btn-info btn-lg d-inline-block m-1">EOD Modal Online</a>
<a href="{{ route('daftar.kakitangan') }}" class="btn btn-info btn-lg d-inline-block m-1">Kakitangan</a>
<a href="{{ route('parameter') }}" class="btn btn-info btn-lg d-inline-block m-1">Parameter</a>
<a href="{{ route('komuditi') }}" class="btn btn-info btn-lg d-inline-block m-1">Komoditi</a>
<a href="{{ route('perubatan') }}" class="btn btn-info btn-lg d-inline-block m-1">Perubatan</a>
<a href="{{ route('operasi.laporan.audit') }}" class="btn btn-info btn-lg d-inline-block m-1">Laporan Audit</a>
</div>
</div>
</div>
</div>
@@ -0,0 +1,46 @@
@extends('layouts.app_operasi')
@section('content')
<div class="container">
<div class="row justify-content-center mt-5">
<div class="col-md-10">
<div class="card">
<nav aria-label="breadcrumb">
<ol class="breadcrumb pb-3">
<li class="breadcrumb-item"><a href="{{ route('home') }}">Home</a></li>
<li class="breadcrumb-item active" aria-current="page">Laporan Audit</li>
</ol>
</nav>
<div class="card-header">
<h4 class="text-center">Laporan Audit</h4>
</div>
<div class="card-body">
<form action="{{ route('operasi.audittrail.laporancetak') }}" method="GET">
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="dateaudit">Tarikh Mula:</label>
<input type="date" class="form-control" id="dateaudit" name="dateaudit" required>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="dateaudit_till">Tarikh Akhir:</label>
<input type="date" class="form-control" id="dateaudit_till" name="dateaudit_till" required>
</div>
</div>
</div>
<div class="text-center">
<button type="submit" class="btn btn-info btn-lg">
<i class="fas fa-route"></i> Generate Audit Trail
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection
@@ -267,7 +267,7 @@
</div>
</div>
<script>
var api_url = "<?php echo $api_url ?>";
var api_url = "{{ api_url_for_browser() }}";
var cawangan = "{{ Auth::user()->cawangan }}";
var teller = "{{ Auth::user()->name }}";
@@ -18,10 +18,9 @@
</div>
</div>
<script>
var api_url = "<?php echo $api_url ?>";
var api_url = "{{ api_url_for_browser() }}";
let teller = "{{ Auth::user()->name }}";
let kodcaw = "{{ Auth::user()->cawangan }}";
function pelangganclick()
{
// $getTunai2=Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url').'/api/GetCurrentTunai/'.$auth_user['cawangan'],['kodlaluan' => $kodlaluan])->json();
@@ -74,7 +74,7 @@
</div>
</div>
<script type="text/javascript">
var api_url = "<?php echo $api_url ?>";
var api_url = "{{ api_url_for_browser() }}";
var kodcaw = "<?php echo $cawangan_info['kodcaw'] ?>";
var tablecustomers = $('#myTable').DataTable({
+1 -1
View File
@@ -315,7 +315,7 @@
const d = new Date();
let tahunsemasa = d.getFullYear();
let pusherenv = "{{ env('PUSHER_APP_KEY') }}";
let api_url = "{{ $api_url }}";
let api_url = "{{ api_url_for_browser() }}";
const formatter = new Intl.NumberFormat('en-US', {
minimumFractionDigits: 2
+1 -1
View File
@@ -230,7 +230,7 @@
</div>
</div>
<script>
var api_url = "<?php echo $api_url ?>";
var api_url = "{{ api_url_for_browser() }}";
var cawangan = "{{ Auth::user()->cawangan }}";
+1 -1
View File
@@ -318,7 +318,7 @@
</div>
</div>
<script>
var api_url = "<?php echo $api_url ?>";
var api_url = "{{ api_url_for_browser() }}";
var search = "<?php echo $search ?>";
var type_id = "<?php echo $idtype ?>";
$(document).ready(function(){
+2 -2
View File
@@ -410,11 +410,11 @@
</div>
<script>
var api_url = "<?php echo $api_url ?>";
var api_url = "{{ api_url_for_browser() }}";
var kodcaw = "{{ $cawangan_info['kodcaw'] }}";
var ppc = "{{ Auth::user()->name }}";
var tagarrahnbid = "{{ $cawangan_info['tagarrahnbid'] }}";
var api_arrahnbid = "<?php echo config('app.api_arrahnbid') ?>";
var api_arrahnbid = @json($cawangan_info['api_arrahnbid'] ?? config('app.api_arrahnbid'));
+2 -2
View File
@@ -177,7 +177,7 @@
"scrollX": true
});
let api = "{{ config('api.url') }}";
let api = "{{ api_url_for_browser() }}";
var kodcaw = "{{ Auth::user()->cawangan }}";
const formatter = new Intl.NumberFormat('en-US', {
@@ -271,7 +271,7 @@
async function updatelulus(e)
{
let api = "{{ config('api.url') }}";
let api = "{{ api_url_for_browser() }}";
let kodcaw = "{{ Auth::user()->cawangan }}";
const { value: katalaluan } = await Swal.fire({
@@ -299,7 +299,7 @@
<script>
let api = "{{ config('api.url') }}";
let api = "{{ api_url_for_browser() }}";
$(document).ready(function() {
@@ -51,7 +51,7 @@
</div>
</div>
<script type="text/javascript">
var api_url = "<?php echo $api_url ?>";
var api_url = "{{ api_url_for_browser() }}";
var kodcaw = "<?php echo $cawangan_info['kodcaw'] ?>";
var tablecustomers = $('#myTable').DataTable({
@@ -0,0 +1,63 @@
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row">
<div class="col-md-12">
<div class="card">
<nav aria-label="breadcrumb">
<ol class="breadcrumb pb-3">
<li class="breadcrumb-item"><a href="{{ route('home') }}">Home</a></li>
<li class="breadcrumb-item active" aria-current="page">Aliran Tunai</li>
</ol>
</nav>
@if(session()->has('errormessage'))
<div class="alert alert-danger">
{{ session()->get('errormessage') }}
</div>
@endif
<div class="card-body text-center">
<div class="row form-group">
<div class="col-md-2">
</div>
<div class="col-md-10">
</div>
</div>
<div class="row">
<div class="col-md-5">
<form action="{{ route('alirantunai.laporancetak') }}" method="post">
@csrf
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text" id="basic-addon1">Tarikh</span>
</div>
<input type="text" class="form-control" placeholder="Pilih Tarikh" aria-label="Tarikh" id="datetimepicker1" name="datetimepicker1" aria-describedby="basic-addon1">
<input type="submit" class="btn btn-primary ml-md-5" id="submit_button" name="submit_button" value="Cetak">
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script type="text/javascript">
var api_url = "{{ api_url_for_browser() }}";
var kodcaw = "<?php echo $cawangan_info['kodcaw'] ?>";
$(document).ready( function () {
$('#datetimepicker1').datepicker({
format: 'dd-mm-yyyy'
});
});
var tablecustomers = $('#myTable').DataTable({
"ordering": false
});
</script>
@endsection
@@ -69,7 +69,7 @@
</div>
</div>
<script type="text/javascript">
var api_url = "<?php echo $api_url ?>";
var api_url = "{{ api_url_for_browser() }}";
var kodcaw = "<?php echo $cawangan_info['kodcaw'] ?>";
var tabletebus = $('#myTable').DataTable({

Some files were not shown because too many files have changed in this diff Show More