145 Commits

Author SHA1 Message Date
ismailmasseran 93b99c6eb0 DONE: create dump db into migration for all used database; WIP: merge bsas and api, add feature flags for commodity 2026-10-27 08:45:33 +08:00
ismailmasseran 1385539d31 Merge branch 'main' of https://git.koppkb.com/KoPKB/api_arrahn into feature/shoraka-integration 2026-10-26 15:41:22 +08:00
ismailmasseran 0451ad3dbe Feature/bsas integration (#24)
Reviewed-on: #24
Co-authored-by: ISMAIL MASSERAN <ismail@koppkb.com>
Co-committed-by: ISMAIL MASSERAN <ismail@koppkb.com>
2026-10-26 15:35:48 +08:00
ismailmasseran d5cfe503c5 Merge branch 'main' of https://git.koppkb.com/KoPKB/api_arrahn into feature/shoraka-integration 2026-10-26 15:25:11 +08:00
ismailmasseran 8dd8def358 DONE: drop table shoraka financings, and improve UI checking on shoraka order status 2026-10-26 15:04:43 +08:00
ismailmasseran e3e7694b54 DONE: remove cache, and remove dto financing 2026-10-26 12:49:57 +08:00
ismailmasseran 2786b3be0c Fix/gadai online details (#23)
Build Docker Image / build-backend (push) Successful in 17s
Deploy to Staging / deploy (push) Successful in 19s
Co-authored-by: afrina <afrina@koppkb.com>
Reviewed-on: #23
2026-10-22 11:52:27 +08:00
afrina 4648c7072b fix(shoraka): reuse batal gadai norujukan 2026-10-21 10:32:54 +08:00
afrina ac57e04eac refactor(shoraka): move financing records to master database 2026-10-19 13:31:06 +08:00
afrina a8888e66d1 feat(shoraka): defer gadai persistence until commodity confirmation and add simulation support 2026-10-19 13:02:13 +08:00
ismailmasseran d0f08545d1 DONE: add sectigo certs to docker container 2026-10-11 14:58:02 +08:00
nurafrinaalimi16 6c9f2f4f0f feat(shoraka): integrate transactions with Shoraka API 2026-10-08 12:30:02 +08:00
nurafrinaalimi16 5d208f7a79 Merge remote-tracking branch 'origin/main' into feature/shoraka-integration 2026-10-05 11:02:54 +08:00
afrina aac29bc199 Merge pull request 'fix: recalculate gadai totals after marhun edits' (#21) from fix/marhun-edit-gadai-totals into main
Build Docker Image / build-backend (push) Successful in 18s
Deploy to Staging / deploy (push) Successful in 17s
Reviewed-on: #21
2026-10-05 10:43:45 +08:00
nurafrinaalimi16 9e73f6ad32 fix: recalculate gadai totals after marhun edits 2026-10-05 10:39:19 +08:00
ISMAIL MASSERAN ff21d82af5 DONE: remove nginx for local docker compose 2026-10-01 13:45:34 +08:00
ismailmasseran 62c6d240ce Merge pull request 'DONE: update cicd to auto deploy on staging, clean up garbage files' (#20) from cicd/auto-deploy-staging into main
Build Docker Image / build-backend (push) Successful in 17s
Deploy to Staging / deploy (push) Successful in 17s
Reviewed-on: #20
2026-10-01 12:01:34 +08:00
ISMAIL MASSERAN 1ee7edefff DONE: update cicd to auto deploy on staging, clean up garbage files 2026-10-01 12:00:09 +08:00
ismailmasseran b52f4b6919 Merge pull request 'DONE: add password on laravel log viewer' (#19) from feature/add-pass-logviewer into main
Build Docker Image / build-backend (push) Successful in 18s
Reviewed-on: #19
2026-09-28 16:27:55 +08:00
ISMAIL MASSERAN b8e3e2b369 DONE: add password on laravel log viewer 2026-09-28 16:26:50 +08:00
ismailmasseran 3a86ba5b26 Merge pull request 'DONE: add env based checking for whatsapp before testing at staging' (#18) from feature/env-based-whatsapp-cloud into main
Build Docker Image / build-backend (push) Successful in 18s
Reviewed-on: #18
2026-09-28 10:30:11 +08:00
ISMAIL MASSERAN b1934e1627 DONE: add env based checking for whatsapp before testing at staging 2026-09-28 10:26:06 +08:00
ISMAIL MASSERAN 583d936fd2 DONE: update whatsapp message template
Build Docker Image / build-backend (push) Successful in 17s
2026-09-27 15:57:50 +08:00
ISMAIL MASSERAN 1076ecbaf2 DONE: update env.staging
Build Docker Image / build-backend (push) Successful in 26s
2026-09-26 18:51:47 +08:00
ismailmasseran 92894bad44 Merge pull request 'fix: change pembiayaan online batch 2 cutoff from 16:30 to 15:00' (#17) from fix/pembiayaan-online-batch2-cutoff into main
Reviewed-on: #17
2026-09-24 16:15:43 +08:00
nurafrinaalimi16 9916e81dba fix: change pembiayaan online batch 2 cutoff from 16:30 to 15:00 2026-09-24 16:14:44 +08:00
ismailmasseran 7fb7f75050 Feature/whatsapp blast (#16)
Build Docker Image / build-backend (push) Successful in 18s
Co-authored-by: ISMAIL MASSERAN <topaz@Mac.dlinkrouter.local>
Co-authored-by: ISMAIL MASSERAN <topaz@ISMAILs-Macbook.local>
Reviewed-on: #16
2026-09-24 11:27:48 +08:00
ismailmasseran 9fa0172017 Experiment/merge annur (#15)
Build Docker Image / build-backend (push) Successful in 23s
Co-authored-by: ISMAIL MASSERAN <topaz@Mac.dlinkrouter.local>
Reviewed-on: #15
2026-09-20 12:49:53 +08:00
ismailmasseran 79bc41d211 Merge pull request 'feature: include cara terima in reports' (#14) from feature/tagging-cara-terima into main
Reviewed-on: #14
2026-09-20 09:38:22 +08:00
nurafrinaalimi16 2f6f06d3e3 feature: include cara terima in reports 2026-09-17 16:23:46 +08:00
nurafrinaalimi16 50cf966da7 feature/shoraka-integration 2026-09-17 15:08:43 +08:00
ismailmasseran 8de9d872f9 Merge pull request 'feature/modal-online-pendahuluan' (#13) from feature/modal-online-pendahuluan into main
Reviewed-on: #13
2026-09-17 07:58:26 +08:00
nurafrinaalimi16 20268cd136 feature/modal-online-pendahuluan 2026-09-14 15:26:35 +08:00
nurafrinaalimi16 aa4c8ca5ba fix laporan barang kes controller 2026-09-10 09:27:57 +08:00
ismailmasseran 7920eb9f71 Explore/ismail (#12)
Build Docker Image / build-backend (push) Successful in 10s
Co-authored-by: ISMAIL MASSERAN <topaz@ISMAILs-Macbook.local>
Co-authored-by: ISMAIL MASSERAN <topaz@Mac.dlinkrouter.local>
Reviewed-on: #12
Co-authored-by: ismailmasseran <ismail@koppkb.com>
Co-committed-by: ismailmasseran <ismail@koppkb.com>
2026-09-09 14:47:33 +08:00
afrina 5fc212f6af Prod kopkb (#8)
Co-authored-by: nurafrinaalimi16 <nurafrinaalimi16@gmail.com>
Co-authored-by: dev_kopkb <xtrabiz.sb@gmail.com>
Co-authored-by: root <root@erahn.click>
Reviewed-on: #8
Co-authored-by: afrina <afrina@koppkb.com>
Co-committed-by: afrina <afrina@koppkb.com>
2026-09-01 15:09:35 +08:00
ismailmasseran 51860cea37 DONE: add cicd for api_arrahn (#2)
Build Docker Image / build-backend (push) Successful in 8s
Co-authored-by: ISMAIL MASSERAN <topaz@ISMAILs-Macbook.local>
Reviewed-on: #2
2026-08-26 12:03:29 +08:00
afrina f1618c1ffd Rebate anggota (#1)
Co-authored-by: nurafrinaalimi16 <nurafrinaalimi16@gmail.com>
Reviewed-on: #1
2026-08-26 11:40:55 +08:00
nurafrinaalimi16 162f246acc function wtd baru 2026-08-03 12:43:55 +08:00
nurafrinaalimi16 95997841cf Merge remote-tracking branch 'origin/rebate-anggota' into rebate-anggota 2026-06-14 15:58:31 +08:00
nurafrinaalimi16 9ff4393313 include kplama on report marhun 2026-06-14 15:38:38 +08:00
nurafrinaalimi16 5bc5d286b4 fix figure on laporan terkumpul 2026-06-14 15:37:58 +08:00
nurafrinaalimi16 5826f92661 pdpa for koperasi 2026-06-14 15:37:25 +08:00
nurafrinaalimi16 6961697298 include lahad datu on report stok audit 2026-06-14 15:36:33 +08:00
nurafrinaalimi16 482fbbd17e fix bilangan pelanggan baru on report terkumpul 2026-06-14 15:36:33 +08:00
nurafrinaalimi16 cd83999045 disable logs kadar anggota 2026-06-14 15:36:33 +08:00
nurafrinaalimi16 e8da816f35 put excpetion if kadar upah = 0 2026-06-14 15:36:33 +08:00
nurafrinaalimi16 39a2f9a5ef fix kadar upah return 0 2026-06-14 15:36:33 +08:00
nurafrinaalimi16 cc11e3aed6 add column name on operasi approval 2026-06-14 15:36:33 +08:00
nurafrinaalimi16 c7874c15bc fix error url lelong 2026-06-14 15:36:33 +08:00
nurafrinaalimi16 e7650febfc fix url lelong 2026-06-14 15:36:32 +08:00
nurafrinaalimi16 0b1c50676e comment command import csv 2026-06-14 15:36:32 +08:00
nurafrinaalimi16 db9b17f369 import csv anggota koperasi 2026-06-14 15:36:32 +08:00
nurafrinaalimi16 15a3972ae9 route for kadar anggota 2026-06-14 15:36:32 +08:00
nurafrinaalimi16 1717035643 database migration for kadar anggota koperasi 2026-06-14 15:36:32 +08:00
nurafrinaalimi16 a3cb756f6b calculation for kadar anggota koperasi 2026-06-14 15:36:32 +08:00
nurafrinaalimi16 fed2cfea72 update all controllers for kadar anggota koperasi 2026-06-14 15:36:32 +08:00
nurafrinaalimi16 0358878e83 models for kadar anggota koperasi 2026-06-14 15:14:02 +08:00
nurafrinaalimi16 2fa36eabf7 include kplama on report marhun 2026-06-14 15:06:24 +08:00
nurafrinaalimi16 237a5aa44b fix figure on laporan terkumpul 2026-06-14 15:05:02 +08:00
nurafrinaalimi16 e1878e31e4 pdpa for koperasi 2026-06-14 15:02:45 +08:00
Muhd hafifie b669d3f04a Merged in feature/margin-hargaemas-v2 (pull request #408)
Feature/margin hargaemas v2
2026-04-19 07:27:54 +00:00
Hafifie PKB 0dcf421d0e added laporan audit in operasi 2026-04-19 15:10:26 +08:00
Hafifie PKB c76fce70c3 added audit logs for operasi 2026-04-19 15:09:16 +08:00
Hafifie PKB 860b57285d added nilaimarhun for marign teller and pc 2026-04-19 15:09:16 +08:00
Hafifie PKB d540a3461d add validation for editing mutu marhun 2026-04-19 15:09:16 +08:00
Hafifie PKB 041369e049 add specific mutuemas API 2026-04-19 15:09:16 +08:00
Hafifie PKB ab598c12c9 1. added page for edit mutu
2. change from max pinjaman with a static 75% and 80% to manipulate value
2026-04-19 15:09:16 +08:00
nurulnisha muhammad 441a39b80f Merged in fixbugs-wtd-process (pull request #406)
fixbugs-wtd-process
2026-04-12 08:26:57 +00:00
Nisha Muhd 00ee7be836 fixbugs-wtd-process 2026-04-12 12:46:38 +08:00
izzati zulkifli 4eb86b8f27 Merged in izzati-addfeat-laporan-outstanding-v2 (pull request #401)
tambah baik laporan stok audit keseluruhan
2026-02-23 07:26:30 +00:00
Nur Izzati 522727159f tambah baik laporan stok audit keseluruhan 2026-02-23 15:25:29 +08:00
izzati zulkifli 014b49f571 Merged in izzati-addfeat-laporan-outstanding-v2 (pull request #400)
update laporan peti besi
2026-02-22 07:32:01 +00:00
Nur Izzati df734c2482 update laporan peti besi 2026-02-22 15:31:11 +08:00
izzati zulkifli b70bd7ce51 Merged in izzati-addfeat-laporan-outstanding-v2 (pull request #398)
tambah field barang kes dalam laporan outstanding
2026-02-22 06:13:52 +00:00
Nur Izzati edcc0dc0d4 tambah field barang kes dalam laporan outstanding 2026-02-22 14:13:19 +08:00
izzati zulkifli a337b6cfa4 Merged in izzati-feat-uum-bycaw-v2 (pull request #396)
update tast uum bycaw
2026-02-10 02:45:44 +00:00
Nur Izzati a4b235511b update tast uum bycaw 2026-02-10 10:42:18 +08:00
Ain Izazi dbbc18516e fix report petibesi 2026-02-02 12:48:59 +08:00
Ain Izazi 96d70bf2a9 fix report gadaian v2 not sync with v3 2026-02-02 10:44:02 +08:00
izzati zulkifli 6ba62cc7f8 Merged in izzati-hotfix-laporan-lelong-hargaasas (pull request #393)
update lelong keseleuruhan
2026-01-27 08:56:03 +00:00
Nur Izzati 53be2d999e update lelong keseleuruhan 2026-01-27 16:52:00 +08:00
arrahn pkb a28881824f Merged in krk-production (pull request #392)
Add krk changes to prod
2026-01-20 03:44:16 +00:00
Afiq Hamzah 288c10bcc6 Restore composer.json and composer.lock from production 2026-01-20 11:42:43 +08:00
Afiq Hamzah 8fe42c1d9f Refactor tenant database configuration to use dynamic config values
- Replace hardcoded database names (online_arrahn, master_krk, kaunterpkb)
  with configurable values across the codebase
- Rename tennant_master.php to tennant_database.php for clearer naming
- Update config keys: 'database' → 'master_db', add 'kaunter_db'
- Update env variables: TENNANT_MASTER_DB and TENNANT_KAUNTER_DB
- Modify Helper.php to use dynamic database references in
  getOnlineArrahnConnection(), getActiveCawanganDbConnection(),
  and getInactiveCawanganDbConnection() methods
- Update database JOIN queries in CustomerController and MarhunController
  to use config values instead of hardcoded database names
- Simplify migration logic by removing redundant hardcoded database checks
- Update debug messages in CawanganDebugConnections to show actual
  configured database name
- Update bootstrap/app.php to load tennant_database config

This change improves multi-environment flexibility and eliminates
environment-specific hardcoded values, making the application more
configurable for different deployment scenarios (KRK, KGMK, etc).
2026-01-20 11:36:41 +08:00
Afiq Hamzah 0ac42331d4 Add krk changes to prod 2026-01-19 16:41:10 +08:00
izzati zulkifli eae7a0e91a Merged in izzati-hotfix-uum (pull request #390)
update code outstanding
2026-01-18 00:48:34 +00:00
Nur Izzati 7f1a42a4cd update code outstanding 2026-01-18 08:44:37 +08:00
nurulnisha muhammad 1d6ca71ce1 Merged in feature/nisha/checking-wtd (pull request #389)
nisha-checking-wtd
2026-01-15 07:07:55 +00:00
Nisha Muhd ec72c708cd nisha-checking-wtd 2026-01-15 13:07:11 +08:00
izzati zulkifli 1d2c2ed59f Merged in izzati-hotfix-uum (pull request #388)
update nopelanggan dalam task uum
2026-01-15 04:34:56 +00:00
Nur Izzati 302519d723 update nopelanggan dalam task uum 2026-01-15 12:30:25 +08:00
izzati zulkifli f30cdc4d14 Merged in izzati-feat-uum (pull request #385)
update code status aktif pelanggan
2026-01-14 07:36:27 +00:00
Nur Izzati 133d508625 update code status aktif pelanggan 2026-01-14 15:35:29 +08:00
izzati zulkifli 5ac3484199 Merged in izzati-feat-uum (pull request #384)
update task uum
2026-01-14 01:26:34 +00:00
Nur Izzati 8756def2cf update task uum 2026-01-14 09:25:57 +08:00
nurulnisha muhammad 9e25c4a483 Merged in feature/nisha/laporan-wtd (pull request #383)
laporan_rumusan_wtd
2026-01-06 07:52:08 +00:00
Nisha Muhd 2aff962981 laporan_rumusan_wtd 2026-01-06 12:52:02 +08:00
nurulnisha muhammad ccd7447276 Merged in feature/nisha-update-wtd (pull request #378)
update wtd
2025-12-21 03:17:10 +00:00
BTMS3 3a8e74c368 update wtd 2025-12-21 11:15:57 +08:00
nur amirah zulkipli 573ad8a23f Merged in feature/saringan-gadaian-kb1 (pull request #377)
Add reporting saringan gadaian kb1
2025-12-15 03:10:22 +00:00
Amirah e983374a90 Add reporting saringan gadaian kb1 2025-12-15 11:09:24 +08:00
izzati zulkifli 8cce0193b0 Merged in izzati-hotfix-laporan-SA-all (pull request #372)
tambah list laporan SA lelong_tawarruq = 0
2025-11-26 08:35:29 +00:00
Nur Izzati e8e94c714b tambah list laporan SA lelong_tawarruq = 0 2025-11-26 16:35:07 +08:00
Muhd hafifie 67d990ab57 Merged in feature/customerlelong-v2 (pull request #370)
fix certain for sttebus = 'L' only
2025-11-15 15:31:01 +00:00
Hafifie PKB a1f3bc29f5 fix certain for sttebus = 'L' only 2025-11-15 23:23:14 +08:00
izzati zulkifli fb2726eacc Merged in izzati-lelong-batu-permata (pull request #366)
update lelong batu permata
2025-10-30 03:36:17 +00:00
Nur Izzati 0184a0f3e1 update lelong batu permata 2025-10-30 11:16:16 +08:00
Aiman Mazlan 9fdeb7ae4f Merged in feature/aiman/laporan-aliran-tunai-teller (pull request #365)
Feature/aiman/laporan aliran tunai teller

Approved-by: Muhd hafifie
2025-10-27 07:56:14 +00:00
aiman ece4210134 Filter bakiLelong by kodlaluan teller 2025-10-15 14:55:26 +08:00
aiman a42d36f69b Enable datepicker, new route for laporan aliran tunai teller 2025-10-14 15:55:24 +08:00
aiman 04bc76ef89 Add Baki Lelong for laporan aliran tunai teller 2025-10-12 12:38:00 +08:00
Ain Izazi Aziz de65d4d4fd Merged in hotfixreportbrgkes (pull request #363)
add radio button for report brg kes
2025-10-12 03:53:14 +00:00
Ain Izazi d21ad0f129 add radio button for report brg kes 2025-10-12 11:52:18 +08:00
izzati zulkifli f756e3b791 Merged in izzati-hotfix-tlelong-dlm-gadai (pull request #361)
update export lelong to api
2025-10-07 08:10:40 +00:00
Nur Izzati a8b63af59f update export lelong to api 2025-10-07 15:47:29 +08:00
Afiq Hamzah bfb3af49c8 Remove duplicated methods 2025-10-05 16:48:39 +08:00
Afiq Hamzah c80a676429 Resolve remaining merge conflicts 2025-10-05 16:29:00 +08:00
Afiq Hamzah 95ae90b96e Resolve merge conflicts 2025-10-05 16:23:17 +08:00
Afiq Hamzah 6d80afed8f Continue dev on pampasan module 2025-10-05 16:16:22 +08:00
aiman 0a290029a5 Tukar step pampasan v3 ke v2: 2025-10-05 16:14:54 +08:00
Ain Izazi 13a15c354f function barangkes di adminv2 2025-10-05 16:13:06 +08:00
Hafifie PKB 9c5b041497 fix specific norujukan 2025-10-05 16:11:05 +08:00
Nur Izzati 83228e8b2e tambah t_lelong dlm gadai 2025-10-05 16:11:05 +08:00
hafifierahman e320ac8219 added customer info lelong for arrahnpay 2025-10-05 16:11:05 +08:00
hafifierahman 642d9f01bb fix backdate stock audit that exceed 12 months 2025-10-05 16:11:05 +08:00
Ain Izazi Aziz 95555cd195 Merged in adminfeatbarangkesv2 (pull request #358)
function barangkes di adminv2
2025-10-05 03:45:34 +00:00
Ain Izazi 233b95b2d9 function barangkes di adminv2 2025-10-05 11:43:15 +08:00
Muhd hafifie 4200b6c305 Merged in feature/customerlelong-v2 (pull request #356)
fix specific norujukan
2025-10-05 02:00:21 +00:00
Hafifie PKB b76f804d44 fix specific norujukan 2025-10-05 09:59:37 +08:00
izzati zulkifli c76fdfaf7f Merged in izzati-hotfix-tlelong-dlm-gadai (pull request #354)
tambah t_lelong dlm gadai
2025-10-05 01:53:49 +00:00
Nur Izzati b375073520 tambah t_lelong dlm gadai 2025-10-05 09:18:47 +08:00
Muhd hafifie d878b23217 Merged in feature/customerlelong-v2 (pull request #352)
added customer info lelong for arrahnpay
2025-10-04 14:49:43 +00:00
hafifierahman 908e617431 added customer info lelong for arrahnpay 2025-10-04 22:48:45 +08:00
Muhd hafifie 66a92c2566 Merged in bugfix/stokaudit-backdate (pull request #350)
fix backdate stock audit that exceed 12 months
2025-10-01 13:47:51 +00:00
hafifierahman 177b253817 fix backdate stock audit that exceed 12 months 2025-10-01 21:35:44 +08:00
Afiq Hamzah a858d6d59d Add conditional peloperasi field for pampasan withdrawal approvals
- Implement dynamic field assignment for Kelulusan Pengeluaran Pampasan status
- Add peloperasi field only when status matches and request parameter exists
- Improve data structure handling in approval creation workflow
- Enhance conditional logic for pampasan-specific approval requirements
2025-10-01 20:07:28 +08:00
Afiq Hamzah 0a9140c85d Enhance audit trail system for approval workflows
- Add conditional audit data enrichment for specific approval actions
- Implement namapelulus and norujukan tracking for pampasan approvals
- Improve audit data structure handling for scalar and array values
- Add user field validation with fallback defaults for audit records
- Optimize approval controller with better request parameter handling
2025-09-25 10:58:58 +08:00
Nur Izzati 632c2b5190 tambah baik laporan SA by sag 2025-09-23 11:23:53 +08:00
Ain Izazi 3b44cec5a2 add di function customer nota arcc 2025-09-23 11:23:53 +08:00
Afiq Hamzah f5874b956d Update pampasan module with enhanced agreement tracking and semporna processing
- Add agreement_printed_at timestamp to pampasan table for tracking printed agreements
- Update jenis handling for semporna v2 processing logic
- Refactor barangkes controller eager loading for improved performance
- Remove obsolete jenis migration files and consolidate schema changes
- Update related models and controllers for semporna v2 compatibility
2025-09-22 13:00:08 +08:00
Afiq Hamzah 38dd97c4f2 Register pampasan to panjar after tuntutan made by pelanggan 2025-09-17 14:14:06 +08:00
Afiq Hamzah 59aeeb57f9 Continue dev on pampasan module 2025-09-17 12:39:10 +08:00
aiman 977fab853d Tukar step pampasan v3 ke v2: 2025-09-15 13:55:30 +08:00
aiman 1f27d90698 Tukar step pampasan v3 ke v2: 2025-09-14 16:03:49 +08:00
272 changed files with 18242 additions and 8909 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
+28
View File
@@ -0,0 +1,28 @@
APP_NAME=Lumen
APP_ENV=production
APP_KEY=WbPGkYNmXZ6ZQplGlvl0iZ7zyW1b09xa
APP_DEBUG=true
APP_URL=https://apiannurv2.erahn.com.my
APP_TIMEZONE=Asia/Kuala_Lumpur
LOG_CHANNEL=stack
LOG_SLACK_WEBHOOK_URL=
DB_CONNECTION_2=mysql
DB_HOST_2=192.168.1.216
DB_PORT_2=3306
DB_DATABASE_2=annur_tampoi
DB_USERNAME_2=spider
DB_PASSWORD_2=arrahn@123
DB_CONNECTION_7=mysql
DB_HOST_7=192.168.1.216
DB_PORT_7=3306
DB_DATABASE_7=master_annur
DB_USERNAME_7=spider
DB_PASSWORD_7=arrahn@123
CACHE_DRIVER=file
QUEUE_CONNECTION=sync
TENNANT_MASTER_DB=master_annur
+82
View File
@@ -0,0 +1,82 @@
APP_NAME=Lumen
APP_ENV=local
APP_KEY=WbPGkYNmXZ6ZQplGlvl0iZ7zyW1b09xa
APP_DEBUG=true
APP_URL=http://localhost
APP_TIMEZONE=Asia/Kuala_Lumpur
API_ARRAHNBID=http://lelong.test/api/
API_ARRAHNBID_ANNUR=http://lelong-annur.test/api/
LOG_CHANNEL=stack
LOG_LEVEL=debug
LOG_SLACK_WEBHOOK_URL=
DB_CONNECTION=mysql
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=erahn_xtra
DB_USERNAME=root
DB_PASSWORD=root
DB_CONNECTION_7=mysql
DB_HOST_7=mysql
DB_PORT_7=3306
DB_DATABASE_7=master_erahn
DB_USERNAME_7=root
DB_PASSWORD_7=root
# DB_CONNECTION_3=mysql
# DB_HOST_3=127.0.0.1
# DB_PORT_3=3306
# DB_DATABASE_3=erahn_baling
# DB_USERNAME_3=root
# DB_PASSWORD_3=root
# DB_CONNECTION_4=mysql
# DB_HOST_4=127.0.0.1
# DB_PORT_4=3306
# DB_DATABASE_4=arrahn_ld
# DB_USERNAME_4=root
# DB_PASSWORD_4=root
# DB_CONNECTION_5=mysql
# DB_HOST_5=127.0.0.1
# DB_PORT_5=3306
# DB_DATABASE_5=erahn_segamat
# DB_USERNAME_5=root
# DB_PASSWORD_5=root
# DB_CONNECTION_6=mysql
# DB_HOST_6=127.0.0.1
# DB_PORT_6=3306
# DB_DATABASE_6=arrahn_kb2
# DB_USERNAME_6=spider
# DB_PASSWORD_6=G0dz!ll42020
# DB_CONNECTION_8=mysql
# DB_HOST_8=127.0.0.1
# DB_PORT_8=3306
# DB_DATABASE_8=arrahn_klanas
# DB_USERNAME_8=spider
# DB_PASSWORD_8=G0dz!ll42020
TENNANT_MASTER_DB=master_erahn
TENNANT_KAUNTER_DB=kaunter_erahn
KAUNTER_URL=http://host.docker.internal:8081
INTERNAL_NOTIFICATION_SECRET=erahn-local-notification-secret
CACHE_DRIVER=file
QUEUE_CONNECTION=sync
BSAS_BASE_URL=https://bsasdevapi.bursamalaysia.com/api
BSAS_TOKEN_URL=https://bsasdevapi.bursamalaysia.com/oauth/token
BSAS_CLIENT_ID=
BSAS_CLIENT_SECRET=
BSAS_MEMBER_SHORT_NAME=KOOPPKB
BSAS_PRODUCT_CODE=CPO-MSIA-09
BSAS_TENOR=00088
BSAS_MAX_WAIT_TIME=10
BSAS_HTTP_TIMEOUT=30
BSAS_ORDER_RESULT_RETRIES=2
+27 -163
View File
@@ -5,6 +5,7 @@ APP_DEBUG=true
APP_URL=http://api_arrahn.test
APP_TIMEZONE=Asia/Kuala_Lumpur
API_ARRAHNBID=http://lelong.test/api/
API_ARRAHNBID_ANNUR=http://lelong-annur.test/api/
LOG_CHANNEL=stack
LOG_SLACK_WEBHOOK_URL=
@@ -86,172 +87,35 @@ DB_DATABASE_11=arrahn_pputeh
DB_USERNAME_11=spider
DB_PASSWORD_11=G0dz!ll42020
DB_CONNECTION_12=mysql
DB_HOST_12=127.0.0.1
DB_PORT_12=3306
DB_DATABASE_12=arrahn_rpanjang
DB_USERNAME_12=spider
DB_PASSWORD_12=G0dz!ll42020
DB_CONNECTION_13=mysql
DB_HOST_13=127.0.0.1
DB_PORT_13=3306
DB_DATABASE_13=arrahn_tmerah
DB_USERNAME_13=spider
DB_PASSWORD_13=G0dz!ll42020
DB_CONNECTION_14=mysql
DB_HOST_14=127.0.0.1
DB_PORT_14=3306
DB_DATABASE_14=arrahn_wb
DB_USERNAME_14=spider
DB_PASSWORD_14=G0dz!ll42020
DB_CONNECTION_15=mysql
DB_HOST_15=127.0.0.1
DB_PORT_15=3306
DB_DATABASE_15=arrahn_wcy
DB_USERNAME_15=spider
DB_PASSWORD_15=G0dz!ll42020
DB_CONNECTION_16=mysql
DB_HOST_16=127.0.0.1
DB_PORT_16=3306
DB_DATABASE_16=arrahn_bentong
DB_USERNAME_16=spider
DB_PASSWORD_16=G0dz!ll42020
DB_CONNECTION_17=mysql
DB_HOST_17=192.168.0.102
DB_PORT_17=3306
DB_DATABASE_17=arrahn_kajang
DB_USERNAME_17=spider
DB_PASSWORD_17=G0dz!ll42020
DB_CONNECTION_18=mysql
DB_HOST_18=192.168.0.102
DB_PORT_18=3306
DB_DATABASE_18=arrahn_kangar
DB_USERNAME_18=spider
DB_PASSWORD_18=G0dz!ll42020
DB_CONNECTION_19=mysql
DB_HOST_19=192.168.0.102
DB_PORT_19=3306
DB_DATABASE_19=arrahn_kuantan
DB_USERNAME_19=spider
DB_PASSWORD_19=G0dz!ll42020
DB_CONNECTION_20=mysql
DB_HOST_20=192.168.0.102
DB_PORT_20=3306
DB_DATABASE_20=arrahn_pgudang
DB_USERNAME_20=spider
DB_PASSWORD_20=G0dz!ll42020
DB_CONNECTION_21=mysql
DB_HOST_21=192.168.0.102
DB_PORT_21=3306
DB_DATABASE_21=arrahn_raub
DB_USERNAME_21=spider
DB_PASSWORD_21=G0dz!ll42020
DB_CONNECTION_22=mysql
DB_HOST_22=192.168.0.102
DB_PORT_22=3306
DB_DATABASE_22=arrahn_rlaut
DB_USERNAME_22=spider
DB_PASSWORD_22=G0dz!ll42020
DB_CONNECTION_23=mysql
DB_HOST_23=192.168.0.102
DB_PORT_23=3306
DB_DATABASE_23=arrahn_skudai
DB_USERNAME_23=spider
DB_PASSWORD_23=G0dz!ll42020
DB_CONNECTION_24=mysql
DB_HOST_24=192.168.0.102
DB_PORT_24=3306
DB_DATABASE_24=arrahn_spetani
DB_USERNAME_24=spider
DB_PASSWORD_24=G0dz!ll42020
DB_CONNECTION_25=mysql
DB_HOST_25=192.168.0.102
DB_PORT_25=3306
DB_DATABASE_25=arrahn_temerloh
DB_USERNAME_25=spider
DB_PASSWORD_25=G0dz!ll42020
DB_CONNECTION_26=mysql
DB_HOST_26=192.168.0.103
DB_PORT_26=3306
DB_DATABASE_26=arrahn_api2
DB_USERNAME_26=spider
DB_PASSWORD_26=G0dz!ll42020
DB_CONNECTION_27=mysql
DB_HOST_27=192.168.0.103
DB_PORT_27=3306
DB_DATABASE_27=arrahn_beaufort
DB_USERNAME_27=spider
DB_PASSWORD_27=G0dz!ll42020
DB_CONNECTION_28=mysql
DB_HOST_28=192.168.0.103
DB_PORT_28=3306
DB_DATABASE_28=arrahn_kbelud
DB_USERNAME_28=spider
DB_PASSWORD_28=G0dz!ll42020
DB_CONNECTION_29=mysql
DB_HOST_29=192.168.0.103
DB_PORT_29=3306
DB_DATABASE_29=arrahn_keningau
DB_USERNAME_29=spider
DB_PASSWORD_29=G0dz!ll42020
DB_CONNECTION_30=mysql
DB_HOST_30=192.168.0.103
DB_PORT_30=3306
DB_DATABASE_30=arrahn_putatan
DB_USERNAME_30=spider
DB_PASSWORD_30=G0dz!ll42020
DB_CONNECTION_31=mysql
DB_HOST_31=192.168.0.103
DB_PORT_31=3306
DB_DATABASE_31=arrahn_sandakan
DB_USERNAME_31=spider
DB_PASSWORD_31=G0dz!ll42020
DB_CONNECTION_32=mysql
DB_HOST_32=192.168.0.103
DB_PORT_32=3306
DB_DATABASE_32=arrahn_tawau
DB_USERNAME_32=spider
DB_PASSWORD_32=G0dz!ll42020
DB_CONNECTION_33=mysql
DB_HOST_33=192.168.0.103
DB_PORT_33=3306
DB_DATABASE_33=arrahn_tuaran
DB_USERNAME_33=spider
DB_PASSWORD_33=G0dz!ll42020
CACHE_DRIVER=file
QUEUE_CONNECTION=sync
WABOT_API=https://md.wabot.my/api/send.php
WABOT_INSTANCES_ID=63F40BD120DB4
# WABOT_INSTANCES_ID=64892FD7440A6
WABOT_TOKEN_KEYS=cbee7e95cf3ffbfa0531d5fddfd44f87
MAX_MUTUEMAS=true
TENNANT_MASTER_DB=master_erahn
TENNANT_KAUNTER_DB=kaunter_erahn
ONEWAYSMS_API=http://gateway.onewaysms.com.my:10001/api.aspx
ONEWAYSMS_AUTH_USER=APIUK330HDAFO
ONEWAYSMS_AUTH_PASS=APIUK330HDAFOUK330
KAUNTER_URL=http://host.docker.internal:8081
INTERNAL_NOTIFICATION_SECRET=
ADMIN_PHONE_NUMBER=60178916936
ACTIVE_JOB_SERVER=false
LOGVIEWER_PASSWORD=
WHATSAPP_TOKEN= # Paste token from Graph API explorer.
WHATSAPP_PHONE_NUMBER_ID=1323449077519027
WHATSAPP_API_VERSION=v25.0
WHATSAPP_TEMPLATE=hello_world
WHATSAPP_TEMPLATE_LANG=en_US
WHATSAPP_VERIFY_TOKEN= # webhook verify token
WHATSAPP_APP_SECRET= # X-Hub-Signature-256 checks; need to be enable first
ADMIN_PHONE_NUMBER=60193450165
BSAS_BASE_URL=https://bsasdevapi.bursamalaysia.com/api
BSAS_TOKEN_URL=https://bsasdevapi.bursamalaysia.com/oauth/token
BSAS_CLIENT_ID=
BSAS_CLIENT_SECRET=
BSAS_MEMBER_SHORT_NAME=KOOPPKB
BSAS_PRODUCT_CODE=CPO-MSIA-09
BSAS_TENOR=00088
BSAS_MAX_WAIT_TIME=10
BSAS_HTTP_TIMEOUT=30
BSAS_ORDER_RESULT_RETRIES=2
+89
View File
@@ -0,0 +1,89 @@
APP_NAME=Lumen
APP_ENV=production
APP_KEY=WbPGkYNmXZ6ZQplGlvl0iZ7zyW1b09xa
APP_DEBUG=false
APP_URL=https://apiujrah.erahn.com.my
APP_TIMEZONE=Asia/Kuala_Lumpur
API_ARRAHNBID=https://adminbid-kopkb.erahn.com.my/api/
API_ARRAHNBID_ANNUR=https://adminbid-annur.erahn.com.my/api/
LOG_CHANNEL=stack
LOG_SLACK_WEBHOOK_URL=
LOGVIEWER_PASSWORD="$2y$10$k7eiUyZdm3BruwAaT1gbmelTm.PPgZptOEQT1nlBjyiR84a4KBMlm"
DB_CONNECTION_2=mysql
DB_HOST_2=192.168.1.216
DB_PORT_2=3306
DB_DATABASE_2=erahn_xtra
DB_USERNAME_2=spider
DB_PASSWORD_2=arrahn@123
DB_CONNECTION_3=mysql
DB_HOST_3=192.168.1.216
DB_PORT_3=3306
DB_DATABASE_3=erahn_baling
DB_USERNAME_3=spider
DB_PASSWORD_3=arrahn@123
DB_CONNECTION_4=mysql
DB_HOST_4=192.168.1.216
DB_PORT_4=3306
DB_DATABASE_4=arrahn_ld
DB_USERNAME_4=spider
DB_PASSWORD_4=arrahn@123
DB_CONNECTION_5=mysql
DB_HOST_5=192.168.1.216
DB_PORT_5=3306
DB_DATABASE_5=erahn_segamat
DB_USERNAME_5=spider
DB_PASSWORD_5=arrahn@123
DB_CONNECTION_6=mysql
DB_HOST_6=192.168.1.216
DB_PORT_6=3306
DB_DATABASE_6=erahn_terengganu
DB_USERNAME_6=spider
DB_PASSWORD_6=arrahn@123
DB_CONNECTION_8=mysql
DB_HOST_8=192.168.1.216
DB_PORT_8=3306
DB_DATABASE_8=erahn_benoni
DB_USERNAME_8=spider
DB_PASSWORD_8=arrahn@123
DB_CONNECTION_7=mysql
DB_HOST_7=192.168.1.216
DB_PORT_7=3306
DB_DATABASE_7=master_erahn
DB_USERNAME_7=spider
DB_PASSWORD_7=arrahn@123
DB_CONNECTION_9=mysql
DB_HOST_9=192.168.1.216
DB_PORT_9=3306
DB_DATABASE_9=annur_tampoi
DB_USERNAME_9=spider
DB_PASSWORD_9=arrahn@123
CACHE_DRIVER=file
QUEUE_CONNECTION=sync
MAX_MUTUEMAS=true
TENNANT_MASTER_DB=master_erahn
TENNANT_KAUNTER_DB=kaunter_erahn
KAUNTER_URL=https://ujrah.erahn.com.my
INTERNAL_NOTIFICATION_SECRET="nIDTe(u@6[)VLJyd"
WHATSAPP_CLOUD_ENABLED=true
WHATSAPP_TOKEN=EAAZAonT56AO0BSnZAhWLi4UXDRT6iAldbiybxSQZAnGrzIke1QiNWvQkZAbQQTjWeVZC5bL1gEBhW6ff3Shf79nowXiTQEwyxqgZAZB6ZATwFCeTs8MZA5H6ZA76IJBuVqRiAZBvZCfkeSurbUNpEWyi9W7zsT2GWiZCLX8meX0KCfDHu7cp4aBgVu1Ps5ZCRO3bP4ZCQZDZD
WHATSAPP_PHONE_NUMBER_ID=1375899418935337
WHATSAPP_API_VERSION=v25.0
WHATSAPP_TEMPLATE=hebahan_pembiayaan_online
WHATSAPP_TEMPLATE_LANG=ms
WHATSAPP_VERIFY_TOKEN=418ed86d49422071681c76bed25ae1cb7e90456c794a92f1c9ea417945a35a0a
WHATSAPP_APP_SECRET=
ADMIN_PHONE_NUMBER=
+100
View File
@@ -0,0 +1,100 @@
APP_NAME=Lumen
APP_ENV=staging
APP_KEY=WbPGkYNmXZ6ZQplGlvl0iZ7zyW1b09xa
APP_DEBUG=true
APP_URL=http://127.0.0.1:8090
APP_TIMEZONE=Asia/Kuala_Lumpur
API_ARRAHNBID=http://lelong.test/api/
API_ARRAHNBID_ANNUR=http://lelong-annur.test/api/
HTTP_PORT=8090
MYSQL_PUBLISH_PORT=3308
LOG_CHANNEL=stack
LOG_LEVEL=debug
LOG_SLACK_WEBHOOK_URL=
LOGVIEWER_PASSWORD=test123
DB_CONNECTION=mysql
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=erahn_xtra
DB_USERNAME=root
DB_PASSWORD=root
DB_CONNECTION_7=mysql
DB_HOST_7=mysql
DB_PORT_7=3306
DB_DATABASE_7=master_erahn
DB_USERNAME_7=root
DB_PASSWORD_7=root
DB_CONNECTION_2=mysql
DB_HOST_2=mysql
DB_PORT_2=3306
DB_DATABASE_2=erahn_xtra
DB_USERNAME_2=root
DB_PASSWORD_2=root
DB_CONNECTION_3=mysql
DB_HOST_3=mysql
DB_PORT_3=3306
DB_DATABASE_3=erahn_baling
DB_USERNAME_3=root
DB_PASSWORD_3=root
DB_CONNECTION_4=mysql
DB_HOST_4=mysql
DB_PORT_4=3306
DB_DATABASE_4=arrahn_ld
DB_USERNAME_4=root
DB_PASSWORD_4=root
DB_CONNECTION_5=mysql
DB_HOST_5=mysql
DB_PORT_5=3306
DB_DATABASE_5=erahn_segamat
DB_USERNAME_5=root
DB_PASSWORD_5=root
DB_CONNECTION_7=mysql
DB_HOST_7=mysql
DB_PORT_7=3306
DB_DATABASE_7=master_erahn
DB_USERNAME_7=root
DB_PASSWORD_7=root
DB_CONNECTION_6=mysql
DB_HOST_6=mysql
DB_PORT_6=3306
DB_DATABASE_6=erahn_test
DB_USERNAME_6=root
DB_PASSWORD_6=root
DB_CONNECTION_9=mysql
DB_HOST_9=mysql
DB_PORT_9=3306
DB_DATABASE_9=annur_tampoi
DB_USERNAME_9=root
DB_PASSWORD_9=root
CACHE_DRIVER=file
QUEUE_CONNECTION=sync
MAX_MUTUEMAS=true
TENNANT_MASTER_DB=master_erahn
TENNANT_KAUNTER_DB=kaunter_erahn
KAUNTER_URL=https://staging.erahn.com.my/login
INTERNAL_NOTIFICATION_SECRET=staging_noti_secret
WHATSAPP_CLOUD_ENABLED=false
WHATSAPP_TOKEN=EAAZAonT56AO0BSnZAhWLi4UXDRT6iAldbiybxSQZAnGrzIke1QiNWvQkZAbQQTjWeVZC5bL1gEBhW6ff3Shf79nowXiTQEwyxqgZAZB6ZATwFCeTs8MZA5H6ZA76IJBuVqRiAZBvZCfkeSurbUNpEWyi9W7zsT2GWiZCLX8meX0KCfDHu7cp4aBgVu1Ps5ZCRO3bP4ZCQZDZD
WHATSAPP_PHONE_NUMBER_ID=1375899418935337
WHATSAPP_API_VERSION=v25.0
WHATSAPP_TEMPLATE=hebahan_pembiayaan_online
WHATSAPP_TEMPLATE_LANG=ms
WHATSAPP_VERIFY_TOKEN=418ed86d49422071681c76bed25ae1cb7e90456c794a92f1c9ea417945a35a0a
WHATSAPP_APP_SECRET=
ADMIN_PHONE_NUMBER=
-244
View File
@@ -1,244 +0,0 @@
APP_NAME=Lumen
APP_ENV=local
APP_KEY=WbPGkYNmXZ6ZQplGlvl0iZ7zyW1b09xa
APP_DEBUG=false
APP_URL=https://api.arrahn.com.my
APP_TIMEZONE=Asia/Kuala_Lumpur
LOG_CHANNEL=stack
LOG_SLACK_WEBHOOK_URL=
DB_CONNECTION=mysql
DB_HOST=172.16.4.102
DB_PORT=3306
DB_DATABASE=arrahn_kb2
DB_USERNAME=zone2
DB_PASSWORD=G0dz!ll4@2020
DB_CONNECTION_2=mysql
DB_HOST_2=172.16.4.102
DB_PORT_2=3306
DB_DATABASE_2=arrahn_wcy
DB_USERNAME_2=zone2
DB_PASSWORD_2=G0dz!ll4@2020
DB_CONNECTION_3=mysql
DB_HOST_3=172.16.4.102
DB_PORT_3=3306
DB_DATABASE_3=arrahn_1stop
DB_USERNAME_3=zone2
DB_PASSWORD_3=G0dz!ll4@2020
DB_CONNECTION_4=mysql
DB_HOST_4=172.16.4.102
DB_PORT_4=3306
DB_DATABASE_4=arrahn_jlwt
DB_USERNAME_4=zone2
DB_PASSWORD_4=G0dz!ll4@2020
DB_CONNECTION_5=mysql
DB_HOST_5=172.16.4.102
DB_PORT_5=3306
DB_DATABASE_5=arrahn_wb
DB_USERNAME_5=zone2
DB_PASSWORD_5=G0dz!ll4@2020
DB_CONNECTION_6=mysql
DB_HOST_6=172.16.4.102
DB_PORT_6=3306
DB_DATABASE_6=arrahn_kls
DB_USERNAME_6=zone2
DB_PASSWORD_6=G0dz!ll4@2020
DB_CONNECTION_7=mysql
DB_HOST_7=172.16.4.102
DB_PORT_7=3306
DB_DATABASE_7=online_arrahn
DB_USERNAME_7=zone2
DB_PASSWORD_7=G0dz!ll4@2020
DB_CONNECTION_8=mysql
DB_HOST_8=172.16.4.102
DB_PORT_8=3306
DB_DATABASE_8=arrahn_pp
DB_USERNAME_8=zone2
DB_PASSWORD_8=G0dz!ll4@2020
DB_CONNECTION_9=mysql
DB_HOST_9=172.16.4.102
DB_PORT_9=3306
DB_DATABASE_9=arrahn_mace
DB_USERNAME_9=zone2
DB_PASSWORD_9=G0dz!ll4@2020
DB_CONNECTION_10=mysql
DB_HOST_10=172.16.4.102
DB_PORT_10=3306
DB_DATABASE_10=arrahn_pm
DB_USERNAME_10=zone2
DB_PASSWORD_10=G0dz!ll4@2020
DB_CONNECTION_11=mysql
DB_HOST_11=172.16.4.102
DB_PORT_11=3306
DB_DATABASE_11=arrahn_gm
DB_USERNAME_11=zone2
DB_PASSWORD_11=G0dz!ll4@2020
DB_CONNECTION_12=mysql
DB_HOST_12172.16.4.102
DB_PORT_12=3306
DB_DATABASE_12=arrahn_gcl
DB_USERNAME_12=zone2
DB_PASSWORD_12=G0dz!ll4@2020
DB_CONNECTION_13=mysql
DB_HOST_13=172.16.4.102
DB_PORT_13=3306
DB_DATABASE_13=arrahn_jli
DB_USERNAME_13=zone2
DB_PASSWORD_13=G0dz!ll4@2020
DB_CONNECTION_14=mysql
DB_HOST_14=172.16.4.102
DB_PORT_14=3306
DB_DATABASE_14=arrahn_rp
DB_USERNAME_14=zone2
DB_PASSWORD_14=G0dz!ll4@2020
DB_CONNECTION_15=mysql
DB_HOST_15=172.16.4.102
DB_PORT_15=3306
DB_DATABASE_15=arrahn_ktn
DB_USERNAME_15=zone2
DB_PASSWORD_15=G0dz!ll4@2020
DB_CONNECTION_16=mysql
DB_HOST_16=172.16.4.102
DB_PORT_16=3306
DB_DATABASE_16=arrahn_pg
DB_USERNAME_16=zone2
DB_PASSWORD_16=G0dz!ll4@2020
DB_CONNECTION_17=mysql
DB_HOST_17=172.16.4.102
DB_PORT_17=3306
DB_DATABASE_17=arrahn_skudai
DB_USERNAME_17=zone2
DB_PASSWORD_17=G0dz!ll4@2020
DB_CONNECTION_18=mysql
DB_HOST_18=172.16.4.102
DB_PORT_18=3306
DB_DATABASE_18=arrahn_pttn
DB_USERNAME_18=zone2
DB_PASSWORD_18=G0dz!ll4@2020
DB_CONNECTION_19=mysql
DB_HOST_19=172.16.4.102
DB_PORT_19=3306
DB_DATABASE_19=arrahn_btg
DB_USERNAME_19=zone2
DB_PASSWORD_19=G0dz!ll4@2020
DB_CONNECTION_20=mysql
DB_HOST_20=172.16.4.102
DB_PORT_20=3306
DB_DATABASE_20=arrahn_raub
DB_USERNAME_20=zone2
DB_PASSWORD_20=G0dz!ll4@2020
DB_CONNECTION_21=mysql
DB_HOST_21=172.16.4.102
DB_PORT_21=3306
DB_DATABASE_21=arrahn_kgr
DB_USERNAME_21=zone2
DB_PASSWORD_21=G0dz!ll4@2020
DB_CONNECTION_22=mysql
DB_HOST_22=172.16.4.102
DB_PORT_22=3306
DB_DATABASE_22=arrahn_sdk
DB_USERNAME_22=zone2
DB_PASSWORD_22=G0dz!ll4@2020
DB_CONNECTION_23=mysql
DB_HOST_23=172.16.4.102
DB_PORT_23=3306
DB_DATABASE_23=arrahn_sp
DB_USERNAME_23=zone2
DB_PASSWORD_23=G0dz!ll4@2020
DB_CONNECTION_24=mysql
DB_HOST_24=172.16.4.102
DB_PORT_24=3306
DB_DATABASE_24=arrahn_twu
DB_USERNAME_24=zone2
DB_PASSWORD_24=G0dz!ll4@2020
DB_CONNECTION_25=mysql
DB_HOST_25=172.16.4.102
DB_PORT_25=3306
DB_DATABASE_25=arrahn_api2
DB_USERNAME_25=zone2
DB_PASSWORD_25=G0dz!ll4@2020
DB_CONNECTION_26=mysql
DB_HOST_26=172.16.4.102
DB_PORT_26=3306
DB_DATABASE_26=arrahn_kjg
DB_USERNAME_26=zone2
DB_PASSWORD_26=G0dz!ll4@2020
DB_CONNECTION_27=mysql
DB_HOST_27=172.16.4.102
DB_PORT_27=3306
DB_DATABASE_27=arrahn_rl
DB_USERNAME_27=zone2
DB_PASSWORD_27=G0dz!ll4@2020
DB_CONNECTION_28=mysql
DB_HOST_28=172.16.4.102
DB_PORT_28=3306
DB_DATABASE_28=arrahn_bfort
DB_USERNAME_28=zone2
DB_PASSWORD_28=G0dz!ll4@2020
DB_CONNECTION_29=mysql
DB_HOST_29=172.16.4.102
DB_PORT_29=3306
DB_DATABASE_29=arrahn_kbelud
DB_USERNAME_29=zone2
DB_PASSWORD_29=G0dz!ll4@2020
DB_CONNECTION_30=mysql
DB_HOST_30=172.16.4.102
DB_PORT_30=3306
DB_DATABASE_30=arrahn_tuaran
DB_USERNAME_30=zone2
DB_PASSWORD_30=G0dz!ll4@2020
DB_CONNECTION_31=mysql
DB_HOST_31=172.16.4.102
DB_PORT_31=3306
DB_DATABASE_31=arrahn_kngau
DB_USERNAME_31=zone2
DB_PASSWORD_31=G0dz!ll4@2020
DB_CONNECTION_32=mysql
DB_HOST_32=172.16.4.102
DB_PORT_32=3306
DB_DATABASE_32=arrahn_tm
DB_USERNAME_32=zone2
DB_PASSWORD_32=G0dz!ll4@2020
DB_CONNECTION_33=mysql
DB_HOST_33=172.16.4.102
DB_PORT_33=3306
DB_DATABASE_33=arrahn_tmlh
DB_USERNAME_33=zone2
DB_PASSWORD_33=G0dz!ll4@2020
CACHE_DRIVER=file
QUEUE_CONNECTION=sync
+49
View File
@@ -0,0 +1,49 @@
name: Build Docker Image
on:
push:
branches:
- staging
tags:
- "v*"
workflow_dispatch:
jobs:
build-backend:
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 backend image
run: |
set -e
IMAGE="git.koppkb.com/kopkb/api_arrahn"
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 backend: ${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 .
+141
View File
@@ -0,0 +1,141 @@
name: Deploy to Staging
on:
push:
tags:
- "v*"
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/api_arrahn
DEPLOY_DIR: /home/arrahn/Project/api_arrahn
jobs:
deploy:
runs-on: host
steps:
- name: Resolve image tag
id: resolve
run: |
set -e
if [ "${{ gitea.event_name }}" = "workflow_dispatch" ]; then
IMAGE_TAG="${{ inputs.image_tag }}"
else
IMAGE_TAG="${{ gitea.ref_name }}"
fi
echo "IMAGE_TAG=${IMAGE_TAG}" >> "$GITHUB_OUTPUT"
echo "Deploying image tag: ${IMAGE_TAG}"
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ steps.resolve.outputs.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="${{ steps.resolve.outputs.IMAGE_TAG }}"
IMAGE="${APP_IMAGE}:${IMAGE_TAG}"
echo "Checking if image exists: ${IMAGE}"
# Tag pushes build in parallel — wait for the image to appear
for i in $(seq 1 30); do
if docker manifest inspect "${IMAGE}" > /dev/null 2>&1; then
echo "✓ ${IMAGE} found"
exit 0
fi
echo "Image not ready yet (attempt ${i}/30), waiting 20s..."
sleep 20
done
echo "ERROR: Image ${IMAGE} does not exist in registry after waiting!"
echo "Run the Build Docker Image workflow first."
exit 1
- name: Pull Docker image
run: |
set -e
IMAGE_TAG="${{ steps.resolve.outputs.IMAGE_TAG }}"
docker pull "${APP_IMAGE}:${IMAGE_TAG}"
echo "✓ Image pulled successfully"
- name: Sync compose file and deploy
run: |
set -e
IMAGE_TAG="${{ steps.resolve.outputs.IMAGE_TAG }}"
mkdir -p "${DEPLOY_DIR}/docker/nginx" "${DEPLOY_DIR}/docker/mysql"
cp docker-compose.staging.yml "${DEPLOY_DIR}/docker-compose.yml"
cp docker/nginx/default.conf "${DEPLOY_DIR}/docker/nginx/default.conf"
cp -R docker/mysql/init "${DEPLOY_DIR}/docker/mysql/"
if [ ! -f .env.staging ]; then
echo "ERROR: .env.staging is missing in the repository"
exit 1
fi
cp .env.staging "${DEPLOY_DIR}/.env.staging"
echo "✓ Synced .env.staging from repo"
cd "${DEPLOY_DIR}"
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:-8090}
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 "api" "http://127.0.0.1:${HTTP_PORT}/health"
+2 -1
View File
@@ -5,6 +5,7 @@ Homestead.yaml
.env
.phpunit.result.cache
ray.php
docker-compose.yml
/docker_src
/.vscode
**/.DS_Store
db-dump
+29
View File
@@ -1 +1,30 @@
You know what is this when you in this project
If you does't know what is this you probably in wrong project
```sql
--- for all ip
CREATE USER 'master_erahn'@'%' IDENTIFIED BY 'password';
--- for specific ip
CREATE USER 'kaunter_erahn'@'localhost' IDENTIFIED BY 'password';
GRANT ALL PRIVILEGES ON kaunter_erahn.* TO 'kaunter_erahn'@'%';
FLUSH PRIVILEGES;
```
Gadai=Pembiayaan
Tebus=penyelesaian penuh/ansuran
## Load db dump without using docker
```bash
docker exec -i api_arrahn_staging_mysql mysql -uroot -proot erahn_test < erahn_test.sql
```
## Disable the cawangan in arrahn_master_db using feature flag
## Feature Flag
1. Register the flag in api_arrahn/app/Feature.php
2. Block the real work in the API
3. Hide the UI in kaunter from the
4. Enable a pilot from Admin → Ciri Cawangan (or SQL):
BIN
View File
Binary file not shown.
+22
View File
@@ -0,0 +1,22 @@
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class BatchPembiayaanOnline extends Model
{
protected $table = 'batch_pembiayaan_online';
protected $fillable = [
'batch_no',
'kodcaw',
'tarikh',
'slot',
'bilangan',
'jumlah_keseluruhan',
'status',
'pc_submitted_by',
'submitted_at',
];
}
+44
View File
@@ -0,0 +1,44 @@
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class BsasOrder extends Model
{
protected $connection = 'mysql7';
protected $table = 'bsas_orders';
protected $fillable = [
'uuid',
'norujukan',
'kodcaw',
'serial_number',
'product_code',
'bid_value',
'otc_murabaha_value',
'otc_counter_party',
'status',
'status_code',
'status_message',
'e_cert_no',
'bid_ecert_response',
'otc_ecert_response',
'stb_ecert_response',
'request_payload',
'order_response',
'result_response',
'created_by',
];
protected $casts = [
'bid_value' => 'decimal:2',
'otc_murabaha_value' => 'decimal:2',
'request_payload' => 'array',
'order_response' => 'array',
'result_response' => 'array',
'bid_ecert_response' => 'array',
'otc_ecert_response' => 'array',
'stb_ecert_response' => 'array',
];
}
+11
View File
@@ -11,6 +11,17 @@ class Cawangan extends Model
public $timestamps = false;
/**
* Display name from master table (`details_caw`).
* Keep this as the single mapping so callers do not guess nama/namacaw.
*/
public function getNamaCawanganAttribute()
{
$nama = trim((string) ($this->attributes['details_caw'] ?? ''));
return $nama !== '' ? $nama : (string) ($this->attributes['kodcaw'] ?? '');
}
/**
* The attributes that are mass assignable.
*
+21
View File
@@ -0,0 +1,21 @@
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class CawanganFeature extends Model
{
protected $table = 'cawangan_feature';
protected $connection = 'mysql7';
protected $fillable = [
'kodcaw',
'feature',
'enabled',
];
protected $casts = [
'enabled' => 'integer',
];
}
@@ -80,7 +80,7 @@ class CawanganDebugConnections extends Command
return $idToNameMap[$item['mysql']] !== $item['database'];
});
} catch (\Throwable $th) {
dump(sprintf('Connection %s not found in table online_arrahn.arrahn_master_db but present in config. Make sure both of connection exist in both app/config and arrahn_master_db', $tested_connection));
dump(sprintf('Connection %s not found in table %s.arrahn_master_db but present in config. Make sure both of connection exist in both app/config and arrahn_master_db', $tested_connection, config('tennant_database.master_db')));
return;
}
+56
View File
@@ -0,0 +1,56 @@
<?php
namespace App\Console\Commands;
use App\Services\Whatsapp\WhatsappCloudClient;
use Illuminate\Console\Command;
class WhatsappTestSend extends Command
{
protected $signature = 'whatsapp:test {--to= : Recipient MSISDN, default ADMIN_PHONE_NUMBER}';
protected $description = 'Send the configured WhatsApp Cloud template to a test number';
public function handle()
{
$client = new WhatsappCloudClient();
if (!$client->isConfigured()) {
$this->error('Set WHATSAPP_TOKEN and WHATSAPP_PHONE_NUMBER_ID in .env first.');
return 1;
}
$to = $client->normalizeMsisdn($this->option('to') ?: env('ADMIN_PHONE_NUMBER'));
if (!$to) {
$this->error('Pass --to=60193450165 or set ADMIN_PHONE_NUMBER.');
return 1;
}
$template = config('whatsapp.template', 'hello_world');
$lang = config('whatsapp.template_lang', 'en_US');
$bodyParams = [];
if (strtolower($template) !== 'hello_world') {
// pembiayaan_online_paid: SAG, amaun, bank, 4 digit akhir, tarikh, rujukan
$bodyParams = [
'TEST-SAG',
'100.00',
'Maybank',
'1234',
date('d/m/Y'),
'TEST-REF',
];
}
$this->info("Sending {$template} ({$lang}) to {$to} …");
$response = $client->sendTemplate($to, $template, $lang, $bodyParams);
if ($response->successful()) {
$this->info('OK ' . $response->status() . ' ' . $response->body());
return 0;
}
$this->error('FAIL ' . $response->status() . ' ' . $response->body());
return 1;
}
}
+1
View File
@@ -24,6 +24,7 @@ class Kernel extends ConsoleKernel
Commands\ClearCachesAll::class,
Commands\RestartSupervisor::class,
Commands\InsertSempornaData::class,
Commands\WhatsappTestSend::class,
//Commands\ImportCsv::class,
];
+2 -1
View File
@@ -62,7 +62,8 @@ class Customer extends Model
'telefon2',
'nota_pekerjaan',
'tagpelanggan',
'tag_anggota'
'tag_anggota',
'gambar_mykad'
];
/**
+172
View File
@@ -0,0 +1,172 @@
<?php
namespace App;
class Feature
{
const PEMBIAYAAN_ONLINE = 'pembiayaan_online';
const REBET_ANNUR = 'rebet_annur';
/**
* Known flag keys. New features: add a constant here, then INSERT rows for pilot kodcaw.
*
* @return string[]
*/
public static function keys()
{
return [
self::PEMBIAYAAN_ONLINE,
self::REBET_ANNUR,
];
}
/**
* @return array<string,int>
*/
public static function defaults()
{
$map = [];
foreach (self::keys() as $key) {
$map[$key] = 0;
}
return $map;
}
/**
* Display labels for admin UI. New features: add a label here.
*
* @return array<string,string>
*/
public static function labels()
{
return [
self::PEMBIAYAAN_ONLINE => 'Pembiayaan Online',
self::REBET_ANNUR => 'Rebet / diskaun Annur',
];
}
/**
* Lelong portal base URL for this branch. rebet_annur branches (Annur/Tampoi)
* use API_ARRAHNBID_ANNUR; everyone else uses API_ARRAHNBID.
*
* Always returned with a trailing slash so callers can append 'portalApi'.
*
* @param string $kodcaw
* @return string
*/
public static function arrahnbidUrl($kodcaw)
{
$url = config('app_parameter.api_arrahnbid');
if ($kodcaw && self::enabled($kodcaw, self::REBET_ANNUR)) {
$annur = config('app_parameter.api_arrahnbid_annur');
if ($annur) {
$url = $annur;
}
}
return rtrim((string) $url, '/') . '/';
}
/**
* Missing row is off (fail closed).
*
* @param string $kodcaw
* @param string $feature
* @return bool
*/
public static function enabled($kodcaw, $feature)
{
return CawanganFeature::on('mysql7')
->where('kodcaw', $kodcaw)
->where('feature', $feature)
->where('enabled', 1)
->exists();
}
/**
* @param string $kodcaw
* @return array<string,int>
*/
public static function mapFor($kodcaw)
{
$map = self::defaults();
$rows = CawanganFeature::on('mysql7')
->where('kodcaw', $kodcaw)
->get(['feature', 'enabled']);
foreach ($rows as $row) {
$map[$row->feature] = ((int) $row->enabled) === 1 ? 1 : 0;
}
return $map;
}
/**
* @param string[] $kodcaws
* @return array<string,array<string,int>>
*/
public static function mapForMany(array $kodcaws)
{
$defaults = self::defaults();
$result = [];
foreach ($kodcaws as $kodcaw) {
$result[$kodcaw] = $defaults;
}
if (count($kodcaws) === 0) {
return $result;
}
$rows = CawanganFeature::on('mysql7')
->whereIn('kodcaw', $kodcaws)
->get(['kodcaw', 'feature', 'enabled']);
foreach ($rows as $row) {
$result[$row->kodcaw][$row->feature] = ((int) $row->enabled) === 1 ? 1 : 0;
}
return $result;
}
/**
* @param string $kodcaw
* @param string $feature
* @param bool|int $enabled
* @return bool
*/
public static function setEnabled($kodcaw, $feature, $enabled)
{
if (!in_array($feature, self::keys(), true)) {
return false;
}
CawanganFeature::on('mysql7')->updateOrCreate(
[
'kodcaw' => $kodcaw,
'feature' => $feature,
],
[
'enabled' => $enabled ? 1 : 0,
]
);
return true;
}
/**
* @param string|null $message
* @return array{ok:bool,status:int,payload:array}
*/
public static function denied($message = null)
{
return [
'ok' => false,
'status' => 403,
'payload' => [
'message' => $message ?: 'Ciri ini tidak diaktifkan untuk cawangan ini',
],
];
}
}
+8 -1
View File
@@ -9,6 +9,10 @@ class Gadai extends Model
protected $table = 'gadai';
public $timestamps = false;
protected $attributes = [
'cara_terima' => 'TUNAI',
];
/**
* The attributes that are mass assignable.
*
@@ -25,6 +29,7 @@ class Gadai extends Model
'nilaimarhun',
'berat',
'pinjaman',
'cara_terima',
'telahbayar',
'upahtelahbyr',
'cajtelahbyr',
@@ -73,7 +78,9 @@ class Gadai extends Model
'tagkomuditi',
'tempohbayar',
'lelong_tawarruq',
'tag_tebus'
'tag_tebus',
'kadarrebet',
'diskaun'
];
/**
+3 -3
View File
@@ -12,7 +12,7 @@ class Helper
{
$online_arrahn_connection = array_filter(config('database.connections'), function ($connection) {
if ($connection['database'] === 'online_arrahn') {
if ($connection['database'] === config('tennant_database.master_db')) {
return $connection;
}
});
@@ -43,7 +43,7 @@ class Helper
}
});
$non_cawangan_database = ['lelong', 'online_arrahn', 'kaunterpkb'];
$non_cawangan_database = ['lelong', config('tennant_database.master_db'), config('tennant_database.kaunter_db')];
$all_cawangan_db = array_filter($all_active_db_keys, function ($db_key) use ($non_cawangan_database) {
$isNotCawangan = in_array(config('database.connections.' . $db_key . '.database'), $non_cawangan_database);
@@ -74,7 +74,7 @@ class Helper
}
});
$non_cawangan_database = ['lelong', 'online_arrahn', 'kaunterpkb'];
$non_cawangan_database = ['lelong', config('tennant_database.master_db'), config('tennant_database.kaunter_db')];
$all_cawangan_db = array_filter($all_inactive_db_keys, function ($db_key) use ($non_cawangan_database) {
$isNotCawangan = in_array(config('database.connections.' . $db_key . '.database'), $non_cawangan_database);
@@ -331,7 +331,7 @@ class BackupStokAuditController extends Controller
}
public function kiraupahTawarruqStokAudit($tempoh,$tempohbayar,$kadarupah,$bakipinjaman){
$tempoh = ($tempoh > 12) ? 12 : $tempoh;
$bilang_bln = $tempoh - $tempohbayar;
if($bilang_bln > 12){
@@ -458,6 +458,7 @@ class BackupStokAuditController extends Controller
public function kiraupahTawarruqStokAuditUjrahOnePercent($tempoh,$tempohbayar,$kadarupah,$bakipinjaman,$nilaimarhun,$margin_semasa){
$onepercentservices = new V2Calculation();
$upah = $onepercentservices->kiraanKeuntunganRebet($bakipinjaman,$nilaimarhun,$kadarupah,$margin_semasa);
$tempoh = ($tempoh > 12) ? 12 : $tempoh;
$bilang_bln = $tempoh - $tempohbayar;
$keuntungan_semasa = $bilang_bln * $upah['onepercent'];
$ujrah_semasa = $bilang_bln * $upah['ujrah'];
+171 -19
View File
@@ -6,38 +6,190 @@ use App\Audit;
use App\Polis;
use App\Cawangan;
use App\LookupAlasan;
use App\Palsu;
use App\Gadai;
use App\Pampasan;
use Illuminate\Http\Request;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
class BarangkesController extends Controller
{
public function LaporanBarangKes(Request $request)
{
$status = $request->status;
$cawangan = Cawangan::on('mysql7')->where('kodcaw', $request->kodcaw)->first();
$cawangan = Cawangan::on('mysql7')->where('kodcaw', $request->kodcaw)->first();
if (!$cawangan) {
return response()->json(['error' => 'Invalid kodcaw'], 403);
if (!$cawangan) {
return response()->json(['error' => 'Invalid kodcaw'], 403);
}
$palsu_query = Palsu::on($cawangan['mysql'])
->select([
'tarikh',
'norujukan',
'nota',
'pembiayaan',
'nilaimarhun',
'jbg',
'untung',
'untungonepercent',
'untungcajlain'
])
->when($request->filled('tarikh_mula') && $request->filled('tarikh_akhir'), function ($query) use ($request) {
return $query->whereBetween('tarikh', [$request->tarikh_mula, $request->tarikh_akhir]);
})
->with(['tebus:norujukan', 'gadai:norujukan,nokp']);
$palsu_array = $palsu_query->get()->toArray();
$pampasan_array = Pampasan::on($cawangan['mysql'])->where('kodcaw', $request->kodcaw)->get()->toArray();
$indexedArray2 = collect($pampasan_array)->keyBy('norujukan');
$merged = collect($palsu_array)->map(function ($item) use ($indexedArray2) {
$id = $item['norujukan'];
$itemArray = $item; // Already an array from toArray()
$additional = $indexedArray2->get($id, []);
// Add exists_in_tebus flag based on relationship
$itemArray['gadaian_sudah_tebus'] = !is_null($item['tebus']);
// Add nokp customer from gadai relationship
$itemArray['nokp'] = $item['gadai']['nokp'] ?? null;
// Remove the tebus relationship data to keep response clean
unset($itemArray['tebus']);
unset($itemArray['gadai']);
return array_merge($itemArray, $additional);
})->values()->toArray();
$filteredResults = [];
$sudahTuntutSecaraManual = [
'011237240719-0015',
'011237240806-0011',
'011237240828-0001',
'011237240731-0026',
'011237240912-0009'
];
// if ($status == 'dituntut') {
// foreach ($merged as $pampasan) {
// if (isset($pampasan['status']) && $pampasan['status'] == 1) {
// $filteredResults[] = $pampasan;
// }
// }
// }
if ($status == 'dituntut') {
foreach ($merged as $pampasan) {
if (
(isset($pampasan['status']) && $pampasan['status'] == 1)
|| in_array($pampasan['norujukan'], $sudahTuntutSecaraManual)
) {
$filteredResults[] = $pampasan;
}
}
}
// if ($status == 'belum-tuntut') {
// foreach ($merged as $pampasan) {
// if (!isset($pampasan['status'])) {
// $filteredResults[] = $pampasan;
// }
// }
// }
if ($status == 'belum-tuntut') {
foreach ($merged as $pampasan) {
if (
!isset($pampasan['status'])
&& !in_array($pampasan['norujukan'], $sudahTuntutSecaraManual)
) {
$filteredResults[] = $pampasan;
}
}
}
if ($status == 'all' || $status == null) {
$filteredResults = $merged;
}
return response()->json($filteredResults);
}
$query = DB::connection($cawangan['mysql'])->table('palsu')
->select([
'tarikh',
'norujukan',
'nota',
'pembiayaan',
'nilaimarhun',
'jbg',
'untung',
'untungonepercent',
'untungcajlain'
])
->whereBetween('tarikh', [$request->tarikh_mula, $request->tarikh_akhir]);
public function store(Request $request) //cara fifi ajar
{
$result = $query->get();
$current = Carbon::now();
$kodcaw = $request->kodcaw;
$cawangan = Cawangan::on('mysql7')
->where('kodcaw', $kodcaw)
->first();
return response()->json($result);
$insert = Palsu::on($cawangan['mysql'])->create([
'kodcaw' => $request->kodcaw,
'tarikh' => $current->toDateString(),
'norujukan' => $request->norujukan,
'teller' => $request->teller,
'nota' => $request->nota ? $request->nota : '',
'pembiayaan' => $request->pinjaman,
'nilaimarhun' => $request->nilaimarhun,
'jbg' => $request->jbg,
'untung' => $request->tunggakan,
'untungonepercent' => $request->tunggakanonepercent,
'untungcajlain' => $request->tunggakanujrah,
]);
// return $insert;
return response()->json([
'success' => true,
'message' => 'Barang kes berjaya didaftar',
'data' => $insert
], 201);
}
public function update(Request $request)
{
$current = Carbon::now();
$kodcaw = $request->kodcaw;
Log::info($request->all());
// Cari connection cawangan
$cawangan = Cawangan::on('mysql7')
->where('kodcaw', $kodcaw)
->first();
if (!$cawangan) {
return response()->json([
'success' => false,
'message' => 'Cawangan tidak dijumpai',
], 404);
}
// Update direct (ikut norujukan)
$palsu = palsu::on($cawangan['mysql'])
->where('norujukan', $request->norujukan)
->update([
'kodcaw' => $request->kodcaw,
'tarikh' => $request->tarikh,
'teller' => $request->teller,
'nota' => $request->nota ?? '',
'pembiayaan' => $request->pinjaman,
'nilaimarhun' => $request->nilaimarhun,
'jbg' => $request->jbg,
'untung' => $request->tunggakan,
'untungonepercent' => $request->tunggakanonepercent,
'untungcajlain' => $request->tunggakanujrah,
]);
return response()->json([
'success' => $palsu ? true : false,
'message' => $palsu ? 'Rekod gadai berjaya dikemaskini' : 'Rekod tidak dijumpai',
], 200);
}
}
}
+11 -1
View File
@@ -3,6 +3,7 @@
namespace App\Http\Controllers;
use App\Cawangan;
use App\Feature;
use Illuminate\Http\Request;
class CawanganController extends Controller
@@ -18,7 +19,16 @@ class CawanganController extends Controller
$cawangan = Cawangan::on('mysql7')
->where('kodcaw','=',$code_cawangan)
->first();
return response()->json($cawangan);
if (!$cawangan) {
return response()->json($cawangan);
}
$data = $cawangan->toArray();
$data['features'] = Feature::mapFor($code_cawangan);
$data['api_arrahnbid'] = Feature::arrahnbidUrl($code_cawangan);
return response()->json($data);
}
public function create(Request $request)
+255 -25
View File
@@ -5,6 +5,7 @@ namespace App\Http\Controllers;
use App\Audit;
use App\Marhun;
use App\BankMaster;
use App\ARFiles;
use App\Customer;
use App\Gadai;
use App\Lelong;
@@ -14,6 +15,7 @@ use App\Blacklist;
use App\AuditCustomer;
use Illuminate\Http\Request;
use App\TuntutBaki;
use Illuminate\Support\Facades\Auth;
use App\Anggota;
use Carbon\Carbon;
@@ -63,7 +65,7 @@ class CustomerController extends Controller
->orWhere('kpbaru','=',$ic)
->first();
return response()->json($customer);
return response()->json($this->withMykadPhoto($customer));
}
public function showOneCustomerByICAdmin(Request $request){
@@ -73,7 +75,7 @@ class CustomerController extends Controller
->orWhere('kpbaru','=',$request->ic)
->first();
return response()->json($customer);
return response()->json($this->withMykadPhoto($customer));
}
public function showOneCustomerByICMobile($ic){
@@ -367,6 +369,8 @@ class CustomerController extends Controller
'tujuangadai'=>$request->tujuangadai
]);
$this->attachMykadPhoto($customer, $request);
$audit = new Audit();
$audit->users = $request->teller;
$audit->actions = "Daftar Pelanggan Baru";
@@ -376,7 +380,7 @@ class CustomerController extends Controller
$audit->updated_at = $current;
$audit->save();
return response()->json($customer, 201);
return response()->json($this->withMykadPhoto($customer), 201);
}
public function customerUpdate($nokp,Request $request){
@@ -395,37 +399,38 @@ class CustomerController extends Controller
// 'nota_arcc'=>$request->nota_arcc,
'telefon' => $request->telefon1,
'telefon2' => $request->telefon2,
'telefon2' => $request->telefon2,
));
return response()->json($customer_update, 200);
}
public function assignAnggota($noic, Request $request)
{
// Semak sama ada pelanggan wujud
$customer = Customer::on('mysql7')
->where('kpbaru', $noic)
->orWhere('kplama', $noic)
->first();
{
// Semak sama ada pelanggan wujud
$customer = Customer::on('mysql7')
->where('kpbaru', $noic)
->orWhere('kplama', $noic)
->first();
if (!$customer) {
return response()->json([
'success' => false,
'message' => 'Pelanggan tidak dijumpai.'
], 404);
}
// Kemaskini tag_anggota
$customer->tag_anggota = 1;
$customer->save();
if (!$customer) {
return response()->json([
'success' => false,
'message' => 'Pelanggan tidak dijumpai.'
], 404);
'success' => true,
'message' => 'Tag anggota berjaya dikemaskini.',
'data' => $customer
], 200);
}
// Kemaskini tag_anggota
$customer->tag_anggota = 1;
$customer->save();
return response()->json([
'success' => true,
'message' => 'Tag anggota berjaya dikemaskini.',
'data' => $customer
], 200);
}
public function customerUpdateKaunter($nokp,$kodcaw, Request $request){
$current = Carbon::now();
@@ -746,7 +751,7 @@ class CustomerController extends Controller
$customer = Customer::on($cawangan['mysql'])
->where('kplama','=',$nokp)
->orWhere('kpbaru','=',$nokp)
->rightJoin('online_arrahn.bank', 'bank.id', '=', 'customer.kodbank')
->rightJoin(config('tennant_database.master_db') . '.bank', 'bank.id', '=', 'customer.kodbank')
->first();
if(!empty($customer))
@@ -939,6 +944,69 @@ class CustomerController extends Controller
return response()->json($customer);
}
public function AdminsearchCustomerBySAG(Request $request)
{
// Ambil kodcaw dari user login
$kodcaw = $request->kodcaw;
// $norujukan = $request->input('norujukan');
// Cari connection untuk cawangan
$cawangan = Cawangan::on('mysql7')->where('kodcaw', $kodcaw)->first();
if (!$cawangan) {
return response()->json([], 404);
}
$gadai = optional(Gadai::on($cawangan['mysql'])->where('norujukan',$request->norujukan)->first())->toArray() ?? [];
if(!empty($gadai)){
$customer = optional(Customer::on('mysql7')->where(function ($query) use ($gadai) {
$query->where('kpbaru',$gadai['nokp'])
->orWhere('kplama',$gadai['nokp']);
})->first())->toArray() ?? [];
}else{
return response()->json(['success' => false,'message' => 'norujukan tidak dijumpainya'], 404);
}
// cara fifi ajar
$result = [[
'id' => $customer['id'],
'kodcaw' => $gadai['kodcaw'],
'norujukan' => $gadai['norujukan'],
'teller' => $gadai['teller'],
'nota' => $customer['nota'],
'pinjaman' => $gadai['pinjaman'],
'nilaimarhun' => $gadai['nilaimarhun'],
'jbg' => $gadai['jbg'],
'tunggakan' => $gadai['tunggakan'],
'tunggakanonepercent' => $gadai['tunggakanonepercent'],
'tunggakanujrah' => $gadai['tunggakanujrah'],
'nama' => $customer['nama'],
'kplama' => $customer['kplama'],
'kpbaru' => $customer['kpbaru'],
]];
// saya buat sendiri
// Join customer + gadai ikut nokp
// $result = DB::connection($cawangan['mysql'])
// ->table('gadai')
// ->join('online_arrahn.customer as c', function($join) {
// $join->on('gadai.nokp', '=', 'c.kpbaru')
// ->orOn('gadai.nokp', '=', 'c.kplama');
// })
// ->where('norujukan', $request->norujukan)
// ->select(
// 'id',
// 'gadai.kodcaw','gadai.norujukan','gadai.teller',
// 'c.nota','gadai.pinjaman','gadai.nilaimarhun','gadai.jbg',
// 'gadai.tunggakan','gadai.tunggakanonepercent','gadai.tunggakanujrah',
// 'c.nama','c.kpbaru','c.kplama'
// )
// ->take(5)
// ->get();
return response()->json($result);
}
public function changepassword(Request $request, $kodcaw, $id) {
$current = Carbon::now();
@@ -1038,6 +1106,168 @@ class CustomerController extends Controller
return response()->json($results);
}
public function updateTagPdpa(Request $request)
{
$tarikh_cetak_pdpa = Carbon::now()->toDateString();
$no_ic = $request->input('no_ic');
$customer = Customer::where('kplama', $no_ic)
->orWhere('kpbaru', $no_ic)
->first();
if (!$customer) {
return response()->json(['error' => 'Customer not found.'], 404);
}
$customer->tag_pdpa = $request->input('tag_pdpa');
$customer->tarikh_cetak_pdpa = $tarikh_cetak_pdpa;
$customer->save();
return response()->json(['message' => 'Customer PDPA updated successfully.'], 200);
}
/**
* Store MyKad photo for an existing customer only when gambar_mykad is empty.
*/
public function storeMykadPhotoIfMissing($ic, Request $request)
{
$customer = Customer::on('mysql7')
->where('kplama', '=', $ic)
->orWhere('kpbaru', '=', $ic)
->first();
if (!$customer) {
return response()->json(['error' => 'Customer not found.', 'stored' => false], 404);
}
if (!empty($customer->gambar_mykad)) {
return response()->json([
'message' => 'MyKad photo already stored.',
'gambar_mykad' => $customer->gambar_mykad,
'stored' => false,
], 200);
}
$this->attachMykadPhoto($customer, $request);
return response()->json([
'message' => !empty($customer->gambar_mykad)
? 'MyKad photo stored.'
: 'No photo provided or store failed.',
'gambar_mykad' => $customer->gambar_mykad,
'stored' => !empty($customer->gambar_mykad),
], 200);
}
/**
* Save MyKad photo into arrahn_files and link it on customer.gambar_mykad.
*/
protected function attachMykadPhoto($customer, Request $request)
{
if (!$customer) {
return;
}
$raw = $this->photoBase64FromRequest($request);
if ($raw === '') {
return;
}
try {
$fileId = $this->storeMykadPhoto($raw, $request->input('photo_mime', $request->json('photo_mime')), $customer);
if (!$fileId) {
return;
}
Customer::on('mysql7')
->where('kpbaru', $customer->kpbaru)
->where('kplama', $customer->kplama)
->update(['gambar_mykad' => $fileId]);
$customer->gambar_mykad = $fileId;
} catch (\Throwable $e) {
Log::error('Failed to attach MyKad photo: ' . $e->getMessage());
}
}
protected function photoBase64FromRequest(Request $request)
{
$raw = $request->input('photo_base64');
if ($raw === null || $raw === '') {
$raw = $request->json('photo_base64');
}
return is_string($raw) ? $raw : '';
}
protected function storeMykadPhoto($raw, $mime, $customer)
{
$commaPos = strpos($raw, ',');
if ($commaPos !== false) {
$raw = substr($raw, $commaPos + 1);
}
$raw = preg_replace('/\s+/', '', $raw);
if ($raw === '') {
return null;
}
$decoded = base64_decode($raw, true);
if ($decoded === false) {
$decoded = base64_decode($raw);
}
if ($decoded === false || $decoded === '') {
Log::warning('MyKad photo skipped: invalid base64');
return null;
}
if (strlen($raw) > 400000) {
Log::warning('MyKad photo skipped: payload too large');
return null;
}
$mime = strtolower((string) ($mime ?: 'image/jpeg'));
if (!in_array($mime, ['image/jpeg', 'image/jpg', 'image/png'], true)) {
$mime = 'image/jpeg';
}
$nric = preg_replace('/[^0-9A-Za-z]/', '', (string) ($customer->kpbaru ?: ''));
$ext = $mime === 'image/png' ? 'png' : 'jpg';
$file = new ARFiles();
$file->data = $raw;
$file->filename = ($nric !== '' ? $nric : 'mykad') . '.' . $ext;
$file->type = $mime;
$file->tarikhmasa = Carbon::now()->toDateTimeString();
$file->save();
return $file->id;
}
protected function withMykadPhoto($customer)
{
if (!$customer) {
return $customer;
}
$payload = $customer->toArray();
$payload['photo_base64'] = null;
$payload['photo_mime'] = null;
if (empty($customer->gambar_mykad)) {
return $payload;
}
$file = ARFiles::on('mysql7')->where('id', $customer->gambar_mykad)->first();
if ($file && $file->data) {
$payload['photo_base64'] = $file->data;
$payload['photo_mime'] = $file->type ?: 'image/jpeg';
}
return $payload;
}
// public function createNotaArcc($kodcaw, Request $request)
// {
// $current = Carbon::now();
@@ -0,0 +1,59 @@
<?php
namespace App\Http\Controllers;
use App\Cawangan;
use App\Feature;
use Illuminate\Http\Request;
class FeatureController extends Controller
{
public function index()
{
return response()->json([
'keys' => Feature::keys(),
'labels' => Feature::labels(),
]);
}
public function cawangan()
{
$cawangan = Cawangan::on('mysql7')
->orderBy('kodcaw')
->get(['kodcaw', 'details_caw']);
$maps = Feature::mapForMany($cawangan->pluck('kodcaw')->all());
$data = $cawangan->map(function ($c) use ($maps) {
return [
'kodcaw' => $c->kodcaw,
'details_caw' => $c->nama_cawangan,
'features' => $maps[$c->kodcaw] ?? Feature::defaults(),
];
});
return response()->json($data);
}
public function update($kodcaw, $feature, Request $request)
{
$cawangan = Cawangan::on('mysql7')->where('kodcaw', $kodcaw)->first();
if (!$cawangan) {
return response()->json(['message' => 'Cawangan tidak dijumpai'], 404);
}
if (!in_array($feature, Feature::keys(), true)) {
return response()->json(['message' => 'Ciri tidak sah'], 422);
}
$enabled = (int) $request->input('enabled') ? 1 : 0;
Feature::setEnabled($kodcaw, $feature, $enabled);
return response()->json([
'kodcaw' => $kodcaw,
'feature' => $feature,
'enabled' => $enabled,
'features' => Feature::mapFor($kodcaw),
]);
}
}
+103 -42
View File
@@ -29,6 +29,7 @@ use App\KomuditiTransaksi;
use App\Advansur;
use App\HistoryEmasSandaran;
use App\InOutMar;
use App\Services\Miscellaneous\StatistikService;
use Illuminate\Http\Request;
@@ -157,7 +158,6 @@ class GadaiController extends Controller
}
public function create($kodcaw,Request $request){
$master = new MasterServices($kodcaw);
$response = $master->createGadai($request);
@@ -227,11 +227,14 @@ class GadaiController extends Controller
}
public function daftarGadaiMarhun($norujukan,Request $request){
// dd("ggwp");
$master = new MasterServices($request->kodcaw);
$response = $master->createMarhunGadai($request->norujukan,$request);
public function daftarGadaiMarhun($kodcaw, $norujukan, Request $request)
{
if (\App\ShorakaTransaction::where('norujukan', $norujukan)->exists()) {
abort(409, 'Kontrak telah dihantar ke Shoraka dan tidak boleh diubah.');
}
$request->merge(['kodcaw' => $kodcaw, 'norujukan' => $norujukan]);
$master = new MasterServices($kodcaw);
$response = $master->createMarhunGadai($norujukan, $request);
return response()->json($response, 200);
}
@@ -368,6 +371,9 @@ class GadaiController extends Controller
}
public function batalGadai($kodcaw, $norujukan,Request $request){
if (\App\ShorakaTransaction::where('norujukan', $norujukan)->exists()) {
abort(409, 'Transaksi Shoraka telah dihantar. Semakan operasi diperlukan sebelum pembatalan.');
}
$current = Carbon::now();
$current = new Carbon();
@@ -405,6 +411,11 @@ class GadaiController extends Controller
}
public function pembelianKomuditi($kodcaw,$norujukan){
if (\App\ShorakaTransaction::where('norujukan', $norujukan)->exists()
|| !\App\KomuditiTransaksi::on('mysql7')->where('norujukan', $norujukan)->exists()) {
app(\App\Services\ShorakaGadaiService::class)->assertCompleted($kodcaw, $norujukan);
}
$cawangan = Cawangan::on('mysql7')->where('kodcaw','=',$kodcaw)->first();
$gadai = Gadai::on($cawangan['mysql'])
@@ -417,6 +428,10 @@ class GadaiController extends Controller
}
public function cetakSAG($kodcaw,$norujukan,Request $request){
if (\App\ShorakaTransaction::where('norujukan', $norujukan)->exists()) {
app(\App\Services\ShorakaGadaiService::class)->assertCompleted($kodcaw, $norujukan);
}
$harini = Carbon::now();
$cawangan = Cawangan::on('mysql7')->where('kodcaw','=',$kodcaw)->first();
@@ -441,6 +456,10 @@ class GadaiController extends Controller
}
public function serahMarhun($kodcaw,$norujukan){
if (\App\ShorakaTransaction::where('norujukan', $norujukan)->exists()) {
app(\App\Services\ShorakaGadaiService::class)->assertCompleted($kodcaw, $norujukan);
}
$cawangan = Cawangan::on('mysql7')->where('kodcaw','=',$kodcaw)->first();
$gadai = Gadai::on($cawangan['mysql'])
->where('norujukan','=',$norujukan)
@@ -536,7 +555,7 @@ class GadaiController extends Controller
public function getLaporanGadai($kodcaw,$tarikh){
$cawangan = Cawangan::on('mysql7')->where('kodcaw','=',$kodcaw)->first();
$gadai_data = Gadai::on($cawangan['mysql'])->select('t_gadai','norujukan','nokp','semakbarcode','masa','berat','nilaimarhun','pinjaman','upah12','percentpinj','teller','nokp','ujrah','onepercent')
$gadai_data = Gadai::on($cawangan['mysql'])->select('cara_terima','t_gadai','norujukan','nokp','semakbarcode','masa','berat','nilaimarhun','pinjaman','upah12','percentpinj','teller','nokp','ujrah','onepercent','kadarrebet','diskaun')
->where('t_gadai','=',$tarikh)
->orderBy('teller')
->orderBy('sirig')
@@ -550,6 +569,7 @@ class GadaiController extends Controller
array_push($gadai,[
"t_gadai"=> $gadaiData['t_gadai'],
"norujukan"=> $gadaiData['norujukan'],
"cara_terima"=> $gadaiData['cara_terima'],
"nama"=> $customer_info['nama'],
"nokp"=> $gadaiData['nokp'],
"semakbarcode"=> $gadaiData['semakbarcode'],
@@ -562,7 +582,9 @@ class GadaiController extends Controller
"alamat1"=> $customer_info['alamat1'],
"teller"=> $gadaiData['teller'],
"onepercent"=> $gadaiData['onepercent'],
"ujrah"=> $gadaiData['ujrah']
"ujrah"=> $gadaiData['ujrah'],
"kadarrebet"=> $gadaiData['kadarrebet'],
"diskaun"=> $gadaiData['diskaun']
]);
}
}
@@ -587,44 +609,80 @@ class GadaiController extends Controller
public function getLaporanGadaiRangeDate($kodcaw,Request $request){
$cawangan = Cawangan::on('mysql7')->where('kodcaw','=',$kodcaw)->first();
$mula = $request->mula;
$akhir = $request->akhir;
$gadai_data = Gadai::on($cawangan['mysql'])->select('t_gadai','norujukan','nokp','semakbarcode','masa','berat','nilaimarhun','pinjaman','upah12','percentpinj','teller','nokp','ujrah','onepercent','tagbaru')
->where('t_gadai','>=',$request->mula)
->where('t_gadai','<=',$request->akhir)
->orderBy('t_gadai')
->orderBy('teller')
->orderBy('sirig')
->get();
$cawangan = Cawangan::on('mysql7')->where('kodcaw', $kodcaw)->first();
$gadai = [];
$gadai_data = Gadai::on($cawangan['mysql'])
->whereBetween('t_gadai', [$request->mula, $request->akhir])
->orderBy('t_gadai')
->orderBy('teller')
->orderBy('sirig')
->get();
if($gadai_data){
foreach($gadai_data as $index=>$gadaiData){
$customer_info = Customer::on('mysql7')->where('kplama','=',$gadaiData['nokp'])->orWhere('kpbaru','=',$gadaiData['nokp'])->first();
array_push($gadai,[
"t_gadai"=> $gadaiData['t_gadai'],
"norujukan"=> $gadaiData['norujukan'],
"nama"=> $customer_info['nama'],
"nokp"=> $gadaiData['nokp'],
"semakbarcode"=> $gadaiData['semakbarcode'],
"masa"=> $gadaiData['masa'],
"berat"=> $gadaiData['berat'],
"nilaimarhun"=> $gadaiData['nilaimarhun'],
"pinjaman"=> $gadaiData['pinjaman'],
"upah12"=> $gadaiData['upah12'],
"percentpinj"=> $gadaiData['percentpinj'],
"alamat1"=> $customer_info['alamat1'],
"teller"=> $gadaiData['teller'],
"ujrah"=> $gadaiData['ujrah'],
"onepercent"=> $gadaiData['onepercent'],
"tagbaru"=> $gadaiData['tagbaru']
$nokps = $gadai_data->pluck('nokp')->unique();
]);
$customers = Customer::on('mysql7')
->where(function($query) use ($nokps) {
$query->whereIn('kpbaru', $nokps)
->orWhereIn('kplama', $nokps);
})
->get()
->flatMap(fn($c) => [
$c->kpbaru => $c,
$c->kplama => $c
]);
$gadai = $gadai_data->map(function ($g) use ($customers) {
foreach($customers as $key=>$value){
if($g->nokp == $value->kpbaru || $g->nokp == $value->kplama){
$customer = $value;
}
}
}
return response()->json($gadai);
return [
"nama" => optional($customer)->nama,
"alamat1" => optional($customer)->alamat1,
"t_gadai" => $g->t_gadai,
"norujukan" => $g->norujukan,
"cara_terima" => strtoupper((string) ($g->cara_terima ?: 'TUNAI')),
"nokp"=> $g->nokp,
"semakbarcode"=> $g->semakbarcode,
"masa"=> $g->masa,
"berat"=> $g->berat,
"nilaimarhun"=> $g->nilaimarhun,
"pinjaman"=> $g->pinjaman,
"upah12"=> $g->upah12,
"percentpinj"=> $g->percentpinj,
"alamat1"=> optional($customer)->alamat1,
"teller"=> $g->teller,
"ujrah"=> $g->ujrah,
"onepercent"=> $g->onepercent,
"kadarrebet"=> $g->kadarrebet,
"diskaun"=> $g->diskaun,
"tagbaru"=> $g->tagbaru,
"kodkerja"=> optional($customer)->kodkerja,
"bangsa"=> optional($customer)->bangsa,
];
});
// dd($gadai);
$statistikService = new StatistikService($kodcaw,$mula,$akhir);
$reportgadai = $statistikService->LaporanStatistik($gadai);
// dd($nokps);
return response()->json([
'gadai' => $gadai,
'kodcaw' => $kodcaw,
'mula' => $mula,
'akhir' => $akhir,
'statistik' => $reportgadai,
'jumlah' => $gadai->count(),
]);
}
public function getLaporanBatalGadai($kodcaw,$tarikh){
@@ -3871,11 +3929,13 @@ class GadaiController extends Controller
public function updateTagLelong(Request $request){
$norujukan = $request->norujukan;
$kodcaw = $request->kodcaw;
$t_lelong = $request->t_lelong;
$cawangan = Cawangan::on('mysql7')->where('kodcaw',$request->kodcaw)->first();
$updateTag = Gadai::on($cawangan->mysql)->where('norujukan',$norujukan)->update([
'taglel'=>1
'taglel'=>1,
't_lelong' => $t_lelong,
]);
return response()->json('success');
@@ -3888,7 +3948,8 @@ class GadaiController extends Controller
$cawangan = Cawangan::on('mysql7')->where('kodcaw',$request->kodcaw)->first();
$updateTag = Gadai::on($cawangan->mysql)->where('norujukan',$norujukan)->update([
'taglel'=>0
'taglel'=>0,
't_lelong' => null,
]);
return response()->json('success');
+164 -20
View File
@@ -8,6 +8,7 @@ use App\Cawangan;
use App\Mohonid;
use App\MohonAnggota;
use App\Customer;
use App\Services\StaffNotificationClient;
use Carbon\Carbon;
@@ -98,6 +99,7 @@ class KelulusanController extends Controller
{
$harini = Carbon::now();
$harini = new Carbon;
$kelulusan = null;
$cawangan = Cawangan::on('mysql7')->where('kodcaw','=',$kodcaw)->first();
$pengesahan = $request->pengesahan;
@@ -195,7 +197,33 @@ class KelulusanController extends Controller
$audit = new Audit();
$audit->users = (isset($request->namakhazanah) ? $request->namakhazanah : '');
$audit->actions = "Pengesahan Kelulusan 75%";
$audit->actions = $request->actions ?? "pengesahan kelulusan 75%";
// Only prepare detailed audit data for Pampasan approval
if($request->actions == 'Pengesahan Pembayaran Pampasan') {
// Prepare data for audit - ensure it's an array
$auditData = [];
// If kelulusan is a scalar (update result), create array structure
if (is_scalar($kelulusan) || is_null($kelulusan)) {
$auditData = ['update_result' => $kelulusan];
} else {
$auditData = $kelulusan;
}
// Add namapelulus and norujukanasal to the audit data
if(isset($request->namaoperasi)) {
$auditData['namapelulus'] = !empty($request->namaoperasi) ? $request->namaoperasi : null;
}
if(isset($request->norujukan)) {
$auditData['norujukan'] = !empty($request->norujukan) ? $request->norujukan : null;
}
} else {
// For other actions, use simple kelulusan data
$auditData = $kelulusan;
}
$audit->new_data = json_encode($auditData);
$audit->kodcaw = $kodcaw;
$audit->created_at = $harini;
$audit->updated_at = $harini;
@@ -323,9 +351,7 @@ public function create(REQUEST $request,$kodcaw,$norujukan,$roles)
if($roles == "khazanah")
{
$kelulusan=Mohon::on($cawangan['mysql'])
->create([
$createData = [
'tarikh'=> $harini->toDateString(),
'norujukan'=> $norujukan,
'siri' => $request->siri,
@@ -339,10 +365,14 @@ public function create(REQUEST $request,$kodcaw,$norujukan,$roles)
'jenistebus' => $request->jenistebus,
'teller' => $request->teller,
'pelulus' => ($request->pelulus) ? $request->pelulus : ''
];
// Add peloperasi if status is Kelulusan Pengeluaran Pampasan
if($request->status == 'Kelulusan Pengeluaran Pampasan' && isset($request->peloperasi)) {
$createData['peloperasi'] = $request->peloperasi;
}
]);
$kelulusan=Mohon::on($cawangan['mysql'])->create($createData);
}
else if($roles == "operasi")
{
@@ -369,7 +399,7 @@ public function create(REQUEST $request,$kodcaw,$norujukan,$roles)
$audit = new Audit();
$audit->users = (isset($request->teller) ? $request->teller : '');
$audit->actions = "Minta Pengesahan kelulusan 75%";
$audit->actions = $request->actions ?? "Minta Pengesahan kelulusan 75%";
$audit->new_data = json_encode($kelulusan);
$audit->kodcaw = $kodcaw;
$audit->created_at = $harini;
@@ -415,6 +445,8 @@ public function create(REQUEST $request,$kodcaw,$norujukan,$roles)
$audit->updated_at = $harini;
$audit->save();
$this->notifyOperasiAssignAnggota($kodcaw, $cawangan, $kelulusan, $request);
// ✅ Return JSON response ke frontend
return response()->json([
'success' => true,
@@ -437,6 +469,58 @@ public function create(REQUEST $request,$kodcaw,$norujukan,$roles)
}
}
/**
* Inbox for all operasi staff. Failures must not block the teller request.
*
* @param string $kodcaw
* @param \App\Cawangan $cawangan
* @param \App\MohonAnggota $kelulusan
* @param \Illuminate\Http\Request $request
* @return void
*/
protected function notifyOperasiAssignAnggota($kodcaw, $cawangan, $kelulusan, Request $request)
{
try {
$nama = trim((string) ($request->nama ?? ''));
if ($nama === '' && !empty($kelulusan->nokp)) {
$customer = Customer::on('mysql7')
->where('kpbaru', $kelulusan->nokp)
->orWhere('kplama', $kelulusan->nokp)
->first();
if ($customer && !empty($customer->nama)) {
$nama = trim((string) $customer->nama);
}
}
$namaCaw = $kodcaw;
if ($cawangan) {
$resolved = trim((string) $cawangan->nama_cawangan);
if ($resolved !== '') {
$namaCaw = $resolved;
}
}
app(StaffNotificationClient::class)->notify([
'type' => 'anggota.pending_operasi',
'cawangan' => $kodcaw,
'audience' => ['role' => 'operasi'],
'data' => [
'id' => $kelulusan->id,
'nokp' => $kelulusan->nokp,
'nama' => $nama,
'teller' => $kelulusan->teller,
'komen' => $kelulusan->komen,
'cawangan_nama' => $namaCaw,
],
]);
} catch (\Throwable $e) {
Log::warning('Assign anggota operasi notification failed: '.$e->getMessage(), [
'kodcaw' => $kodcaw,
'nokp' => isset($kelulusan->nokp) ? $kelulusan->nokp : null,
]);
}
}
//KEPOHONAN EDIT DAN HAPUS
public function kepohonancreate(REQUEST $request,$kodcaw,$norujukan)
@@ -571,26 +655,86 @@ public function create(REQUEST $request,$kodcaw,$norujukan,$roles)
return response()->json($arraykelulusan,200);
}
public function listoperasikelulusan()
public function listoperasikelulusan(Request $request)
{
$harini = Carbon::now();
$harini = new Carbon;
$dateFrom = $request->query('date_from');
$dateTo = $request->query('date_to');
$search = trim((string) $request->query('search', ''));
// Default to last 30 days to avoid loading entire history
if (!$dateFrom && !$dateTo) {
$dateTo = Carbon::now()->toDateString();
$dateFrom = Carbon::now()->subDays(30)->toDateString();
}
$cawangan_all = Cawangan::on('mysql7')
->select('mysql', 'details_caw', 'kodcaw')
->get();
$cawangan_all = Cawangan::on('mysql7')->get();
$arraykelulusan = [];
foreach($cawangan_all as $index=>$cawangan){
$kelulusan=Mohon::on($cawangan['mysql'])
->where('peloperasi','=','operasi')
->where('operasi','!=','')
->get();
foreach ($cawangan_all as $cawangan) {
$query = Mohon::on($cawangan['mysql'])
->select([
'tarikh',
'norujukan',
'komen',
'jumpinj',
'nilaimarhun',
'berat',
'margin',
'status',
'operasi',
'masalulus',
])
->where('peloperasi', '=', 'operasi')
->where('operasi', '!=', '');
if(sizeof($kelulusan) != 0){
array_push($arraykelulusan,["kelulusanall" => $kelulusan,"cawangan" => $cawangan['details_caw'],"cawangankod" => $cawangan['kodcaw']]);
if ($dateFrom) {
$query->where('tarikh', '>=', $dateFrom);
}
if ($dateTo) {
$query->where('tarikh', '<=', $dateTo);
}
if ($search !== '') {
$query->where(function ($q) use ($search) {
$q->where('norujukan', 'like', "%{$search}%")
->orWhere('operasi', 'like', "%{$search}%")
->orWhere('status', 'like', "%{$search}%")
->orWhere('komen', 'like', "%{$search}%");
});
}
}
return response()->json($arraykelulusan,200);
$kelulusan = $query->orderBy('tarikh', 'desc')->get();
foreach ($kelulusan as $kel) {
$komenParts = explode('|', (string) ($kel->komen ?? ''));
$komenParts = array_values(array_filter($komenParts, function ($part) {
return $part !== '';
}));
$arraykelulusan[] = [
'tarikh' => $kel->tarikh ?? '-',
'norujukan' => $kel->norujukan ?? '-',
'status' => $kel->status ?? '-',
'komen' => $komenParts[0] ?? ($kel->komen ?: '-'),
'pinjaman' => $kel->jumpinj ?? '-',
'nilaimarhun' => $komenParts[1] ?? ($kel->nilaimarhun ?? '-'),
'berat' => $komenParts[2] ?? ($kel->berat ?? '-'),
'margin' => $komenParts[3] ?? ($kel->margin ?? '-'),
'pelulus' => $kel->operasi ?? '-',
'masalulus' => $kel->masalulus ?? '-',
'cawangan' => $cawangan['details_caw'],
'cawangankod' => $cawangan['kodcaw'],
];
}
}
usort($arraykelulusan, function ($a, $b) {
return strcmp((string) ($b['tarikh'] ?? ''), (string) ($a['tarikh'] ?? ''));
});
return response()->json($arraykelulusan, 200);
}
public function UpdateHapusAnsuranOperasi(Request $request){
+208 -25
View File
@@ -3,15 +3,18 @@
namespace App\Http\Controllers;
use App\Audit;
use App\BsasOrder;
use App\Komuditi;
use App\KomuditiPurchase;
use App\KomuditiTransaksi;
use App\KomuditiProduk;
use App\Cawangan;
use App\Services\Bsas\BsasOrderService;
use DB;
use Illuminate\Http\Request;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
class KomuditiController extends Controller
{
@@ -117,6 +120,9 @@ class KomuditiController extends Controller
}
public function updateKomuditi(Request $request){
if (\App\ShorakaTransaction::where('norujukan', $request->norujukan)->exists()) {
abort(409, 'Transaksi Shoraka memerlukan semakan operasi; stok lama tidak boleh diubah.');
}
$current = Carbon::now();
$current = new Carbon();
@@ -138,34 +144,20 @@ class KomuditiController extends Controller
return response()->json($komuditi_transaksi);
}
public function transaksiKomuditi(Request $request){
$current = Carbon::now();
$current = new Carbon();
$komuditi_current = KomuditiPurchase::on('mysql7')->where('date_purchase','=',$current->toDateString())->orderBy('created_at','desc')->first();
$unit_current = $komuditi_current['unit_komuditi'];
$update_unit = $unit_current - $request->unit_komuditi;
$update_komuditi = KomuditiPurchase::on('mysql7')->where('id','=',$komuditi_current['id'])->update([
'unit_komuditi' => $update_unit
]);
$komuditi_transaksi = KomuditiTransaksi::on('mysql7')->create([
'unit_komuditi' => $request->unit_komuditi,
'nosijil'=>$komuditi_current['no_sijil'],
'norujukan' => $request->norujukan,
'kodcaw' => $request->kodcaw ,
'teller' => $request->teller ,
'masa' => $current->toTimeString(),
'tarikh' => $current->toDateString()
]);
return response()->json($komuditi_transaksi);
public function transaksiKomuditi(Request $request)
{
$this->validate($request, ['kodcaw' => 'required|string', 'norujukan' => 'required|string']);
return app(ShorakaGadaiController::class)->submitGadai(
$request->kodcaw, $request->norujukan,
app(\App\Services\ShorakaGadaiService::class),
app(\App\Services\ShorakaService::class)
);
}
public function batalKomuditi(Request $request){
if (\App\ShorakaTransaction::where('norujukan', $request->norujukan)->exists()) {
abort(409, 'Transaksi Shoraka memerlukan semakan operasi; stok lama tidak boleh diubah.');
}
$komuditi_current = KomuditiPurchase::on('mysql7')->orderBy('created_at','desc')->first();
@@ -335,4 +327,195 @@ class KomuditiController extends Controller
return response()->json($rekodkomoditi);
}
/**
* List BSAS commodity orders for operasi rekod view.
*/
public function listBsasOrders(Request $request)
{
$query = BsasOrder::on('mysql7')
->select([
'id',
'uuid',
'norujukan',
'kodcaw',
'serial_number',
'product_code',
'bid_value',
'otc_murabaha_value',
'otc_counter_party',
'status',
'status_code',
'status_message',
'e_cert_no',
'bid_ecert_response',
'otc_ecert_response',
'stb_ecert_response',
'created_by',
'created_at',
'updated_at',
])
->orderBy('created_at', 'desc')
->orderBy('id', 'desc');
if ($request->filled('status')) {
$query->where('status', $request->input('status'));
}
if ($request->filled('kodcaw')) {
$query->where('kodcaw', $request->input('kodcaw'));
}
if ($request->filled('limit_view')) {
$query->limit((int) $request->input('limit_view'));
}
$rows = $query->get()->map(function (BsasOrder $order) {
$payload = $order->toArray();
$payload['has_bid_ecert'] = !empty($order->bid_ecert_response);
$payload['has_otc_ecert'] = !empty($order->otc_ecert_response);
$payload['has_stb_ecert'] = !empty($order->stb_ecert_response);
unset($payload['bid_ecert_response'], $payload['otc_ecert_response'], $payload['stb_ecert_response']);
return $payload;
});
return response()->json($rows);
}
/**
* SAG-ready komoditi/product payload from confirmed BSAS order for a norujukan.
* Replaces daily KomuditiPurchase lookup for per-Terima BSAS purchases.
*/
public function getBsasOrderByNorujukan($norujukan)
{
$order = BsasOrder::on('mysql7')
->where('norujukan', $norujukan)
->where('status', 'confirmed')
->orderBy('id', 'desc')
->first();
if (!$order) {
return response()->json(['message' => 'BSAS order tidak dijumpai untuk norujukan ini.'], 404);
}
$otc = is_array($order->otc_ecert_response) ? $order->otc_ecert_response : [];
$productCode = (string) ($order->product_code ?: config('bsas.product_code'));
$description = trim((string) ($otc['PNAME'] ?? ''));
if ($description === '') {
$produk = KomuditiProduk::on('mysql7')->where('kodproduk', $productCode)->first();
$description = $produk ? (string) $produk->description : $productCode;
}
$price = (float) ($otc['PRICE_MYR_EQUIVALENT'] ?? $otc['PRICE'] ?? 0);
$volume = (float) ($otc['PVOLUME'] ?? 0);
$pinjaman = (float) $order->bid_value;
if ($volume <= 0 && $price > 0 && $pinjaman > 0) {
$volume = round($pinjaman / $price, 4);
}
return response()->json([
'source' => 'bsas',
'uuid' => $order->uuid,
'komoditi' => [
'no_sijil' => $order->e_cert_no ?: $order->uuid,
'kodproduct' => $productCode,
'nilai' => $price > 0 ? $price : null,
],
'product' => [
'product_code' => $productCode,
'kodproduk' => $productCode,
'description' => $description,
'unit' => 'TAN',
],
'kuantiti' => $volume,
]);
}
/**
* Return stored BSAS eCert payload only (no live BSAS refresh).
*/
public function getBsasOrderEcert($uuid)
{
$order = BsasOrder::on('mysql7')->where('uuid', $uuid)->first();
if (!$order) {
return response()->json(['message' => 'BSAS order tidak dijumpai.'], 404);
}
if (empty($order->bid_ecert_response) && empty($order->otc_ecert_response) && empty($order->stb_ecert_response)) {
return response()->json(['message' => 'eCert belum tersedia untuk order ini.'], 404);
}
return response()->json([
'uuid' => $order->uuid,
'norujukan' => $order->norujukan,
'e_cert_no' => $order->e_cert_no,
'serial_number' => $order->serial_number,
'product_code' => $order->product_code,
'bid_value' => $order->bid_value,
'otc_murabaha_value' => $order->otc_murabaha_value,
'otc_counter_party' => $order->otc_counter_party,
'bid_ecert' => $order->bid_ecert_response,
'otc_ecert' => $order->otc_ecert_response,
'stb_ecert' => $order->stb_ecert_response,
]);
}
/**
* Place BSAS commodity order for a gadai Terima, long-poll result, fetch eCert.
*/
public function placeBsasOrder(Request $request)
{
try {
$service = new BsasOrderService();
$order = $service->placeAndConfirm([
'norujukan' => $request->input('norujukan'),
'kodcaw' => $request->input('kodcaw'),
'pinjaman' => $request->input('pinjaman'),
'hargajualan' => $request->input('hargajualan'),
'nama_pelanggan' => $request->input('nama_pelanggan'),
'teller' => $request->input('teller'),
]);
return response()->json([
'message' => 'BSAS order confirmed',
'order' => $order,
], 200);
} catch (\Throwable $e) {
Log::error('BSAS place order failed', [
'norujukan' => $request->input('norujukan'),
'error' => $e->getMessage(),
]);
return response()->json([
'message' => $e->getMessage() ?: 'Gagal pembelian komoditi BSAS.',
], 422);
}
}
/**
* Re-query BSAS orderResult / retry eCert by stored uuid.
*/
public function getBsasOrder($uuid)
{
try {
$service = new BsasOrderService();
$order = $service->refreshByUuid($uuid);
return response()->json([
'message' => 'OK',
'order' => $order,
], 200);
} catch (\Throwable $e) {
Log::error('BSAS refresh order failed', [
'uuid' => $uuid,
'error' => $e->getMessage(),
]);
return response()->json([
'message' => $e->getMessage() ?: 'Gagal semak status BSAS.',
], 422);
}
}
}
+294 -163
View File
@@ -11,6 +11,7 @@ use App\Negeri;
use App\Lelong;
use App\Transaction;
use App\Cawangan;
use App\Feature;
use App\Gadai;
use App\TuntutBaki;
use App\WTD;
@@ -26,6 +27,7 @@ use App\Tunai;
use App\Audit;
use App\GlDetail;
use App\Pembeli;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
use App\Services\RingkasanHarianServices;
@@ -604,16 +606,7 @@ class LelongController extends Controller
$cawangan = Cawangan::on('mysql7')->where('kodcaw','=',$request->kodcaw)->first();
if($cawangan['tagarrahnbid'] == 1){
if(env('APP_ENV') != 'production'){
$link = 'https://adminbid-kopkb.erahn.com.my/api/portalApi';
}else{
$link = 'https://adminbid-kopkb.erahn.com.my/api/portalApi';
}
$lelong = Http::withOptions(['verify' => true])->get($link,[
'norujukan' => $request->norujukan
])->json();
$lelong = ($lelong != null) ? $lelong['lelong'] : [];
$lelong = $this->fetchPortalLelong($request->kodcaw, $request->norujukan);
if( $lelong == null )
{
$lelong = Lelong::on($cawangan['mysql'])
@@ -656,10 +649,7 @@ class LelongController extends Controller
$cawangan = Cawangan::on('mysql7')->where('kodcaw', '=', $request->kodcaw)->first();
if ($cawangan['tagarrahnbid'] == 0){
$lelong = Http::withOptions(['verify' => true])->get(config('app_parameter.api_arrahnbid').'portalApi',[
'norujukan' => $request->norujukan
])->json();
$lelong = ($lelong != null) ? $lelong['lelong'] : [];
$lelong = $this->fetchPortalLelong($request->kodcaw, $request->norujukan);
if( $lelong == null )
{
$lelong = Lelong::on($cawangan['mysql'])
@@ -729,8 +719,8 @@ class LelongController extends Controller
if($cawangan['tagarrahnbid'] == 1)
{
$lelong = Http::withOptions(['verify' => true])->get(config('app_parameter.api_arrahnbid').'portalApi',['norujukan' => $request->norujukan])->json();
$lelong = ($lelong != null) ? $lelong['lelong'] : [];
$lelong = $this->fetchPortalLelong($request->kodcaw, $request->norujukan);
$lelong = ($lelong != null) ? $lelong : [];
}else{
$lelong = Lelong::on($cawangan['mysql'])->where([['norujukan',$request->norujukan]])->first();
}
@@ -1069,44 +1059,79 @@ class LelongController extends Controller
public function exportLelong(Request $request)
{
$currentDate = Carbon::now();
$currentDate = $currentDate->toDateString();
$currentDate = Carbon::now()->toDateString();
$cawangan = Cawangan::on('mysql7')->where('kodcaw', $request->kodcaw)->first();
// --- Step 0: Dapatkan Cawangan ---
$cawangan = Cawangan::on('mysql7')
->where('kodcaw', $request->kodcaw)
->first();
$batchlel = BatchLel::on($cawangan['mysql'])
->select('batchno')
->orderBy('batchno', 'desc')
->first();
if (!$cawangan) {
Log::error("Export Lelong gagal: cawangan tidak ditemui", ['kodcaw' => $request->kodcaw]);
return response()->json(['error' => 'Cawangan tidak ditemui'], 404);
}
$newBatchNo = $batchlel->batchno + 1;
$newBatchLel = BatchLel::on($cawangan['mysql'])->create([
'kodcaw' => $request->kodcaw,
'batchno' => $newBatchNo,
'tarikh' => $request->tarikh,
'tarikh2' => $request->tarikh,
't_daftar' => $currentDate,
'masadari' => $request->masadari,
'masahingga' => $request->masahingga
Log::info("Mula Export Lelong", [
'kodcaw' => $cawangan->kodcaw,
'nama_cawangan' => $cawangan->details_caw,
]);
$lelong = Lelong::on($cawangan['mysql'])
->select('sirilel')
->orderBy('batchno', 'desc')
->first();
// --- Step 1: Batch (Cegah duplicate) ---
$batchlel = BatchLel::on($cawangan['mysql'])
->where('kodcaw', $request->kodcaw)
->whereDate('tarikh', $request->tarikh)
->where('masadari', $request->masadari)
->where('masahingga', $request->masahingga)
->first();
if (!$batchlel) {
$lastBatchNo = BatchLel::on($cawangan['mysql'])->max('batchno') ?? 0;
$newBatchNo = $lastBatchNo + 1;
$batchlel = BatchLel::on($cawangan['mysql'])->create([
'kodcaw' => $request->kodcaw,
'batchno' => $newBatchNo,
'tarikh' => $request->tarikh,
'tarikh2' => $request->tarikh,
't_daftar' => $currentDate,
'masadari' => $request->masadari,
'masahingga' => $request->masahingga
]);
Log::info("Batch baru dicipta", ['batchno' => $newBatchNo]);
} else {
$newBatchNo = $batchlel->batchno;
Log::info("Batch sedia ada digunakan", ['batchno' => $newBatchNo]);
}
// --- Step 2: Siri Lelong Terakhir ---
$lastSiriLelong = Lelong::on($cawangan['mysql'])->max('sirilel') ?? 0;
$lelong_arr = [];
// --- Step 3: Cegah duplicate Lelong ---
$existingNorujukan = Lelong::on($cawangan['mysql'])
->whereIn('norujukan', collect($request->lelongbid)->pluck('norujukan'))
->pluck('norujukan')
->toArray();
foreach ($request->lelongbid as $index) {
if (in_array($index['norujukan'], $existingNorujukan)) {
Log::warning("Skip Lelong duplicate", ['norujukan' => $index['norujukan']]);
continue;
}
$no_pembeli = Pembeli::on($cawangan['mysql'])->where('nokp', '=', $index['user_details']['nokp'])->select('nopembeli')->first();
if ($no_pembeli) {
$no_pembeli_data = $no_pembeli->nopembeli;
} else {
$last_no_pembeli = Pembeli::on($cawangan['mysql'])->select('nopembeli')->orderBy('nopembeli', 'desc')->first();
$no_pembeli_data = $last_no_pembeli->nopembeli + 1;
// --- Step 4: Semak / cipta pembeli ---
$no_pembeli = Pembeli::on($cawangan['mysql'])
->where('nokp', $index['user_details']['nokp'])
->value('nopembeli');
$newPembeli = Pembeli::on($cawangan['mysql'])->create([
'nopembeli' => $no_pembeli_data,
if (!$no_pembeli) {
$last_no_pembeli = Pembeli::on($cawangan['mysql'])
->max('nopembeli') ?? 0;
$no_pembeli = $last_no_pembeli + 1;
Pembeli::on($cawangan['mysql'])->create([
'nopembeli' => $no_pembeli,
'nokp' => $index['user_details']['nokp'],
'nama' => $index['user_details']['name'],
'alamat1' => $index['user_details']['alamat'],
@@ -1114,105 +1139,147 @@ class LelongController extends Controller
]);
}
$update_gadai = Gadai::on($cawangan['mysql'])->where('norujukan','=',$index['norujukan'])->update(array(
'sttebus' => "L",
't_update' => $request->tarikh
));
// --- Step 5: Update gadai ---
Gadai::on($cawangan['mysql'])
->where('norujukan', $index['norujukan'])
->update([
'sttebus' => "L",
't_update' => $request->tarikh
]);
$lelong_to_be_inserted = new Lelong();
$lelong_to_be_inserted->batchno = $newBatchNo;
$lelong_to_be_inserted->sirilel = $lelong->sirilel + 1;
$lelong_to_be_inserted->kodcaw = $index['kodcaw'];
$lelong_to_be_inserted->norujukan = $index['norujukan'];
$lelong_to_be_inserted->t_lelong = $request->tarikh;
$lelong_to_be_inserted->hargaasas = $index['hargaasas'];
$lelong_to_be_inserted->hargajual = $index['hargajual'];
$lelong_to_be_inserted->glcode = "1000/010";
$lelong_to_be_inserted->t_jual = $index['t_jual'];
$lelong_to_be_inserted->nopembeli = $no_pembeli_data;
$lelong_to_be_inserted->jbg = $index['jbg'];
$lelong_to_be_inserted->kadarupah = $index['kadarupah'];
$lelong_to_be_inserted->upahsimpan = $index['upahsimpan'];
$lelong_to_be_inserted->ansuran = $index['ansuran'];
$lelong_to_be_inserted->nilaimarhun = $index['nilaimarhun'];
$lelong_to_be_inserted->nilaimarhuns = $index['nmarhuns'];
$lelong_to_be_inserted->pinjaman = $index['pinjaman'];
$lelong_to_be_inserted->bakiupah = $index['bakiupah'];
$lelong_to_be_inserted->cajlelong = $index['cajlelong'];
$lelong_to_be_inserted->baki = $index['baki'];
$lelong_to_be_inserted->teller = $index['teller'];
$lelong_to_be_inserted->prkod = "01";
$lelong_to_be_inserted->kodgl = "1000/013";
$lelong_to_be_inserted->berat = $index['berat'];
$lelong_to_be_inserted->marhun = $index['marhun'];
$lelong_to_be_inserted->fasa = $index['fasa'];
$lelong_to_be_inserted->ujrah = $index['ujrah'];
$lelong_to_be_inserted->onepercent = $index['onepercent'];
$lelong_to_be_inserted->tagujrah = $index['tagujrah'];
$lelong_arr[] = $lelong_to_be_inserted->attributesToArray();
// --- Step 6: Simpan data Lelong ---
$lelong_arr[] = [
'batchno' => $newBatchNo,
'sirilel' => ++$lastSiriLelong,
'kodcaw' => $index['kodcaw'],
'norujukan' => $index['norujukan'],
't_lelong' => $request->tarikh,
'hargaasas' => $index['hargaasas'],
'hargajual' => $index['hargajual'],
'glcode' => "1000/010",
't_jual' => $index['t_jual'],
'nopembeli' => $no_pembeli,
'jbg' => $index['jbg'],
'kadarupah' => $index['kadarupah'],
'upahsimpan' => $index['upahsimpan'],
'ansuran' => $index['ansuran'],
'nilaimarhun' => $index['nilaimarhun'],
'nilaimarhuns' => $index['nmarhuns'],
'pinjaman' => $index['pinjaman'],
'bakiupah' => $index['bakiupah'],
'cajlelong' => $index['cajlelong'],
'baki' => $index['baki'],
'teller' => $index['teller'],
'prkod' => "01",
'kodgl' => "1000/013",
'berat' => $index['berat'],
'marhun' => $index['marhun'],
'fasa' => $index['fasa'],
'ujrah' => $index['ujrah'],
'onepercent' => $index['onepercent'],
'tagujrah' => $index['tagujrah'],
];
}
Lelong::on($cawangan['mysql'])->insert($lelong_arr);
foreach ($request->lelongbidmarhun as $data) {
$lelmarhun_to_be_inserted = new LelMarhun();
$lelmarhun_to_be_inserted->kodcaw = $request->kodcaw;
$lelmarhun_to_be_inserted->norujukan = $data['norujukan'];
$lelmarhun_to_be_inserted->bil = $data['bil'];
$lelmarhun_to_be_inserted->marhun = $data['marhun'];
$lelmarhun_to_be_inserted->nota = $data['nota'];
$lelmarhun_to_be_inserted->karat = $data['karat'];
$lelmarhun_to_be_inserted->berat = $data['berat'];
$lelmarhun_to_be_inserted->harga = $data['harga'];
$lelmarhun_to_be_inserted->n_marhun = $data['n_marhun'];
$lelmarhun_to_be_inserted->hargaasas = $data['hargaasas'];
$lelmarhun_to_be_inserted->batch_no = $newBatchNo;
// $lelmarhun_to_be_inserted->beratasal = $;
$lelmarhun_arr[] = $lelmarhun_to_be_inserted->attributesToArray();
if ($lelong_arr) {
Lelong::on($cawangan['mysql'])->insert($lelong_arr);
Log::info("Berjaya simpan lelong baru", ['total' => count($lelong_arr)]);
}
LelMarhun::on($cawangan['mysql'])->insert($lelmarhun_arr);
// --- Step 7: LELMARHUN (cegah duplicate) ---
if ($request->lelongbidmarhun) {
$existingMarhun = LelMarhun::on($cawangan['mysql'])
->whereIn('norujukan', collect($request->lelongbidmarhun)->pluck('norujukan'))
->pluck('norujukan')
->toArray();
if ($request->tunai != null) {
$tunai = Tunai::on($cawangan['mysql'])->create([
'tarikh' => $request->tarikh,
'kodcaw' => $request->kodcaw,
'kodlaluan' => "LELONG",
'masuk' => $request->tunai,
'baki' => $request->tunai,
]);
$lelmarhun_arr = collect($request->lelongbidmarhun)
->reject(fn($data) => in_array($data['norujukan'], $existingMarhun))
->map(function ($data) use ($request, $newBatchNo) {
return [
'kodcaw' => $request->kodcaw,
'norujukan' => $data['norujukan'],
'bil' => $data['bil'],
'marhun' => $data['marhun'],
'nota' => $data['nota'],
'karat' => $data['karat'],
'berat' => $data['berat'],
'harga' => $data['harga'],
'n_marhun' => $data['n_marhun'],
'hargaasas' => $data['hargaasas'],
'batch_no' => $newBatchNo
];
})
->toArray();
if ($lelmarhun_arr) {
LelMarhun::on($cawangan['mysql'])->insert($lelmarhun_arr);
Log::info("LelMarhun baru disimpan", ['total' => count($lelmarhun_arr)]);
}
}
if ($request->gldetail != null) {
$gldetail = GlDetail::on($cawangan['mysql'])->create([
'kodcaw' => $request->kodcaw,
'tarikh' => $request->tarikh,
'dtacct' => "1000/010",
'ktacct' => "1000/020",
'descpt' => "SERAHAN LELONG ONLINE, " . $request->tarikh,
'amaun' => $request->gldetail,
'teller' => "LELONG ONLINE",
'serahan' => $request->gldetail,
]);
// --- Step 8: GL dan Tunai (check duplicate descpt) ---
if ($request->tunai > 0) {
$existsTunai = Tunai::on($cawangan['mysql'])
->where('kodcaw', $request->kodcaw)
->where('tarikh', $request->tarikh)
->where('kodlaluan', 'LELONG')
->exists();
if (!$existsTunai) {
Tunai::on($cawangan['mysql'])->create([
'tarikh' => $request->tarikh,
'kodcaw' => $request->kodcaw,
'kodlaluan' => "LELONG",
'masuk' => $request->tunai,
'baki' => $request->tunai,
]);
Log::info("Rekod Tunai lelong dimasukkan");
} else {
Log::warning("Skip Tunai duplicate");
}
}
if ($request->gldetailcdm != null) {
GlDetail::on($cawangan['mysql'])->create([
'kodcaw' => $request->kodcaw,
'tarikh' => $request->tarikh,
'dtacct' => "1000/010",
'ktacct' => "1000/020",
'descpt' => "SERAHAN LELONG CDM, " . $request->tarikh,
'amaun' => $request->gldetailcdm,
'teller' => "LELONG ONLINE",
'serahan' => $request->gldetailcdm,
]);
$glDescs = [
'SERAHAN LELONG ONLINE' => $request->gldetail,
'SERAHAN LELONG CDM' => $request->gldetailcdm,
];
foreach ($glDescs as $desc => $amount) {
if ($amount > 0) {
$existsGl = GlDetail::on($cawangan['mysql'])
->where('kodcaw', $request->kodcaw)
->where('tarikh', $request->tarikh)
->where('descpt', 'like', "%{$desc}%")
->exists();
if (!$existsGl) {
GlDetail::on($cawangan['mysql'])->create([
'kodcaw' => $request->kodcaw,
'tarikh' => $request->tarikh,
'dtacct' => "1000/010",
'ktacct' => "1000/020",
'descpt' => "{$desc}, {$request->tarikh}",
'amaun' => $amount,
'teller' => "LELONG ONLINE",
'serahan' => $amount,
]);
Log::info("Rekod GL dimasukkan", ['desc' => $desc]);
} else {
Log::warning("Skip GL duplicate", ['desc' => $desc]);
}
}
}
return response()->json("success");
// --- Step 9: Log Akhir ---
Log::info('Berjaya Export Cawangan:', [
'kodcaw' => $cawangan->kodcaw,
'nama_cawangan' => $cawangan->details_caw,
'batch_baru' => $newBatchNo,
'total_lelong' => count($lelong_arr),
]);
return response()->json(['status' => 'success']);
}
public function getRingkasanHarian(Request $request){
@@ -1318,42 +1385,53 @@ class LelongController extends Controller
public function showAllLelongByCawanganBatchno($kodcaw)
{
$item = array('PMT','batu','permata');
$cawangan = Cawangan::on('mysql7')->where('kodcaw','=',$kodcaw)->first();
$item = ['PMT', 'batu', 'permata'];
// Semak kewujudan cawangan dulu
$cawangan = Cawangan::on('mysql7')->where('kodcaw', $kodcaw)->first();
if (!$cawangan) {
return response()->json(['error' => 'Cawangan tidak dijumpai.'], 404);
}
// Ambil data lelong
$lelong_data = Lelong::on($cawangan['mysql'])
->leftJoin('gadai', 'gadai.norujukan', '=', 'lelong.norujukan')
->join('lelmarhun', 'lelmarhun.norujukan', '=', 'lelong.norujukan')
->whereNull('upah12')
// ->where('lelong.marhun', 'like', '%PMT%')
// ->orWhere('lelong.marhun', 'like', '%batu%')
// ->orWhere('lelong.marhun', 'like', '%permata%')
->Where(function ($query) use($item) {
for ($i = 0; $i < count($item); $i++){
$query->orwhere('lelmarhun.nota', 'like', '%' . $item[$i] .'%');
}
})
->orderBy('lelong.t_lelong','asc')
->select('lelong.t_lelong','gadai.t_gadai','gadai.nokp','lelong.pinjaman','lelong.baki','lelong.batchno','lelong.norujukan')
->whereNull('upah12')
->distinct()
->get();
// dd($lelong_data);
->leftJoin('gadai', 'gadai.norujukan', '=', 'lelong.norujukan')
->join('lelmarhun', 'lelmarhun.norujukan', '=', 'lelong.norujukan')
->whereNull('upah12')
->where(function ($query) use ($item) {
foreach ($item as $i) {
$query->orWhere('lelmarhun.nota', 'like', '%' . $i . '%');
}
})
->orderBy('lelong.t_lelong', 'asc')
->select(
'lelong.t_lelong',
'gadai.t_gadai',
'gadai.nokp',
'lelong.pinjaman',
'lelong.baki',
'lelong.batchno',
'lelong.norujukan'
)
->distinct()
->get();
// Group by batch number
$groupbybatch = $lelong_data->groupBy('batchno');
$lelong = [];
foreach($groupbybatch as $index=>$lelongData){
foreach ($groupbybatch as $batchno => $lelongData) {
$total_pembiayaan = $lelongData->sum('pinjaman');
// dd($total_pembiayaan);
$total_lelong = $lelongData->count('norujukan');
array_push($lelong,[
// "upah12"=> $lelongData->upah12,
"t_lelong"=> $lelongData->groupBy('t_lelong'),
"batchno"=> $index,
"total_pembiayaan"=> $total_pembiayaan,
"total_lelong"=> $total_lelong,
"kodcaw"=> $kodcaw,
]);
// dd($lelong);
$total_lelong = $lelongData->count();
$t_lelong = optional($lelongData->first())->t_lelong; // elak error kalau kosong
$lelong[] = [
't_lelong' => $t_lelong,
'batchno' => $batchno,
'total_pembiayaan' => $total_pembiayaan,
'total_lelong' => $total_lelong,
'kodcaw' => $kodcaw,
];
}
return response()->json($lelong);
}
@@ -1895,4 +1973,57 @@ class LelongController extends Controller
return response()->json($arrayblm);
}
public function getBakiLelongByTeller($kodcaw, Request $request)
{
$cawangan = Cawangan::on('mysql7')->where('kodcaw', $kodcaw)->first();
$date = $request->tarikh;
$rugiLelong = TuntutBaki::on($cawangan['mysql'])
->whereDate('tarikh', $date)
->where('jumlah', '<', 0)
->where('teller','=', $request->kodlaluan)
->sum('jumlah');
$bakiLelong = TuntutBaki::on($cawangan['mysql'])
->whereDate('tarikh', $date)
->where('jumlah', '>', 0)
->where('teller','=', $request->kodlaluan)
->sum('jumlah');
return response()->json([
'tarikh' => $date,
'cawangan' => $kodcaw,
'rugi_lelong' => $rugiLelong,
'baki_lelong' => $bakiLelong
], 200);
}
/**
* Fetch lelong status from arrahnbid. Returns null when the portal is
* unreachable so callers can fall back to the local lelong table.
*/
private function fetchPortalLelong($kodcaw, $norujukan)
{
try {
$payload = Http::withOptions(['verify' => true])
->timeout(5)
->get(Feature::arrahnbidUrl($kodcaw).'portalApi', [
'norujukan' => $norujukan,
])
->json();
return ($payload != null) ? ($payload['lelong'] ?? null) : null;
} catch (ConnectionException $e) {
Log::warning('Arrahnbid portal unreachable; falling back to local lelong.', [
'kodcaw' => $kodcaw,
'norujukan' => $norujukan,
'url' => Feature::arrahnbidUrl($kodcaw).'portalApi',
'error' => $e->getMessage(),
]);
return null;
}
}
}
+65 -30
View File
@@ -3,6 +3,7 @@
namespace App\Http\Controllers;
use App\Marhun;
use App\Gadai;
use App\Jemas;
use App\Cawangan;
use Illuminate\Http\Request;
@@ -58,6 +59,11 @@ class MarhunController extends Controller
public function create($kodcaw,Request $request)
{
$order = \App\ShorakaTransaction::where('norujukan', $request->norujukan)->first();
if ($order) {
abort(409, 'Kontrak Shoraka telah dihantar dan tidak boleh diubah.');
}
$current = Carbon::now();
$current = new Carbon();
@@ -126,6 +132,10 @@ class MarhunController extends Controller
public function deletemarhun($kodcaw,$norujukan,Request $request)
{
$order = \App\ShorakaTransaction::where('norujukan', $norujukan)->first();
if ($order) {
abort(409, 'Kontrak Shoraka telah dihantar dan tidak boleh diubah.');
}
$current = Carbon::now();
$current = new Carbon();
@@ -151,6 +161,11 @@ class MarhunController extends Controller
public function editmarhun($kodcaw,$norujukan,Request $request)
{
$order = \App\ShorakaTransaction::where('norujukan', $norujukan)->first();
if ($order) {
abort(409, 'Kontrak Shoraka telah dihantar dan tidak boleh diubah.');
}
$current = Carbon::now();
$current = new Carbon();
@@ -160,39 +175,53 @@ class MarhunController extends Controller
$cawangan = Cawangan::on('mysql7')->where('kodcaw','=',$kodcaw)->first();
$old_data = Marhun::on($cawangan['mysql'])->where([['recno','=',$request->recno],['norujukan','=',$norujukan]])->first();
// Lock the parent first so concurrent edits to this SAG are serialized.
(new Marhun)->setConnection($cawangan['mysql'])->getConnection()->transaction(function () use ($cawangan, $norujukan, $request, $harga, $nilai_marhun, $kodcaw, $current) {
Gadai::on($cawangan['mysql'])->where('norujukan', $norujukan)->lockForUpdate()->firstOrFail();
$old_data = Marhun::on($cawangan['mysql'])->where([['recno','=',$request->recno],['norujukan','=',$norujukan]])->firstOrFail();
$marhunToUpdate = [
'karat' => $request->karat,
'marhun' => $request->marhun,
'nota' => $request->nota,
'tag_permata' => $request->tag_permata,
'berat_batu_permata' => $request->berat_batu_permata,
'berat' => $request->berat,
'harga' => $harga['harga'],
'n_marhun' => $nilai_marhun,
'bil' => $request->kuantiti,
];
$marhunToUpdate = [
'karat' => $request->karat,
'marhun' => $request->marhun,
'nota' => $request->nota,
'tag_permata' => $request->tag_permata,
'berat_batu_permata' => $request->berat_batu_permata,
'berat' => $request->berat,
'harga' => $harga['harga'],
'n_marhun' => $nilai_marhun,
'bil' => $request->kuantiti,
];
// Set value berat_batu_permata to null if value passed is null
if($marhunToUpdate['berat_batu_permata'] == '') {
$marhunToUpdate['berat_batu_permata'] = null;
};
// Set value berat_batu_permata to null if value passed is null
if($marhunToUpdate['berat_batu_permata'] == '') {
$marhunToUpdate['berat_batu_permata'] = null;
};
$jemas = Marhun::on($cawangan['mysql'])->where([['recno','=',$request->recno],['norujukan','=',$norujukan]])->update($marhunToUpdate);
$jemas = Marhun::on($cawangan['mysql'])->where([['recno','=',$request->recno],['norujukan','=',$norujukan]])->update($marhunToUpdate);
$new_data = Marhun::on($cawangan['mysql'])->where([['recno','=',$request->recno],['norujukan','=',$norujukan]])->first();
// Recalculate from every item belonging to this SAG, after the edit.
$totals = Marhun::on($cawangan['mysql'])->where('norujukan', $norujukan)
->selectRaw('COALESCE(SUM(berat), 0) AS total_berat, COALESCE(SUM(n_marhun), 0) AS total_nilai, COALESCE(SUM(bil), 0) AS total_kuantiti')
->first();
Gadai::on($cawangan['mysql'])->where('norujukan', $norujukan)->update([
'berat' => $totals->total_berat,
'nilaimarhun' => $totals->total_nilai,
'kuantiti' => $totals->total_kuantiti,
]);
$audit = new Audit();
$audit->users = $request->teller;
$audit->actions = 'Ubah Marhun';
$audit->norujukan = $norujukan;
$audit->old_data = $old_data;
$audit->new_data = $new_data;
$audit->kodcaw = $kodcaw;
$audit->created_at = $current;
$audit->updated_at = $current;
$audit->save();
$new_data = Marhun::on($cawangan['mysql'])->where([['recno','=',$request->recno],['norujukan','=',$norujukan]])->first();
$audit = new Audit();
$audit->users = $request->teller;
$audit->actions = 'Ubah Marhun';
$audit->norujukan = $norujukan;
$audit->old_data = $old_data;
$audit->new_data = $new_data;
$audit->kodcaw = $kodcaw;
$audit->created_at = $current;
$audit->updated_at = $current;
$audit->save();
});
return response('Edit Successfully', 200);
}
@@ -203,7 +232,10 @@ class MarhunController extends Controller
$marhun = Marhun::on($cawangan['mysql'])
->select('marhun.norujukan','gadai.nokp','customer.nama','marhun.marhun','marhun.nota','marhun.berat','marhun.karat','marhun.harga','marhun.n_marhun','marhun.t_gadai','marhun.tag_permata', 'marhun.berat_batu_permata')
->rightJoin('gadai', 'gadai.norujukan', '=', 'marhun.norujukan')
->rightJoin('master_erahn.customer as customer', 'customer.kpbaru','=','gadai.nokp')
->rightJoin('master_erahn.customer as customer', function($join) {
$join->on('customer.kpbaru', '=', 'gadai.nokp')
->orOn('customer.kplama', '=', 'gadai.nokp');
})
->where('marhun.t_gadai','=',$tarikh)
->orderBy('marhun.t_gadai')
->get();
@@ -221,7 +253,10 @@ class MarhunController extends Controller
$marhun = Marhun::on($cawangan['mysql'])
->select('marhun.norujukan','gadai.sttebus','gadai.pinjaman','gadai.nokp','customer.nama','marhun.marhun','marhun.nota','marhun.berat','marhun.karat','marhun.harga','marhun.n_marhun','marhun.t_gadai')
->rightJoin('gadai', 'gadai.norujukan', '=', 'marhun.norujukan')
->rightJoin('master_erahn.customer as customer', 'customer.kpbaru','=','gadai.nokp')
->rightJoin('master_erahn.customer as customer', function($join) {
$join->on('customer.kpbaru', '=', 'gadai.nokp')
->orOn('customer.kplama', '=', 'gadai.nokp');
})
->where('marhun.t_gadai','<=',$tarikh)
->whereYear('marhun.t_gadai','=',$current->year)
->where('gadai.sttebus','=','')
+195 -3
View File
@@ -2,14 +2,206 @@
namespace App\Http\Controllers;
use App\Audit;
use App\MaxMutuEmas;
use App\MaxPinjaman;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
class MaxPinjamanController extends Controller
{
public function getAllMaxPinjaman(){
$pinjaman = MaxPinjaman::on('mysql7')->get();
public function getAllMaxPinjaman(Request $request)
{
if (config('parameter.max_mutuemas')) {
$data = MaxMutuEmas::on('mysql7')->select('karat', 'teller', 'pc', 'operasi')->get();
$calculateMargin = $this->calculateMaxPinjamanMutu($data, $request->mutu,$request->total_nilai_marhun);
$pinjaman = [
[
"user_type" => "teller",
"max" => $calculateMargin['teller'],
"teller_pinjaman" => $calculateMargin['teller_pinjaman']
],
[
"user_type" => "khazanah",
"max" => $calculateMargin['pc'],
"pc_pinjaman" => $calculateMargin['pc_pinjaman']
],
[
"user_type" => "operasi",
"max" => $calculateMargin['operasi'],
"operasi_pinjaman" => $calculateMargin['operasi_pinjaman']
]
];
} else {
$pinjaman = MaxPinjaman::on('mysql7')->get();
}
return response()->json($pinjaman);
}
public function calculateMaxPinjamanMutu($data, $mutu,$totalMarhun)
{
$totalMaxMarhunPC = 0;
$totalMaxMarhunTeller = 0;
$totalMaxMarhunOperasi = 0;
if($mutu == null || $totalMarhun == 0){
return [
"pc" => 0,
"pc_pinjaman" => 0,
"teller" => 0,
"teller_pinjaman" => 0,
"operasi" => 0,
"operasi_pinjaman" => 0
];
}
$mutuKarat = array_map(function($item) {
return $item[0];
}, $mutu);
$mutuNilai = array_map(function($item) {
return $item[1];
}, $mutu);
foreach($mutuKarat as $index => $karat){
$margin = $data->where('karat', $karat)->first();
if($margin){
$nilai = $mutuNilai[$index];
$totalMaxMarhunPC += round(($margin->pc/100) * $nilai, 2);
$totalMaxMarhunTeller += round(($margin->teller/100) * $nilai, 2);
$totalMaxMarhunOperasi += round(($margin->operasi/100) * $nilai, 2);
}
}
$totalMaxPC = $totalMarhun > 0 ? ($totalMaxMarhunPC / $totalMarhun) * 100 : 0;
$totalMaxTeller = $totalMarhun > 0 ? ($totalMaxMarhunTeller / $totalMarhun) * 100 : 0;
$totalMaxOperasi = $totalMarhun > 0 ? ($totalMaxMarhunOperasi / $totalMarhun) * 100 : 0;
return [
"pc" => round($totalMaxPC,2),
"pc_pinjaman" => round(($totalMaxPC / 100) * $totalMarhun, 2),
"teller" => round($totalMaxTeller,2),
"teller_pinjaman" => round(($totalMaxTeller / 100) * $totalMarhun, 2),
"operasi" => round($totalMaxOperasi,2),
"operasi_pinjaman" => round(($totalMaxOperasi / 100) * $totalMarhun, 2)
];
}
public function getAllMaxMutuEmas(Request $request)
{
$maxmutuemas = MaxMutuEmas::on('mysql7')->get();
return response()->json($maxmutuemas);
}
public function getSpecificMaxMutuEmas(Request $request)
{
$karat = $request->karat;
$nilaimarhun = $request->nilaimarhun;
$nilaimarhun = preg_replace('/[^0-9.]/', '', $nilaimarhun); // Remove non-numeric characters except decimal point
$nilaimarhun = is_numeric($nilaimarhun) ? (float)$nilaimarhun : 0;
$maxmutuemas = MaxMutuEmas::on('mysql7')->where('karat', $karat)->first();
if($maxmutuemas && $nilaimarhun > 0){
$maxmutuemas->maxteller = round(($maxmutuemas->teller/100) * $nilaimarhun, 2);
$maxmutuemas->maxpc = round(($maxmutuemas->pc/100) * $nilaimarhun, 2);
$maxmutuemas->maxoperasi = round(($maxmutuemas->operasi/100) * $nilaimarhun, 2);
} else if($maxmutuemas) {
$maxmutuemas->maxteller = 0;
$maxmutuemas->maxpc = 0;
$maxmutuemas->maxoperasi = 0;
}
return response()->json($maxmutuemas);
}
public function updateMaxMutuEmas(Request $request)
{
// Validate the request manually for Lumen
$validator = \Illuminate\Support\Facades\Validator::make($request->all(), [
'recno' => 'required|integer',
'karat' => 'required|string|max:10',
'keterangan' => 'required|string|max:255',
'teller' => 'required|numeric|min:0|max:100',
'pc' => 'required|numeric|min:0|max:100',
'operasi' => 'required|numeric|min:0|max:100',
'username' => 'required|string|max:255'
], [
'recno.required' => 'Nombor rekod diperlukan.',
'recno.integer' => 'Nombor rekod mesti berupa nombor.',
'karat.required' => 'Karat diperlukan.',
'karat.string' => 'Karat mesti berupa teks.',
'karat.max' => 'Karat tidak boleh lebih dari 10 aksara.',
'keterangan.required' => 'Keterangan diperlukan.',
'keterangan.string' => 'Keterangan mesti berupa teks.',
'keterangan.max' => 'Keterangan tidak boleh lebih dari 255 aksara.',
'teller.required' => 'Peratus teller diperlukan.',
'teller.numeric' => 'Peratus teller mesti berupa nombor.',
'teller.min' => 'Peratus teller mestilah sekurang-kurangnya 0.',
'teller.max' => 'Peratus teller tidak boleh melebihi 100.',
'pc.required' => 'Peratus PC diperlukan.',
'pc.numeric' => 'Peratus PC mesti berupa nombor.',
'pc.min' => 'Peratus PC mestilah sekurang-kurangnya 0.',
'pc.max' => 'Peratus PC tidak boleh melebihi 100.',
'operasi.required' => 'Peratus operasi diperlukan.',
'operasi.numeric' => 'Peratus operasi mesti berupa nombor.',
'operasi.min' => 'Peratus operasi mestilah sekurang-kurangnya 0.',
'operasi.max' => 'Peratus operasi tidak boleh melebihi 100.',
]);
if ($validator->fails()) {
return response()->json([
'status' => 'error',
'message' => 'Pengesahan gagal',
'errors' => $validator->errors()
], 422);
}
$recno = $request->recno;
$karat = $request->karat;
$keterangan = $request->keterangan;
$teller = $request->teller;
$pc = $request->pc;
$operasi = $request->operasi;
$username = $request->username;
$current = \Carbon\Carbon::now();
$maxmutuemas = MaxMutuEmas::on('mysql7')->where('recno', $recno)->first();
if ($maxmutuemas) {
$oldmax = $maxmutuemas->toJson();
$maxmutuemas->karat = $karat;
$maxmutuemas->keterangan = $keterangan;
$maxmutuemas->teller = $teller;
$maxmutuemas->pc = $pc;
$maxmutuemas->operasi = $operasi;
$maxmutuemas->tarikhupdate = \Carbon\Carbon::now();
$maxmutuemas->save();
Audit::create([
'users' => $username,
'actions' => 'Penukaran Mutu Margin',
'norujukan' => '-',
'new_data' => $maxmutuemas->toJson(),
'old_data' => $oldmax,
'kodcaw' => 'OPERASI',
'created_at' => $current,
'updated_at' => $current,
]);
return response()->json([
'status' => 'success',
'message' => 'Max Mutu Emas updated successfully',
'data' => $maxmutuemas
]);
} else {
return response()->json([
'status' => 'error',
'message' => 'Record not found'
], 404);
}
}
}
@@ -0,0 +1,160 @@
<?php
namespace App\Http\Controllers;
use App\Services\PembiayaanOnline\ModalOnlineService;
use Illuminate\Http\Request;
class ModalOnlineController extends Controller
{
protected $service;
public function __construct(ModalOnlineService $service = null)
{
$this->service = $service ?: new ModalOnlineService();
}
protected function respond(array $result)
{
return response()->json($result['payload'], $result['status']);
}
/**
* GET /api/modal-online/{kodcaw}
* Read-only: does not create today's row.
*/
public function get($kodcaw)
{
return $this->respond($this->service->get($kodcaw));
}
/**
* @deprecated Alias of get() — no longer auto-creates RM100k.
*/
public function getOrCreate($kodcaw)
{
return $this->get($kodcaw);
}
/**
* POST /api/modal-online/{kodcaw}/reserve
*/
public function reserve($kodcaw, Request $request)
{
return $this->respond($this->service->reserve($kodcaw, $request->input('amaun', 0)));
}
/**
* GET /api/initiate/modal-online?zone=all|{zone}
*/
public function initiate(Request $request)
{
return $this->respond($this->service->initiate($request->input('zone', 'all')));
}
/**
* POST /api/modal-online/{kodcaw}/topup/request
* PC mohon had (hari pertama) atau tambah modal. Body: amaun, dimohon_oleh, nota?
*/
public function requestTopUp($kodcaw, Request $request)
{
return $this->respond($this->service->requestTopUp(
$kodcaw,
$request->input('amaun', 0),
$request->input('dimohon_oleh', ''),
$request->input('nota')
));
}
/**
* GET /api/modal-online/{kodcaw}/topup/requests?status=&tarikh=
*/
public function listTopUpByCawangan($kodcaw, Request $request)
{
return $this->respond($this->service->listTopUpRequests(
$kodcaw,
$request->input('status'),
$request->input('tarikh')
));
}
/**
* GET /api/modal-online/operasi/topup/requests?status=PENDING&tarikh=
*/
public function listTopUpOperasi(Request $request)
{
$status = $request->input('status', 'PENDING');
if ($status === 'all' || $status === '') {
$status = null;
}
return $this->respond($this->service->listTopUpRequests(
null,
$status,
$request->input('tarikh')
));
}
/**
* POST /api/modal-online/operasi/topup/{id}/approve
* Body: diluluskan_oleh, nota?, amaun? (optional override)
*/
public function approveTopUp($id, Request $request)
{
return $this->respond($this->service->approveTopUp(
$id,
$request->input('diluluskan_oleh', ''),
$request->input('nota'),
null,
$request->input('amaun')
));
}
/**
* POST /api/modal-online/operasi/topup/{id}/reject
* Body: diluluskan_oleh, nota?
*/
public function rejectTopUp($id, Request $request)
{
return $this->respond($this->service->rejectTopUp(
$id,
$request->input('diluluskan_oleh', ''),
$request->input('nota')
));
}
/**
* POST /api/modal-online/{kodcaw}/close
* Body: ditutup_oleh
*/
public function close($kodcaw, Request $request)
{
return $this->respond($this->service->close(
$kodcaw,
$request->input('ditutup_oleh', '')
));
}
/**
* GET /api/modal-online/operasi/eod?zone=all&tarikh=
*/
public function listToday(Request $request)
{
return $this->respond($this->service->listToday(
$request->input('zone', 'all'),
$request->input('tarikh')
));
}
/**
* POST /api/modal-online/operasi/eod/close-all
* Body: ditutup_oleh, zone?
*/
public function closeAll(Request $request)
{
return $this->respond($this->service->closeAll(
$request->input('ditutup_oleh', ''),
$request->input('zone', 'all')
));
}
}
+198
View File
@@ -0,0 +1,198 @@
<?php
namespace App\Http\Controllers;
use App\Cawangan;
use App\Palsu;
use App\Pampasan;
use App\Panjar;
use App\Tebus;
use Illuminate\Http\Request;
use Carbon\Carbon;
use Illuminate\Log\Logger;
use Illuminate\Support\Facades\Log;
class PampasanController extends Controller
{
public function terimaPampasan(Request $request)
{
$validated = $this->validate($request, [
'norujukan' => 'required|string',
'kodcaw' => 'required|string',
'jumlah_pampasan' => 'required|numeric',
'denominasi' => 'required|array',
'teller' => 'required|string',
]);
try {
$cawangan = $this->getCawangan($request->kodcaw);
if (!$cawangan) {
return $this->errorResponse('Cawangan tidak dijumpai untuk kodcaw: ' . $request->kodcaw, 404);
}
$pampasan = Pampasan::on($cawangan['mysql'])
->where('norujukan', $validated['norujukan'])
->first();
$pampasan->update([
'jumlah_pampasan' => $validated['jumlah_pampasan'],
'denominasi' => isset($validated['denominasi']) ? json_encode($validated['denominasi']) : null,
'teller' => $validated['teller'],
]);
return $this->successResponse('Pampasan berjaya dibayar.', $pampasan, 201);
} catch (\Exception $e) {
return $this->errorResponse('Database error: ' . $e->getMessage(), 500);
}
}
public function updateStatusPampasan(Request $request)
{
$norujukan = $request->norujukan;
$cawangan = $this->getCawangan($request->kodcaw);
if (!$cawangan) {
return $this->errorResponse('Cawangan tidak dijumpai untuk kodcaw: ' . $request->kodcaw, 404);
}
$tebus = Tebus::on($cawangan['mysql'])->where('norujukan', $norujukan)->first();
$pampasan = Pampasan::on($cawangan['mysql'])->where("norujukan", $norujukan)->first();
$pampasan->status = 1;
$pampasan->save();
// Update corresponding palsu table record status to 1
Palsu::on($cawangan['mysql'])
->where('norujukan', $norujukan)
->update(['status' => 1]);
return $this->successResponse('Status Pampasan berjaya dituntut.', $pampasan, 201);
}
public function getPampasan(Request $request)
{
$cawangan_connection = Cawangan::where('kodcaw', $request->kodcaw)->first()->mysql;
$pampasan = Pampasan::on($cawangan_connection)
->when($request->filled('norujukan'), function ($query) use ($request) {
return $query->where('norujukan', $request->norujukan);
})
->get();
return $this->successResponse('Senarai pampasan', $pampasan, 201);
}
public function updateAgreementPrintedAt(Request $request)
{
$validated = $this->validate($request, [
'norujukan' => 'required|string',
'kodcaw' => 'required|string',
]);
try {
$cawangan = $this->getCawangan($validated['kodcaw']);
if (!$cawangan) {
return $this->errorResponse('Cawangan tidak dijumpai untuk kodcaw: ' . $validated['kodcaw'], 404);
}
$pampasan = Pampasan::on($cawangan['mysql'])->where('norujukan', $validated['norujukan'])->first();
if (!$pampasan) {
return $this->errorResponse('Pampasan tidak dijumpai untuk norujukan: ' . $validated['norujukan'], 404);
}
$pampasan->agreement_printed_at = Carbon::now();
$pampasan->save();
return $this->successResponse('Agreement printed timestamp berjaya dikemaskini.', $pampasan, 200);
} catch (\Exception $e) {
return $this->errorResponse('Database error: ' . $e->getMessage(), 500);
}
}
public function initiatePampasan(Request $request)
{
$validated = $this->validate($request, [
'norujukan' => 'required|string',
'kodcaw' => 'required|string',
'teller' => 'required|string',
'baucerno' => 'required|string',
]);
try {
$cawangan = $this->getCawangan($validated['kodcaw']);
if (!$cawangan) {
return $this->errorResponse('Cawangan tidak dijumpai untuk kodcaw: ' . $validated['kodcaw'], 404);
}
// Find the palsu record
$palsu = Palsu::on($cawangan['mysql'])->where('norujukan', $validated['norujukan'])->first();
if (!$palsu) {
return $this->errorResponse('Barang kes tidak dijumpai untuk norujukan: ' . $validated['norujukan'], 404);
}
// Check if pampasan already exists for this norujukan
$existingPampasan = Pampasan::on($cawangan['mysql'])->where('norujukan', $validated['norujukan'])->first();
if ($existingPampasan) {
// If pampasan exists, just update the agreement_printed_at
$existingPampasan->agreement_printed_at = Carbon::now();
$existingPampasan->save();
return $this->successResponse('Agreement printed timestamp berjaya dikemaskini untuk pampasan sedia ada.', $existingPampasan, 200);
}
// Create new pampasan record based on palsu data
$pampasan = Pampasan::on($cawangan['mysql'])->create([
'norujukan' => $palsu->norujukan,
'baucerno' => $validated['baucerno'],
'kodcaw' => $palsu->kodcaw,
'jumlah_pampasan' => $palsu->nilaimarhun,
'teller' => $validated['teller'],
'status' => 0,
'agreement_printed_at' => Carbon::now(),
]);
return $this->successResponse('Pampasan berjaya daftar dari rekod barang kes.', $pampasan, 201);
} catch (\Exception $e) {
return $this->errorResponse('Database error: ' . $e->getMessage(), 500);
}
}
private function getCawangan($kodcaw)
{
$cawangan = Cawangan::on('mysql7')->where('kodcaw', $kodcaw)->first();
if (!$cawangan) {
return null;
}
return $cawangan;
}
private function errorResponse($message, $statusCode = 404)
{
return response()->json([
'success' => false,
'message' => $message,
], $statusCode);
}
private function successResponse($message, $data = null, $statusCode = 200)
{
return response()->json([
'success' => true,
'message' => $message,
'data' => $data,
], $statusCode);
}
}
@@ -0,0 +1,250 @@
<?php
namespace App\Http\Controllers;
use App\Services\PembiayaanOnline\PembiayaanOnlineService;
use Illuminate\Http\Request;
class PembiayaanOnlineController extends Controller
{
protected $service;
public function __construct(PembiayaanOnlineService $service = null)
{
$this->service = $service ?: new PembiayaanOnlineService();
}
protected function respond(array $result)
{
return response()->json($result['payload'], $result['status']);
}
/**
* POST /api/pembiayaan-online/{kodcaw}
*/
public function store($kodcaw, Request $request)
{
return $this->respond($this->service->store($kodcaw, $request->all()));
}
/**
* GET /api/pembiayaan-online/{kodcaw}/by-norujukan/{norujukan}
*/
public function show($kodcaw, $norujukan)
{
return $this->respond($this->service->show($kodcaw, $norujukan));
}
/**
* GET /api/pembiayaan-online/{kodcaw}/lines/{id}
*/
public function showLine($kodcaw, $id)
{
return $this->respond($this->service->showLine($kodcaw, $id));
}
/**
* GET /api/pembiayaan-online/{kodcaw}/queue
*/
public function queue($kodcaw, Request $request)
{
return $this->respond($this->service->queue($kodcaw, $request->input('status')));
}
/**
* PUT /api/pembiayaan-online/{kodcaw}/lines/{id}
*/
public function updateLine($kodcaw, $id, Request $request)
{
// Prefer only present keys so "has" semantics match previous controller.
$input = [];
foreach ([
'kodbank', 'noakaun', 'nama_bank', 'nama_akaun', 'nota',
'bukti_akaun_file_id', 'borang_file_id', 'update_customer_bank',
'bukti_akaun_data', 'bukti_akaun_filename', 'bukti_akaun_type',
'borang_data', 'borang_filename', 'borang_type',
] as $key) {
if ($request->has($key)) {
$input[$key] = $request->input($key);
}
}
return $this->respond($this->service->updateLine($kodcaw, $id, $input));
}
/**
* POST /api/pembiayaan-online/{kodcaw}/lines/{id}/verify
*/
public function verifyLine($kodcaw, $id, Request $request)
{
return $this->respond($this->service->verifyLine(
$kodcaw,
$id,
$request->input('pc_verified_by', '')
));
}
/**
* POST /api/pembiayaan-online/{kodcaw}/lines/{id}/unverify
*/
public function unverifyLine($kodcaw, $id)
{
return $this->respond($this->service->unverifyLine($kodcaw, $id));
}
/**
* GET /api/pembiayaan-online/{kodcaw}/batches
*/
public function listBatches($kodcaw, Request $request)
{
return $this->respond($this->service->listBatches(
$kodcaw,
$request->input('tarikh'),
$request->input('status')
));
}
/**
* GET /api/pembiayaan-online/{kodcaw}/batches/{batchId}
*/
public function showBatch($kodcaw, $batchId)
{
return $this->respond($this->service->showBatch($kodcaw, $batchId));
}
/**
* POST /api/pembiayaan-online/{kodcaw}/batches/submit
*/
public function submitBatch($kodcaw, Request $request)
{
return $this->respond($this->service->submitBatch(
$kodcaw,
$request->input('ids', []),
$request->input('pc_submitted_by', ''),
$request->input('slot')
));
}
/**
* GET /api/pembiayaan-online/operasi/batches
*/
public function operasiListBatches(Request $request)
{
return $this->respond($this->service->operasiListBatches(
$request->input('status', 'SUBMITTED'),
$request->input('tarikh')
));
}
/**
* GET /api/pembiayaan-online/operasi/{kodcaw}/batches/{batchId}
*/
public function operasiShowBatch($kodcaw, $batchId)
{
return $this->showBatch($kodcaw, $batchId);
}
/**
* POST /api/pembiayaan-online/operasi/{kodcaw}/lines/{id}/approve
*/
public function operasiApproveLine($kodcaw, $id, Request $request)
{
return $this->respond($this->service->operasiApproveLine(
$kodcaw,
$id,
$request->input('ops_by', '')
));
}
/**
* POST /api/pembiayaan-online/operasi/{kodcaw}/lines/{id}/reject
*/
public function operasiRejectLine($kodcaw, $id, Request $request)
{
return $this->respond($this->service->operasiRejectLine(
$kodcaw,
$id,
$request->input('ops_by', ''),
$request->input('reject_reason', '')
));
}
/**
* POST /api/pembiayaan-online/operasi/{kodcaw}/batches/{batchId}/approve
*/
public function operasiApproveBatch($kodcaw, $batchId, Request $request)
{
return $this->respond($this->service->operasiApproveBatch(
$kodcaw,
$batchId,
$request->input('ops_by', '')
));
}
/**
* POST /api/pembiayaan-online/operasi/{kodcaw}/lines/{id}/paid
* Body: ops_by, no_rujukan_bayaran, tarikh_bayaran?,
* bukti_data + bukti_filename + bukti_type OR bukti_file_id
*/
public function operasiMarkPaid($kodcaw, $id, Request $request)
{
return $this->respond($this->service->operasiMarkPaid($kodcaw, $id, $request->all()));
}
/**
* POST /api/pembiayaan-online/operasi/{kodcaw}/batches/{batchId}/paid
*/
public function operasiMarkPaidBatch($kodcaw, $batchId, Request $request)
{
return $this->respond($this->service->operasiMarkPaidBatch($kodcaw, $batchId, $request->all()));
}
/**
* GET /api/pembiayaan-online/{kodcaw}/lines/{id}/bukti
*/
public function getBukti($kodcaw, $id)
{
return $this->respond($this->service->getBukti($kodcaw, $id));
}
/**
* GET /api/pembiayaan-online/{kodcaw}/lines/{id}/bukti-akaun
*/
public function getBuktiAkaun($kodcaw, $id)
{
return $this->respond($this->service->getBuktiAkaun($kodcaw, $id));
}
/**
* GET /api/pembiayaan-online/{kodcaw}/lines/{id}/borang
*/
public function getBorang($kodcaw, $id)
{
return $this->respond($this->service->getBorang($kodcaw, $id));
}
/**
* GET /api/pembiayaan-online/{kodcaw}/paid?blast_sent=0
*/
public function paidQueue($kodcaw, Request $request)
{
return $this->respond($this->service->paidQueue(
$kodcaw,
$request->input('blast_sent')
));
}
/**
* POST /api/pembiayaan-online/{kodcaw}/paid/blast
* Body: ids[] (or ids), blast_by?
*/
public function markBlastSent($kodcaw, Request $request)
{
$ids = $request->input('ids', $request->input('id'));
return $this->respond($this->service->markBlastSent(
$kodcaw,
$ids,
$request->input('blast_by')
));
}
}
@@ -11,6 +11,11 @@ use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use App\Services\Reports\GadaianService as ReportGadaianService;
use App\Helper;
use Carbon\Carbon;
use App\Pekerjaan;
use App\Poskod;
use App\TransactionOnline;
use Log;
use App\Services\Reports\RingkasanHarianService as ReportRingkasanHarianService;
@@ -162,11 +167,484 @@ public function getSummaryLaporanPelangganAktif(Request $request)
return response()->json($response);
}
public function getActiveCawanganConnections()
{
$cawangan_details = Cawangan::select(['details_caw', 'mysql'])->get();
$cawangan_details = $cawangan_details->filter(fn($x) => !in_array(strtolower($x->details_caw), config('report.excluded_cawangan')));
$connection_active_cawangan = Helper::getActiveCawanganDbConnection();
return collect($cawangan_details->filter(fn($x) => in_array($x->mysql, $connection_active_cawangan)));
}
public function getSummaryLaporanDataPelanggan(Request $request)
{
$year = $request->year;
if (!$year) {
return response()->json(['error' => 'Sila pilih tahun'], 400);
}
$startOfYear = Carbon::create($year, 1, 1)->startOfDay();
$endOfYear = Carbon::create($year, 12, 31)->endOfDay();
Carbon::setLocale('ms');
// 1️⃣ Ambil semua pelanggan tahun ini
$pelanggan = Customer::whereYear('tarikhdaftar', $year)
->select('id','nama','kpbaru','kplama','t_lahir','jantina',
'kodkerja','poskod','kodcaw','tarikhdaftar','tujuangadai','nopelanggan')
->get();
$connections = $this->getActiveCawanganConnections();
// Ambil semua pekerjaan & poskod sekali untuk map cepat
$pekerjaanAll = Pekerjaan::pluck('keterangan','kodkerja');
$poskodAll = Poskod::with('daerah')->get()->keyBy('poskod');
// 2️⃣ Ambil semua nokp pelanggan
$nokps = $pelanggan->map(fn($p) => $p->kpbaru ?? $p->kplama)->filter()->values();
// 3️⃣ Ambil semua transaksi gadai + tebus untuk nokp tu dari semua cawangan
$allTransaksi = collect();
foreach ($connections as $conn) {
$data = DB::connection($conn->mysql)
->table('gadai as g')
->leftJoin('tebus as t', 'g.norujukan', '=', 't.norujukan')
->leftJoin('lelong as l', 'g.norujukan', '=', 'l.norujukan')
->whereIn('g.nokp', $nokps)
->whereBetween('g.t_gadai', [$startOfYear, $endOfYear]) // ⬅️ dalam tahun sahaja
->select(
'g.nokp',
'g.norujukan',
'g.t_gadai',
't.t_tebus',
'g.pinjaman',
'g.upah12',
'g.tempoh',
'l.t_lelong',
'g.sttebus',
)
->get();
$allTransaksi = $allTransaksi->concat($data);
}
// 4️⃣ Group by nokp
$transGrouped = $allTransaksi->groupBy('nokp');
// 5️⃣ Ambil semua norujukan transaksi online
$onlineNorujukan = TransactionOnline::pluck('norujukan')->unique()->values();
// 6️⃣ Map pelanggan
$result = $pelanggan->map(function($row) use ($transGrouped, $onlineNorujukan, $pekerjaanAll, $poskodAll, $year) {
$nokp = $row->kpbaru ?? $row->kplama ?? null;
$tempohData = $transGrouped->get($nokp, collect());
// Norujukan pelanggan spesifik
$norujukanPelanggan = $tempohData->pluck('norujukan')->unique()->values();
// Cara Bayaran
// $caraBayaran = $norujukanPelanggan->intersect($onlineNorujukan)->isNotEmpty() ? 'Online' : 'Kaunter';
$adaOnline = $norujukanPelanggan->contains(fn($nr) => isset($onlineNorujukan[$nr]));
$caraBayaran = $adaOnline ? 'Online' : 'Kaunter';
if ($adaOnline) {
\Log::info("Pelanggan {$row->nama} ({$nokp}): Bayaran Online - YA");
}
// Bilangan transaksi
$bilTransaksi = $tempohData->count();
// Bilangan Belum tebus
$endOfYear = Carbon::create($year, 12, 31)->endOfDay();
$bilOutstanding = $tempohData
->filter(function ($row) use ($endOfYear) {
// masih belum tebus langsung
if (empty($row->sttebus)) {
return true;
}
// dah tebus, tapi tebus selepas hujung tahun
return Carbon::parse($row->t_tebus)->gt($endOfYear);
})
->count();
// Bilangan tebus
$bilTebus = $tempohData->whereNotNull('t_tebus')->count();
// Jumlah pinjaman
$jumlahPinjaman = $tempohData->sum('pinjaman');
// Jumlah ujrah ikut tempoh surat
$jumlahUjrah = $tempohData->reduce(function ($carry, $trx) {
if (!$trx->upah12 || !$trx->tempoh) return $carry;
return $carry + ($trx->upah12 / 12 * $trx->tempoh);
}, 0);
// Tarikh mula & tamat
$tarikhMula = $tempohData->min('t_gadai');
$tarikhTamat = $tempohData->max('t_tebus');
$tarikhLelong = $tempohData->max('t_lelong');
// Tempoh Pajakan
if (!$tarikhMula) {
$tempohPajakan = '-';
} elseif (!$tarikhTamat || $tarikhMula == $tarikhTamat) {
$tempohPajakan = Carbon::parse($tarikhMula)->translatedFormat('M Y');
} else {
$tempohPajakan = Carbon::parse($tarikhMula)->translatedFormat('M Y') .
' - ' .
Carbon::parse($tarikhTamat)->translatedFormat('M Y');
}
// Hari sejak transaksi terakhir
$hariTempohPajakan = $tarikhMula
? Carbon::parse($tarikhMula)->diffInDays($tarikhTamat ? Carbon::parse($tarikhTamat) : Carbon::now())
: null;
// Tujuan gadai
$tujuangadai = $row->tujuangadai ?: '-';
// Status aktif: ada transaksi dalam tahun tu
$now = Carbon::now();
$endOfYear = Carbon::create($year, 12, 31)->endOfDay();
$startOfYear = Carbon::create($year, 1, 1)->startOfDay();
$statusAktif = $tempohData->contains(function ($trx) use ($startOfYear, $endOfYear) {
if (!$trx->t_gadai) return false;
$tGadai = Carbon::parse($trx->t_gadai);
$tTebus = $trx->t_tebus ? Carbon::parse($trx->t_tebus) : null;
// SAG wujud dalam tahun laporan
return
$tGadai->lte($endOfYear) &&
(
is_null($tTebus) || // belum ditebus
$tTebus->gte($startOfYear) // ditebus dalam / selepas tahun
);
});
// Umur
$umur = $row->t_lahir ? Carbon::parse($row->t_lahir)->age : null;
// Pekerjaan
$pekerjaan = $row->kodkerja
? ($pekerjaanAll[ltrim($row->kodkerja,'0')] ?? 'Lain-lain')
: '-';
// Daerah / lokasi
$lokasi = $row->poskod && isset($poskodAll[$row->poskod])
? $poskodAll[$row->poskod]->daerah->namadaerah ?? 'Lain-lain'
: 'Lain-lain';
return [
'cawangan' => $row->kodcaw ?? '-',
'nama' => $row->nama,
'nopelanggan' => $row->nopelanggan,
'nokp' => $nokp ?? '-',
'tarikhdaftar' => Carbon::parse($row->tarikhdaftar)->format('d-m-Y'),
'umur' => $umur,
'jantina' => $row->jantina ?? '-',
'pekerjaan' => $pekerjaan,
'daerah' => $lokasi,
'tempoh_pajakan' => $tempohPajakan,
'tarikh_mula' => $tarikhMula ? Carbon::parse($tarikhMula)->format('d-m-Y') : null,
'tarikh_tamat' => $tarikhTamat ? Carbon::parse($tarikhTamat)->format('d-m-Y') : null,
'hari_sejak_transaksi' => $hariTempohPajakan,
'bil_transaksi' => $bilTransaksi,
'bil_outstanding' => $bilOutstanding,
'bil_tebus' => $bilTebus,
'jumlah_pinjaman' => $jumlahPinjaman,
'norujukan' => $norujukanPelanggan,
'jumlah_ujrah' => $jumlahUjrah,
'tujuan_gadai' => $tujuangadai,
'status_aktif' => $statusAktif ? 'AKTIF' : 'TIDAK AKTIF',
'cara_bayaran' => $caraBayaran,
'tarikh_lelong' => $tarikhLelong ? Carbon::parse($tarikhLelong)->format('d-m-Y') : null,
];
});
return response()->json([
'year' => $year,
'summary' => [
'total_pelanggan' => $result->count(),
'jumlah_cawangan' => $result->pluck('cawangan')->unique()->count(),
],
'pelanggan' => $result->values(),
], 200);
}
private function formatTempohPajakan($start, $end)
{
if (!$start || !$end) {
return '-';
}
$mula = Carbon::parse($start);
$tamat = Carbon::parse($end);
if ($mula->year === $tamat->year) {
return $mula->translatedFormat('M') . ' – ' .
$tamat->translatedFormat('M Y');
}
return $mula->translatedFormat('M Y') . ' – ' .
$tamat->translatedFormat('M Y');
}
public function getSummaryLaporanDataPelangganBycaw(Request $request)
{
$year = $request->year;
if (!$year) {
return response()->json(['error' => 'Sila pilih tahun'], 400);
}
// ===============================
// INPUT CAWANGAN (MULTIPLE)
// ===============================
$cawangan = $request->input('cawangan', []);
if (!is_array($cawangan)) {
$cawangan = [$cawangan];
}
$startOfYear = Carbon::create($year, 1, 1)->startOfDay();
$endOfYear = Carbon::create($year, 12, 31)->endOfDay();
Carbon::setLocale('ms');
// ===============================
// 1️⃣ AMBIL PELANGGAN (FILTER CAWANGAN)
// ===============================
$pelangganQuery = Customer::whereYear('tarikhdaftar', $year);
if (!empty($cawangan)) {
$pelangganQuery->whereIn('kodcaw', $cawangan);
}
$pelanggan = $pelangganQuery
->select(
'id','nama','kpbaru','kplama','t_lahir','jantina',
'kodkerja','poskod','kodcaw','tarikhdaftar',
'tujuangadai','nopelanggan'
)
->get();
if ($pelanggan->isEmpty()) {
return response()->json([
'year' => $year,
'summary' => [
'total_pelanggan' => 0,
'jumlah_cawangan' => 0,
],
'pelanggan' => []
], 200);
}
// ===============================
// 2️⃣ CONNECTION CAWANGAN
// ===============================
$connections = $this->getActiveCawanganConnections();
// ===============================
// 3️⃣ MASTER DATA
// ===============================
$pekerjaanAll = Pekerjaan::pluck('keterangan', 'kodkerja');
$poskodAll = Poskod::with('daerah')->get()->keyBy('poskod');
// ===============================
// 4️⃣ NOKP PELANGGAN
// ===============================
$nokps = $pelanggan
->map(fn($p) => $p->kpbaru ?? $p->kplama)
->filter()
->values();
// ===============================
// 5️⃣ TRANSAKSI GADAI / TEBUS
// ===============================
$allTransaksi = collect();
foreach ($connections as $conn) {
$data = DB::connection($conn->mysql)
->table('gadai as g')
->leftJoin('tebus as t', 'g.norujukan', '=', 't.norujukan')
->leftJoin('lelong as l', 'g.norujukan', '=', 'l.norujukan')
->whereIn('g.nokp', $nokps)
->whereBetween('g.t_gadai', [$startOfYear, $endOfYear])
->select(
'g.nokp',
'g.norujukan',
'g.t_gadai',
't.t_tebus',
'g.pinjaman',
'g.upah12',
'g.tempoh',
'l.t_lelong',
'g.sttebus'
)
->get();
$allTransaksi = $allTransaksi->concat($data);
}
$transGrouped = $allTransaksi->groupBy('nokp');
// ===============================
// 6️⃣ NORUJUKAN BAYARAN ONLINE
// ===============================
$onlineNorujukan = TransactionOnline::pluck('norujukan')->unique();
// ===============================
// 7️⃣ MAP DATA PELANGGAN
// ===============================
$result = $pelanggan->map(function ($row) use (
$transGrouped,
$onlineNorujukan,
$pekerjaanAll,
$poskodAll,
$year
) {
$nokp = $row->kpbaru ?? $row->kplama;
$tempohData = $transGrouped->get($nokp, collect());
$norujukanPelanggan = $tempohData->pluck('norujukan')->unique();
// Cara Bayaran
$adaOnline = $norujukanPelanggan
->intersect($onlineNorujukan)
->isNotEmpty();
$caraBayaran = $adaOnline ? 'Online' : 'Kaunter';
// Bilangan
$bilTransaksi = $tempohData->count();
$bilOutstanding = $tempohData->filter(function ($row) use ($year) {
$endOfYear = Carbon::create($year, 12, 31)->endOfDay();
if (empty($row->sttebus)) return true;
return Carbon::parse($row->t_tebus)->gt($endOfYear);
})->count();
$bilTebus = $tempohData->whereNotNull('t_tebus')->count();
// Jumlah
$jumlahPinjaman = $tempohData->sum('pinjaman');
$jumlahUjrah = $tempohData->reduce(function ($carry, $trx) {
if (!$trx->upah12 || !$trx->tempoh) return $carry;
return $carry + ($trx->upah12 / 12 * $trx->tempoh);
}, 0);
// Tarikh
$tarikhMula = $tempohData->min('t_gadai');
$tarikhTamat = $tempohData->max('t_tebus');
$tarikhLelong = $tempohData->max('t_lelong');
// Tempoh Pajakan
if (!$tarikhMula) {
$tempohPajakan = '-';
} elseif (!$tarikhTamat || $tarikhMula == $tarikhTamat) {
$tempohPajakan = Carbon::parse($tarikhMula)->translatedFormat('M Y');
} else {
$tempohPajakan =
Carbon::parse($tarikhMula)->translatedFormat('M Y') .
' - ' .
Carbon::parse($tarikhTamat)->translatedFormat('M Y');
}
// Hari sejak transaksi terakhir
$hariTempohPajakan = $tarikhMula
? Carbon::parse($tarikhMula)->diffInDays($tarikhTamat ? Carbon::parse($tarikhTamat) : Carbon::now())
: null;
// Tujuan gadai
$tujuangadai = $row->tujuangadai ?: '-';
// Status aktif: ada transaksi dalam tahun tu
$now = Carbon::now();
// Status Aktif
$startOfYear = Carbon::create($year, 1, 1)->startOfDay();
$endOfYear = Carbon::create($year, 12, 31)->endOfDay();
$statusAktif = $tempohData->contains(function ($trx) use ($startOfYear, $endOfYear) {
if (!$trx->t_gadai) return false;
$tGadai = Carbon::parse($trx->t_gadai);
$tTebus = $trx->t_tebus ? Carbon::parse($trx->t_tebus) : null;
return $tGadai->lte($endOfYear) &&
(is_null($tTebus) || $tTebus->gte($startOfYear));
});
// Umur
$umur = $row->t_lahir ? Carbon::parse($row->t_lahir)->age : null;
// Pekerjaan
$pekerjaan = $row->kodkerja
? ($pekerjaanAll[ltrim($row->kodkerja, '0')] ?? 'Lain-lain')
: '-';
// Daerah
$lokasi = $row->poskod && isset($poskodAll[$row->poskod])
? ($poskodAll[$row->poskod]->daerah->namadaerah ?? 'Lain-lain')
: 'Lain-lain';
return [
'cawangan' => $row->kodcaw,
'nama' => $row->nama,
'nopelanggan' => $row->nopelanggan,
'nokp' => $nokp,
'tarikhdaftar' => Carbon::parse($row->tarikhdaftar)->format('d-m-Y'),
'umur' => $umur,
'jantina' => $row->jantina,
'pekerjaan' => $pekerjaan,
'daerah' => $lokasi,
'tempoh_pajakan' => $tempohPajakan,
'tarikh_mula' => $tarikhMula ? Carbon::parse($tarikhMula)->format('d-m-Y') : null,
'tarikh_tamat' => $tarikhTamat ? Carbon::parse($tarikhTamat)->format('d-m-Y') : null,
'hari_sejak_transaksi' => $hariTempohPajakan,
'bil_transaksi' => $bilTransaksi,
'bil_outstanding' => $bilOutstanding,
'bil_tebus' => $bilTebus,
'jumlah_pinjaman' => $jumlahPinjaman,
'jumlah_ujrah' => $jumlahUjrah,
'tujuan_gadai' => $tujuangadai,
'cara_bayaran' => $caraBayaran,
'status_aktif' => $statusAktif ? 'AKTIF' : 'TIDAK AKTIF',
'tarikh_lelong' => $tarikhLelong
? Carbon::parse($tarikhLelong)->format('d-m-Y')
: null,
];
});
$result = $result->sortBy('cawangan')->values();
return response()->json([
'year' => $year,
'cawangan' => $cawangan,
'summary' => [
'total_pelanggan' => $result->count(),
'jumlah_cawangan' => $result->pluck('cawangan')->unique()->count(),
],
'pelanggan' => $result,
], 200);
}
}
@@ -0,0 +1,78 @@
<?php
namespace App\Http\Controllers\Reports;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Audit;
class AuditTrailController extends Controller
{
public function getAuditTrail(Request $request)
{
$limit = $request->get('limit', 100);
$audit_trails = Audit::query()
->when($request->filled('norujukan'), fn($q) => $q->where('norujukan', $request->norujukan))
->when($request->filled('actions'), fn($q) => $q->where('actions', $request->actions))
->orderBy('created_at', 'desc')
->limit($limit)
->get();
return response()->json($audit_trails);
}
public function AuditTrailOperasi(Request $request)
{
$date_first = $request->get('date', null);
$date_till = $request->get('date_till', null);
$audit_trails = Audit::query()
->where('kodcaw', 'operasi')
->whereDate('created_at', '>=', $date_first)
->whereDate('created_at', '<=', $date_till)
->orderBy('created_at', 'asc')
->get();
$audit_trails->map(function ($audit) {
// Convert from UTC to Malaysia timezone (UTC+8)
$audit->created_at = $audit->created_at->format('Y-m-d H:i:s');
// Get differences between old_data and new_data
$old_data = json_decode($audit->old_data, true) ?? [];
$new_data = json_decode($audit->new_data, true) ?? [];
$differences = [];
// Find changed fields
foreach ($new_data as $key => $new_value) {
if ($key === 'tarikhupdate') continue;
$old_value = $old_data[$key] ?? null;
if ($old_value !== $new_value) {
$differences[$key] = [
'old' => $old_value,
'new' => $new_value
];
}
}
// Check for removed fields
foreach ($old_data as $key => $old_value) {
if ($key === 'tarikhupdate') continue;
if (!array_key_exists($key, $new_data)) {
$differences[$key] = [
'old' => $old_value,
'new' => null
];
}
}
$audit->differences = $differences;
return $audit;
});
return response()->json($audit_trails);
}
}
@@ -60,6 +60,7 @@ class LelonganController extends Controller
sum(lelong.cajlain) as sum_cajlain,
sum(lelong.cajlelong) as sum_cajlelong,
sum(lelong.hargajual) as sum_hargajual,
sum(lelong.hargaasas) as sum_hargaasas,
sum(lelong.baki) as sum_baki'));
@@ -46,10 +46,17 @@ class OutstandingController extends Controller
// Query outstanding summary for each branch
$summary_Outstanding_data = array_map(function ($db_connection) use ($endDate) {
// SAG KES
$queryKes = Gadai::on($db_connection)
->selectRaw('COUNT(norujukan) as total_gadaian_kes, SUM(pinjaman) as sum_pinjaman_kes')
->where('tagbaru', 0)
->where('tagpalsu', 1)
->where('sttebus', '');
// SAG LAMA
$queryLama = Gadai::on($db_connection)
->selectRaw('COUNT(norujukan) as total_gadaian, SUM(pinjaman) as sum_pinjaman')
->where('tagbaru', 0)
->where('tagpalsu', 0)
->where('sttebus', '');
// SAG BARU
@@ -64,12 +71,14 @@ class OutstandingController extends Controller
->where('sttebus', '');
if (!empty($endDate)) {
$queryKes->where('t_gadai', '<=', $endDate);
$queryLama->where('t_gadai', '<=', $endDate);
$queryBaru->where('t_gadai', '<=', $endDate);
$queryAll->where('t_gadai', '<=', $endDate);
}
return array_merge(
$queryKes->first()->toArray(),
$queryLama->first()->toArray(),
$queryBaru->first()->toArray(),
$queryAll->first()->toArray()
@@ -118,10 +127,12 @@ class OutstandingController extends Controller
if (!empty($endDate)) {
$queryAll->where('tarikh', '=', $endDate);
}
return array_merge(
$queryAll->first()->toArray()
);
$record = $queryAll->first();
return $record
? $record->toArray()
: ['bakieod' => 0];
}, $active_cawangan_db_connection);
// Combine data with branch info
@@ -0,0 +1,129 @@
<?php
namespace App\Http\Controllers\Reports;
use App\Http\Controllers\Controller;
use App\Cawangan;
use App\Gadai;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use App\Services\Reports\GadaianService as ReportGadaianService;
use App\Helper;
use App\Tebus;
use Illuminate\Support\Carbon;
class SaringanGadaianController extends Controller
{
public function getLaporanGadai(Request $request)
{
$startDate = $request->startDate ?? now()->startOfMonth()->toDateString();
$endDate = $request->endDate ?? now()->endOfMonth()->toDateString();
$active_dbs = Helper::getActiveCawanganDbConnection();
$cawangan = Cawangan::select('kodcaw', 'details_caw', 'mysql')
->whereIn('mysql', $active_dbs)
->get();
$responses = [];
foreach ($cawangan as $caw) {
$db = $caw->mysql;
$tgb = Tebus::on($db)
->join('gadai as g1', 'tebus.norujukan', '=', 'g1.norujukan')
->join('gadai as g2', function ($join) {
$join->on('g2.nokp', '=', 'g1.nokp')
->on('g2.t_gadai', '=', 'tebus.t_tebus')
->whereRaw('ABS(g2.berat - g1.berat) <= 0.02')
->whereColumn('g2.norujukan', '!=', 'g1.norujukan');
})
->whereBetween('tebus.t_tebus', [$startDate, $endDate])
->groupBy('tebus.norujukan')
->selectRaw("
tebus.norujukan as norujukan_tebus,
MAX(g2.norujukan) as norujukan_gadai_balik,
MAX(g2.nokp) as nokp,
MAX(g2.berat) as berat,
MAX(g2.nilaimarhun) as nilaimarhun,
MAX(g2.pinjaman) as pinjaman,
MAX(g2.t_gadai) as t_gadai,
MAX(g1.taglel) as taglel_lama
")
->get()
->map(function ($row) {
return [
'norujukan_tebus' => $row->norujukan_tebus,
'norujukan_gadai_balik' => $row->norujukan_gadai_balik,
'nokp' => $row->nokp,
'berat' => (float) $row->berat,
'nilaimarhun' => (float) $row->nilaimarhun,
'pinjaman' => (float) $row->pinjaman,
'taglel_lama' => $row->taglel_lama,
't_gadai' => $row->t_gadai,
'jenis' => $row->taglel_lama == 1 ? 'TGB_LELONG' : 'TGB_BIASA',
];
})
->toArray();
$tgb_norujukan = array_column($tgb, 'norujukan_gadai_balik');
$gadai_biasa = DB::connection($db)
->table('gadai')
->whereBetween('t_gadai', [$startDate, $endDate])
->whereNotIn('norujukan', $tgb_norujukan)
->select([
DB::raw('NULL as norujukan_tebus'),
'norujukan as norujukan_gadai_balik',
'nokp',
'berat',
'nilaimarhun',
'pinjaman',
DB::raw('NULL as taglel_lama'),
't_gadai',
])
->get()
->map(function ($row) {
return [
'norujukan_tebus' => null,
'norujukan_gadai_balik' => $row->norujukan_gadai_balik,
'nokp' => $row->nokp,
'berat' => (float) $row->berat,
'nilaimarhun' => (float) $row->nilaimarhun,
'pinjaman' => (float) $row->pinjaman,
'taglel_lama' => null,
't_gadai' => $row->t_gadai,
'jenis' => 'GADAI_BIASA',
];
})
->toArray();
$details = collect($tgb)
->merge($gadai_biasa)
->unique('norujukan_gadai_balik')
->sortBy(['jenis', 't_gadai'])
->values()
->all();
$responses[] = [
'kodcaw' => $caw->kodcaw,
'details_caw' => $caw->details_caw,
'jumlah_surat' => count($details),
'total_tgb_lelong' => collect($details)->where('jenis', 'TGB_LELONG')->count(),
'total_tgb_biasa' => collect($details)->where('jenis', 'TGB_BIASA')->count(),
'total_gadai_biasa' => collect($details)->where('jenis', 'GADAI_BIASA')->count(),
'sum_berat' => collect($details)->sum('berat'),
'sum_nilai_marhun' => collect($details)->sum('nilaimarhun'),
'sum_pinjaman' => collect($details)->sum('pinjaman'),
'details' => $details,
];
}
return response()->json($responses);
}
}
@@ -2,9 +2,11 @@
namespace App\Http\Controllers\Reports;
use App\Cawangan;
use App\Gadai;
use App\Helper;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Carbon\Carbon;
class StokAuditController extends Controller
{
@@ -38,6 +40,40 @@ class StokAuditController extends Controller
public function getSummaryLaporanStokAudit(Request $request)
{
$connections = $this->getActiveCawanganConnections();
// MODE 1: TARIKH SEMASA
if ($request->dateOption === 'current') {
$today = Carbon::today();
$total_audit = $connections->map(function ($connection) {
$data = Gadai::on($connection->mysql)
->where('tagpalsu', 0)
->where(function($q) {
$q->where('sttebus', '')
->orWhereNull('sttebus');
})
->get();
return [
'cawangan' => $connection->details_caw,
'bilangan_pembiayaan' => $data->count(),
'berat' => $data->sum('berat'),
'nilai_marhun' => $data->sum('nilaimarhun'),
'baki_pembiayaan' => $data->sum('bakipjm'),
'pembiayaan' => $data->sum('pinjaman'),
'upah_belum_bayar' => $data->sum('upahblmbyar'),
'total_utg_onepercent' => 0,
'total_ujrah' => $data->sum('ujrah'),
];
});
}
// MODE 2: BULAN TERTENTU
else {
$month = $request->month;
$year = $request->year;
@@ -59,34 +95,24 @@ class StokAuditController extends Controller
'total_ujrah' => $data->sum('ujrah'),
];
});
}
$getTotalAcrossCawangan = function($property) use($total_audit) {
$total = array_reduce($total_audit->toArray(), function($carry, $subarray) use($property) {
return $carry + $subarray[$property];
}, 0);
return $total;
};
$total_all_cawangan_audit = [
// TOTAL SEMUA CAWANGAN
$total_all = [
'cawangan' => 'Total Semua Cawangan',
'bilangan_pembiayaan' => $getTotalAcrossCawangan('bilangan_pembiayaan'),
'berat' => $getTotalAcrossCawangan('berat'),
'nilai_marhun' => $getTotalAcrossCawangan('nilai_marhun'),
'baki_pembiayaan' => $getTotalAcrossCawangan('baki_pembiayaan'),
'pembiayaan' => $getTotalAcrossCawangan('pembiayaan'),
'upah_belum_bayar' => $getTotalAcrossCawangan('upah_belum_bayar'),
'total_utg_onepercent' => $getTotalAcrossCawangan('total_utg_onepercent'),
'total_ujrah' => $getTotalAcrossCawangan('total_ujrah'),
'bilangan_pembiayaan' => $total_audit->sum('bilangan_pembiayaan'),
'berat' => $total_audit->sum('berat'),
'nilai_marhun' => $total_audit->sum('nilai_marhun'),
'baki_pembiayaan' => $total_audit->sum('baki_pembiayaan'),
'pembiayaan' => $total_audit->sum('pembiayaan'),
'upah_belum_bayar' => $total_audit->sum('upah_belum_bayar'),
'total_utg_onepercent' => $total_audit->sum('total_utg_onepercent'),
'total_ujrah' => $total_audit->sum('total_ujrah'),
];
$total_audit->push($total_all_cawangan_audit);
$total_audit->push($total_all);
if (sizeof($total_audit) == 0) {
return response()->json('failed', 200);
} else {
return response()->json($total_audit, 200);
}
return response()->json($total_audit, 200);
}
}
+291
View File
@@ -0,0 +1,291 @@
<?php
namespace App\Http\Controllers;
use App\ShorakaTransaction;
use App\Services\ShorakaService;
use Carbon\Carbon;
use Illuminate\Database\QueryException;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Throwable;
class ShorakaController extends Controller
{
public function index(Request $request)
{
$this->validate($request, [
'kodcaw' => 'nullable|string|max:30',
'norujukan' => 'nullable|string|max:100',
'status' => 'nullable|string|max:30',
'date_from' => 'nullable|date_format:Y-m-d',
'date_to' => 'nullable|date_format:Y-m-d',
'per_page' => 'nullable|integer|min:1|max:100',
'page' => 'nullable|integer|min:1',
'branch_scope' => 'nullable|string|max:30',
]);
$query = ShorakaTransaction::query();
foreach (['kodcaw', 'norujukan', 'status'] as $field) {
if ($request->filled($field)) {
$query->where($field, $request->input($field));
}
}
if ($request->filled('date_from')) {
$query->where('created_at', '>=', $request->input('date_from') . ' 00:00:00');
}
if ($request->filled('date_to')) {
$query->where('created_at', '<', Carbon::parse($request->input('date_to'))->addDay()->format('Y-m-d') . ' 00:00:00');
}
$records = $query->orderByDesc('created_at')->orderByDesc('id')
->paginate(min(100, max(1, (int) $request->input('per_page', 25))));
$payload = [
'data' => $records->getCollection()->map(function (ShorakaTransaction $record) {
return [
'id' => $record->id,
'kodcaw' => $record->kodcaw,
'norujukan' => $record->norujukan,
'teller' => $record->teller,
'status' => $record->status,
'order_id' => $record->order_id,
'created_at' => optional($record->created_at)->toDateTimeString(),
'updated_at' => optional($record->updated_at)->toDateTimeString(),
];
})->values(),
'total' => $records->total(),
'current_page' => $records->currentPage(),
'per_page' => $records->perPage(),
'last_page' => $records->lastPage(),
];
if ($request->input('with_branches')) {
$branches = ShorakaTransaction::query()->whereNotNull('kodcaw');
if ($request->filled('branch_scope')) {
$branches->where('kodcaw', $request->input('branch_scope'));
}
$payload['branches'] = $branches->distinct()->orderBy('kodcaw')->pluck('kodcaw')->values();
}
return response()->json($payload);
}
public function submit(Request $request, ShorakaService $shoraka)
{
$validated = $this->validate($request, [
'norujukan' => 'required|string|max:255',
'ownership' => 'required|string|max:255',
'order_amount' => 'required|numeric|min:0.01|max:9999999999999.99',
'murabaha_amount' => 'required|numeric|min:0.01|max:9999999999999.99',
'tenor_other' => 'required|integer|min:1',
'teller' => 'nullable|string|max:255',
]);
if (!$shoraka->configured()) {
return $this->unconfigured();
}
$existing = ShorakaTransaction::where('norujukan', $validated['norujukan'])->first();
if ($existing) {
return $this->duplicate($existing);
}
$payload = [
'product_type' => 'FINANCING',
'commodity_type' => '000-COPPER',
'ownership' => $validated['ownership'],
'ownership_doc_no' => $validated['norujukan'],
'value_date' => Carbon::now()->format('d/m/Y'),
'order_currency' => 'MYR',
'order_amount' => number_format((float) $validated['order_amount'], 2, '.', ''),
'murabaha_amount' => number_format((float) $validated['murabaha_amount'], 2, '.', ''),
// Appendix codes in v1.5: OTHERS and MONTH.
'tenor' => 'OTHERS',
'tenor_other' => $validated['tenor_other'],
'tenor_other_unit' => 'MONTH',
'order_type' => 'Buy & Sell',
];
try {
// The unique norujukan index is the atomic claim for concurrent submissions.
$attributes = [
'norujukan' => $validated['norujukan'],
'kodcaw' => $request->attributes->get('shoraka_kodcaw'),
'teller' => $validated['teller'] ?? null,
'status' => 'PENDING',
'product_type' => $payload['product_type'],
'commodity_type' => $payload['commodity_type'],
'ownership_name' => $payload['ownership'],
'ownership_doc_no' => $payload['ownership_doc_no'],
'order_amount' => $payload['order_amount'],
'murabaha_amount' => $payload['murabaha_amount'],
'request_payload' => $payload,
'attempt_count' => 1,
];
$transaction = ShorakaTransaction::create($attributes);
} catch (QueryException $exception) {
$existing = ShorakaTransaction::where('norujukan', $validated['norujukan'])->first();
if ($existing) {
return $this->duplicate($existing);
}
throw $exception;
}
try {
$response = $shoraka->submitOrder($payload);
$this->saveOrder($transaction, $response);
return response()->json([
'success' => true,
'message' => 'Order berjaya dihantar ke Shoraka.',
'data' => $this->orderData($transaction),
]);
} catch (Throwable $exception) {
// A timeout or invalid response does not prove the remote trade failed.
// Block resubmission until this reference has been reconciled with Shoraka.
$transaction->update([
'status' => $transaction->order_id ? $transaction->status : 'UNKNOWN',
'error_message' => 'Submission outcome requires reconciliation with Shoraka.',
]);
return $this->upstreamError('submit', $transaction, $exception);
}
}
public function refresh(string $norujukan, ShorakaService $shoraka)
{
if (!$shoraka->configured()) {
return $this->unconfigured();
}
$transaction = ShorakaTransaction::where('norujukan', $norujukan)->firstOrFail();
if (!$transaction->order_id) {
return $this->missingOrderId();
}
try {
$response = $shoraka->orderStatus($transaction->order_id);
$this->saveOrder($transaction, $response);
return response()->json(['success' => true, 'data' => $this->orderData($transaction)]);
} catch (Throwable $exception) {
return $this->upstreamError('status', $transaction, $exception);
}
}
public function certificate(string $norujukan, ShorakaService $shoraka)
{
if (!$shoraka->configured()) {
return $this->unconfigured();
}
$transaction = ShorakaTransaction::where('norujukan', $norujukan)->firstOrFail();
if (!$transaction->order_id) {
return $this->missingOrderId();
}
try {
$pdf = $shoraka->certificate($transaction->order_id);
return response($pdf, 200, [
'Content-Type' => 'application/pdf',
'Content-Disposition' => 'attachment; filename="shoraka-' . $transaction->id . '.pdf"',
'Cache-Control' => 'private, no-store',
]);
} catch (Throwable $exception) {
return $this->upstreamError('certificate', $transaction, $exception);
}
}
public function callback(Request $request, ShorakaService $shoraka)
{
if (!$shoraka->configured()) {
return $this->unconfigured();
}
$payload = $request->json()->all();
if (!$shoraka->validCallback($payload)) {
return response()->json(['success' => false, 'message' => 'Invalid callback signature.'], 401);
}
// ownershipDocNo and certificateUrl are NOT signed. Never use them to link
// a callback to a local record or download a file.
$transaction = ShorakaTransaction::where('order_id', $payload['orderId'])->first();
if (!$transaction) {
return response()->json(['success' => false, 'message' => 'Order not found.'], 404);
}
try {
// There is no signed callback timestamp. Query the current order so a
// delayed/replayed callback cannot directly restore an old status.
$response = $shoraka->orderStatus($transaction->order_id);
unset($payload['signature']);
$this->saveOrder($transaction, $response, ['callback_payload' => $payload]);
// Exact acknowledgement required by section 6.
return response('OK', 200, ['Content-Type' => 'text/plain']);
} catch (Throwable $exception) {
return $this->upstreamError('callback', $transaction, $exception);
}
}
protected function saveOrder(ShorakaTransaction $transaction, array $response, array $extra = []): void
{
$transaction->update(array_merge([
'order_id' => (string) $response['orderId'],
'status' => $response['status'],
'certificate_url' => $response['certificateUrl'] ?? null,
'response_payload' => $response,
'submitted_at' => $transaction->submitted_at ?: Carbon::now(),
'completed_at' => $response['status'] === 'Completed'
? ($transaction->completed_at ?: Carbon::now()) : $transaction->completed_at,
'error_message' => null,
], $extra));
app(\App\Services\ShorakaGadaiService::class)->sync($transaction);
}
protected function orderData(ShorakaTransaction $transaction): array
{
return [
'norujukan' => $transaction->norujukan,
'kodcaw' => $transaction->kodcaw,
'teller' => $transaction->teller,
'order_id' => $transaction->order_id,
'status' => $transaction->status,
'certificate_url' => $transaction->certificate_url,
];
}
protected function duplicate(ShorakaTransaction $transaction)
{
return response()->json([
'success' => false,
'message' => 'Transaksi telah direkodkan. Semak status dengan Shoraka sebelum penghantaran semula.',
'data' => $this->orderData($transaction),
], 409);
}
protected function missingOrderId()
{
return response()->json([
'success' => false,
'message' => 'Order ID belum tersedia. Semakan dengan Shoraka diperlukan.',
], 409);
}
protected function unconfigured()
{
return response()->json([
'success' => false,
'message' => 'API ID dan Secret Key Shoraka belum dikonfigurasi.',
], 503);
}
protected function upstreamError(string $operation, ShorakaTransaction $transaction, Throwable $exception)
{
// Connection exceptions may include request details; log only the class.
Log::error('Shoraka request failed', [
'operation' => $operation,
'transaction_id' => $transaction->id,
'exception_type' => get_class($exception),
]);
return response()->json([
'success' => false,
'message' => 'Permintaan Shoraka gagal. Semak status transaksi sebelum mencuba semula.',
], 502);
}
}
@@ -0,0 +1,76 @@
<?php
namespace App\Http\Controllers;
use App\KomuditiTransaksi;
use App\Services\ShorakaGadaiService;
use App\Services\ShorakaService;
use Illuminate\Http\Request;
class ShorakaGadaiController extends ShorakaController
{
public function readiness(ShorakaService $shoraka)
{
try {
\App\ShorakaTransaction::query()->limit(1)->exists();
$storeReady = true;
} catch (\Throwable $exception) {
$storeReady = false;
}
return response()->json(['provider' => 'shoraka', 'ready' => $storeReady && $shoraka->configured()]);
}
public function showGadai(string $kodcaw, string $norujukan, ShorakaGadaiService $gadaiService)
{
$gadai = $gadaiService->gadai($kodcaw, $norujukan);
$order = $gadaiService->transaction($kodcaw, $norujukan);
return response()->json([
'success' => true,
'data' => [
'norujukan' => $norujukan, 'kodcaw' => $kodcaw,
'order_amount' => $gadai->pinjaman, 'murabaha_amount' => $gadai->hargajualan,
'order' => $order ? $this->orderData($order) : null,
'details' => $order ? $order->response_payload : null,
'simulation' => $order && strpos((string) $order->order_id, 'SIM-') === 0,
'can_complete' => $gadai->exists && $order && $order->status === 'Completed'
&& number_format((float) $gadai->pinjaman, 2, '.', '') === $order->order_amount
&& number_format((float) $gadai->hargajualan, 2, '.', '') === $order->murabaha_amount,
],
]);
}
public function submitGadai(string $kodcaw, string $norujukan, Request $request, ShorakaGadaiService $gadaiService, ShorakaService $shoraka)
{
$gadai = $gadaiService->gadai($kodcaw, $norujukan);
$existing = $gadaiService->transaction($kodcaw, $norujukan);
if ($existing) {
return $this->duplicate($existing);
}
// Historical commodity purchases must never be traded again.
if (KomuditiTransaksi::on('mysql7')->where('norujukan', $norujukan)->exists()
|| $gadai->cetak || $gadai->sttebus) {
abort(409, 'Gadaian lama atau telah selesai tidak boleh dihantar sebagai order baharu.');
}
$payload = $gadaiService->payload($gadai);
$teller = $request->input('teller');
if (is_string($teller) && $teller !== '') {
$payload['teller'] = $teller;
}
$submission = new Request($payload);
$submission->attributes->set('shoraka_kodcaw', $kodcaw);
return $this->submit($submission, $shoraka);
}
public function refreshGadai(string $kodcaw, string $norujukan, ShorakaGadaiService $gadaiService, ShorakaService $shoraka)
{
$gadaiService->gadai($kodcaw, $norujukan);
$gadaiService->transaction($kodcaw, $norujukan);
return $this->refresh($norujukan, $shoraka);
}
public function certificateGadai(string $kodcaw, string $norujukan, ShorakaGadaiService $gadaiService, ShorakaService $shoraka)
{
$gadaiService->assertCompleted($kodcaw, $norujukan);
return $this->certificate($norujukan, $shoraka);
}
}
@@ -475,6 +475,9 @@ class StokAuditController extends Controller
// Filter audit
$rekodaudit = $audit->filter(function ($item) use ($tarikhTerkiniT, $tarikhTerkiniA) {
if ($item->lelong_tawarruq == 0) {
return true;
}
$norujukan = $item->norujukan;
$adaTebusTerkini = isset($tarikhTerkiniT[$norujukan]) &&
+75 -4
View File
@@ -20,6 +20,7 @@ use App\Panjar;
use App\HargaEmas;
use App\Moratorium;
use App\Audit;
use App\Feature;
use App\LookupWakil;
use App\StokAudit1;
@@ -193,7 +194,7 @@ class TebusController extends Controller
$cawangan = Cawangan::on('mysql7')->where('kodcaw','=',$kodcaw)->first();
$tebus_data = Tebus::on($cawangan['mysql'])
->select('tebus.t_tebus', 'gad.t_gadai','gad.hargajualan','gad.bakihargajualan','gad.bakipjm as gadaibakipjm','tebus.nilaimarhun','tebus.tagterima', 'tebus.teller','tebus.jenistebus','tebus.pinjaman','tebus.bakiupah','tebus.bakipjm','gad.norujukan','gad.nokp','tebus.jbg','tebus.ujrah','tebus.onepercent','gad.tagbaru')
->select('tebus.t_tebus', 'gad.t_gadai','gad.hargajualan','gad.bakihargajualan','gad.bakipjm as gadaibakipjm','tebus.nilaimarhun','tebus.tagterima', 'tebus.teller','tebus.jenistebus','tebus.pinjaman','tebus.bakiupah','tebus.bakipjm','gad.norujukan','gad.nokp','tebus.jbg','tebus.ujrah','tebus.onepercent','gad.tagbaru','tebus.diskaun_rebate')
->join('gadai as gad','gad.norujukan','=','tebus.norujukan')
// ->join('online_arrahn.customer as cust', function($join)
// {
@@ -232,7 +233,8 @@ class TebusController extends Controller
"nokp"=>$tebusData['nokp'],
"jbg"=>$tebusData['jbg'],
"ujrah"=>$trans['ujrah_rebet'],
"onepercent"=>$trans['onepercent_rebet']
"onepercent"=>$trans['onepercent_rebet'],
"diskaun_rebate"=>$tebusData['diskaun_rebate'],
]);
}
@@ -274,7 +276,8 @@ class TebusController extends Controller
'tebus.jbg',
'tebus.ujrah',
'tebus.onepercent',
'gad.tagbaru'
'gad.tagbaru',
'tebus.diskaun_rebate'
)
->join('gadai as gad', 'gad.norujukan', '=', 'tebus.norujukan')
->where('tebus.t_tebus', '>=', $request->mula)
@@ -310,7 +313,8 @@ class TebusController extends Controller
"jbg"=>$tebusData['jbg'],
"ujrah"=>$trans['ujrah_rebet'],
"onepercent"=>$trans['onepercent_rebet'],
"tagbaru"=>$tebusData['tagbaru']
"tagbaru"=>$tebusData['tagbaru'],
"diskaun_rebate"=>$tebusData['diskaun_rebate'],
]);
}
@@ -1672,6 +1676,53 @@ class TebusController extends Controller
return response($rebet);
}
public function RebetPromoCalculation($kodcaw, Request $request)
{
$kodcaw = $request->kodcaw ?: $kodcaw;
if (!Feature::enabled($kodcaw, Feature::REBET_ANNUR)) {
return response(0);
}
$cawangan = Cawangan::on('mysql7')->where('kodcaw', '=', $kodcaw)->first();
$rebet = Tebus::on($cawangan['mysql'])
->join('gadai as db2', 'tebus.norujukan', '=', 'db2.norujukan')
->where('tebus.t_tebus', '<=', $request->tarikh)
->where('db2.hargajualan', '>', '0')
->whereNotNull('db2.hargajualan')
->sum('diskaun_rebate');
return response($rebet);
}
public function PelarasanRebate($kodcaw, Request $request)
{
$kodcaw = $request->kodcaw ?: $kodcaw;
if (!Feature::enabled($kodcaw, Feature::REBET_ANNUR)) {
return response(0);
}
$cawangan = Cawangan::on('mysql7')
->where('kodcaw', $kodcaw)
->first();
$tarikhLaporan = Carbon::parse($request->tarikh)->toDateString();
$tarikhTamatPelarasan = '2026-09-02';
$tarikhAkhir = $tarikhLaporan > $tarikhTamatPelarasan
? $tarikhTamatPelarasan
: $tarikhLaporan;
$rebet = Tebus::on($cawangan['mysql'])
->join('gadai as db2', 'tebus.norujukan', '=', 'db2.norujukan')
->whereDate('tebus.t_tebus', '>=', '2025-05-13')
->whereDate('tebus.t_tebus', '<=', $tarikhAkhir)
->where('db2.hargajualan', '>', 0)
->whereDate('db2.t_gadai', '>', '2023-05-21')
->sum('tebus.diskaun_rebate');
return response($rebet);
}
public function RebetCalculationLejer($kodcaw,Request $request)
{
$cawangan = Cawangan::on('mysql7')->where('kodcaw','=',$request->kodcaw)->first();
@@ -1687,6 +1738,26 @@ class TebusController extends Controller
return response($rebet);
}
public function RebetPromoCalculationLejer($kodcaw, Request $request)
{
$kodcaw = $request->kodcaw ?: $kodcaw;
if (!Feature::enabled($kodcaw, Feature::REBET_ANNUR)) {
return response(0);
}
$cawangan = Cawangan::on('mysql7')->where('kodcaw', '=', $kodcaw)->first();
$rebet = Tebus::on($cawangan['mysql'])
->join('gadai as db2', 'tebus.norujukan', '=', 'db2.norujukan')
->where('tebus.t_tebus', '>=', $request->mula)
->where('tebus.t_tebus', '<=', $request->hingga)
->where('db2.hargajualan', '>', '0')
->whereNotNull('db2.hargajualan')
->sum('diskaun_rebate');
return response($rebet);
}
public function getUpahLamaToday($kodcaw,$norujukan)
{
$current = Carbon::now();
@@ -8,6 +8,7 @@ use App\Cawangan;
use App\Gadai;
use App\GlDetail;
use App\Http\Controllers\TebusController as TebusController;
use App\Lelong;
use App\Services\Loggers\OnlineTransactionLoggerService;
use App\Services\OnePercentServices;
use App\Support\v2\Calculation;
@@ -767,8 +768,52 @@ class TransactionOnlineController extends Controller
return 'rejected';
}
}
public function CustomerInfoLelong(Request $request){
$cawangan_all = Cawangan::on('mysql7')->get();
$totalBakiLelong = 0;
$totalRugiLelong = 0;
foreach ($cawangan_all as $index => $cawangan) {
$gadai = Gadai::on($cawangan['mysql'])
->where('nokp', '=', $request->ic)
->where('sttebus','=','L')
->orderBy('t_gadai', 'desc')
->get()->toArray();
if (sizeof($gadai) != 0) {
foreach ($gadai as $gad) {
$bakilelong = Lelong::on($cawangan['mysql'])
->where(function($query) {
$query->whereNull('trkhtuntut')
->orWhere('trkhtuntut', '=', '');
})
->where('norujukan', '=' ,$gad['norujukan'])
->where('baki', '>=', 0)
->sum('baki');
$rugilelong = Lelong::on($cawangan['mysql'])
->where(function($query) {
$query->whereNull('trkhtuntut')
->orWhere('trkhtuntut', '=', '');
})
->where('norujukan', '=' ,$gad['norujukan'])
->where('baki','<',0)
->sum('baki');
$totalBakiLelong += $bakilelong;
$totalRugiLelong += $rugilelong;
}
}
}
return response()->json([
'success' => true,
'message' => 'Data retrieved successfully',
'data' => [
'bakilelong' => $totalBakiLelong,
'rugilelong' => $totalRugiLelong,
]
], 200);
}
}
+60
View File
@@ -13,6 +13,7 @@ use App\Vault;
use Auth0\SDK\API\Management\Users;
use Illuminate\Http\Request;
use Carbon\Carbon;
use App\Services\StaffNotificationClient;
use Illuminate\Support\Facades\Log;
class TunaiController extends Controller
{
@@ -165,6 +166,21 @@ class TunaiController extends Controller
}
public function getTunaiByTarikh(Request $request)
{
$cawangan = Cawangan::on('mysql7')->where('kodcaw','=',$request->kodcaw)->first();
$tunai = Tunai::on($cawangan['mysql'])
->where([['kodcaw','=',$request->kodcaw],
['tarikh','=',$request->tarikh],
['kodlaluan','=',$request->kodlaluan]
])
->orderBy('tarikh','desc')
->first();
return response()->json($tunai);
}
public function getTunaiPast($kodcaw,Request $request)
{
@@ -461,10 +477,54 @@ class TunaiController extends Controller
$audit->updated_at = $current;
$audit->save();
$this->notifyKhazanahAliranTunai($kodcaw, $request, $reqteller);
return response()->json($reqteller,200);
}
/**
* Inbox for PC/PPC at the branch. Failures must not block the request.
*
* @param string $kodcaw
* @param \Illuminate\Http\Request $request
* @param \App\RequestTeller $reqteller
* @return void
*/
protected function notifyKhazanahAliranTunai($kodcaw, Request $request, $reqteller)
{
try {
$cawangan = Cawangan::on('mysql7')->where('kodcaw', '=', $kodcaw)->first();
$namaCaw = $kodcaw;
if ($cawangan) {
$nama = trim((string) $cawangan->nama_cawangan);
if ($nama !== '') {
$namaCaw = $nama;
}
}
$modal = (float) $request->modal;
$serahan = (float) $request->serahan;
app(StaffNotificationClient::class)->notify([
'type' => 'aliran_tunai.pending_khazanah',
'cawangan' => $kodcaw,
'audience' => ['role' => 'pc'],
'data' => [
'request_no' => isset($reqteller->no) ? $reqteller->no : null,
'kodlaluan' => $request->kodlaluan,
'modal' => $modal,
'serahan' => $serahan,
'cawangan_nama' => $namaCaw,
],
]);
} catch (\Throwable $e) {
Log::warning('Aliran tunai khazanah notification failed: '.$e->getMessage(), [
'kodcaw' => $kodcaw,
'kodlaluan' => $request->kodlaluan,
]);
}
}
public function getTunaiSemalam($kodcaw)
{
$current = new Carbon();
+137 -80
View File
@@ -12,6 +12,7 @@ use App\GlDetail;
use App\imbangdugalama;
use App\GlCode;
use App\Panjar;
use App\ModalOnline;
use Illuminate\Http\Request;
use Carbon\Carbon;
@@ -168,37 +169,64 @@ class VaultController extends Controller
return response()->json($updateVault, 200);
}
public function updateVaultDeletePendahuluanSerahan($kodcaw, Request $request)
{
$current = Carbon::now();
$current = new Carbon();
$cawangan = Cawangan::on('mysql7')->where('kodcaw','=',$kodcaw)->first();
$vault=Vault::on($cawangan['mysql'])->where('kodcaw','=',$kodcaw)->where('tarikh','=',$current->toDateString())->orderby('tarikh','desc')->first();
public function updateVaultDeletePendahuluanSerahan($kodcaw, Request $request)
{
$current = new Carbon();
$bakiawal = $vault['bakiawal'];
$keluar = $vault['keluar'];
$masuk = $vault['masuk'];
$bakiakhir = $vault['bakiakhir'];
$bakieod = $vault['bakieod'];
$pendahuluan = $vault['pendahuluan'];
$serahan = $vault['serahan'];
$totpanjaran = $vault['totpanjar'];
$cawangan = Cawangan::on('mysql7')->where('kodcaw', '=', $kodcaw)->first();
$glpend = GlDetail::on($cawangan['mysql'])->where('tarikh','=', $current->toDateString())->sum('pendahuluan');
$glser = GlDetail::on($cawangan['mysql'])->where('tarikh','=', $current->toDateString())->where('teller','<>','ONLINE')->sum('serahan');
if (!$cawangan) {
return response()->json(['message' => 'Cawangan tidak dijumpai'], 404);
}
$bakiakhir = $bakiawal - $keluar + $masuk + $glpend - $glser - $totpanjaran;
$vault = Vault::on($cawangan['mysql'])
->where('kodcaw', '=', $kodcaw)
->where('tarikh', '=', $current->toDateString())
->first();
$updateVault=Vault::on($cawangan['mysql'])->where([['tarikh','=',$current->toDateString()],['kodcaw','=', $kodcaw]])->update(array(
'pendahuluan' => $glpend,
'serahan' => $glser,
'bakiakhir'=>$bakiakhir,
'bakieod'=>$bakiakhir
));
if (!$vault) {
return response()->json(['message' => 'Rekod vault hari ini tidak dijumpai'], 404);
}
return response()->json($updateVault, 200);
}
$bakiawal = $vault['bakiawal'];
$keluar = $vault['keluar'];
$masuk = $vault['masuk'];
$totpanjaran = $vault['totpanjar'];
// Pendahuluan HQ (dari GL)
$glpend = GlDetail::on($cawangan['mysql'])
->where('tarikh', '=', $current->toDateString())
->sum('pendahuluan');
// Serahan (GL, exclude teller ONLINE)
$glser = GlDetail::on($cawangan['mysql'])
->where('tarikh', '=', $current->toDateString())
->where('teller', '<>', 'ONLINE')
->sum('serahan');
// Pendahuluan Online (had yang diluluskan hari ini)
$modalOnlineHad = ModalOnline::on($cawangan['mysql'])
->where('kodcaw', '=', $kodcaw)
->where('tarikh', '=', $current->toDateString())
->value('had') ?? 0;
// Gabungan pendahuluan HQ + pendahuluan online
$totalPendahuluan = $glpend + $modalOnlineHad;
$bakiakhir = $bakiawal - $keluar + $masuk + $totalPendahuluan - $glser - $totpanjaran;
$updateVault = Vault::on($cawangan['mysql'])
->where([['tarikh', '=', $current->toDateString()], ['kodcaw', '=', $kodcaw]])
->update([
'pendahuluan' => $totalPendahuluan,
'serahan' => $glser,
'bakiakhir' => $bakiakhir,
'bakieod' => $bakiakhir,
]);
return response()->json($updateVault, 200);
}
public function create($kodcaw,Request $request)
{
@@ -400,12 +428,14 @@ class VaultController extends Controller
}
$bakiakhirVault = $vault ? $vault['bakiakhir'] : 0;
if($total != 0)
{
$totalbakiakhirtunai = $vault['bakiakhir'] + $total;
$totalbakiakhirtunai = $bakiakhirVault + $total;
}else
{
$totalbakiakhirtunai = $vault['bakiakhir'];
$totalbakiakhirtunai = $bakiakhirVault;
}
return response()->json($totalbakiakhirtunai);
@@ -454,42 +484,49 @@ class VaultController extends Controller
return response()->json($arraytunaiditangan,200);
}
public function laporanPetiBesi($kodcaw,$tarikh)
{
$current = Carbon::now();
$current = new Carbon();
$current = Carbon::now();
$current = new Carbon();
$cawangan = Cawangan::on('mysql7')->where('kodcaw','=',$kodcaw)->first();
$vault=Vault::on($cawangan['mysql'])
->where('kodcaw','=',$kodcaw)
->where('tarikh','=', $tarikh)
->orderby('tarikh','desc')->first();
if (!$cawangan) {
return response()->json(['error' => 'Cawangan tidak dijumpai'], 404);
}
$vault = Vault::on($cawangan['mysql'])
->where('kodcaw','=',$kodcaw)
->where('tarikh','=', $tarikh)
->orderby('tarikh','desc')
->first();
if (!$vault) {
return response()->json(['error' => 'Tiada data pada tarikh tersebut'], 404);
}
return response()->json($vault);
}
public function CalculationPendahuluanSerahan($kodcaw,Request $request)
{
$cawangan = Cawangan::on('mysql7')->where('kodcaw','=',$kodcaw)->first();
{
$cawangan = Cawangan::on('mysql7')->where('kodcaw','=',$kodcaw)->first();
$serahanpkb = '1000/020';
$serahanjmc = '1000/026';
$pendahuluanpkb = '7000/102';
$pendahuluanjmc = '7000/106';
$serahankoperasi = '1000/090';
$serahanpkb = '1000/020';
$serahanjmc = '1000/026';
$pendahuluanpkb = '7000/102';
$pendahuluanjmc = '7000/106';
$serahankoperasi = '1000/090';
//Selepas Tawarruq
//Selepas Tawarruq
$serahanpkbTawarruq = GlDetail::on($cawangan['mysql'])
->where('ktacct','=',$serahanpkb)
->where('tarikh','<=', $request->tarikh)
->where('tarikh','>','2021-03-24')
->sum('serahan');
$serahanpkbTawarruq = GlDetail::on($cawangan['mysql'])
->where('ktacct','=',$serahanpkb)
->where('tarikh','<=', $request->tarikh)
->where('tarikh','>','2021-03-24')
->sum('serahan');
//Sebelum Tawarruq
//Sebelum Tawarruq
if($kodcaw == 'KB2' || $kodcaw == 'WCY' || $kodcaw == '1STOP' || $kodcaw == 'ARM' || $kodcaw == 'APM' || $kodcaw == '010621' || $kodcaw == '010915' || $kodcaw == '020303')
{
$serahanpkb = GlDetail::on($cawangan['mysql'])
@@ -536,47 +573,53 @@ class VaultController extends Controller
//Selepas Tawarruq
//Selepas Tawarruq
$pendahuluanPKBtawarruq = GlDetail::on($cawangan['mysql'])
$pendahuluanPKBtawarruq = GlDetail::on($cawangan['mysql'])
->where('ktacct','=',$pendahuluanpkb)
->where([['tarikh','<=', $request->tarikh],['tarikh','>','2021-03-24']])
->sum('pendahuluan');
//Sebelum Tawarruq
//Sebelum Tawarruq
$pendahuluanPKB = GlDetail::on($cawangan['mysql'])
->where('ktacct','=',$pendahuluanpkb)
->where([['tarikh','<=', $request->tarikh],['tarikh','>','2013-12-31']])
->sum('amaun');
$pendahuluanJMC = GlDetail::on($cawangan['mysql'])
->where('ktacct','=','7000/106')
->where([['tarikh','<=', $request->tarikh],['tarikh','>','2013-12-31']])
->sum('amaun');
$pendahuluanpkblama = imbangdugalama::on($cawangan['mysql'])
->sum('pendahuluan_pkb');
$pendahuluanjmclama = imbangdugalama::on($cawangan['mysql'])
->sum('pendahuluan_jmc');
$total = $pendahuluanPKBtawarruq + $pendahuluanjmclama + $pendahuluanpkblama + $pendahuluanPKB + $pendahuluanJMC;
$pindahkekpkb = GlCode::on($cawangan['mysql'])
->where('glcode','=',$serahankoperasi)
$pendahuluanPKB = GlDetail::on($cawangan['mysql'])
->where('ktacct','=',$pendahuluanpkb)
->where([['tarikh','<=', $request->tarikh],['tarikh','>','2013-12-31']])
->sum('amaun');
if($kodcaw == '011229')
$totalserahan -= 0.10;
$pendahuluanJMC = GlDetail::on($cawangan['mysql'])
->where('ktacct','=','7000/106')
->where([['tarikh','<=', $request->tarikh],['tarikh','>','2013-12-31']])
->sum('amaun');
if($kodcaw == '011013')
$totalserahan += 641000;
$pendahuluanpkblama = imbangdugalama::on($cawangan['mysql'])
->sum('pendahuluan_pkb');
$arraypend = ['pendahuluan' => $total,'serahan' => $totalserahan,'pindahkekpkb' => $pindahkekpkb];
$pendahuluanjmclama = imbangdugalama::on($cawangan['mysql'])
->sum('pendahuluan_jmc');
return response()->json($arraypend);
}
//Pendahuluan Online (had)
$modalOnlineHad = ModalOnline::on($cawangan['mysql'])
->where('kodcaw','=', $kodcaw)
->where('tarikh','=', $request->tarikh)
->value('had') ?? 0;
$total = $pendahuluanPKBtawarruq + $pendahuluanjmclama + $pendahuluanpkblama + $pendahuluanPKB + $pendahuluanJMC + $modalOnlineHad;
$pindahkekpkb = GlCode::on($cawangan['mysql'])
->where('glcode','=',$serahankoperasi)
->sum('amaun');
if($kodcaw == '011229')
$totalserahan -= 0.10;
if($kodcaw == '011013')
$totalserahan += 641000;
$arraypend = ['pendahuluan' => $total,'serahan' => $totalserahan,'pindahkekpkb' => $pindahkekpkb];
return response()->json($arraypend);
}
public function CalculationPendahuluanSerahanLejer($kodcaw,Request $request)
@@ -722,5 +765,19 @@ class VaultController extends Controller
return response()->json($vault);
}
public function CalculationModalOnlineLejer($kodcaw, Request $request)
{
$cawangan = Cawangan::on('mysql7')->where('kodcaw','=',$kodcaw)->first();
$tarikh = $request->hingga;
$modal_online = ModalOnline::on($cawangan['mysql'])
->where('kodcaw', '=', $kodcaw)
->where('tarikh', '=', $tarikh)
->first();
return response()->json([
'had' => $modal_online ? floatval($modal_online->had) : 0,
]);
}
}
+154
View File
@@ -5,8 +5,12 @@ namespace App\Http\Controllers;
use App\WTDDraft;
use App\TuntutBaki;
use App\Cawangan;
use App\Customer;
use Illuminate\Http\Request;
use Carbon\Carbon;
use App\Helper;
use App\SerahBaki;
use App\WTD;
use Illuminate\Support\Facades\Http;
@@ -34,4 +38,154 @@ class WTDController extends Controller
return response()->json($tuntutbaki);
}
public function getSummaryWtd(Request $request){
$startDate = $request->startDate ?? now()->startOfMonth()->toDateString();
$endDate = $request->endDate ?? now()->endOfMonth()->toDateString();
$active_dbs = Helper::getActiveCawanganDbConnection();
$cawangan = Cawangan::select('kodcaw', 'details_caw', 'mysql')
->whereIn('mysql', $active_dbs)
->get();
$responses = [];
foreach ($cawangan as $caw) {
$db = $caw->mysql;
$tuntutbaki = TuntutBaki::on($db)
->join('serahbaki', 'serahbaki.t_jual', '=', 'tuntutbaki.tarikh')
->join('gadai', 'gadai.norujukan', '=', 'tuntutbaki.norujukan')
->whereBetween('tuntutbaki.tarikh', [$startDate, $endDate])
->where('tuntutbaki.teller', 'DM')
->where('serahbaki.aktif', 1)
->selectRaw("
gadai.nokp as nokp,
tuntutbaki.norujukan as norujukan,
tuntutbaki.jumlah as jumlah,
serahbaki.batchno as batchno,
serahbaki.t_jual as t_jual,
serahbaki.t_htr_ag as t_htr_ag
")
->orderByRaw('CAST(serahbaki.batchno AS UNSIGNED)')
->orderBy('tuntutbaki.norujukan')
->get();
$nokps = $tuntutbaki
->pluck('nokp')
->filter() // elak null
//->unique()
->values();
$customers = Customer::on('mysql7')
->whereIn('kplama', $nokps)
->orWhereIn('kpbaru', $nokps)
->get()
->keyBy(function ($item) {
return $item->kpbaru ?: $item->kplama;
});
$wtd_det = [];
foreach ($tuntutbaki as $tuntut) {
$nokp = $tuntut->nokp;
$cust = $customers->get($nokp);
$wtd_det[] = [
"norujukan" => $tuntut->norujukan,
"nokp" => $nokp,
"jumlah" => (float) $tuntut->jumlah,
"batchno" => $tuntut->batchno,
"t_proses" => $tuntut->t_jual,
"t_htr_ag" => $tuntut->t_htr_ag,
"nama_cust" => $cust->nama ?? null,
];
}
$details = collect($wtd_det)
->values()
->all();
$responses[] = [
'kodcaw' => $caw->kodcaw,
'details_caw' => $caw->details_caw,
// WEB
'jumlah_surat' => count($details),
'sum_baki' => collect($details)->sum('jumlah'),
// EXCEL
'details' => $details,
];
}
return response()->json($responses);
}
public function getDebugWtd(Request $request){
$startDate = $request->startDate ?? now()->startOfMonth()->toDateString();
$endDate = $request->endDate ?? now()->endOfMonth()->toDateString();
$active_dbs = Helper::getActiveCawanganDbConnection();
$cawangan = Cawangan::select('kodcaw', 'details_caw', 'mysql')
->whereIn('mysql', $active_dbs)
->get();
$responses = [];
foreach ($cawangan as $caw) {
$db = $caw->mysql;
$serahbaki = SerahBaki::on($db)
->whereBetween('t_jual', [$startDate, $endDate])
->where('aktif', 1)
//->selectRaw("baki as serahb")
->sum('baki');;
//->get();
$tuntutbaki = TuntutBaki::on($db)
->whereBetween('tarikh', [$startDate, $endDate])
->where('teller', 'DM')
//->selectRaw("jumlah as tuntutb")
->sum('jumlah');
//->get();
$twtd = WTD::on($db)
->whereBetween('tarikh', [$startDate, $endDate])
//->selectRaw("wtd as twtd")
->sum('wtd');
//->get();
$wtd_det = [];
$wtd_det[] = [
"serahb" => $serahbaki,
"tuntutb" => $tuntutbaki,
"twtd" => $twtd,
];
$details = collect($wtd_det)
->values()
->all();
//dd($details);
$responses[] = [
'kodcaw' => $caw->kodcaw,
'details_caw' => $caw->details_caw,
//collect($details)->sum('jumlah'),
// WEB
'serahb' => collect($details)->sum('serahb'),
'tuntutb' => collect($details)->sum('tuntutb'),
'twtd' => collect($details)->sum('twtd'),
//'debugwtd' => $details,
];
}
return response()->json($responses);
}
}
@@ -0,0 +1,122 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
/**
* Meta WhatsApp Cloud webhook: verify challenge + acknowledge events.
* Sufficient for production onboarding; delivery statuses are logged only.
*/
class WhatsappWebhookController extends Controller
{
/**
* GET /api/whatsapp/webhook
* Meta sends hub.mode, hub.verify_token, hub.challenge.
*/
public function verify(Request $request)
{
$mode = $request->query('hub_mode', $request->query('hub.mode'));
$token = $request->query('hub_verify_token', $request->query('hub.verify_token'));
$challenge = $request->query('hub_challenge', $request->query('hub.challenge'));
$expected = (string) config('whatsapp.verify_token');
if ($mode === 'subscribe' && $expected !== '' && hash_equals($expected, (string) $token)) {
Log::info('WhatsApp webhook verified');
return response((string) $challenge, 200)
->header('Content-Type', 'text/plain');
}
Log::warning('WhatsApp webhook verify failed', [
'mode' => $mode,
'token_match' => $expected !== '' && hash_equals($expected, (string) $token),
]);
return response('Forbidden', 403);
}
/**
* POST /api/whatsapp/webhook
* Acknowledge quickly; optionally check X-Hub-Signature-256 when app secret is set.
*/
public function handle(Request $request)
{
$raw = $request->getContent();
if (!$this->signatureOk($request, $raw)) {
Log::warning('WhatsApp webhook invalid signature');
return response('Invalid signature', 403);
}
$payload = json_decode($raw, true);
if (!is_array($payload)) {
return response('OK', 200);
}
try {
$this->logEvents($payload);
} catch (\Throwable $e) {
Log::error('WhatsApp webhook log failed: ' . $e->getMessage());
}
return response('OK', 200);
}
protected function signatureOk(Request $request, $raw)
{
$secret = trim((string) config('whatsapp.app_secret'));
if ($secret === '') {
return true;
}
$header = (string) $request->header('X-Hub-Signature-256', '');
if ($header === '' || strpos($header, 'sha256=') !== 0) {
return false;
}
$expected = 'sha256=' . hash_hmac('sha256', $raw, $secret);
return hash_equals($expected, $header);
}
protected function logEvents(array $payload)
{
$entries = $payload['entry'] ?? [];
if (!is_array($entries)) {
return;
}
foreach ($entries as $entry) {
$changes = $entry['changes'] ?? [];
if (!is_array($changes)) {
continue;
}
foreach ($changes as $change) {
$value = $change['value'] ?? [];
if (!is_array($value)) {
continue;
}
foreach ($value['statuses'] ?? [] as $status) {
Log::info('WhatsApp status', [
'id' => $status['id'] ?? null,
'status' => $status['status'] ?? null,
'recipient' => $status['recipient_id'] ?? null,
'errors' => $status['errors'] ?? null,
]);
}
foreach ($value['messages'] ?? [] as $message) {
Log::info('WhatsApp inbound', [
'from' => $message['from'] ?? null,
'type' => $message['type'] ?? null,
'id' => $message['id'] ?? null,
]);
}
}
}
}
}
+32
View File
@@ -0,0 +1,32 @@
<?php
namespace App\Http\Middleware;
use Closure;
class LogViewerPassword
{
public function handle($request, Closure $next)
{
$passwordHash = config('logviewer.password');
if (!is_string($passwordHash) || $passwordHash === '') {
return response('Akses log belum dikonfigurasi.', 503)
->header('Cache-Control', 'no-store');
}
$provided = $request->getPassword();
if ($request->getUser() !== 'logviewer' || !is_string($provided) ||
!password_verify($provided, $passwordHash)) {
return response('Kata laluan diperlukan untuk mengakses log.', 401, [
'WWW-Authenticate' => 'Basic realm="Log Viewer", charset="UTF-8"',
'Cache-Control' => 'no-store',
]);
}
$response = $next($request);
$response->headers->set('Cache-Control', 'no-store');
return $response;
}
}
+153
View File
@@ -0,0 +1,153 @@
<?php
namespace App\Jobs;
use App\BlastWasap;
use App\Customer;
use App\Services\Whatsapp\WhatsappCloudClient;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
class WhatsappPembiayaanOnlinePaid extends Job
{
public $kodcaw;
public $cawName;
public $nokp;
public $norujukan;
public $pinjaman;
public $namaBank;
public $noakaun;
public $tarikhBayaran;
public $noRujukanBayaran;
public function __construct(array $data)
{
$this->kodcaw = $data['kodcaw'] ?? null;
$this->cawName = $data['caw_name'] ?? null;
$this->nokp = $data['nokp'] ?? null;
$this->norujukan = $data['norujukan'] ?? null;
$this->pinjaman = $data['pinjaman'] ?? null;
$this->namaBank = $data['nama_bank'] ?? null;
$this->noakaun = $data['noakaun'] ?? null;
$this->tarikhBayaran = $data['tarikh_bayaran'] ?? null;
$this->noRujukanBayaran = $data['no_rujukan_bayaran'] ?? null;
}
public function handle()
{
$client = new WhatsappCloudClient();
$now = Carbon::now();
if (!$client->isConfigured()) {
Log::warning('WhatsApp Cloud skipped: token or phone_number_id missing', [
'norujukan' => $this->norujukan,
]);
return;
}
$customer = Customer::on('mysql7')
->where(function ($q) {
$q->where('kpbaru', $this->nokp)->orWhere('kplama', $this->nokp);
})
->first();
$storedPhone = $customer ? ($customer->telefon ?: $customer->telefon2) : null;
$to = $client->normalizeMsisdn($storedPhone);
if (!config('whatsapp.whatsapp_cloud_enabled')) {
$admin = $client->normalizeMsisdn(env('ADMIN_PHONE_NUMBER'));
if (!$admin) {
Log::warning('WhatsApp Cloud skipped: ADMIN_PHONE_NUMBER required when WHATSAPP_ENABLED is false', [
'norujukan' => $this->norujukan,
]);
$this->logBlast($now, $storedPhone, 'skipped: ADMIN_PHONE_NUMBER empty');
return;
}
$to = $admin;
}
if (!$to) {
Log::warning('WhatsApp Cloud skipped: no phone', [
'nokp' => $this->nokp,
'norujukan' => $this->norujukan,
]);
$this->logBlast($now, $storedPhone, 'skipped: no phone');
return;
}
$template = config('whatsapp.template', 'hello_world');
$lang = config('whatsapp.template_lang', 'en_US');
$bodyParams = $this->isHelloWorld($template) ? [] : $this->bodyParams();
try {
$response = $client->sendTemplate($to, $template, $lang, $bodyParams);
$this->logBlast($now, $to, $response->body());
} catch (\Throwable $e) {
Log::error('WhatsappPembiayaanOnlinePaid failed: ' . $e->getMessage(), [
'norujukan' => $this->norujukan,
]);
$this->logBlast($now, $to, 'exception: ' . $e->getMessage());
}
}
protected function isHelloWorld($template)
{
return strtolower((string) $template) === 'hello_world';
}
/**
* Matches Meta template pembiayaan_online_paid (ms):
* AR-RAHN KoPKB: Pembiayaan bagi No. SAG *{{1}}* sebanyak *RM{{2}}*
* telah berjaya dikreditkan ke akaun *{{3}}* berakhir *{{4}}* pada *{{5}}*.
* Rujukan: *{{6}}*
*
* {{1}} SAG, {{2}} amaun, {{3}} nama bank/akaun, {{4}} 4 digit akhir, {{5}} tarikh, {{6}} rujukan bayaran
*
* @return array<int,string>
*/
protected function bodyParams()
{
$bank = trim((string) $this->namaBank);
$tail = $this->accountTail($this->noakaun);
$tarikh = $this->tarikhBayaran
? Carbon::parse($this->tarikhBayaran)->format('d/m/Y')
: Carbon::now()->format('d/m/Y');
return [
$this->norujukan ?: '-',
number_format((float) $this->pinjaman, 2, '.', ''),
$bank !== '' ? $bank : '-',
$tail !== '' ? $tail : '-',
$tarikh,
$this->noRujukanBayaran ?: '-',
];
}
protected function accountTail($noakaun)
{
$digits = preg_replace('/\D+/', '', (string) $noakaun);
if ($digits === '') {
return '';
}
return substr($digits, -4);
}
protected function logBlast($now, $telefon, $status)
{
try {
$log = new BlastWasap();
$log->kodcaw = $this->kodcaw;
$log->nokp = $this->nokp;
$log->notelefon = $telefon;
$log->norujukan = $this->norujukan;
$log->status = is_string($status) ? $status : json_encode($status);
$log->created_at = $now;
$log->updated_at = $now;
$log->save();
} catch (\Throwable $e) {
Log::error('log_blast_wasap save failed: ' . $e->getMessage());
}
}
}
+18
View File
@@ -0,0 +1,18 @@
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class KadarRebet extends Model
{
protected $table = 'kadar_rebet';
protected $connection = 'mysql7';
public $timestamps = false;
protected $fillable = [
'pembiayaan_min',
'pembiayaan_max',
'kadarrebet',
];
}
+40
View File
@@ -0,0 +1,40 @@
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class MaxMutuEmas extends Model
{
protected $table = 'max_mutuemas';
protected $primaryKey = 'recno';
public $timestamps = false;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'recno', 'karat', 'keterangan', 'teller', 'pc', 'operasi', 'tarikhupdate'
];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'teller' => 'decimal:2',
'pc' => 'decimal:2',
'operasi' => 'decimal:2',
'tarikhupdate' => 'datetime',
];
/**
* The attributes excluded from the model's JSON form.
*
* @var array
*/
// protected $hidden = [];
}
+23
View File
@@ -0,0 +1,23 @@
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class ModalOnline extends Model
{
protected $table = 'modal_online';
protected $fillable = [
'tarikh',
'kodcaw',
'had',
'digunakan',
'baki',
'tambah',
'kurang',
'ditutup',
'ditutup_oleh',
'ditutup_at',
];
}
+13
View File
@@ -4,6 +4,7 @@ namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasOne;
use App\Audit;
use Illuminate\Support\Carbon;
@@ -21,6 +22,7 @@ class Palsu extends Model
protected $fillable = [
'recno',
'kodcaw',
'tarikh',
'norujukan',
'teller',
'nota',
@@ -30,6 +32,7 @@ class Palsu extends Model
'untungonepercent',
'untungcajlain',
'pembiayaan',
'status',
];
// public static function boot()
@@ -87,4 +90,14 @@ class Palsu extends Model
// {
// return $this->belongsTo(Daerah::class, 'koddaerah', 'koddaerah');
// }
public function tebus(): HasOne
{
return $this->hasOne(Tebus::class, 'norujukan', 'norujukan');
}
public function gadai(): HasOne
{
return $this->hasOne(Gadai::class, 'norujukan', 'norujukan');
}
}
+35
View File
@@ -0,0 +1,35 @@
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Pampasan extends Model
{
protected $table = 'pampasan';
public $timestamps = true;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'id',
'norujukan',
'baucerno',
'kodcaw',
'jumlah_pampasan',
'denominasi',
'teller',
'nota',
'agreement_printed_at',
];
/**
* The attributes excluded from the model's JSON form.
*
* @var array
*/
// protected $hidden = [];
}
+41
View File
@@ -0,0 +1,41 @@
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class PembiayaanOnline extends Model
{
protected $table = 'pembiayaan_online';
protected $fillable = [
'norujukan',
'nokp',
'nopelanggan',
'nama_pelanggan',
'kodcaw',
'pinjaman',
'kodbank',
'nama_bank',
'noakaun',
'nama_akaun',
'bukti_akaun_file_id',
'borang_file_id',
'status',
'reject_reason',
'rejected_by',
'rejected_at',
'batch_id',
'bukti_file_id',
'no_rujukan_bayaran',
'tarikh_bayaran',
'paid_at',
'created_by',
'pc_submitted_by',
'pc_verified_by',
'pc_verified_at',
'ops_by',
'nota',
'blast_sent',
];
}
+21
View File
@@ -0,0 +1,21 @@
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class RequestModalOnline extends Model
{
protected $table = 'request_modal_online';
protected $connection = 'mysql7';
protected $fillable = [
'kodcaw',
'tarikh',
'amaun',
'dimohon_oleh',
'status',
'diluluskan_oleh',
'nota',
];
}
+193
View File
@@ -0,0 +1,193 @@
<?php
namespace App\Services\Bsas;
use Illuminate\Http\Client\PendingRequest;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use RuntimeException;
class BsasClient
{
const TOKEN_CACHE_KEY = 'bsas.oauth.token';
const TOKEN_CACHE_SECONDS = 82800; // 23 hours
public function getAccessToken(): string
{
$cached = Cache::get(self::TOKEN_CACHE_KEY);
if (is_string($cached) && $cached !== '') {
return $cached;
}
// Drop bad cache entries (e.g. whole JSON array accidentally stored).
if ($cached !== null) {
Cache::forget(self::TOKEN_CACHE_KEY);
}
$tokenUrl = config('bsas.token_url');
$clientId = config('bsas.client_id');
$clientSecret = config('bsas.client_secret');
if (!$tokenUrl || !$clientId || !$clientSecret) {
throw new RuntimeException('BSAS OAuth is not configured (token_url / client_id / client_secret).');
}
$response = $this->http()
->asForm()
->post($tokenUrl, [
'grant_type' => 'client_credentials',
'client_id' => $clientId,
'client_secret' => $clientSecret,
]);
$payload = $response->json();
if (!is_array($payload)) {
$payload = [];
}
if (!$response->successful()) {
Log::error('BSAS OAuth token request failed', [
'status' => $response->status(),
'body' => $payload ?: $response->body(),
]);
throw new RuntimeException('Gagal mendapatkan BSAS access token.');
}
$token = $payload['access_token'] ?? null;
if (!is_string($token) || $token === '') {
Log::error('BSAS OAuth response missing string access_token', [
'body' => $payload,
]);
throw new RuntimeException('BSAS OAuth response missing access_token.');
}
$expiresIn = (int) ($payload['expires_in'] ?? self::TOKEN_CACHE_SECONDS);
$ttl = max(60, min(self::TOKEN_CACHE_SECONDS, $expiresIn - 300));
Cache::put(self::TOKEN_CACHE_KEY, $token, $ttl);
return $token;
}
public function placeOrder(array $payload): array
{
return $this->requestJson('post', '/process/svc/bsas/order', $payload);
}
public function orderResult(string $uuid, array $body = []): array
{
$maxWait = (string) ($body['maxWaitTime'] ?? config('bsas.max_wait_time', 10));
$payload = [
'header' => [
'memberShortName' => config('bsas.member_short_name'),
'uuid' => $uuid,
],
'body' => [
'serialNumbers' => (string) ($body['serialNumbers'] ?? '1'),
'forceYN' => (string) ($body['forceYN'] ?? 'Y'),
'maxWaitTime' => $maxWait,
'waitAllDoneYN' => (string) ($body['waitAllDoneYN'] ?? 'Y'),
],
];
// Long poll: client timeout must exceed BSAS maxWaitTime.
$timeout = max(
(int) config('bsas.http_timeout', 30),
(int) $maxWait + 20
);
return $this->requestJson('get', '/process/svc/bsas/orderResult', $payload, $timeout);
}
public function getBidEcert(string $ecertNo): array
{
return $this->requestEcert('/process/svc/bsas/bidXML', $ecertNo);
}
public function getOtcEcert(string $ecertNo): array
{
return $this->requestEcert('/process/svc/bsas/otcXML', $ecertNo);
}
public function getStbEcert(string $ecertNo): array
{
return $this->requestEcert('/process/svc/bsas/stbXML', $ecertNo);
}
protected function requestEcert(string $path, string $ecertNo): array
{
$payload = [
'input' => [
'ecertno' => $ecertNo,
'membershortname' => config('bsas.member_short_name'),
],
];
return $this->requestJson('get', $path, $payload);
}
protected function requestJson(string $method, string $path, array $payload, ?int $timeout = null): array
{
$url = rtrim(config('bsas.base_url'), '/') . '/' . ltrim($path, '/');
$timeout = $timeout ?? (int) config('bsas.http_timeout', 30);
$token = $this->getAccessToken();
$pending = $this->http($timeout)
->withToken($token)
->acceptJson();
if (strtolower($method) === 'get') {
$response = $pending
->withHeaders(['Content-Type' => 'application/json'])
->send('GET', $url, [
'body' => json_encode($payload),
]);
} else {
$response = $pending->asJson()->post($url, $payload);
}
$json = $response->json();
if (!is_array($json)) {
$body = $response->body();
$cloudflare = stripos($body, 'cloudflare') !== false || stripos($body, 'Attention Required') !== false;
if ($cloudflare) {
throw new RuntimeException(
'BSAS blocked by Cloudflare (check IP whitelist). HTTP ' . $response->status()
);
}
throw new RuntimeException('BSAS returned invalid JSON response.');
}
if (!$response->successful()) {
Log::error('BSAS HTTP error', [
'method' => $method,
'url' => $url,
'status' => $response->status(),
'body' => $json,
]);
throw new RuntimeException('BSAS request failed (HTTP ' . $response->status() . ').');
}
Log::info('BSAS API call', [
'method' => $method,
'path' => $path,
'status' => $response->status(),
'header' => $json['header'] ?? null,
]);
return $json;
}
protected function http(?int $timeout = null): PendingRequest
{
$timeout = $timeout ?? (int) config('bsas.http_timeout', 30);
return Http::withOptions([
'timeout' => $timeout,
'connect_timeout' => min(15, $timeout),
]);
}
}
+374
View File
@@ -0,0 +1,374 @@
<?php
namespace App\Services\Bsas;
use App\BsasOrder;
use App\KomuditiTransaksi;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use RuntimeException;
class BsasOrderService
{
/** @var BsasClient */
protected $client;
public function __construct(BsasClient $client = null)
{
$this->client = $client ?: new BsasClient();
}
/**
* Place BSAS order for a gadai Terima, long-poll result, fetch eCert.
*
* @param array $input norujukan, kodcaw, pinjaman, hargajualan, nama_pelanggan, teller
* @return BsasOrder
*/
public function placeAndConfirm(array $input): BsasOrder
{
$norujukan = (string) ($input['norujukan'] ?? '');
$kodcaw = (string) ($input['kodcaw'] ?? '');
$pinjaman = round((float) ($input['pinjaman'] ?? 0), 2);
$hargajualan = round((float) ($input['hargajualan'] ?? 0), 2);
$counterParty = trim((string) ($input['nama_pelanggan'] ?? ''));
$createdBy = (string) ($input['teller'] ?? '');
if ($norujukan === '' || $kodcaw === '' || $pinjaman <= 0) {
throw new RuntimeException('BSAS order requires norujukan, kodcaw and pinjaman.');
}
$existing = BsasOrder::on('mysql7')
->where('norujukan', $norujukan)
->orderBy('id', 'desc')
->first();
if ($existing && $existing->status === 'confirmed') {
if ($this->needsEcertFetch($existing)) {
$this->fetchAndStoreEcert($existing);
}
$this->ensureKomuditiTransaksi($existing, $createdBy);
return $existing->fresh();
}
if ($existing && in_array($existing->status, ['pending', 'submitted'], true) && !empty($existing->uuid)) {
return $this->completeSubmittedOrder($existing, $createdBy);
}
$serial = '1';
$payload = $this->buildOrderPayload($pinjaman, $hargajualan, $counterParty, $serial);
$order = BsasOrder::on('mysql7')->create([
'uuid' => null,
'norujukan' => $norujukan,
'kodcaw' => $kodcaw,
'serial_number' => $serial,
'product_code' => config('bsas.product_code'),
'bid_value' => $pinjaman,
'otc_murabaha_value' => $hargajualan,
'otc_counter_party' => $counterParty,
'status' => 'pending',
'request_payload' => $payload,
'created_by' => $createdBy,
]);
try {
$orderResponse = $this->client->placeOrder($payload);
} catch (\Throwable $e) {
$order->status = 'failed';
$order->status_message = $e->getMessage();
$order->save();
throw $e;
}
$order->order_response = $orderResponse;
$uuid = data_get($orderResponse, 'header.uuid');
$errorCode = trim((string) data_get($orderResponse, 'header.errorCode', ''));
$errorMsg = trim((string) data_get($orderResponse, 'header.errorMsg', ''));
if ($errorCode !== '' || !$uuid) {
$order->status = 'failed';
$order->status_message = $errorMsg !== '' ? $errorMsg : 'BSAS order rejected (missing uuid or errorCode set).';
$order->save();
throw new RuntimeException($order->status_message);
}
$bodyStatus = data_get($orderResponse, 'body.0.statusCode');
if ($bodyStatus !== null && (int) $bodyStatus !== 0) {
$order->uuid = $uuid;
$order->status = 'failed';
$order->status_code = (int) $bodyStatus;
$order->status_message = (string) data_get($orderResponse, 'body.0.statusMessage', 'BSAS order body statusCode != 0');
$order->save();
throw new RuntimeException($order->status_message);
}
$order->uuid = $uuid;
$order->status = 'submitted';
$order->save();
return $this->completeSubmittedOrder($order, $createdBy);
}
/**
* Re-query orderResult / eCert for an existing uuid.
*/
public function refreshByUuid(string $uuid): BsasOrder
{
$order = BsasOrder::on('mysql7')->where('uuid', $uuid)->first();
if (!$order) {
throw new RuntimeException('BSAS order not found for uuid.');
}
if ($order->status === 'confirmed') {
if ($this->needsEcertFetch($order)) {
$this->fetchAndStoreEcert($order);
}
return $order->fresh();
}
return $this->completeSubmittedOrder($order, (string) $order->created_by);
}
protected function completeSubmittedOrder(BsasOrder $order, string $createdBy): BsasOrder
{
if (empty($order->uuid)) {
throw new RuntimeException('BSAS order has no uuid to poll.');
}
$result = $this->longPollOrderResult($order->uuid, (string) $order->serial_number);
$order->result_response = $result;
$headerError = trim((string) data_get($result, 'header.errorCode', ''));
$headerMsg = trim((string) data_get($result, 'header.errorMsg', ''));
$statusCode = data_get($result, 'body.0.statusCode');
$statusMessage = (string) data_get($result, 'body.0.statusMessage', '');
$bidErrNo = trim((string) data_get($result, 'body.0.bidErrNo', ''));
$otcErrNo = trim((string) data_get($result, 'body.0.otcErrNo', ''));
$stbErrNo = trim((string) data_get($result, 'body.0.stbErrNo', ''));
$bidMsg = trim((string) data_get($result, 'body.0.bidMsg', ''));
$otcMsg = trim((string) data_get($result, 'body.0.otcMsg', ''));
$stbMsg = trim((string) data_get($result, 'body.0.stbMsg', ''));
$ecertNo = $this->extractEcertNo($result, $order->order_response);
if ($headerError !== '') {
$order->status = 'failed';
$order->status_code = is_numeric($statusCode) ? (int) $statusCode : null;
$order->status_message = $headerMsg !== '' ? $headerMsg : 'BSAS orderResult header errorCode set.';
$order->save();
throw new RuntimeException($order->status_message);
}
// Live BSAS result often has no statusCode; success is ecertNo and/or bid/otc/stb ErrNo=999 (OK).
$successByStatusCode = $statusCode !== null && (int) $statusCode === 0;
$successByErrNo = ($bidErrNo === '999' || strtoupper($bidMsg) === 'OK')
&& ($otcErrNo === '999' || strtoupper($otcMsg) === 'OK')
&& ($stbErrNo === '999' || strtoupper($stbMsg) === 'OK');
$successByEcert = $ecertNo !== null && $ecertNo !== '';
if (!$successByStatusCode && !$successByErrNo && !$successByEcert) {
if ($statusCode !== null && (int) $statusCode !== 0) {
$order->status = 'failed';
$order->status_code = (int) $statusCode;
$order->status_message = $statusMessage !== '' ? $statusMessage : 'BSAS orderResult statusCode != 0';
$order->save();
throw new RuntimeException($order->status_message);
}
$order->status = 'submitted';
$order->status_message = 'BSAS orderResult belum selesai / tiada ecert.';
$order->save();
throw new RuntimeException('BSAS orderResult belum selesai. Sila cuba semula.');
}
$order->status_code = $statusCode !== null ? (int) $statusCode : 0;
$order->status_message = $statusMessage !== '' ? $statusMessage : ($bidMsg ?: 'OK');
$order->status = 'confirmed';
if ($ecertNo) {
$order->e_cert_no = $ecertNo;
}
$order->save();
if ($this->needsEcertFetch($order)) {
$this->fetchAndStoreEcert($order);
}
if (empty($order->e_cert_no)) {
Log::warning('BSAS order confirmed but ecertno missing', [
'norujukan' => $order->norujukan,
'uuid' => $order->uuid,
]);
}
$this->ensureKomuditiTransaksi($order, $createdBy);
return $order->fresh();
}
protected function longPollOrderResult(string $uuid, string $serial): array
{
$retries = max(1, (int) config('bsas.order_result_retries', 2));
$last = [];
for ($i = 0; $i < $retries; $i++) {
$last = $this->client->orderResult($uuid, [
'serialNumbers' => $serial,
'forceYN' => 'Y',
'maxWaitTime' => (string) config('bsas.max_wait_time', 10),
'waitAllDoneYN' => 'Y',
]);
// Done when statusCode is set, or bid, OTC and STB have all reported.
$bidDone = trim((string) data_get($last, 'body.0.bidErrNo', '')) !== '';
$otcDone = trim((string) data_get($last, 'body.0.otcErrNo', '')) !== '';
$stbDone = trim((string) data_get($last, 'body.0.stbErrNo', '')) !== '';
$hasEcert = trim((string) data_get($last, 'body.0.ecertNo', '')) !== ''
|| trim((string) data_get($last, 'body.0.eCertNo', '')) !== '';
if (
data_get($last, 'body.0.statusCode') !== null
|| ($bidDone && $otcDone && $stbDone)
|| ($hasEcert && $stbDone)
) {
return $last;
}
}
return $last;
}
protected function needsEcertFetch(BsasOrder $order): bool
{
return empty($order->bid_ecert_response)
|| empty($order->otc_ecert_response)
|| empty($order->stb_ecert_response);
}
protected function fetchAndStoreEcert(BsasOrder $order): void
{
$ecertNo = $order->e_cert_no;
if (!$ecertNo) {
$ecertNo = $this->extractEcertNo($order->result_response, $order->order_response);
if ($ecertNo) {
$order->e_cert_no = $ecertNo;
$order->save();
}
}
if (!$ecertNo) {
return;
}
$calls = [
'bid_ecert_response' => 'getBidEcert',
'otc_ecert_response' => 'getOtcEcert',
'stb_ecert_response' => 'getStbEcert',
];
foreach ($calls as $column => $method) {
if (!empty($order->{$column})) {
continue;
}
try {
$ecert = $this->client->{$method}($ecertNo);
$order->{$column} = $ecert;
if ($column === 'otc_ecert_response' && empty($order->e_cert_no) && !empty($ecert['ECERTNO'])) {
$order->e_cert_no = $ecert['ECERTNO'];
}
$order->save();
} catch (\Throwable $e) {
Log::error('BSAS eCert fetch failed after confirmed order', [
'norujukan' => $order->norujukan,
'uuid' => $order->uuid,
'e_cert_no' => $ecertNo,
'certificate' => $column,
'error' => $e->getMessage(),
]);
}
}
}
protected function ensureKomuditiTransaksi(BsasOrder $order, string $teller): void
{
$exists = KomuditiTransaksi::on('mysql7')
->where('norujukan', $order->norujukan)
->first();
if ($exists) {
return;
}
$current = Carbon::now();
KomuditiTransaksi::on('mysql7')->create([
'unit_komuditi' => round((float) $order->bid_value, 2),
'nosijil' => $order->e_cert_no ?: $order->uuid,
'norujukan' => $order->norujukan,
'kodcaw' => $order->kodcaw,
'teller' => $teller !== '' ? $teller : $order->created_by,
'masa' => $current->toTimeString(),
'tarikh' => $current->toDateString(),
]);
}
protected function buildOrderPayload(float $pinjaman, float $hargajualan, string $counterParty, string $serial): array
{
return [
'header' => [
'memberShortName' => config('bsas.member_short_name'),
'uuid' => $this->uuidV4(),
],
'body' => [
'serialNumber' => $serial,
'bidOption' => 'Y',
'otcOption' => 'Y',
'stbOption' => 'Y',
'productCode' => config('bsas.product_code'),
'purchaseType' => 'P',
'clientName' => '',
'currency' => 'MYR',
'bidValue' => number_format($pinjaman, 2, '.', ''),
'valueDate' => Carbon::now()->format('Ymd'),
'tenor' => config('bsas.tenor'),
'otcCounterParty' => $counterParty,
'otcMurabaha' => '',
'otcMurabahaValue' => number_format($hargajualan, 2, '.', ''),
'eCertNo' => '',
],
];
}
/**
* Best-effort extract ecert number from result/order payloads.
*/
protected function extractEcertNo($result, $orderResponse): ?string
{
$candidates = [
data_get($result, 'body.0.eCertNo'),
data_get($result, 'body.0.ecertNo'),
data_get($result, 'body.0.ECERTNO'),
data_get($result, 'body.0.ecertno'),
data_get($result, 'body.0.certificateNo'),
data_get($orderResponse, 'body.0.eCertNo'),
data_get($orderResponse, 'body.0.ecertNo'),
data_get($orderResponse, 'body.0.ECERTNO'),
];
foreach ($candidates as $value) {
$value = trim((string) $value);
if ($value !== '') {
return $value;
}
}
return null;
}
protected function uuidV4(): string
{
$data = random_bytes(16);
$data[6] = chr((ord($data[6]) & 0x0f) | 0x40);
$data[8] = chr((ord($data[8]) & 0x3f) | 0x80);
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
}
}
@@ -0,0 +1,165 @@
<?php
namespace App\Services\Miscellaneous;
use App\Http\Controllers\Controller;
use App\Cawangan;
use App\Gadai;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use App\Services\Reports\GadaianService as ReportGadaianService;
use App\Helper;
use App\Tebus;
use Illuminate\Support\Carbon;
class StatistikService
{
protected $kodcaw;
protected $mysql;
protected $mula;
protected $akhir;
public function __construct($kodcaw,$mula,$akhir)
{
$this->kodcaw = $kodcaw;
$this->mysql = Cawangan::on('mysql7')->where('kodcaw', $kodcaw)->value('mysql');
$this->mula = $mula;
$this->akhir = $akhir;
}
public function LaporanStatistik($gadai) : array
{
$reportPembiayaan = $this->countPawnByType($gadai);
$reportLoanAmount = $this->countPawnByLoan($gadai);
$reportMarhunValue = $this->countMarhunByValue($gadai);
$reportMarhunMargin = $this->countMarhunByMargin($gadai);
$reportCustomerJob = $this->countCustomerByJob($gadai);
$reportCustomerRace = $this->countCustomerByRace($gadai);
// dd($reportCustomerRace);
return [
'reportPembiayaan' => $reportPembiayaan,
'reportLoanAmount' => $reportLoanAmount,
'reportMarhunValue' => $reportMarhunValue,
'reportMarhunMargin' => $reportMarhunMargin,
'reportCustomerJob' => $reportCustomerJob,
'reportCustomerRace' => $reportCustomerRace,
];
}
private function countPawnByType($gadai)
{
$pembiayaanbaru = Tebus::on($this->mysql)
->join('gadai as g1', 'tebus.norujukan', '=', 'g1.norujukan')
->join('gadai as g2', function ($join) {
$join->on('g2.nokp', '=', 'g1.nokp')
->on('g2.t_gadai', '=', 'tebus.t_tebus')
->whereRaw('ABS(g2.berat - g1.berat) <= 0.02')
->whereColumn('g2.norujukan', '!=', 'g1.norujukan');
})
->whereBetween('tebus.t_tebus', [$this->mula, $this->akhir])
->groupBy('tebus.norujukan')
->selectRaw("
tebus.norujukan as norujukan_tebus,
MAX(g2.norujukan) as norujukanpembiayaansemula,
MAX(g1.taglel) as taglel_lama
")
->get()
->toArray();
$pembiayaanbaruNorujukan = array_column($pembiayaanbaru, 'norujukanpembiayaansemula');
$filteredNorujukanLelongan = array_column(
array_filter($pembiayaanbaru, function ($item) {
return $item['taglel_lama'] == 1;
}),
'norujukanpembiayaansemula'
);
$filteredPSNorujukan = array_column(
array_filter($pembiayaanbaru, function ($item) {
return $item['taglel_lama'] == 0;
}),
'norujukanpembiayaansemula'
);
$pembiayaanbaru = $gadai->whereIn('norujukan', $filteredPSNorujukan)->count();
$pembiayaansemula = $gadai->whereNotIn('norujukan', $pembiayaanbaruNorujukan)->count();
$lelongan = $gadai->whereIn('norujukan', $filteredNorujukanLelongan)->count();
return [
'pembiayaanbaru' => $pembiayaanbaru,
'pembiayaansemula' => $pembiayaansemula,
'lelongan' => $lelongan
];
}
private function countPawnByLoan($gadai)
{
return collect($gadai)->countBy(function ($item) {
$loan = $item['pinjaman'];
if ($loan <= 400.99) return '< 400.99';
if ($loan >= 401 && $loan <= 2000) return '401 - 2000';
if ($loan >= 2001 && $loan <= 24999) return '2001 - 2499';
if ($loan >= 2500) return '>= 2500';
if ($loan > 101000) return '>101000';
})->toArray();
}
private function countMarhunByValue($gadai)
{
return collect($gadai)->countBy(function ($item) {
$nilaimarhun = $item['nilaimarhun'];
if ($nilaimarhun <= 400.99) return '< 1-400';
if ($nilaimarhun >= 401 && $nilaimarhun <= 2000) return '401-2000';
if ($nilaimarhun > 2001) return '> 2001';
})->toArray();
}
private function countMarhunByMargin($gadai)
{
return collect($gadai)->countBy(function ($item) {
$margin= $item['percentpinj'];
if ($margin >= 10 && $margin <= 40.99) return '10-40.99';
if ($margin >= 41 && $margin <= 60.99) return '41-60.99';
if ($margin >= 61 && $margin <= 80) return '> 61-80';
})->toArray();
}
private function countCustomerByJob($gadai)
{
$jobMap = [
1 => 'Peniaga',
2 => 'Pekerja Swasta',
3 => 'Pekerja Kerajaan',
4 => 'Bekerja Sendiri',
5 => 'Suri Rumah',
6 => 'Lain-lain',
];
return collect($gadai)->countBy(function ($item) use ($jobMap) {
$kodkerja = floatval($item['kodkerja']);
return $jobMap[$kodkerja] ?? 'Lain-lain';
})->toArray();
}
private function countCustomerByRace($gadai){
$raceMap = [
'M' => 'Melayu',
'C' => 'Cina',
'I' => 'India',
'L' => 'Lain-lain',
];
return collect($gadai)->countBy(function ($item) use ($raceMap) {
return $raceMap[$item['bangsa']] ?? 'Lain-lain';
})->toArray();
}
}
@@ -0,0 +1,918 @@
<?php
namespace App\Services\PembiayaanOnline;
use App\Cawangan;
use App\Feature;
use App\ModalOnline;
use App\RequestModalOnline;
use App\Services\StaffNotificationClient;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class ModalOnlineService
{
/**
* @return array{ok:bool,status:int,payload:mixed,mysql?:string}
*/
public function resolveMysql($kodcaw)
{
$cawangan = Cawangan::on('mysql7')->where('kodcaw', '=', $kodcaw)->first();
if (!$cawangan) {
return [
'ok' => false,
'status' => 404,
'payload' => ['message' => 'Cawangan tidak dijumpai'],
];
}
return [
'ok' => true,
'status' => 200,
'payload' => null,
'mysql' => $cawangan['mysql'],
];
}
protected function todayDate()
{
return Carbon::now()->toDateString();
}
protected function findPending($kodcaw)
{
return RequestModalOnline::on('mysql7')
->where('kodcaw', $kodcaw)
->where('status', 'PENDING')
->first();
}
protected function pendingPayload($pending)
{
return $pending ? $pending->toArray() : null;
}
protected function missingSnapshot($kodcaw, $tarikh, $pending = null)
{
return [
'tarikh' => $tarikh,
'kodcaw' => $kodcaw,
'had' => 0.00,
'digunakan' => 0.00,
'baki' => 0.00,
'tambah' => 0.00,
'kurang' => 0.00,
'ditutup' => 0,
'missing' => true,
'pending_request' => $this->pendingPayload($pending),
];
}
protected function recalcBaki($modal)
{
$modal->baki = round(
(float) $modal->had + (float) $modal->tambah - (float) $modal->kurang - (float) $modal->digunakan,
2
);
}
/**
* Get today's modal_online row. Does not create — PC must request had from Operasi.
*
* @return array{ok:bool,status:int,payload:mixed}
*/
public function get($kodcaw)
{
$resolved = $this->resolveMysql($kodcaw);
if (!$resolved['ok']) {
return $resolved;
}
$mysql = $resolved['mysql'];
$tarikh = $this->todayDate();
$pending = $this->findPending($kodcaw);
$modal = ModalOnline::on($mysql)
->where('kodcaw', $kodcaw)
->where('tarikh', $tarikh)
->first();
if (!$modal) {
return [
'ok' => true,
'status' => 200,
'payload' => $this->missingSnapshot($kodcaw, $tarikh, $pending),
];
}
$payload = $modal->toArray();
$payload['missing'] = false;
$payload['pending_request'] = $this->pendingPayload($pending);
return [
'ok' => true,
'status' => 200,
'payload' => $payload,
];
}
/**
* @deprecated Use get(). Kept so existing callers do not auto-seed RM100k.
*/
public function getOrCreate($kodcaw)
{
return $this->get($kodcaw);
}
/**
* Reserve amount inside an existing branch DB connection (caller may wrap in transaction).
*
* @return array{ok:bool,status:int,payload:mixed}
*/
public function reserveAmountOnConnection($mysql, $kodcaw, $amaun, $tarikh = null)
{
$amaun = round((float) $amaun, 2);
if ($amaun <= 0) {
return [
'ok' => false,
'status' => 422,
'payload' => ['message' => 'Amaun tidak sah'],
];
}
$tarikh = $tarikh ?: Carbon::now()->toDateString();
$modal = ModalOnline::on($mysql)
->where('kodcaw', $kodcaw)
->where('tarikh', $tarikh)
->lockForUpdate()
->first();
if (!$modal) {
return [
'ok' => false,
'status' => 422,
'payload' => [
'message' => 'Modal online belum diwujudkan. Sila mohon had daripada Operasi terlebih dahulu.',
'missing' => true,
],
];
}
if ((int) ($modal->ditutup ?? 0) === 1) {
return [
'ok' => false,
'status' => 422,
'payload' => [
'message' => 'Modal online hari ini sudah ditutup (EOD)',
'modal_online' => $modal,
],
];
}
if ((float) $modal->baki < $amaun) {
return [
'ok' => false,
'status' => 422,
'payload' => [
'message' => 'Baki modal online tidak mencukupi',
'baki' => (float) $modal->baki,
'amaun' => $amaun,
],
];
}
$modal->digunakan = round((float) $modal->digunakan + $amaun, 2);
$modal->baki = round(
(float) $modal->had + (float) $modal->tambah - (float) $modal->kurang - (float) $modal->digunakan,
2
);
$modal->save();
return [
'ok' => true,
'status' => 200,
'payload' => $modal,
];
}
/**
* Reserve amount from today's online float (transactional).
*
* @return array{ok:bool,status:int,payload:mixed}
*/
public function reserve($kodcaw, $amaun)
{
$resolved = $this->resolveMysql($kodcaw);
if (!$resolved['ok']) {
return $resolved;
}
if (!Feature::enabled($kodcaw, Feature::PEMBIAYAAN_ONLINE)) {
return Feature::denied('Pembiayaan online tidak diaktifkan untuk cawangan ini');
}
$mysql = $resolved['mysql'];
$amaun = round((float) $amaun, 2);
if ($amaun <= 0) {
return [
'ok' => false,
'status' => 422,
'payload' => ['message' => 'Amaun tidak sah'],
];
}
try {
return DB::connection($mysql)->transaction(function () use ($mysql, $kodcaw, $amaun) {
return $this->reserveAmountOnConnection($mysql, $kodcaw, $amaun);
});
} catch (\Throwable $e) {
Log::error('ModalOnline reserve failed: ' . $e->getMessage());
return [
'ok' => false,
'status' => 500,
'payload' => ['message' => 'Gagal reserve modal online'],
];
}
}
/**
* Had is no longer auto-seeded. Returns how many branches already have a row today.
*
* @return array{ok:bool,status:int,payload:mixed}
*/
public function initiate($zone = 'all')
{
$current = $this->todayDate();
if ($zone == 'all') {
$cawanganAll = Cawangan::on('mysql7')->get();
} else {
$cawanganAll = Cawangan::on('mysql7')->where('zone', $zone)->get();
}
$existing = 0;
$missing = 0;
foreach ($cawanganAll as $cawangan) {
try {
$exists = ModalOnline::on($cawangan['mysql'])
->where('kodcaw', $cawangan['kodcaw'])
->where('tarikh', $current)
->exists();
if ($exists) {
$existing++;
} else {
$missing++;
}
} catch (\Throwable $e) {
Log::warning('initiate modal_online skip ' . $cawangan['kodcaw'] . ': ' . $e->getMessage());
}
}
return [
'ok' => true,
'status' => 200,
'payload' => [
'tarikh' => $current,
'created' => 0,
'existing' => $existing,
'missing' => $missing,
'message' => 'Modal online tidak di-initiate automatik. PC perlu mohon had daripada Operasi.',
],
];
}
/**
* PC request opening had (or extra top-up) from Operasi.
*
* @return array{ok:bool,status:int,payload:mixed}
*/
public function requestTopUp($kodcaw, $amaun, $dimohonOleh, $nota = null)
{
$resolved = $this->resolveMysql($kodcaw);
if (!$resolved['ok']) {
return $resolved;
}
$mysql = $resolved['mysql'];
if (!Feature::enabled($kodcaw, Feature::PEMBIAYAAN_ONLINE)) {
return Feature::denied('Pembiayaan online tidak diaktifkan untuk cawangan ini');
}
$amaun = round((float) $amaun, 2);
$dimohonOleh = trim((string) $dimohonOleh);
if ($amaun <= 0) {
return [
'ok' => false,
'status' => 422,
'payload' => ['message' => 'Amaun tidak sah'],
];
}
if ($dimohonOleh === '') {
return [
'ok' => false,
'status' => 422,
'payload' => ['message' => 'dimohon_oleh diperlukan'],
];
}
$tarikh = $this->todayDate();
$pending = $this->findPending($kodcaw);
if ($pending) {
return [
'ok' => false,
'status' => 409,
'payload' => [
'message' => 'Masih ada permohonan modal online PENDING untuk cawangan ini',
'request' => $pending,
],
];
}
$modal = ModalOnline::on($mysql)
->where('kodcaw', $kodcaw)
->where('tarikh', $tarikh)
->first();
if ($modal && (int) ($modal->ditutup ?? 0) === 1) {
return [
'ok' => false,
'status' => 422,
'payload' => [
'message' => 'Modal online hari ini sudah ditutup (EOD). Tidak boleh mohon.',
'modal_online' => $modal,
],
];
}
$jenis = $modal ? 'TOPUP' : 'HAD';
$request = RequestModalOnline::on('mysql7')->create([
'kodcaw' => $kodcaw,
'tarikh' => $tarikh,
'amaun' => $amaun,
'dimohon_oleh' => $dimohonOleh,
'status' => 'PENDING',
'nota' => $nota,
]);
$this->notifyOperasiModalRequest($kodcaw, $request, $jenis);
return [
'ok' => true,
'status' => 201,
'payload' => [
'request' => $request,
'jenis' => $jenis,
'modal_online' => $modal ?: $this->missingSnapshot($kodcaw, $tarikh, $request),
],
];
}
/**
* Inbox for all operasi staff. Failures must not block the request.
*
* @param string $kodcaw
* @param \App\RequestModalOnline $request
* @param string $jenis
* @return void
*/
protected function notifyOperasiModalRequest($kodcaw, $request, $jenis)
{
try {
app(StaffNotificationClient::class)->notify([
'type' => 'modal_online.pending_operasi',
'cawangan' => $kodcaw,
'audience' => ['role' => 'operasi'],
'data' => [
'request_id' => $request->id,
'amaun' => (float) $request->amaun,
'dimohon_oleh' => $request->dimohon_oleh,
'jenis' => $jenis,
'nota' => $request->nota,
'cawangan_nama' => $this->cawanganNama($kodcaw),
],
]);
} catch (\Throwable $e) {
Log::warning('Operasi modal-online notification failed: '.$e->getMessage(), [
'kodcaw' => $kodcaw,
'request_id' => $request->id,
]);
}
}
/**
* @param string $kodcaw
* @return string
*/
protected function cawanganNama($kodcaw)
{
$cawangan = Cawangan::on('mysql7')->where('kodcaw', '=', $kodcaw)->first();
if (!$cawangan) {
return $kodcaw;
}
$nama = trim((string) $cawangan->nama_cawangan);
return $nama !== '' ? $nama : $kodcaw;
}
/**
* List top-up requests.
* For PC: pass kodcaw. For Operasi: omit kodcaw (all branches).
*
* @return array{ok:bool,status:int,payload:mixed}
*/
public function listTopUpRequests($kodcaw = null, $status = null, $tarikh = null)
{
$query = RequestModalOnline::on('mysql7')->newQuery();
if ($kodcaw) {
$query->where('kodcaw', $kodcaw);
}
if ($status) {
$query->where('status', $status);
}
if ($tarikh) {
$query->where('tarikh', $tarikh);
}
$rows = $query->orderBy('id', 'desc')->get();
$cawMap = [];
if (!$kodcaw) {
foreach (Cawangan::on('mysql7')->get() as $caw) {
$cawMap[$caw->kodcaw] = $caw->nama_cawangan;
}
}
$data = [];
foreach ($rows as $row) {
$item = $row->toArray();
if (!$kodcaw) {
$item['cawangan_nama'] = $cawMap[$row->kodcaw] ?? null;
}
$data[] = $item;
}
return [
'ok' => true,
'status' => 200,
'payload' => [
'bilangan' => count($data),
'data' => $data,
],
];
}
/**
* Operasi approve: first request of the day sets had; later requests add tambah.
* Optional $amaunOverride lets operasi lulus a different amount than dimohon.
*
* @return array{ok:bool,status:int,payload:mixed}
*/
public function approveTopUp($requestId, $diluluskanOleh, $nota = null, $expectedKodcaw = null, $amaunOverride = null)
{
$diluluskanOleh = trim((string) $diluluskanOleh);
if ($diluluskanOleh === '') {
return [
'ok' => false,
'status' => 422,
'payload' => ['message' => 'diluluskan_oleh diperlukan'],
];
}
$req = RequestModalOnline::on('mysql7')->where('id', $requestId)->first();
if (!$req) {
return [
'ok' => false,
'status' => 404,
'payload' => ['message' => 'Permohonan tidak dijumpai'],
];
}
if ($expectedKodcaw && $req->kodcaw !== $expectedKodcaw) {
return [
'ok' => false,
'status' => 404,
'payload' => ['message' => 'Permohonan tidak dijumpai'],
];
}
if ($req->status !== 'PENDING') {
return [
'ok' => false,
'status' => 422,
'payload' => [
'message' => 'Hanya permohonan PENDING boleh diluluskan',
'status' => $req->status,
],
];
}
$kodcaw = $req->kodcaw;
$resolved = $this->resolveMysql($kodcaw);
if (!$resolved['ok']) {
return $resolved;
}
$mysql = $resolved['mysql'];
$amaunDimohon = round((float) $req->amaun, 2);
$amaun = $amaunDimohon;
if ($amaunOverride !== null && $amaunOverride !== '') {
$amaun = round((float) $amaunOverride, 2);
if ($amaun <= 0) {
return [
'ok' => false,
'status' => 422,
'payload' => ['message' => 'Amaun diluluskan tidak sah'],
];
}
}
$tarikh = $this->todayDate();
try {
$applied = DB::connection($mysql)->transaction(function () use ($mysql, $kodcaw, $tarikh, $amaun) {
$modal = ModalOnline::on($mysql)
->where('kodcaw', $kodcaw)
->where('tarikh', $tarikh)
->lockForUpdate()
->first();
$jenis = 'TOPUP';
if (!$modal) {
$modal = ModalOnline::on($mysql)->create([
'tarikh' => $tarikh,
'kodcaw' => $kodcaw,
'had' => $amaun,
'digunakan' => 0.00,
'baki' => $amaun,
'tambah' => 0.00,
'kurang' => 0.00,
'ditutup' => 0,
]);
$modal = ModalOnline::on($mysql)
->where('id', $modal->id)
->lockForUpdate()
->first();
$jenis = 'HAD';
} else {
if ((int) ($modal->ditutup ?? 0) === 1) {
throw new \RuntimeException('MODAL_ONLINE_CLOSED');
}
$modal->tambah = round((float) $modal->tambah + $amaun, 2);
$this->recalcBaki($modal);
$modal->save();
}
return ['modal' => $modal, 'jenis' => $jenis];
});
$modal = $applied['modal'];
$jenis = $applied['jenis'];
$req->status = 'APPROVED';
$req->diluluskan_oleh = $diluluskanOleh;
$req->amaun = $amaun;
$notes = [];
if (abs($amaun - $amaunDimohon) >= 0.01) {
$notes[] = 'Amaun dimohon RM ' . number_format($amaunDimohon, 2)
. ', diluluskan RM ' . number_format($amaun, 2);
}
if ($nota !== null && trim((string) $nota) !== '') {
$notes[] = trim((string) $nota);
}
if ($notes) {
$existing = trim((string) $req->nota);
$req->nota = $existing === ''
? implode(' | ', $notes)
: $existing . ' | ' . implode(' | ', $notes);
}
$req->save();
return [
'ok' => true,
'status' => 200,
'payload' => [
'request' => $req->fresh(),
'jenis' => $jenis,
'modal_online' => $modal,
],
];
} catch (\Throwable $e) {
if ($e->getMessage() === 'MODAL_ONLINE_CLOSED') {
return [
'ok' => false,
'status' => 422,
'payload' => ['message' => 'Modal online hari ini sudah ditutup (EOD). Tidak boleh lulus permohonan.'],
];
}
Log::error('approveTopUp failed: ' . $e->getMessage());
return [
'ok' => false,
'status' => 500,
'payload' => ['message' => 'Gagal lulus permohonan modal online'],
];
}
}
/**
* Operasi reject top-up request.
*
* @return array{ok:bool,status:int,payload:mixed}
*/
public function rejectTopUp($requestId, $diluluskanOleh, $nota = null, $expectedKodcaw = null)
{
$diluluskanOleh = trim((string) $diluluskanOleh);
if ($diluluskanOleh === '') {
return [
'ok' => false,
'status' => 422,
'payload' => ['message' => 'diluluskan_oleh diperlukan'],
];
}
$req = RequestModalOnline::on('mysql7')->where('id', $requestId)->first();
if (!$req) {
return [
'ok' => false,
'status' => 404,
'payload' => ['message' => 'Permohonan tidak dijumpai'],
];
}
if ($expectedKodcaw && $req->kodcaw !== $expectedKodcaw) {
return [
'ok' => false,
'status' => 404,
'payload' => ['message' => 'Permohonan tidak dijumpai'],
];
}
if ($req->status !== 'PENDING') {
return [
'ok' => false,
'status' => 422,
'payload' => [
'message' => 'Hanya permohonan PENDING boleh ditolak',
'status' => $req->status,
],
];
}
$req->status = 'REJECTED';
$req->diluluskan_oleh = $diluluskanOleh;
if ($nota !== null && trim((string) $nota) !== '') {
$req->nota = trim(
trim((string) $req->nota) === ''
? (string) $nota
: $req->nota . ' | ' . $nota
);
}
$req->save();
return [
'ok' => true,
'status' => 200,
'payload' => ['request' => $req],
];
}
/**
* EOD close for one branch: return unused baki via kurang, mark ditutup.
* Also auto-rejects PENDING top-up requests for the branch.
*
* @return array{ok:bool,status:int,payload:mixed}
*/
public function close($kodcaw, $ditutupOleh)
{
$resolved = $this->resolveMysql($kodcaw);
if (!$resolved['ok']) {
return $resolved;
}
$mysql = $resolved['mysql'];
$ditutupOleh = trim((string) $ditutupOleh);
if ($ditutupOleh === '') {
return [
'ok' => false,
'status' => 422,
'payload' => ['message' => 'ditutup_oleh diperlukan'],
];
}
$tarikh = Carbon::now()->toDateString();
$now = Carbon::now();
try {
$result = DB::connection($mysql)->transaction(function () use ($mysql, $kodcaw, $tarikh, $ditutupOleh, $now) {
$modal = ModalOnline::on($mysql)
->where('kodcaw', $kodcaw)
->where('tarikh', $tarikh)
->lockForUpdate()
->first();
if (!$modal) {
return [
'ok' => false,
'status' => 404,
'payload' => [
'message' => 'Tiada modal online hari ini untuk ditutup. Teller belum mohon / PC belum lulus had.',
'missing' => true,
],
];
}
if ((int) ($modal->ditutup ?? 0) === 1) {
return [
'ok' => false,
'status' => 409,
'payload' => [
'message' => 'Modal online sudah ditutup untuk hari ini',
'modal_online' => $modal,
],
];
}
$unused = round((float) $modal->baki, 2);
$modal->kurang = round((float) $modal->kurang + $unused, 2);
$modal->baki = round(
(float) $modal->had + (float) $modal->tambah - (float) $modal->kurang - (float) $modal->digunakan,
2
);
$modal->ditutup = 1;
$modal->ditutup_oleh = $ditutupOleh;
$modal->ditutup_at = $now;
$modal->save();
return [
'ok' => true,
'status' => 200,
'payload' => [
'modal_online' => $modal,
'dikembalikan' => $unused,
],
];
});
if (!$result['ok']) {
return $result;
}
$pendingRejected = RequestModalOnline::on('mysql7')
->where('kodcaw', $kodcaw)
->where('status', 'PENDING')
->get();
foreach ($pendingRejected as $req) {
$req->status = 'REJECTED';
$req->diluluskan_oleh = $ditutupOleh;
$tag = 'Auto reject: EOD close';
$req->nota = trim((string) $req->nota) === ''
? $tag
: trim($req->nota) . ' | ' . $tag;
$req->save();
}
$result['payload']['pending_topup_ditolak'] = $pendingRejected->count();
return $result;
} catch (\Throwable $e) {
Log::error('modal online close failed: ' . $e->getMessage());
return [
'ok' => false,
'status' => 500,
'payload' => ['message' => 'Gagal tutup modal online'],
];
}
}
/**
* Operasi: list today's modal_online across branches.
*
* @return array{ok:bool,status:int,payload:mixed}
*/
public function listToday($zone = 'all', $tarikh = null)
{
$tarikh = $tarikh ?: Carbon::now()->toDateString();
if ($zone == 'all') {
$cawanganAll = Cawangan::on('mysql7')->get();
} else {
$cawanganAll = Cawangan::on('mysql7')->where('zone', $zone)->get();
}
$rows = [];
foreach ($cawanganAll as $cawangan) {
try {
$modal = ModalOnline::on($cawangan['mysql'])
->where('kodcaw', $cawangan['kodcaw'])
->where('tarikh', $tarikh)
->first();
$row = $modal ? $modal->toArray() : [
'tarikh' => $tarikh,
'kodcaw' => $cawangan['kodcaw'],
'had' => 0,
'digunakan' => 0,
'baki' => 0,
'tambah' => 0,
'kurang' => 0,
'ditutup' => 0,
'missing' => true,
];
$row['cawangan_nama'] = $cawangan->nama_cawangan;
$row['missing'] = empty($modal);
$rows[] = $row;
} catch (\Throwable $e) {
Log::warning('listToday modal_online skip ' . $cawangan['kodcaw'] . ': ' . $e->getMessage());
}
}
usort($rows, function ($a, $b) {
return strcmp((string) ($a['kodcaw'] ?? ''), (string) ($b['kodcaw'] ?? ''));
});
$open = 0;
$closed = 0;
$missing = 0;
foreach ($rows as $row) {
if (!empty($row['missing'])) {
$missing++;
} elseif ((int) ($row['ditutup'] ?? 0) === 1) {
$closed++;
} else {
$open++;
}
}
return [
'ok' => true,
'status' => 200,
'payload' => [
'tarikh' => $tarikh,
'bilangan' => count($rows),
'open' => $open,
'closed' => $closed,
'missing' => $missing,
'data' => $rows,
],
];
}
/**
* Operasi bulk EOD close for open branches today.
*
* @return array{ok:bool,status:int,payload:mixed}
*/
public function closeAll($ditutupOleh, $zone = 'all')
{
$ditutupOleh = trim((string) $ditutupOleh);
if ($ditutupOleh === '') {
return [
'ok' => false,
'status' => 422,
'payload' => ['message' => 'ditutup_oleh diperlukan'],
];
}
$list = $this->listToday($zone);
$closed = 0;
$skipped = 0;
$failed = [];
foreach ($list['payload']['data'] as $row) {
if (!empty($row['missing']) || (int) ($row['ditutup'] ?? 0) === 1) {
$skipped++;
continue;
}
$res = $this->close($row['kodcaw'], $ditutupOleh);
if ($res['ok']) {
$closed++;
} else {
$failed[] = [
'kodcaw' => $row['kodcaw'],
'message' => $res['payload']['message'] ?? 'gagal',
];
}
}
return [
'ok' => true,
'status' => 200,
'payload' => [
'closed' => $closed,
'skipped' => $skipped,
'failed' => $failed,
],
];
}
}
File diff suppressed because it is too large Load Diff
+2 -1
View File
@@ -377,7 +377,8 @@ class ImbangDugaService
return $transaction->trans_type !== 'A';
})->sum('keuntungan_rebet');
$total = $total_ansuran + $total_tebus + $get_imbangduga['keun_sebenar'];
$backdate_keun_sebenar = isset($get_imbangduga['keun_sebenar']) ? $get_imbangduga['keun_sebenar'] : 0;
$total = $total_ansuran + $total_tebus + $backdate_keun_sebenar;
return $total;
}
+60 -28
View File
@@ -58,7 +58,7 @@ class LelonganService
$marhun_lelongan = $this->getMarhunLelongan($total_lelongan, $cawangan_connection);
$cawangan_detail = $this->getCawanganDetail($cawangan_connection);
$total_lelongan = $total_lelongan->zip($customer_info, $marhun_lelongan)->map(function ($data) {
$total_lelongan = $total_lelongan->zip($customer_info, $marhun_lelongan)->map(function ($data) use ($total_lelongan) {
[$lelong, $customer_info, $marhun_lelongan] = $data;
@@ -76,31 +76,38 @@ class LelonganService
'bakipjm' => $lelong['bakipjm'],
'keuntungan' => $lelong['bakiupah'],
'cajlelong' => $lelong['cajlelong'],
'hargaasas' => $lelong['hargaasas'],
'hargajual' => $lelong['hargajual'],
'baki' => $lelong['baki'],
'teller' => $lelong['teller'],
];
dd($data);
});
$total_lelongan = $total_lelongan->groupBy('teller');
$lelongan_tellers = $total_lelongan->keys();
$result = [];
$total_lelongan = $total_lelongan
->zip($total_summary_lelongan_teller)
->map(function ($data) {
foreach ($total_lelongan as $teller => $lelongan) {
$result[$teller] = [
'lelongan' => $lelongan->values(),
'summary_lelongan_teller' => $total_summary_lelongan_teller[$teller] ?? [
'total_lelongan' => 0,
'total_berat' => 0,
'total_nilai_marhun' => 0,
'total_pembiayaan' => 0,
'total_bakipjm' => 0,
'total_keuntungan' => 0,
'total_cajlelong' => 0,
'total_hargaasas' => 0,
'total_hargajual' => 0,
'total_baki' => 0,
],
];
}
[$lelongan, $summary] = $data;
$total_lelongan = collect($result);
return [
'lelongan' => $lelongan,
'summary_lelongan_teller' => $summary,
];
});
$total_lelongan = $lelongan_tellers->combine($total_lelongan);
$data = [
'query' => [
@@ -143,6 +150,7 @@ class LelonganService
sum(gadai.bakipjm) as total_bakipjm,
sum(lelong.bakiupah) as total_keuntungan,
sum(lelong.cajlelong) as total_cajlelong,
sum(lelong.hargaasas) as total_hargaasas,
sum(lelong.hargajual) as total_hargajual,
sum(lelong.baki) as total_baki')
->get()
@@ -155,32 +163,54 @@ class LelonganService
'total_bakipjm' => $x['total_bakipjm'],
'total_keuntungan' => $x['total_keuntungan'],
'total_cajlelong' => $x['total_cajlelong'],
'total_hargaasas' => $x['total_hargaasas'],
'total_hargajual' => $x['total_hargajual'],
'total_baki' => $x['total_baki']
];
});
// dd($total_summary_lelongan);
return $total_summary_lelongan[0];
}
// private function getTotalSummaryLelonganGroupByTeller(Builder $query_total_lelongan)
// {
// $total_summary_lelongan_groupByTeller = (clone $query_total_lelongan)
// ->selectRaw('count(lelong.norujukan) as total_lelongan,
// sum(lelong.berat) as total_berat,
// sum(lelong.nilaimarhun) as total_nilai_marhun,
// sum(lelong.pinjaman) as total_pembiayaan,
// sum(gadai.bakipjm) as total_bakipjm,
// sum(lelong.bakiupah) as total_keuntungan,
// sum(lelong.cajlelong) as total_cajlelong,
// sum(lelong.hargaasas) as total_hargaasas,
// sum(lelong.hargajual) as total_hargajual,
// sum(lelong.baki) as total_baki')
// ->groupBy('lelong.teller')
// ->get();
// return $total_summary_lelongan_groupByTeller;
// }
private function getTotalSummaryLelonganGroupByTeller(Builder $query_total_lelongan)
{
$total_summary_lelongan_groupByTeller = (clone $query_total_lelongan)
->selectRaw('count(lelong.norujukan) as total_lelongan,
sum(lelong.berat) as total_berat,
sum(lelong.nilaimarhun) as total_nilai_marhun,
sum(lelong.pinjaman) as total_pembiayaan,
sum(gadai.bakipjm) as total_bakipjm,
sum(lelong.bakiupah) as total_keuntungan,
sum(lelong.cajlelong) as total_cajlelong,
sum(lelong.hargajual) as total_hargajual,
sum(lelong.baki) as total_baki')
return (clone $query_total_lelongan)
->selectRaw('
lelong.teller,
count(lelong.norujukan) as total_lelongan,
sum(lelong.berat) as total_berat,
sum(lelong.nilaimarhun) as total_nilai_marhun,
sum(lelong.pinjaman) as total_pembiayaan,
sum(gadai.bakipjm) as total_bakipjm,
sum(lelong.bakiupah) as total_keuntungan,
sum(lelong.cajlelong) as total_cajlelong,
sum(lelong.hargaasas) as total_hargaasas,
sum(lelong.hargajual) as total_hargajual,
sum(lelong.baki) as total_baki
')
->groupBy('lelong.teller')
->get();
return $total_summary_lelongan_groupByTeller;
->get()
->keyBy('teller'); // ⭐ PENTING
}
private function getTotalLelongan(Builder $query_total_lelongan)
{
$total_lelongan = (clone $query_total_lelongan)
@@ -199,6 +229,7 @@ class LelonganService
'gadai.bakipjm',
'lelong.bakiupah',
'lelong.cajlelong',
'lelong.hargaasas',
'lelong.hargajual',
'lelong.baki')
->orderBy('lelong.teller')
@@ -311,6 +342,7 @@ class LelonganService
'bakipjm' => $lelong['bakipjm'],
'keuntungan' => $lelong['bakiupah'],
'cajlelong' => $lelong['cajlelong'],
'hargaasas' => $lelong['hargaasas'],
'hargajual' => $lelong['hargajual'],
'baki' => $lelong['baki'],
'teller' => $lelong['teller'],
@@ -1322,7 +1322,7 @@ class RingkasanHarianService
return floatval($totalSerahan);
}
return floatval(0);
return floatval($serahanpkbTawarruq);
}
+77
View File
@@ -0,0 +1,77 @@
<?php
namespace App\Services;
use App\Cawangan;
use App\Customer;
use App\Gadai;
use App\ShorakaTransaction;
use Illuminate\Validation\ValidationException;
class ShorakaGadaiService
{
public function gadai(string $kodcaw, string $norujukan): Gadai
{
$branch = Cawangan::on('mysql7')->where('kodcaw', $kodcaw)->firstOrFail();
return Gadai::on($branch->mysql)->where('kodcaw', $kodcaw)->where('norujukan', $norujukan)->first()
?? abort(404, 'Maklumat gadaian tidak tersedia. Semakan diperlukan.');
}
public function payload(Gadai $gadai): array
{
$customer = Customer::on('mysql7')->where(function ($query) use ($gadai) {
$query->where('kpbaru', $gadai->nokp)->orWhere('kplama', $gadai->nokp);
})->firstOrFail();
if (!$customer->nama || $gadai->pinjaman <= 0 || $gadai->hargajualan < $gadai->pinjaman) {
throw ValidationException::withMessages([
'norujukan' => 'Nama pelanggan, pembiayaan dan harga jualan mesti lengkap.',
]);
}
return [
'norujukan' => $gadai->norujukan,
'ownership' => $customer->nama,
'order_amount' => $gadai->pinjaman,
'murabaha_amount' => $gadai->hargajualan,
// Both GadaiV1 and GadaiV2 price the contract using upah12.
// "tempoh" is elapsed charge time, NOT the contractual tenor.
'tenor_other' => 12,
];
}
public function transaction(string $kodcaw, string $norujukan)
{
$order = ShorakaTransaction::where('norujukan', $norujukan)->first();
if ($order && (string) $order->kodcaw !== $kodcaw) {
abort(409, 'Rekod Shoraka tidak dipadankan dengan cawangan ini.');
}
return $order;
}
public function sync(ShorakaTransaction $order): void
{
if (!$order->kodcaw) {
return;
}
$gadai = $this->gadai($order->kodcaw, $order->norujukan);
if (!$gadai->exists) {
return;
}
$sameAmounts = number_format((float) $gadai->pinjaman, 2, '.', '') === $order->order_amount
&& number_format((float) $gadai->hargajualan, 2, '.', '') === $order->murabaha_amount;
$gadai->getConnection()->table('gadai')->where('norujukan', $order->norujukan)->update([
'tagkomuditi' => $order->status === 'Completed' && $sameAmounts ? 1 : 0,
]);
}
public function assertCompleted(string $kodcaw, string $norujukan): void
{
$gadai = $this->gadai($kodcaw, $norujukan);
$order = $this->transaction($kodcaw, $norujukan);
if (!$gadai->exists || !$order || $order->status !== 'Completed'
|| number_format((float) $gadai->pinjaman, 2, '.', '') !== $order->order_amount
|| number_format((float) $gadai->hargajualan, 2, '.', '') !== $order->murabaha_amount) {
abort(409, 'Pembiayaan dan SAG ditahan sehingga transaksi Shoraka selesai dan amaun sepadan.');
}
}
}
+220
View File
@@ -0,0 +1,220 @@
<?php
namespace App\Services;
use Illuminate\Http\Client\Response;
use Illuminate\Support\Facades\Http;
use RuntimeException;
class ShorakaService
{
const STATUSES = ['Active', 'Completed', 'Cancelled', 'Pending Sell', 'Take Delivery'];
const CALLBACK_FIELDS = [
'orderId', 'status', 'bankName', 'ownershipName', 'commodityType',
'unit', 'volume', 'productType', 'valueDate', 'cancelDate', 'orderType',
'orderCurrency', 'orderAmount', 'murabahaAmount', 'tenor',
'tenorOther', 'tenorOtherUnit',
];
protected $baseUrl;
protected $apiId;
protected $secretKey;
protected $timeout;
public function __construct()
{
$this->baseUrl = rtrim((string) config('services.shoraka.base_url'), '/');
$this->apiId = (string) config('services.shoraka.api_id');
$this->secretKey = (string) config('services.shoraka.secret_key');
$this->timeout = (int) config('services.shoraka.timeout', 60);
}
public function configured(): bool
{
if ($this->simulationRequested()) {
return $this->simulationAllowed() && in_array($this->simulationMode(), ['failed', 'pending'], true);
}
return $this->apiId !== '' && $this->secretKey !== '';
}
protected function simulationAllowed(): bool
{
return app()->environment('local', 'testing');
}
protected function simulationMode(): string
{
return strtolower(trim((string) config('services.shoraka.simulation', 'off')));
}
protected function simulationRequested(): bool
{
return !in_array($this->simulationMode(), ['', 'off'], true);
}
private function simulatedResponse(string $orderId): array
{
if (!$this->simulationAllowed()) {
throw new RuntimeException('Shoraka simulation is restricted to local/testing.');
}
if (strpos($orderId, 'SIM-FAILED-') === 0) {
return ['orderId' => $orderId, 'status' => 'Cancelled'];
}
if (strpos($orderId, 'SIM-PENDING-') === 0) {
return ['orderId' => $orderId, 'status' => 'Active'];
}
throw new RuntimeException('Invalid simulation order ID.');
}
public function submitOrder(array $data): array
{
if ($this->simulationRequested()) {
if (!$this->simulationAllowed() || !in_array($this->simulationMode(), ['failed', 'pending'], true)) {
throw new RuntimeException('Shoraka simulation configuration is not allowed.');
}
return $this->simulatedResponse('SIM-' . strtoupper($this->simulationMode()) . '-' . bin2hex(random_bytes(12)));
}
$payload = ['api_id' => $this->apiId];
foreach ([
'product_type', 'commodity_type', 'ownership', 'value_date',
'order_currency', 'order_amount', 'murabaha_amount', 'tenor',
'tenor_other', 'tenor_other_unit', 'order_type', 'ownership_doc_no',
] as $field) {
$payload[$field] = $data[$field] ?? '';
}
foreach (['order_amount', 'murabaha_amount'] as $field) {
$payload[$field] = number_format((float) $payload[$field], 2, '.', '');
}
// ownership_doc_no is deliberately excluded by the v1.5 signature contract.
$signed = $payload;
unset($signed['api_id'], $signed['ownership_doc_no']);
$payload['signature'] = $this->signature(array_values($signed));
return $this->orderResponse($this->request()->post(
$this->baseUrl . '/api/submitorder', $payload
));
}
public function orderStatus(string $orderId): array
{
if (strpos($orderId, 'SIM-') === 0) {
return $this->simulatedResponse($orderId); // Never send simulated references to the real provider.
}
if ($this->simulationRequested()) {
throw new RuntimeException('Real order status is unavailable while simulation is enabled.');
}
return $this->orderResponse($this->request()->post(
$this->baseUrl . '/api/orderstatus', $this->orderPayload($orderId)
), $orderId);
}
public function certificate(string $orderId): string
{
if ($this->simulationRequested() || strpos($orderId, 'SIM-') === 0) {
throw new RuntimeException('Certificates are unavailable for simulation.');
}
// Section 5 specifies POST, despite GET in the document history.
$response = $this->request()->accept('application/pdf')->post(
$this->baseUrl . '/api/certificate', $this->orderPayload($orderId)
);
$this->checkResponse($response);
$type = strtolower(trim(explode(';', $response->header('Content-Type'))[0]));
if ($type !== 'application/pdf' || substr($response->body(), 0, 5) !== '%PDF-') {
throw new RuntimeException('Shoraka returned an invalid PDF certificate.');
}
return $response->body();
}
public function validCallback(array $payload): bool
{
if ($this->simulationRequested() || strpos((string) ($payload['orderId'] ?? ''), 'SIM-') === 0) {
return false;
}
if (!$this->configured()
|| !isset($payload['apiId'], $payload['signature'])
|| !is_string($payload['apiId'])
|| !hash_equals($this->apiId, $payload['apiId'])
|| !is_string($payload['signature'])
|| !preg_match('/\A[0-9a-fA-F]{64}\z/', $payload['signature'])) {
return false;
}
$values = [];
foreach (self::CALLBACK_FIELDS as $field) {
// Preserve the original strings: decimal formatting affects the hash.
if (!array_key_exists($field, $payload) || !is_string($payload[$field])) {
return false;
}
$values[] = $payload[$field];
}
return $payload['orderId'] !== ''
&& in_array($payload['status'], self::STATUSES, true)
&& hash_equals($this->signature($values), strtolower($payload['signature']));
}
protected function signature(array $values): string
{
return hash('sha256', implode(';', array_merge(
[$this->secretKey, $this->apiId], $values
)));
}
protected function orderPayload(string $orderId): array
{
if ($orderId === '') {
throw new RuntimeException('Shoraka order ID is required.');
}
return [
'api_id' => $this->apiId,
'order_id' => $orderId,
'signature' => $this->signature([$orderId]),
];
}
protected function request()
{
if (!$this->configured()) {
throw new RuntimeException('Shoraka credentials are not configured.');
}
// Never forward signed requests through redirects or retry a trade automatically.
return Http::asForm()->acceptJson()->timeout($this->timeout)
->withOptions(['allow_redirects' => false]);
}
protected function checkResponse(Response $response): void
{
if (!$response->successful()) {
// Do not expose upstream bodies which may contain credentials or customer data.
throw new RuntimeException('Shoraka request failed. HTTP ' . $response->status());
}
}
protected function orderResponse(Response $response, string $expectedId = null): array
{
$this->checkResponse($response);
$data = $response->json();
if (!is_array($data)
|| !isset($data['orderId'], $data['status'])
|| !(is_string($data['orderId']) || is_int($data['orderId']))
|| (string) $data['orderId'] === ''
|| !in_array($data['status'], self::STATUSES, true)
|| ($expectedId !== null && (string) $data['orderId'] !== $expectedId)) {
throw new RuntimeException('Shoraka returned an invalid order response.');
}
if (isset($data['certificateUrl']) && !is_string($data['certificateUrl'])) {
throw new RuntimeException('Shoraka returned an invalid certificate URL.');
}
return $data;
}
}
+53
View File
@@ -0,0 +1,53 @@
<?php
namespace App\Services;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
class StaffNotificationClient
{
/**
* Ask kaunter to persist a staff inbox row. Never throws.
*
* @param array $payload
* @return void
*/
public function notify(array $payload)
{
$base = rtrim((string) config('kaunter.url'), '/');
$secret = (string) config('kaunter.notification_secret');
if ($base === '' || $secret === '') {
Log::info('Staff notification skipped: KAUNTER_URL or INTERNAL_NOTIFICATION_SECRET is empty');
return;
}
$timeout = (int) config('kaunter.notification_timeout', 3);
if ($timeout < 1) {
$timeout = 3;
}
try {
$response = Http::timeout($timeout)
->withHeaders([
'X-Internal-Token' => $secret,
'Accept' => 'application/json',
])
->asJson()
->post($base . '/internal/notifications', $payload);
if (!$response->successful()) {
Log::warning('Staff notification to kaunter failed', [
'status' => $response->status(),
'body' => $response->body(),
'type' => isset($payload['type']) ? $payload['type'] : null,
]);
}
} catch (\Throwable $e) {
Log::warning('Staff notification to kaunter errored: '.$e->getMessage(), [
'type' => isset($payload['type']) ? $payload['type'] : null,
]);
}
}
}
@@ -0,0 +1,100 @@
<?php
namespace App\Services\Whatsapp;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
class WhatsappCloudClient
{
public function isConfigured()
{
return trim((string) config('whatsapp.token')) !== ''
&& trim((string) config('whatsapp.phone_number_id')) !== '';
}
/**
* Malaysia MSISDN for Graph API, e.g. 60193450165.
*
* @return string|null
*/
public function normalizeMsisdn($raw)
{
$digits = preg_replace('/\D+/', '', (string) $raw);
if ($digits === '') {
return null;
}
if (strpos($digits, '60') === 0) {
return $digits;
}
if (strpos($digits, '0') === 0) {
return '60' . substr($digits, 1);
}
return '60' . $digits;
}
/**
* @param string $to
* @param string $templateName
* @param string $lang
* @param array<int,string> $bodyParams
* @return \Illuminate\Http\Client\Response
*/
public function sendTemplate($to, $templateName, $lang, array $bodyParams = [])
{
$payload = [
'messaging_product' => 'whatsapp',
'to' => $to,
'type' => 'template',
'template' => [
'name' => $templateName,
'language' => ['code' => $lang],
],
];
if (count($bodyParams) > 0) {
$payload['template']['components'] = [[
'type' => 'body',
'parameters' => array_map(function ($text) {
return [
'type' => 'text',
'text' => $this->sanitizeParam($text),
];
}, array_values($bodyParams)),
]];
}
$version = config('whatsapp.api_version', 'v25.0');
$phoneId = config('whatsapp.phone_number_id');
$url = 'https://graph.facebook.com/' . $version . '/' . $phoneId . '/messages';
$response = Http::withToken(config('whatsapp.token'))
->acceptJson()
->asJson()
->post($url, $payload);
if (!$response->successful()) {
Log::error('WhatsApp Cloud send failed', [
'to' => $to,
'template' => $templateName,
'status' => $response->status(),
'body' => $response->json() ?: $response->body(),
]);
}
return $response;
}
protected function sanitizeParam($text)
{
$text = trim(preg_replace('/\s+/', ' ', (string) $text));
if ($text === '') {
return '-';
}
return mb_substr($text, 0, 200);
}
}
+41
View File
@@ -0,0 +1,41 @@
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class ShorakaTransaction extends Model
{
protected $fillable = [
'norujukan',
'kodcaw',
'teller',
'order_id',
'status',
'product_type',
'commodity_type',
'ownership_name',
'ownership_doc_no',
'order_amount',
'murabaha_amount',
'certificate_url',
'certificate_path',
'request_payload',
'response_payload',
'callback_payload',
'attempt_count',
'error_message',
'submitted_at',
'completed_at',
];
protected $casts = [
'order_amount' => 'decimal:2',
'murabaha_amount' => 'decimal:2',
'request_payload' => 'array',
'response_payload' => 'array',
'callback_payload' => 'array',
'submitted_at' => 'datetime',
'completed_at' => 'datetime',
];
}
+3
View File
@@ -2,11 +2,14 @@
namespace App\Support;
use App\Cawangan;
use App\KadarUpah;
use App\Pampasan;
use App\Support\v1\GadaiV1 as Version1Gadai;
use App\Services\MasterServices;
use App\Support\v1\Penebusan;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request as HttpRequest;
use Illuminate\Support\Facades\Log;
+3 -4
View File
@@ -88,9 +88,7 @@ class GadaiV1
$hargajualan = $request->pinjaman + $upah12;
$gadai = Gadai::on($cawangan['mysql'])
->where('norujukan','=',$norujukan)
->update(array(
$gadaiFields = array(
'nilaimarhun' => $total_nilai_marhun,
'berat'=> $total_berat,
'pinjaman' => $request->pinjaman,
@@ -115,7 +113,8 @@ class GadaiV1
'upahdibayar' => 0.00,
'tempohbayar' => 0.00,
'lelong_tawarruq' => 1
));
);
$gadai = Gadai::on($cawangan['mysql'])->where('norujukan', $norujukan)->update($gadaiFields);
$new_data = Gadai::on($cawangan['mysql'])->where('norujukan','=',$norujukan)->first();
+19 -35
View File
@@ -11,10 +11,10 @@ use Carbon\Carbon;
use App\Gadai;
use App\HargaEmas;
use App\InOutMar;
use App\KomuditiPurchase;
use App\KomuditiTransaksi;
use App\Marhun;
use App\Moratorium;
use App\Palsu;
use App\Pampasan;
use App\Support\OnePercent;
use App\Support\Tawarruq;
use App\Support\v1\Calculation as V1Calculation;
@@ -479,6 +479,17 @@ class Penebusan
$upah_rebet_sebenar = $harga_tebus - $gadai['bakipjm'];
}
$isPampasan = Pampasan::on($cawangan['mysql'])->where('norujukan', $request->norujukan)->exists();
if($isPampasan){
$barang_kes = Palsu::on($cawangan['mysql'])->where('norujukan', $norujukan)->first();
$harga_tebus = $barang_kes->untung + $barang_kes->pembiayaan;
$upah_tebus = $barang_kes->untung;
$upah_rebet = $this->kiraUpahRebetSebenar($kodcaw,$norujukan);
$upah_rebet_sebenar = $barang_kes->untung;
$rebet = $gadai['hargajualan'] - $harga_tebus;
}
$create_tebus = Tebus::on($cawangan['mysql'])->create([
'kodcaw'=> $kodcaw,
@@ -796,19 +807,18 @@ class Penebusan
$array_tebus = $request->only(['jeniskeuntungan', 'nopelanggan','jenistebus','addpinjaman','nowakil','marhun','teller','bakipinjaman','bakiupah']);
$array_trans = $request->only(['nopelanggan', 'noic','jenisAT','pinjaman','pinjamanbaru','marhun','teller','jenistebus']);
$array_tebus_base = $request->only(['totalbayaran', 'bakiupah','jeniskeuntungan','bakipinjaman','jenistebus','teller','nowakil','duitpelanggan']);
$array_trans_base = $request->only(['pinjaman','teller','pinjamanbaru']);
try{
DB::connection($cawangan['mysql'])->beginTransaction();
$this->saveTebus($kodcaw,$norujukan,$gadai,$cawangan,$array_tebus);
$this->createTransaction($cawangan,$kodcaw,$norujukan,$array_trans);
$newReference = $this->createTransaction($cawangan,$kodcaw,$norujukan,$array_trans);
DB::connection('mysql7')->beginTransaction();
$this->saveTebusBase($kodcaw,$norujukan,$gadai,$array_tebus_base);
$this->createTransactionBase($norujukan,$kodcaw,$array_trans_base);
$array_response = [
'result' => 'success',
'data' => 1
'data' => 1,
'norujukan_baru' => $newReference
];
DB::connection($cawangan['mysql'])->commit();
@@ -936,7 +946,7 @@ class Penebusan
$gadaiTransaction = Gadai::on($cawangan['mysql'])->where('t_gadai','=',$current->toDateString())->orderBy('sirig', 'desc')->first();
$gadaiTransaction = optional($gadaiTransaction)->toArray();
$new_sirig = ($gadaiTransaction) ? $gadaiTransaction['sirig'] + 1 : 1;
$new_sirig = (int) ($gadaiTransaction['sirig'] ?? 0) + 1;
$curent_year = substr($current->year,2,2);
$curent_month = sprintf("%02d", $current->month);
$current_day = sprintf("%02d", $current->day);
@@ -1064,37 +1074,11 @@ class Penebusan
't_update' => $current->toDateString(),
));
return $new_sag_number;
}catch(\Exception $e){
throw new \Exception("Error in Transaction: " . $e->getMessage());
}
}
private function createTransactionBase($norujukan,$kodcaw,$array_trans_base){
$current = Carbon::now();
$komuditi_current = KomuditiPurchase::on('mysql7')->where('date_purchase','=',$current->toDateString())->orderBy('created_at','desc')->first();
$unit_current = isset($komuditi_current['unit_komuditi']) ? $komuditi_current['unit_komuditi']: 0;
try{
$update_unit = $unit_current - $array_trans_base['pinjamanbaru'];
KomuditiPurchase::on('mysql7')->where('id','=',$komuditi_current['id'])->update([
'unit_komuditi' => $update_unit
]);
KomuditiTransaksi::on('mysql7')->create([
'unit_komuditi' => $update_unit,
'nosijil'=>$komuditi_current['no_sijil'],
'norujukan' => $norujukan,
'kodcaw' => $kodcaw,
'teller' => $array_trans_base['teller'],
'masa' => $current->toTimeString(),
'tarikh' => $current->toDateString()
]);
}catch(\Exception $e){
throw new \Exception("Error in TransactionBase: " . $e->getMessage());
}
}
}
}
+56 -2
View File
@@ -2,8 +2,11 @@
namespace App\Support\v2;
use App\Feature;
use App\KadarRebet;
use App\KadarUpah;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Schema;
class Calculation
{
@@ -55,7 +58,7 @@ class Calculation
return $ujrah_sebenar;
}
public function kiraanKeuntunganRebet(float $pembiayaan = 0.0,float $nilaimarhun = 0.0,float $kadarujrah = 0.0,float $margin = 0.0){
public function kiraanKeuntunganRebet(float $pembiayaan = 0.0,float $nilaimarhun = 0.0,float $kadarujrah = 0.0,float $margin = 0.0, $kodcaw = null){
$this->one_percent = $this->getKadarUpah($nilaimarhun);
@@ -66,8 +69,59 @@ class Calculation
$ujrah_sebenar = (!empty($ujrah_sebenar)) ? $this->getRoundedMethod($ujrah_sebenar) : 0;
$keuntungan = $onepercent + $ujrah_sebenar;
$annur = $this->kiraanDiskaunAnnur($kodcaw, $pembiayaan, $keuntungan);
return ['keuntungan' => $keuntungan,'onepercent' => $onepercent, 'ujrah' => $ujrah_sebenar];
return [
'keuntungan' => $keuntungan,
'onepercent' => $onepercent,
'ujrah' => $ujrah_sebenar,
'diskaun' => $annur['diskaun'],
'kadarrebet' => $annur['kadarrebet'],
];
}
/**
* Annur promotional discount. Other cawangan get 0 unless rebet_annur is enabled.
*/
public function kiraanDiskaunAnnur($kodcaw, float $pembiayaan = 0.0, float $keuntungan = 0.0)
{
$empty = ['kadarrebet' => 0, 'diskaun' => 0];
try {
if (!$kodcaw || !Feature::enabled($kodcaw, Feature::REBET_ANNUR)) {
return $empty;
}
} catch (\Exception $e) {
return $empty;
}
$percent = $this->kiraanRebetAnnur($pembiayaan);
if ($percent <= 0 || $keuntungan <= 0) {
return ['kadarrebet' => $percent, 'diskaun' => 0];
}
return [
'kadarrebet' => $percent,
'diskaun' => $this->getRoundedMethod($keuntungan * ($percent / 100)),
];
}
private function kiraanRebetAnnur(float $pembiayaan = 0.0)
{
try {
if (!Schema::connection('mysql7')->hasTable('kadar_rebet')) {
return 0;
}
$kadarrebet = KadarRebet::on('mysql7')
->where('pembiayaan_min', '<=', $pembiayaan)
->where('pembiayaan_max', '>=', $pembiayaan)
->sum('kadarrebet');
return (float) $kadarrebet;
} catch (\Exception $e) {
return 0;
}
}
public function kiraanKeuntunganSebenar(float $pembiayaan = 0.0,float $nilaimarhun = 0.0,float $kadarujrah = 0.0,float $margin = 0.0){
+31 -9
View File
@@ -5,6 +5,7 @@ namespace App\Support\v2;
use App\Audit;
use App\Cawangan;
use App\Customer;
use App\Feature;
use App\KadarUpah;
use App\Support\v2\Calculation;
use Carbon\Carbon;
@@ -137,14 +138,13 @@ if (is_null($kadar_raw)) {
$upah12 = $onepercentservices->getRoundedMethod($upah1 * 12);
$ujrah = $onepercentservices->getRoundedMethod($keuntungan['ujrah']);
$onepercent = $onepercentservices->getRoundedMethod($keuntungan['onepercent']);
$annur = $onepercentservices->kiraanDiskaunAnnur($request->kodcaw, $request->pinjaman, $keuntungan['keuntungan']);
$hargajualan = $request->pinjaman + $upah12;
$akrual = $this->kiraupahTawarruqStokAuditUjrahOnePercent(0.5,0,$kadar_raw,$request->pinjaman,$total_nilai_marhun,$margin_pinjam);
$gadai = Gadai::on($cawangan['mysql'])
->where('norujukan','=',$norujukan)
->update(array(
$gadaiFields = [
'nilaimarhun' => $total_nilai_marhun,
'berat'=> $total_berat,
'pinjaman' => $request->pinjaman,
@@ -176,7 +176,23 @@ if (is_null($kadar_raw)) {
'tagbaru' => 1,
'tunggakanujrah' => $akrual['ujrah'],
'tunggakanonepercent' => $akrual['keuntungan'],
));
];
if (Feature::enabled($request->kodcaw, Feature::REBET_ANNUR)) {
$requestedKadar = (float) $request->input('kadar_rebate', 0);
$requestedDiskaun = (float) $request->input('rebate', 0);
if ($requestedKadar > 0 || $requestedDiskaun > 0) {
$gadaiFields['kadarrebet'] = $requestedKadar;
$gadaiFields['diskaun'] = $requestedDiskaun;
} elseif (($request->transaction ?? '') === 'Daftar Gadai') {
$gadaiFields['kadarrebet'] = $annur['kadarrebet'];
$gadaiFields['diskaun'] = $annur['diskaun'];
}
}
$gadai = Gadai::on($cawangan['mysql'])
->where('norujukan','=',$norujukan)
->update($gadaiFields);
$new_data = Gadai::on($cawangan['mysql'])->where('norujukan','=',$norujukan)->first();
@@ -240,14 +256,15 @@ if (is_null($kadar_raw)) {
}
$bilang_bln = $tempoh - $gadai_data['tempohbayar'];
$upah = $onepercentservices->kiraanKeuntunganRebet($gadai_data['bakipjm'],$gadai_data['nilaimarhun'],$gadai_data['kadarupah'],$gadai_data['margin_semasa']);
$upah = $onepercentservices->kiraanKeuntunganRebet($gadai_data['bakipjm'],$gadai_data['nilaimarhun'],$gadai_data['kadarupah'],$gadai_data['margin_semasa'], $this->kodcaw);
//$upahrebet_raw = $bilang_bln * $upah['keuntungan'];
$onepercent = $onepercentservices->getRoundedMethod($bilang_bln * $upah['onepercent']);
$ujrah = $onepercentservices->getRoundedMethod($bilang_bln * $upah['ujrah']);
$upahrebet_raw = $onepercent + $ujrah;
$upahrebet_raw = $onepercentservices->getRoundedMethod($upahrebet_raw);
$upahrebet_raw = $upahrebet_raw - ($upah['diskaun'] ?? 0);
return $upahrebet_raw;
}
@@ -282,7 +299,7 @@ if (is_null($kadar_raw)) {
$margin_semasa = ($gadai['bakipjm'] / $gadai['nilaimarhun']) * 100;
$keuntungan = $onepercent->kiraanKeuntunganRebet($gadai['bakipjm'],$gadai['nilaimarhun'],$gadai['kadarupah'],$gadai['margin_semasa']);
$keuntungan = $onepercent->kiraanKeuntunganRebet($gadai['bakipjm'],$gadai['nilaimarhun'],$gadai['kadarupah'],$gadai['margin_semasa'], $kodcaw);
$ujrahasal = $onepercent->kiraanUjrahSebenar($gadai['nilaimarhun'],$gadai['kadarupah'],$gadai['percentpinj']);
$onepercentasal = $gadai['pinjaman'] * (1/100);
@@ -319,7 +336,7 @@ if (is_null($kadar_raw)) {
$keun = $onepercent->getRoundedMethod($onepercentasal * $bilang_bln);
$keuntungan_rebate = $onepercent->getRoundedMethod($keuntungan['onepercent'] * $bilang_bln);
$transaction_upah = TransaksiKeuntungan::on('mysql7')->create([
$transactionUpahFields = [
'norujukan' => $norujukan,
'bayaran_keuntungan' => $upahsemasa,
'bayaran_pembiayaan' => $baki_bayaran,
@@ -333,7 +350,12 @@ if (is_null($kadar_raw)) {
'tempoh' => $gadai['tempoh'],
'beza_tempoh' => $bilang_bln,
'margin_semasa' => $margin_semasa,
]);
];
if (Feature::enabled($kodcaw, Feature::REBET_ANNUR)) {
$transactionUpahFields['kadardiskaun'] = $keuntungan['diskaun'] ?? 0;
}
$transaction_upah = TransaksiKeuntungan::on('mysql7')->create($transactionUpahFields);
$transaksi_gadai = TransaksiGadai::on('mysql7')->create([
'kodcaw' => $old_data['kodcaw'],
+63 -51
View File
@@ -5,14 +5,13 @@ namespace App\Support\v2;
use App\Advansur;
use App\Audit;
use App\Cawangan;
use App\Feature;
use App\KadarUpah;
use App\Support\v2\Calculation;
use Carbon\Carbon;
use App\Gadai;
use App\HargaEmas;
use App\InOutMar;
use App\KomuditiPurchase;
use App\KomuditiTransaksi;
use App\Marhun;
use App\FailoverTransaction;
use App\FailoverTransactionFailed;
@@ -39,6 +38,32 @@ class Penebusan
$this->cawangan_info = $cawangan_info;
}
private function diskaunAnnur($kodcaw, $gadai)
{
try {
if (!Feature::enabled($kodcaw, Feature::REBET_ANNUR)) {
return 0.0;
}
} catch (\Exception $e) {
return 0.0;
}
return (float) ($gadai['diskaun'] ?? 0);
}
private function tebusAnnurFields($kodcaw, $diskaun)
{
try {
if (!Feature::enabled($kodcaw, Feature::REBET_ANNUR)) {
return [];
}
} catch (\Exception $e) {
return [];
}
return ['diskaun_rebate' => $diskaun];
}
public function tebusTawarruq($kodcaw,$norujukan){
$onepercentservices = new Calculation();
@@ -53,7 +78,8 @@ class Penebusan
$jeniskeuntungan = ($gadai_data['tagbaru'] == 1) ? 'onepercent' : 'asal';
$rebet = $this->rebetselepastawarruq($kodcaw,$norujukan,$jeniskeuntungan);
$harga_tebus = $gadai_data['bakihargajualan'] - $rebet;
$diskaun = $this->diskaunAnnur($kodcaw, $gadai_data);
$harga_tebus = $gadai_data['bakihargajualan'] - $rebet - $diskaun;
// $hargatebus = round($harga_tebus,PHP_ROUND_HALF_UP);
// $hargatebus = bcdiv(round($harga_tebus,2),1,1);
@@ -485,6 +511,8 @@ class Penebusan
$gadai = Gadai::on($cawangan['mysql'])->where('norujukan','=',$norujukan)->first();
$diskaun = $this->diskaunAnnur($kodcaw, $gadai);
//check duplicate
$data = Transaction::on('mysql7')->where('norujukan','=',$norujukan)->whereIn('trans_type', ['P', 'T', 'S'])->first();
@@ -502,7 +530,6 @@ class Penebusan
$harga_tebus = $this->hargaTebusTawarruq($kodcaw,$norujukan,$request->jeniskeuntungan);
$upah_tebus = $this->kiraupahTawarruq($norujukan);
$upah_rebet = $this->kiraUpahRebetSebenar($kodcaw,$norujukan);
$upah_rebet_sebenar = $harga_tebus['harga'] - $gadai['bakipjm'];
if($request->jeniskeuntungan == 'onepercent'){
$array_onepercent = $this->kiraupahUjrahOnePercent($kodcaw,$norujukan);
@@ -514,10 +541,15 @@ class Penebusan
$ujrah = $array_onepercent['ujrah'];
$onepercent = $array_onepercent['onepercent'];
}
$harga_tebus['harga'] -= $diskaun;
$upah_rebet_sebenar = $harga_tebus['harga'] - $gadai['bakipjm'];
}
$bayaran_keuntungan_bersih = max(0, $upah_rebet_sebenar);
$create_tebus = Tebus::on($cawangan['mysql'])->create([
$create_tebus = Tebus::on($cawangan['mysql'])->create(array_merge([
'kodcaw'=> $kodcaw,
'norujukan'=> $norujukan,
'nopelanggan' => $request->nopelanggan,
@@ -543,7 +575,7 @@ class Penebusan
'teller' => $request->teller,
'ujrah' => $ujrah_rebet,
'onepercent' => $onepercent_rebet,
]);
], $this->tebusAnnurFields($kodcaw, $diskaun)));
$create_inoutmar = InOutMar::on($cawangan['mysql'])->create([
'tarikh' => $current->toDateString(),
@@ -561,7 +593,7 @@ class Penebusan
$transaction_upah = TransaksiKeuntungan::on('mysql7')->create([
'norujukan' => $norujukan,
'bayaran_keuntungan' => $upah_tebus,
'bayaran_keuntungan' => $bayaran_keuntungan_bersih,
'bayaran_pembiayaan' => $gadai['bakipjm'],
'keuntungan_rebet' => $upah_rebet_sebenar,
'tarikh_bayaran'=> $current,
@@ -651,6 +683,8 @@ class Penebusan
$gadai = Gadai::on($cawangan['mysql'])->where('norujukan','=',$norujukan)->first();
$diskaun = $this->diskaunAnnur($kodcaw, $gadai);
//check duplicate
$data = Transaction::on('mysql7')->where('norujukan','=',$norujukan)->where('trans_type','=','AT')->first();
@@ -668,7 +702,6 @@ class Penebusan
$harga_tebus = $this->hargaTebusTawarruq($kodcaw,$norujukan,$request->jeniskeuntungan);
$upah_tebus = $this->kiraupahTawarruq($norujukan);
$upah_rebet = $this->kiraUpahRebetSebenar($kodcaw,$norujukan);
$upah_rebet_sebenar = $harga_tebus['harga'] - $gadai['bakipjm'];
if($request->jeniskeuntungan == 'onepercent'){
$array_onepercent = $this->kiraupahUjrahOnePercent($kodcaw,$norujukan);
@@ -681,6 +714,9 @@ class Penebusan
$ujrah = $array_onepercent['ujrah'];
$onepercent = $array_onepercent['onepercent'];
}
$harga_tebus['harga'] -= $diskaun;
$upah_rebet_sebenar = $harga_tebus['harga'] - $gadai['bakipjm'];
}
$update_gadai = Gadai::on($cawangan['mysql'])->where('norujukan','=',$norujukan)->update(array(
@@ -690,7 +726,7 @@ class Penebusan
// $upah_rebet = $this->kiraUpahRebet($kodcaw,$norujukan);
$create_tebus = Tebus::on($cawangan['mysql'])->create([
$create_tebus = Tebus::on($cawangan['mysql'])->create(array_merge([
'kodcaw'=> $kodcaw,
'norujukan'=> $norujukan,
'nopelanggan' => $request->nopelanggan,
@@ -716,7 +752,7 @@ class Penebusan
'teller' => $request->teller,
'ujrah' => $ujrah_rebet,
'onepercent' => $onepercent_rebet,
]);
], $this->tebusAnnurFields($kodcaw, $diskaun)));
$create_inoutmar = InOutMar::on($cawangan['mysql'])->create([
'tarikh' => $current->toDateString(),
@@ -734,9 +770,11 @@ class Penebusan
$bayaran_pembiayaan = $request->totalbayaran - $request->bakiupah;
$bayaran_keuntungan_bersih = max(0, $upah_rebet_sebenar);
$transaction_upah = TransaksiKeuntungan::on('mysql7')->create([
'norujukan' => $norujukan,
'bayaran_keuntungan' => $upah_tebus,
'bayaran_keuntungan' => $bayaran_keuntungan_bersih,
'bayaran_pembiayaan' => $bayaran_pembiayaan,
'keuntungan_rebet' => $upah_rebet_sebenar,
'tarikh_bayaran'=> $current,
@@ -944,20 +982,19 @@ class Penebusan
$array_tebus = $request->only(['jeniskeuntungan', 'nopelanggan','jenistebus','addpinjaman','nowakil','marhun','teller']);
$array_trans = $request->only(['nopelanggan', 'noic','jenisAT','pinjaman','pinjamanbaru','marhun','teller','jenistebus']);
$array_tebus_base = $request->only(['totalbayaran', 'bakiupah','jeniskeuntungan','bakipinjaman','jenistebus','teller','nowakil','duitpelanggan']);
$array_trans_base = $request->only(['pinjaman','teller','pinjamanbaru']);
try{
DB::connection($cawangan['mysql'])->beginTransaction();
$this->saveTebus($kodcaw,$norujukan,$gadai,$cawangan,$array_tebus);
$this->createTransaction($cawangan,$kodcaw,$norujukan,$array_trans);
$newReference = $this->createTransaction($cawangan,$kodcaw,$norujukan,$array_trans);
DB::connection('mysql7')->beginTransaction();
$this->saveTebusBase($kodcaw,$norujukan,$gadai,$array_tebus_base);
$this->createTransactionBase($norujukan,$kodcaw,$array_trans_base);
$array_response = [
'result' => 'success',
'data' => 1
'data' => 1,
'norujukan_baru' => $newReference
];
DB::connection($cawangan['mysql'])->commit();
@@ -1004,19 +1041,18 @@ class Penebusan
$array_tebus = $request->only(['jeniskeuntungan', 'nopelanggan','jenistebus','addpinjaman','nowakil','marhun','teller']);
$array_trans = $request->only(['nopelanggan', 'noic','pinjaman','pinjamanbaru','marhun','teller','jenistebus']);
$array_tebus_base = $request->only(['totalbayaran', 'bakiupah','jeniskeuntungan','bakipinjaman','jenistebus','teller','nowakil','duitpelanggan']);
$array_trans_base = $request->only(['pinjaman','teller','pinjamanbaru']);
try{
DB::connection($cawangan['mysql'])->beginTransaction();
$this->saveTebus($kodcaw,$norujukan,$gadai,$cawangan,$array_tebus);
$this->createTransaction($cawangan,$kodcaw,$norujukan,$array_trans);
$newReference = $this->createTransaction($cawangan,$kodcaw,$norujukan,$array_trans);
DB::connection('mysql7')->beginTransaction();
$this->saveTebusBase($kodcaw,$norujukan,$gadai,$array_tebus_base);
$this->createTransactionBase($norujukan,$kodcaw,$array_trans_base);
$array_response = [
'result' => 'success',
'data' => 1
'data' => 1,
'norujukan_baru' => $newReference
];
DB::connection($cawangan['mysql'])->commit();
@@ -1131,12 +1167,16 @@ class Penebusan
$onepercent_rebet = $array_onepercent_rebet['onepercent'] - $upah_rebet['onepercent'];
$ujrah_rebet = $array_onepercent_rebet['ujrah'] - $upah_rebet['ujrah'];
$diskaun = $this->diskaunAnnur($kodcaw, $gadai);
$harga_tebus['harga'] -= $diskaun;
$upah_rebet_sebenar = $harga_tebus['harga'] - $gadai['bakipjm'];
$bayaran_keuntungan_bersih = max(0, $upah_rebet_sebenar);
try{
TransaksiKeuntungan::on('mysql7')->create([
'norujukan' => $norujukan,
'bayaran_keuntungan' => $upah_tebus,
'bayaran_keuntungan' => $bayaran_keuntungan_bersih,
'bayaran_pembiayaan' => $bayaran_pembiayaan,
'keuntungan_rebet' => $upah_rebet_sebenar,
'tarikh_bayaran'=> $current,
@@ -1184,7 +1224,7 @@ class Penebusan
$gadaiTransaction = Gadai::on($cawangan['mysql'])->where('t_gadai','=',$current->toDateString())->orderBy('sirig', 'desc')->first();
$gadaiTransaction = optional($gadaiTransaction)->toArray();
$new_sirig = ($gadaiTransaction) ? $gadaiTransaction['sirig'] + 1 : 1;
$new_sirig = (int) ($gadaiTransaction['sirig'] ?? 0) + 1;
$curent_year = substr($current->year,2,2);
$curent_month = sprintf("%02d", $current->month);
$current_day = sprintf("%02d", $current->day);
@@ -1312,39 +1352,11 @@ class Penebusan
't_update' => $current->toDateString(),
));
return $new_sag_number;
}catch(\Exception $e){
throw new \Exception("Error in Transaction: " . $e->getMessage());
}
}
private function createTransactionBase($norujukan,$kodcaw,$array_trans_base){
$current = Carbon::now();
$komuditi_current = KomuditiPurchase::on('mysql7')->where('date_purchase','=',$current->toDateString())->orderBy('created_at','desc')->first();
$unit_current = isset($komuditi_current['unit_komuditi']) ? $komuditi_current['unit_komuditi']: 0;
try{
$update_unit = $unit_current - $array_trans_base['pinjamanbaru'];
KomuditiPurchase::on('mysql7')->where('id','=',$komuditi_current['id'])->update([
'unit_komuditi' => $update_unit
]);
KomuditiTransaksi::on('mysql7')->create([
'unit_komuditi' => $update_unit,
'nosijil'=>$komuditi_current['no_sijil'],
'norujukan' => $norujukan,
'kodcaw' => $kodcaw,
'teller' => $array_trans_base['teller'],
'masa' => $current->toTimeString(),
'tarikh' => $current->toDateString()
]);
}catch(\Exception $e){
throw new \Exception("Error in TransactionBase: " . $e->getMessage());
}
}
}
}
+2 -1
View File
@@ -52,7 +52,8 @@ class Tebus extends Model
'pinjamanterkini',
'tarikhbyrn',
'ujrah',
'onepercent'
'onepercent',
'diskaun_rebate'
];
/**
+1 -1
View File
@@ -14,7 +14,7 @@ class TransaksiKeuntungan extends Model
* @var array
*/
protected $fillable = [
'norujukan','bayaran_keuntungan','tarikh_bayaran','kodcaw','bayaran_pembiayaan','keuntungan_rebet','ujrah','ujrah_rebet','onepercent','onepercent_rebet','tempoh','beza_tempoh','margin_semasa','rounded'
'norujukan','bayaran_keuntungan','tarikh_bayaran','kodcaw','bayaran_pembiayaan','keuntungan_rebet','ujrah','ujrah_rebet','onepercent','onepercent_rebet','tempoh','beza_tempoh','margin_semasa','rounded','kadardiskaun'
];
/**
+32
View File
@@ -0,0 +1,32 @@
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class WTDSimulasi extends Model
{
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $connection = 'mysql7';
protected $table = 'wtd_simulasi';
protected $fillable = [
'bulan',
'tahun',
'kodcaw',
'jumlah_sag',
'pembiayaan',
'baki',
'simulasi',
];
/**
* The attributes excluded from the model's JSON form.
*
* @var array
*/
protected $hidden = [];
}
+1
View File
@@ -0,0 +1 @@
/cache
+41 -5
View File
@@ -23,6 +23,33 @@ $app = new Laravel\Lumen\Application(
dirname(__DIR__)
);
/*
| Laravel's storage_path() helper resolves app('path.storage'). Lumen does not
| register that binding by default; config files that call storage_path() at
| load time (e.g. config/logging.php) would fail without this.
*/
$app->instance('path.storage', $app->storagePath());
/*
| Laravel's Foundation helpers are loaded before Lumen's and win for names like
| response() and url(). Those helpers resolve Illuminate\Contracts\* from the
| container; Lumen only registers the "url" service, not the contracts. Map the
| contracts to Lumen's implementations so helpers work across the app.
*/
$app->singleton(
\Illuminate\Contracts\Routing\ResponseFactory::class,
function () {
return new \Laravel\Lumen\Http\ResponseFactory();
}
);
$app->singleton(
\Illuminate\Contracts\Routing\UrlGenerator::class,
function ($app) {
return $app->make('url');
}
);
$app->withFacades();
$app->withEloquent();
@@ -60,11 +87,17 @@ $app->singleton(
*/
$app->configure('app');
$app->configure('services');
$app->configure('logging');
$app->configure('logviewer');
$app->configure('tinker');
$app->configure('tennant_master');
$app->configure('tennant_database');
$app->configure('report');
$app->configure('app_parameter');
$app->configure('parameter');
$app->configure('whatsapp');
$app->configure('kaunter');
$app->configure('bsas');
/*
|--------------------------------------------------------------------------
| Register Middleware
@@ -80,9 +113,9 @@ $app->configure('app_parameter');
// App\Http\Middleware\ExampleMiddleware::class
// ]);
// $app->routeMiddleware([
// 'auth' => App\Http\Middleware\Authenticate::class,
// ]);
$app->routeMiddleware([
'logviewer.password' => App\Http\Middleware\LogViewerPassword::class,
]);
/*
|--------------------------------------------------------------------------
@@ -125,4 +158,7 @@ $app->middleware([
$app->register(\Rap2hpoutre\LaravelLogViewer\LaravelLogViewerServiceProvider::class);
$app->register(\Laravel\Tinker\TinkerServiceProvider::class);
$app->register(\App\Providers\AppServiceProvider::class);
$app->register(\KitLoong\MigrationsGenerator\MigrationsGeneratorServiceProvider::class);
return $app;
+1
View File
@@ -15,6 +15,7 @@
},
"require-dev": {
"fzaninotto/faker": "^1.9.1",
"kitloong/laravel-migrations-generator": "^6.11",
"mockery/mockery": "^1.3.1",
"phpunit/phpunit": "^8.5"
},
Generated
+142 -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": "2a35e67752107934701ceea957a4fd34",
"content-hash": "64c3d9455edb548f4cd5ec1f8d9f77be",
"packages": [
{
"name": "auth0/auth0-php",
@@ -6686,6 +6686,81 @@
},
"time": "2020-07-09T08:09:16+00:00"
},
{
"name": "kitloong/laravel-migrations-generator",
"version": "v6.11.1",
"source": {
"type": "git",
"url": "https://github.com/kitloong/laravel-migrations-generator.git",
"reference": "0f007e86dce6e0cfd71dc0b757da18851c376c3b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/kitloong/laravel-migrations-generator/zipball/0f007e86dce6e0cfd71dc0b757da18851c376c3b",
"reference": "0f007e86dce6e0cfd71dc0b757da18851c376c3b",
"shasum": ""
},
"require": {
"doctrine/dbal": "^2.4|^3.0",
"ext-pdo": "*",
"illuminate/support": "^5.6|^6.0|^7.0|^8.0|^9.0|^10.0",
"myclabs/php-enum": "^1.6|^1.7|^1.8",
"php": ">=7.1.3"
},
"require-dev": {
"barryvdh/laravel-ide-helper": "^2.5",
"friendsofphp/php-cs-fixer": "^2.19.0|^3.1",
"larastan/larastan": "^0.4|^0.5|^0.6|^0.7|^1.0|^2.0",
"mockery/mockery": "^1.0",
"orchestra/testbench": "^3.6|^4.0|^5.0|^6.0|^7.0|^8.0",
"phpmd/phpmd": "^2.10",
"slevomat/coding-standard": "^6.0|^7.0|^8.5",
"squizlabs/php_codesniffer": "^3.5"
},
"type": "library",
"extra": {
"laravel": {
"providers": [
"KitLoong\\MigrationsGenerator\\MigrationsGeneratorServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"KitLoong\\MigrationsGenerator\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Kit Loong",
"email": "kitloong1008@gmail.com"
}
],
"description": "Generates Laravel Migrations from an existing database",
"keywords": [
"artisan",
"generator",
"laravel",
"lumen",
"migration",
"migrations"
],
"support": {
"issues": "https://github.com/kitloong/laravel-migrations-generator/issues",
"source": "https://github.com/kitloong/laravel-migrations-generator/tree/v6.11.1"
},
"funding": [
{
"url": "https://www.buymeacoffee.com/kitloong",
"type": "custom"
}
],
"time": "2024-02-06T14:42:33+00:00"
},
{
"name": "mockery/mockery",
"version": "1.5.0",
@@ -6817,6 +6892,69 @@
],
"time": "2022-03-03T13:19:32+00:00"
},
{
"name": "myclabs/php-enum",
"version": "1.8.5",
"source": {
"type": "git",
"url": "https://github.com/myclabs/php-enum.git",
"reference": "e7be26966b7398204a234f8673fdad5ac6277802"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/myclabs/php-enum/zipball/e7be26966b7398204a234f8673fdad5ac6277802",
"reference": "e7be26966b7398204a234f8673fdad5ac6277802",
"shasum": ""
},
"require": {
"ext-json": "*",
"php": "^7.3 || ^8.0"
},
"require-dev": {
"phpunit/phpunit": "^9.5",
"squizlabs/php_codesniffer": "1.*",
"vimeo/psalm": "^4.6.2 || ^5.2"
},
"type": "library",
"autoload": {
"psr-4": {
"MyCLabs\\Enum\\": "src/"
},
"classmap": [
"stubs/Stringable.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP Enum contributors",
"homepage": "https://github.com/myclabs/php-enum/graphs/contributors"
}
],
"description": "PHP Enum implementation",
"homepage": "https://github.com/myclabs/php-enum",
"keywords": [
"enum"
],
"support": {
"issues": "https://github.com/myclabs/php-enum/issues",
"source": "https://github.com/myclabs/php-enum/tree/1.8.5"
},
"funding": [
{
"url": "https://github.com/mnapoli",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/myclabs/php-enum",
"type": "tidelift"
}
],
"time": "2025-01-14T11:49:03+00:00"
},
{
"name": "phar-io/manifest",
"version": "2.0.3",
@@ -8389,12 +8527,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"
}
-44
View File
@@ -1,44 +0,0 @@
{
"name": "laravel/lumen",
"description": "The Laravel Lumen Framework.",
"keywords": ["framework", "laravel", "lumen"],
"license": "MIT",
"type": "project",
"require": {
"php": "^7.2.5",
"auth0/auth0-php": "^7.4",
"laravel/lumen-framework": "^7.0",
"rap2hpoutre/laravel-log-viewer": "^1.7"
},
"require-dev": {
"fzaninotto/faker": "^1.9.1",
"mockery/mockery": "^1.3.1",
"phpunit/phpunit": "^8.5"
},
"autoload": {
"classmap": [
"database/seeds",
"database/factories"
],
"psr-4": {
"App\\": "app/"
}
},
"autoload-dev": {
"classmap": [
"tests/"
]
},
"config": {
"preferred-install": "dist",
"sort-packages": true,
"optimize-autoloader": true
},
"minimum-stability": "dev",
"prefer-stable": true,
"scripts": {
"post-root-package-install": [
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
]
}
}
-7182
View File
File diff suppressed because it is too large Load Diff
+5 -2
View File
@@ -1,6 +1,9 @@
<?php
return [
'url' => env('APP_URL', url('/')),
'api_arrahnbid' => env('API_ARRAHNBID', 'http://lelong.test/api/')
'url' => env('APP_URL', 'http://localhost'),
'api_arrahnbid' => env('API_ARRAHNBID', 'http://lelong.test/api/'),
'api_arrahnbid_annur' => env('API_ARRAHNBID_ANNUR', env('API_ARRAHNBID', 'http://lelong.test/api/')),
'api_v3' => env('API_V3', 'http://apiv3.localhost/api/')
];
+14
View File
@@ -0,0 +1,14 @@
<?php
return [
'base_url' => rtrim(env('BSAS_BASE_URL', 'https://bsasdevapi.bursamalaysia.com/api'), '/'),
'token_url' => env('BSAS_TOKEN_URL', 'https://bsasdevapi.bursamalaysia.com/oauth/token'),
'client_id' => env('BSAS_CLIENT_ID'),
'client_secret' => env('BSAS_CLIENT_SECRET'),
'member_short_name' => env('BSAS_MEMBER_SHORT_NAME', 'KOOPPKB'),
'product_code' => env('BSAS_PRODUCT_CODE', 'CPO-MSIA-09'),
'tenor' => env('BSAS_TENOR', '00088'),
'max_wait_time' => (int) env('BSAS_MAX_WAIT_TIME', 10),
'http_timeout' => (int) env('BSAS_HTTP_TIMEOUT', 30),
'order_result_retries' => (int) env('BSAS_ORDER_RESULT_RETRIES', 2),
];
+7
View File
@@ -0,0 +1,7 @@
<?php
return [
'url' => env('KAUNTER_URL', ''),
'notification_secret' => env('INTERNAL_NOTIFICATION_SECRET', ''),
'notification_timeout' => (int) env('KAUNTER_NOTIFICATION_TIMEOUT', 3),
];
+103
View File
@@ -0,0 +1,103 @@
<?php
use Monolog\Handler\NullHandler;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\SyslogUdpHandler;
return [
/*
|--------------------------------------------------------------------------
| Default Log Channel
|--------------------------------------------------------------------------
|
| This option defines the default log channel that gets used when writing
| messages to the logs. The name specified in this option should match
| one of the channels defined in the "channels" configuration array.
|
*/
'default' => env('LOG_CHANNEL', 'stack'),
/*
|--------------------------------------------------------------------------
| Log Channels
|--------------------------------------------------------------------------
|
| Here you may configure the log channels for your application. Out of
| the box, Laravel uses the Monolog PHP logging library. This gives
| you a variety of powerful log handlers / formatters to utilize.
|
| Available Drivers: "single", "daily", "slack", "syslog",
| "errorlog", "monolog", "custom", "stack"
|
*/
'channels' => [
'stack' => [
'driver' => 'stack',
'channels' => ['single'],
'ignore_exceptions' => false,
],
'single' => [
'driver' => 'single',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
],
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
'days' => 14,
],
'slack' => [
'driver' => 'slack',
'url' => env('LOG_SLACK_WEBHOOK_URL'),
'username' => 'Laravel Log',
'emoji' => ':boom:',
'level' => env('LOG_LEVEL', 'critical'),
],
'papertrail' => [
'driver' => 'monolog',
'level' => env('LOG_LEVEL', 'debug'),
'handler' => SyslogUdpHandler::class,
'handler_with' => [
'host' => env('PAPERTRAIL_URL'),
'port' => env('PAPERTRAIL_PORT'),
],
],
'stderr' => [
'driver' => 'monolog',
'handler' => StreamHandler::class,
'formatter' => env('LOG_STDERR_FORMATTER'),
'with' => [
'stream' => 'php://stderr',
],
],
'syslog' => [
'driver' => 'syslog',
'level' => env('LOG_LEVEL', 'debug'),
],
'errorlog' => [
'driver' => 'errorlog',
'level' => env('LOG_LEVEL', 'debug'),
],
'null' => [
'driver' => 'monolog',
'handler' => NullHandler::class,
],
'emergency' => [
'path' => storage_path('logs/laravel.log'),
],
],
];
+16
View File
@@ -0,0 +1,16 @@
<?php
return [
'password' => env('LOGVIEWER_PASSWORD'),
/*
|--------------------------------------------------------------------------
| Pattern and storage path settings
|--------------------------------------------------------------------------
|
| The env key for pattern and storage path with a default value
|
*/
'max_file_size' => 52428800, // size in Byte
'pattern' => env('LOGVIEWER_PATTERN', '*.log'),
'storage_path' => env('LOGVIEWER_STORAGE_PATH', storage_path('logs')),
];
+5
View File
@@ -0,0 +1,5 @@
<?php
return [
'max_mutuemas' => env('MAX_MUTUEMAS', true),
];
+23
View File
@@ -0,0 +1,23 @@
<?php
return [
'shoraka' => [
// Local UI tests only: off, failed or pending. Success is deliberately excluded.
'simulation' => env('SHORAKA_SIMULATION', 'off'),
'base_url' => env(
'SHORAKA_BASE_URL',
'https://uat2.shorakagroup.com'
),
'api_id' => env('SHORAKA_API_ID'),
'secret_key' => env('SHORAKA_SECRET_KEY'),
'timeout' => env(
'SHORAKA_TIMEOUT',
60
),
],
];
+6
View File
@@ -0,0 +1,6 @@
<?php
return [
'master_db' => env('TENNANT_MASTER_DB','online_arrahn'),
'kaunter_db' => env('TENNANT_KAUNTER_DB','kaunterpkb')
];
-5
View File
@@ -1,5 +0,0 @@
<?php
return [
'database' => env('TENNANT_MASTER_DB','master_erahn')
];
+12
View File
@@ -0,0 +1,12 @@
<?php
return [
'whatsapp_cloud_enabled' => env('WHATSAPP_CLOUD_ENABLED', true),
'token' => env('WHATSAPP_TOKEN'),
'phone_number_id' => env('WHATSAPP_PHONE_NUMBER_ID'),
'api_version' => env('WHATSAPP_API_VERSION', 'v25.0'),
'template' => env('WHATSAPP_TEMPLATE', 'hello_world'),
'template_lang' => env('WHATSAPP_TEMPLATE_LANG', 'en_US'),
'verify_token' => env('WHATSAPP_VERIFY_TOKEN'),
'app_secret' => env('WHATSAPP_APP_SECRET'),
];
@@ -42,7 +42,7 @@ class AddColumnsToKomuditiTransaksi extends Migration
{
$online_arrahn_connection_array = array_filter(config('database.connections'), function($connection) {
if($connection['database'] === 'online_arrahn' || $connection['database'] === 'master_krk' || $connection['database'] === config('tennant_master.database')){
if($connection['database'] === config('tennant_database.master_db')){
return true;
}
@@ -53,7 +53,7 @@ class AddColumnsToKomuditiTransaksi extends Migration
if(empty($online_arrahn_connection_array)){
throw new Exception("online_arrahn or master_krk doesn't exist in config.database.connections array");
throw new Exception("Tennant master database (" . config('tennant_database.master_db') . ") doesn't exist in config.database.connections array");
}
return array_keys($online_arrahn_connection_array)[0];

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