Compare commits
22 Commits
feat/detai
...
feat/restr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
285e89d5d0 | ||
|
|
4c3443c2d6 | ||
|
|
df70a47bd1 | ||
|
|
e71dd7d213 | ||
|
|
f2eb998ac5 | ||
|
|
fc54e20fa4 | ||
|
|
6946fa7074 | ||
|
|
236b6f9bfc | ||
|
|
285ff46c2b | ||
|
|
a8b02afad9 | ||
|
|
a0666e78d2 | ||
|
|
799e409ce2 | ||
|
|
780ba60224 | ||
|
|
baed8cc487 | ||
|
|
e17f5beaf0 | ||
|
|
766e1a430c | ||
|
|
6677c320fc | ||
|
|
9437eb949f | ||
|
|
6f77120c33 | ||
|
|
f8d0573e5c | ||
|
|
ca74d0143f | ||
|
|
34e082c31b |
0
.editorconfig
Executable file → Normal file
0
.editorconfig
Executable file → Normal file
0
.env.example
Executable file → Normal file
0
.env.example
Executable file → Normal file
76
.env.local.backup
Normal file
76
.env.local.backup
Normal file
@@ -0,0 +1,76 @@
|
||||
APP_NAME=SIBEDAS-PBG
|
||||
APP_ENV=local
|
||||
APP_KEY=base64:xqCpwixWKqgu1Ca22gFizoOt44p7h+cgTOKuhS/P0Jw=
|
||||
APP_DEBUG=true
|
||||
APP_TIMEZONE=Asia/Jakarta
|
||||
APP_URL=http://localhost:8000
|
||||
|
||||
API_URL=http://localhost:8000
|
||||
|
||||
APP_LOCALE=en
|
||||
APP_FALLBACK_LOCALE=en
|
||||
APP_FAKER_LOCALE=en_US
|
||||
|
||||
APP_MAINTENANCE_DRIVER=file
|
||||
# APP_MAINTENANCE_STORE=database
|
||||
|
||||
PHP_CLI_SERVER_WORKERS=4
|
||||
|
||||
BCRYPT_ROUNDS=12
|
||||
|
||||
LOG_CHANNEL=stack
|
||||
LOG_STACK=single
|
||||
LOG_DEPRECATIONS_CHANNEL=null
|
||||
LOG_LEVEL=debug
|
||||
|
||||
DB_CONNECTION=mariadb
|
||||
DB_HOST=db
|
||||
DB_PORT=3306
|
||||
DB_DATABASE=sibedas_db
|
||||
DB_USERNAME=root
|
||||
DB_PASSWORD=root
|
||||
|
||||
SESSION_DRIVER=database
|
||||
SESSION_LIFETIME=120
|
||||
SESSION_ENCRYPT=false
|
||||
SESSION_PATH=/
|
||||
SESSION_DOMAIN=null
|
||||
|
||||
BROADCAST_CONNECTION=log
|
||||
FILESYSTEM_DISK=local
|
||||
QUEUE_CONNECTION=database
|
||||
|
||||
CACHE_STORE=database
|
||||
CACHE_PREFIX=
|
||||
|
||||
MEMCACHED_HOST=127.0.0.1
|
||||
|
||||
REDIS_CLIENT=phpredis
|
||||
REDIS_HOST=127.0.0.1
|
||||
REDIS_PASSWORD=null
|
||||
REDIS_PORT=6379
|
||||
|
||||
MAIL_MAILER=log
|
||||
MAIL_HOST=127.0.0.1
|
||||
MAIL_PORT=2525
|
||||
MAIL_USERNAME=null
|
||||
MAIL_PASSWORD=null
|
||||
MAIL_ENCRYPTION=null
|
||||
MAIL_FROM_ADDRESS="hello@example.com"
|
||||
MAIL_FROM_NAME="${APP_NAME}"
|
||||
|
||||
AWS_ACCESS_KEY_ID=
|
||||
AWS_SECRET_ACCESS_KEY=
|
||||
AWS_DEFAULT_REGION=us-east-1
|
||||
AWS_BUCKET=
|
||||
AWS_USE_PATH_STYLE_ENDPOINT=false
|
||||
|
||||
VITE_APP_NAME="${APP_NAME}"
|
||||
|
||||
SIMBG_HOST="https://simbg.pu.go.id/"
|
||||
SIMBG_EMAIL="dputr@bandungkab.go.id"
|
||||
SIMBG_PASSWORD="Simbg123"
|
||||
|
||||
API_KEY_GOOGLE="AIzaSyBxfEShFkKmykkc7RJR3lVzkQ_xGHK3qr0"
|
||||
SPREAD_SHEET_ID="1QoXzuLdEX3MK70Yrfigz0Qj5rAt4T819jX85vubBNdY"
|
||||
OPENAI_API_KEY="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"
|
||||
0
.gitattributes
vendored
Executable file → Normal file
0
.gitattributes
vendored
Executable file → Normal file
0
.gitignore
vendored
Executable file → Normal file
0
.gitignore
vendored
Executable file → Normal file
42
Data_2025___Estimasi_Jumlah_Lantai.csv
Executable file
42
Data_2025___Estimasi_Jumlah_Lantai.csv
Executable file
@@ -0,0 +1,42 @@
|
||||
no,pemohon,alamat,activities,luas_lahan,bcr_kawasan,area,no_tapak,no_skkl,no_ukl,fungsi_bangunan,sub_fungsi_bangunan,jumlah_lantai
|
||||
01/222,PT CIPTA INDAH PERTIWI No Telpon : 082217633434,Jl Salam No 51 Desa/Kel. Cihapit Kec. Bandung wetan Kota Bandung,PERUMAHAN LA LA TOWN,16629,0.2449,4072.4421,648.11./222 - SP /TR 07 Januari 2025;644.2./207 - SP /TR 02 Januari 2025,500.10.29.15/631-REKTEK/TR Tanggal 14 Februari 2023;500.10.29.15/2837-REKTEK/Bid. Taru Tanggal 25 Juli 2024,14022310313204030 Tanggal 14 Februari 2023;500.10.29.7/Kep.618-DLH/2024 Tanggal 13 Desember 2024;161023087545 Tanggal 16 Oktober2023,Fungsi Hunian,Fungsi Hunian,1
|
||||
03/481,MUHAMAD GUNTUR JULIADI No Telpon : 081223334486,Cangkuang Residence RT 004/003 Ds Cangkuang Wetan Kec. Dayeuhkolot Kab. Bandung,PERUMAHAN CANGKUANG RESIDENCE 2,2360,0.5924,1398.064,648.11./481 - SP /TR 15 Januari 2025;647./226 - SP /TR 07 Januari 2025;647/225 - SP /TR 07 Januari 2025;644/221 - SP /TR 07 Januari 2025;645.4/557 - SP /TR 20 Januari 2025,500.10.29.15/2894-REKTEK/Bid. Taru Tanggal 08 Agustus 2024;400.7.22/4927-KRK/04/Bid.Taru Tanggal 06 Desember 2024;500.10.29.15/2619-REKTEK/Bid. Taru Tanggal 10 Juli 2024;500.10.29.15/4155-REKTEK/Bid. Taru Tanggal 18 Nopember 2024;503/123/III-DPMPTSP/2020 Tanggal 24 MAret 2020,167240099471 Tanggal 16 Juli 2024;9120111161314 Tanggal 11 Nopember 2019;8120312190151 Tanggal 04 Desember 2023;500.10.29.7/Kep.614-DLH/2024 Tanggal 20 Nopember 2024;2910240290118 Tanggal 29 Oktober 2024;8120001992981 Tanggal 12 Juli 2024;SK.294/Menlhk/Setjen/PLA.4/5/2021 Tanggal 31 Mei 2021,Fungsi Hunian,Fungsi Hunian,1
|
||||
08/480,PT SURYA KHARISMA PARAHYANGAN No Telpon : 081394461006 ,Jalan Cibaligo No 48 Desa /Kel. Cigugur tengah Kec. Cimahi tengah kota Cimahi ,PERUMAHAN PARAHYANGAN GARDEN CITY,116360,0.5164,60088.304,648.11./480 - SP /TR 15 Januari 2025,500.10.29.15/603-REKTEK/TR Tanggal 05 Desember 2023,1810220245722 Tanggal 18 Oktober 2022;500.10.29.7/Kep.632-DLH/2024 Tanggal 24 Desember 2024,Fungsi Hunian,Fungsi Hunian,2
|
||||
09/223,PT CLARICHEM INDONESIA No Telpon : 081315399694,Kawasan pergudangan taman tekno sektor XI blok L2 No 30 Ds/Kel. Setu Kec. Setu kota Tangerag Banten ,PERGUDANGAN DAN PENYIMPANAN,1586,0.7017,1112.8962,644./223 - SP /TR 07 Januari 2025,400.7.22/4427-KRK/03/Bid.Taru Tanggal 06 Desember 2024,9120008832767 Tanggal 26 Nopember 2024,Usaha Besar (Non-Mikro),Fungsi Usaha,1
|
||||
10/657,PT HALKA GITA No Telpon : 0816734374,Jl Raya Majalaya KM 2 RT 02/09 Desa Hegarmanah Kec. Cikancung Kab. Bandung,REVISI 1 KOMPLEK PERGUDANGAN,19100,0.699,13350.9,644./657 - SP /TR 23 Januari 2025,500.10.29.15/6750-REKTEK/TR Tanggal 21 Desember 2023,9120300510839 Tanggal 16 Desember 2023;500.10.29.7/Kep.609-DLH/2024 Tanggal 12 Nopember 2024,Usaha Besar (Non-Mikro),Fungsi Usaha,2
|
||||
11/658,PT SURYA TIRTA KENCANA No Telpon : 08986155493,Kutawaringin Industrial Park Kav. 276-277 Desa Jelegong Kec. Kutawaringin Kab. Bandung,PERGUDANGAN DAN PENYIMPANAN BAHAN KIMIA,3932,0.6995,2750.434,644./658 - SP /TR 23 Januari 2025;640/540 - SP /TR 20 Januari 2025,10112410113204010 Tanggal 10 Nopember 2024;591.4/031/IX-DPMPTSP/2020 Tanggal 03 Sseptember 2020,8120017120594 Tanggal 31 Agustus 2018;600.4.5/056/6838-Ktr/TL Tanggal 24 Desember 2024;0220305221438 Tanggal 20 Nopember 2021;500.10.29.7/4295/TL Tanggal 21 September 2024,Usaha Besar (Non-Mikro),Fungsi Usaha,1
|
||||
13 / 550,PT BERKAH SEPUH JAYA No Telpon : 082118781624,Ruko komplek dewaadru blok R3 RT 005 ds/kec. Bojongsoang kab. Bandung,REVISI 1 PERUMAHAN VILLA CEMARA ASRI,17379,0.5476,9516.7404,648.11/550 - SP/TR 20 Januari 2025,150223101113204049 Tanggal 15 Februari 2023,1233000451657 Tanggal 5 Mei 2023;500.10.29.7/kep.392-DLH/2025 Tgl. 2 Januari 2025,Fungsi Hunian,Fungsi Hunian,2
|
||||
14 / 655,PT RISKI ANUGERAH SEDJAHTERA No Telpon : 082118781624,Griya permata asri blok C2 No 18 Ds Lengkong Kec. Bojongsoang Kab. Bandung,REVISI 1 PERUMAHAN TAMAN KATAPANG INDAH,28425,0.6532,18567.21,648.11 /655 - SP /TR 23 Januari 2025,500.10.29.15/2333-REKTEK/TR Tanggal 22 Mei 2024;500.10.29.15/3811-REKTEK/Bid. Taru Tanggal 10 Oktober 2024,1205000302875 Tanggal 02 April 2024;500.10.29.7/Kep.408-DLH/2025 Tanggal 10 Januari 2025,Fungsi Hunian,Fungsi Hunian,2
|
||||
15 / 551,PT BUMI SUWARNA SEJAHTERA No Telpon : 082118781624,Jl Ciwastra no 73 Kel. Mekarmulya kec. Rancasari kota bandung,REVISI 1 PERUMAHAN BUMI KARA RESIDENCE,29711,0.5424,16115.2464,648.11/551-SP/TR 20 Januari 2025,500.10.29.15/6097-REKTEK/TR Tanggal 27 Nopember 2023,1311230078306 Tgl 13 Nopember 2023;500.10.29.7/Kep.493-DLH/2024 Tanggal 20 Maret 2024,Fungsi Hunian,Fungsi Hunian,2
|
||||
16 / 656,PT AMBER HASYA No Telpon : 081312411661,Jl Sarijadi raya No 111 Kel. Sarijadi Kec. Sukasarii kota bandung,REVISI 1 PERUMAHAN GREEN HARMONI RESIDENCE ,39520,0.2726,10773.152,648.11/656-SP/TR 23 Januari 2025,500.10.29.15/2732-REKTEK/Bid. Taru Tanggal 19 Juli 2024,9120004882226 Tgl 22 Agustus 2019;500.10.29.7/Kep.617-DLH/2024 Tanggal 4 Desember 2024,Fungsi Hunian,Fungsi Hunian,2
|
||||
17 / 224,PT KARYA UTAMA SEJATI JAYA No Telpon : 081320772222,Kp. Andir DS/Kel. Manggahang Kec. Baleendah Kab. Bandung,PERUMAHAN GRAND CIPARAY RESIDENCE,12045,0.5945,7160.7525,648.11/224-SP/TR 07 Januari 2025;644/822-SP/TR 13 Februari 2025;640/524-SP/TR 17 Januari 2025;642/763-SP/TR 11 Februari 2025,500/5300-PERTEKTR/2022/TR Tanggal 04 Agustus 2022;400.7.22/4872-KRK/25/Bid.TR Tanggal 24 Desember 2024;Pu.650/7580-REKTEK/TR Tanggal 20 Desember 2022;01112410113204120 Tanggal 1 November 2024,1801220061544 Tgl 05 September 2022;600.4.5/052/1722-Ktr/TL Tanggal 20 Mei 2024;9120401971173 Tgl 11 Desember 2024;1406220076826 Tgl 15 November 2022;8120103802893 Tgl 29 Agustus 2018,Fungsi Hunian,Fungsi Hunian,2
|
||||
21 / 688,PT PRAKARSA FAJAR PROPERTINDO No Telpon : 082315094712,Jl. Otto Iskandardinata No 429 Kel. Pungkur Kec. Regol Kota Bandung,PERUMAHAN ALAM BUMI CIPARAY,17417,0.6793,11831.3681,648.11/688-SP/TR 03 Februari 2025,500.10.29.15/1075-REKTEK/TR Tanggal 04 Maret 2024,9120007841259 Tgl 15 Agustus 2019;500.10.29.7/Kep.405-DLH/2025 Tanggal 06 Januari 2025,Fungsi Hunian,Fungsi Hunian,2
|
||||
22 / 767,PT CITRA SENTOSA JAYATAMA No Telpon : 087825393706,Jl. Nyengseret No 29 RT 01 Kel. Pelindung hewaqn Kota Bandung,PERUMAHAN CITRA ASRI RESIDENCE,28900,0.692,19998.8,648.11/767-SP/TR 12 Februari 2025,500.10.29.15/2617-REKTEK/Bid. Taru Tanggal 10 Juli 2024,9120107971097 Tgl 19 September 2019;500.10.29.7/Kep.612-DLH/2024 Tanggal 18 November 2024,Fungsi Hunian,Fungsi Hunian,2
|
||||
23/ 641,PT PESONA MITRA KEMBAR MAS No Telpon : 081220763283,Jl Kembar mas IV No 2A RT 05/09 Kel. Pasirluyu Kec. Regol Kota Bandung,REVISI 5 PERUMAHAN PODOMORO PARK,1160011,0.6691,776163.3601,648.11/641- SP/TR 22 Januari 2025,591/057-DPMPTSP/2017 Tanggal 28 Desember 2017,650/Kep.628-DLH/2018 Tanggal 30 Nopember 2018,Fungsi Hunian,Fungsi Hunian,2
|
||||
24 / 659,PT PAVITRA PARA ARTHA No Telpon : 081809063866,Jl . Kubang Beureum No 39 RT 007 / 011 Kelurahan Sekejati Kecamatan Buahbatu,REVISI 4 PERUMAHAN PRIVATE VILLAGE,88245,0.5819,51349.7655,648.11/659 - SP/TR 23 Januari 2025;645/932 - SP/TR 21 Februari 2025;645/552-SP/TR 20 Januari 2025;647/640-SP/TR 22 Januari 2025;647/930-SP/TR 21 Februari 2025,591.4/001-BPMP/2014 Tanggal 06 Januari 2014;591.4/031-DPMPTSP/2019 Tanggal 10 Juli 2019;503/104/XII-DPMPTSP/2019- Tgl 20 Desember 2019;400.1.22/4521-KRK/15/Bid.Taru Tgl 16 Desember 2024;650/2597-KRTR/2021/TR Tgl 9 Desember 2021;500.10.29.15/2839-REKTEK/Bid.TAru Tgl 2 Agustus 2024,500.10.29.6/4898/TL Tanggal 08 Nopember 2023;667/2995/TL Tanggal 11 Agustus 2020;9120000431996 Tanggal 20 Oktober 2021;500.10.29.7/Kep.629-DLH/2024;9120605793306 Tanggal 9 Mei 2023;1601240112040181 Tanggal 16 Januari 2024;8120215192631 Tanggal 3 Januari 2022;34/LH.01.06.05/DLH Tanggal 3 Januari 2025;9120101841825 Tanggal 18 Oktober 2023,Fungsi Hunian,Fungsi Hunian,2
|
||||
29 / 931,PT KARYA HAFANA INTAN MANDIRI No Telpon : 082120002925 ,Kp./Desa Ciluluk Kecamatan Cikancung Kabupaten Bandung,PERUMAHAN GRIYA CIHANYIR PERMAI,24432.33,0.5945,14525.02019,648.11/931 - SP/TR 21 Februari 2025,400.7.22/4840-KRK/21/Bid.Taru Tgl. 18 Desember 2024,2908210007625 Tanggal 06 Mei 2024;500.10.29.7/Kep.414-DLH/2025 Tanggal 30 Januari 2025,Fungsi Hunian,Fungsi Hunian,2
|
||||
30 / 933,PT ARUM JAYA PROPERTI No Telpon : 085100942672,Perumahan Banyu Arum Residence Blok A Kp. Tegal Tengah RT 003/013 Desa Cangkuang Kec. Rancaekek,REVISI 1 PERUMAHAN BUMI ARUM REGENCY,58899,0.4838,28495.3362,648.11/933 - SP/PR 21 FEBRUARI 2025,591.4/026-DPMPTSP/2018 Tgl 24 September 2018,667/7210/TL Tanggal 16 Nopember 2018;500.10.29.6/Kep.420-DLH/2025 Tanmggal 3 Februari 2025,Fungsi Hunian,Fungsi Hunian,2
|
||||
31 / 762,SANDY SALMAN No Telpon : 082118781624,Jl Kembar VIII No 26 RT 005/010 Kel Cigereleng Kec. Regol Kota Bandung 40253,REVISI 1 PERUMAHAN D HEUVEL WIWAHA PADASUKA,3095,0.4295,1329.3025,648.11/ 762 - SP / PR 11 Februari 2025;648/523-SP/TR 17 Januari 2025;647/1140-SP/TR 27 Februari 2025;647/934-SP/TR 21 Februari 2025;647/1233-SP/TR 06 Maret 2025,"650/5689-PETEKTR/2022/TR Tanggal 30 Agustus 2022;650/2748-KRTR/2021/TR Tgl 21 Desember 2021;593.SK.93-BKPMD/90 Tanggal 11 April 1990;593/SK.225-BKPMD/1991 Tanggal 28 Juni 1991;591,4/009-BPMP/2014 Tanggal 27 Maret 2014;500.10.29.15/711-REKTEK/TR Tanggal 15 Februari 2024;500.10.29.15/2877-REKTEK/Bid.Taru Tanggal 19 Agustus 2024 ",LH.01.04/5866-SPPL/TL Tanggal 27 Desember 2022;1402220064386 Tanggal 04 Februari 2022;500.10.29.7/Kep.593-DLH/2024 Tanggal 27 September 2024;667/3257/BPLH Tanggal 06 Oktober 2014;2711230388253 Tanggal 27 Nopember 2023;500.10.29.7/Kep.630-DLH/2024 Tanggal 20 Desember 2024;9120312050674 Tanggal 07 Oktober 2019;500.10.29.7/Kep.426-DLH/2025 Tanggal 14 Februari 2025,Fungsi Hunian,Fungsi Hunian,1
|
||||
36/ 1141,HANDRIAWAN No Telpon : 081313350382,Jalan Taman Holis Indah E-5 No 4-5 RT 03/06 Ds/Kel. Cigondewah Kec. Bandung kulon. Kota Bandung,TOKO DAN GUDANG,2025,0.7848,1589.22,647/1141-SP/TR 27 Februari 2025;647/1211-SP/TR 04 Maret 2025;648.2/1091-SP/TR 25 Februari 2025,400.7.22/559-KRK/14/Bid.Taru Tanggal 17 Januari 2025;400.7.22/859-KRK/38/Bid.Taru Tanggal 17 Februari 2025;500.10.29.15/4064 - REKTEK/Bid.Taru Tanggal 25 Nopember 2024,1401250067962 Tanggal 14 Januari 2025;1812240057832 Tanggal 18 Desember 2024;1611210013009 Tanggal 16 Nopember 2021,Usaha Besar (Non-Mikro),Fungsi Usaha,1
|
||||
39/ 1232,JOHANSJAH SUGIANTO No Telpon : 081314974495,Jalan Kepodang VI/6 blok k2 Rengas Kec. Ciputat timur kota Tangerang selatan,TOKO,2490,21.9,54531,644.2/1232-SP/TR 06 Maret 2025;644.2/1234-SP/TR 06 Maret 2025,400.7.22/1089 - KRK/45//Bid.Taru Tanggal 24 Februari 2025;503/0017-PKKPRNB/DPMPTSP/II/2025 Tanggal 12 Februari 2025,3101250002573 Tanggal 31 Januari 2025;2107230112343 Tanggal 21 Juli 2023,Tidak Diketahui,Tidak Diketahui,3
|
||||
41 / 1090,TIM AD HOC PERUMAHAN SUKAWANGI RESIDENCE No Telpon : 08112255770,Kp. Cihalimun RT 02/04 Ds. Cibeureum Kec. Kertasari Kab. Bandung,PERUMAHAN SUKAWANGI RESIDENCE,,1,,648.11/ 1090 - SP / PR 25 Februari 2025,,,Fungsi Hunian,Fungsi Hunian,2
|
||||
42 / 1298,PT TIGA REKAN INDONESIA No Telpon : 08122444717,Jl Gradiul No 40 RT 04/07 Kel. Rancaekek Kencana Kec. Rancaekek Kab. Bandung,PERUMAHAN GREEN HILL VILLAGE,16548,0.5917,9791.4516,648.11/1298 - SP/TR 17 Maret 2025,50.10.29.15/2742-REKTEK/Bid. Taru Tanggal 24 Juli 2024,2505240130006 Tanggal 25 Mei 2024;600.4.5/061/832-Ktr/Bid. TL/2025 Tgl. 19 Februari 2025,Fungsi Hunian,Fungsi Hunian,2
|
||||
43 / 1322,PT UNILOA ARDIYANTO INVESTAMA No Telpon : 081220180480,Jl Raya Ebah 103 Desa Sukamantri Kecamatan Paseh Kab. Bandung,PERUMAHAN MARISON CIPAKU,19209,0.6146,11805.8514,648.11/1322 - SP/TR 18 Maret 2025,24042410313204051 Tanggal 24 JApril 2024,0238010110358 Tanggal 11 Januari 2024;500.10.29.7/Kep. 451-DLH/2025 Tgl. 13 Maret 2025,Fungsi Hunian,Fungsi Hunian,2
|
||||
44 / 1323,PT RADINAKA KRAMAT ABADI No Telpon : 082118781624,Jl. Sukamukti RT 01/06 Desa Sukamukti Kec. Katapang Kab. Bandung,PERUMAHAN GAHARU PALEDANG RESIDENCE,14665,0.68003,9972.63995,648.11/1323 - SP/TR 18 Maret 2025,02032510213204087 Tanggal 02 Maret 2025,0238010110358 Tanggal 11 Januari 2024;600.4.5/064/1165-Ktr/Bid. TL/2025 Tgl. 12 Maret 2025,Fungsi Hunian,Fungsi Hunian,2
|
||||
45 / 1361,PT SANGKURIANG KRAMAT ABADI No Telpon : 082118781624,Kampung rancakasiat Desa Rancamulya Kec. Pameungpeuk Kab. Bandung,PERUMAHAN PONDOK ASRI SUKAMUKTI,20451,0.6806,13918.9506,648.11/1361 - SP/TR 24 Maret 2025,500.10.29.15/3451-REKTEK/Bid.Taru Tanggal 26 September 2024,0811230037204 Tanggal 08 Nopember 2023;500.10.29.7/Kep.460-DLH/2025 Tgl. 21 Maret 2025,Fungsi Hunian,Fungsi Hunian,2
|
||||
46 / 1360,PT AUF ABDURACCHMAN JAYA No Telpon : 082118781624,Jl. Rancabungur rancakasiat Desa Malakasari Kec. Baleendah Kab. Bandung,PERUMAHAN BUMI SHANGRILA RESIDENCE,12846,0.6412,8236.8552,648.11/1360 - SP/TR 24 Maret 2025,400.7.22/093/1390-KRK/Bid.Taru Tanggal 17 Maret 2025,0603250028082 Tanggal 06 Maret 2025;600.4.5/072/1270-Ktr/Bid.TL/2025 Tgl. 21 Maret 2025,Fungsi Hunian,Fungsi Hunian,2
|
||||
47 / 1142,ANTO DWI HARTANTO No Telpon : 082126215611,Jl. Elang V No 8 RT 08/01 Kel. Garuda Kec. Andir Kota Bandung,PERGUDANGAN DAN PENYIMPANAN,1307,0.5616,734.0112,647/1142 - SP/TR 27 Februari 2025;648.11/823 - SP/TR 13 Februari 2025;644.2/1389 - SP/TR 26 Maret 2025;644.2/1388 - SP/TR 26 Maret 2025,500.10.29.15/3827-REKTEK/Bid.Taru Tanggal 17 Oktober 2024;500.10.29.15/3944-REKTEK/Bid.Taru Tanggal 28 Oktober 2024;503/0072-DPMPTSP/XI/2023 Tanggal 29 November 2023;400.7.22/1046-KRK/Bid.Taru Tanggal 21 Februari 2025,2009240075774 Tanggal 20 September 2024 2025;2009240075774 Tanggal 20 September 2025;500.10.29.7/Hep-461-DLH/2025 Tanggal 21 Maret 2025;0298000921888 Tanggal 24 Januari 2022,Usaha Besar (Non-Mikro),Fungsi Usaha,1
|
||||
51 / 1161,PT INGRIA PRATAMA CAPITALINDO Tbk No Telpon : 087722361043,Ruko Pondok Cabe Mutiara Jl Pondok cabe No 27 Kel. Pamulang Kec. Pamulang Kota Tangerang Selatan,REVISI 2 PERUMAHAN BUKIT ESMA CICALENGKA 2 ,74300,0.6292,46749.56,648.11/1161 - SP/TR 28 Februari 2025,50.10.29.15/2927-REKTEK/Bid. Taru Tanggal 14 Agustus 2024,591.4/005/IX-DPMPTSP/2019 Tanggal 09 September 2019;500.10.29.7/Kep.613-DLH/2024 Tgl. 18 November 2025,Fungsi Hunian,Fungsi Hunian,2
|
||||
52 / 1441 ,PT ABADI MUKTI KIRANA No Telpon : 087821848944,Jl Terusan jamika No 88 Kel. Jamika Kec. Bojongloa kaler Kota Bandung, PERUMAHAN KOTA BARU ARJASARI,222816,1,222816,648.11/1441 - SP/TR 11 April 2025,400.7.28/1710/TR Tanggal 23 April 2024,9120108201236 Tanggal 13 Februari 2019;500.10.29.7/Kep.419-DLH/2025 Tgl. 03 Februari 2025,Fungsi Hunian,Fungsi Hunian,2
|
||||
53 / 1566,PT MAKMUR INDAH DAMAI No Telpon : 081394722234,Jl. Podomoro Boulevard utara I Desa Lengkong Kec. Bojongsoang Kab. Bandung,PERUMAHAN BAROS INDAH RESIDENCE,17513,1,17513,647/1566 - SP/TR 25 April 2025,50.10.29.15/2507-REKTEK/Bid. Taru Tanggal 25 Juni 2024,2106240023693 Tanggal 21 Juni 2024;500.10.29.7/Kep.447-DLH/2024 Tgl. 10 Maret 2025,Fungsi Hunian,Fungsi Hunian,2
|
||||
54 / 1343,PT KARYA BUMI BESTARI No Telpon : 08156080428,Kembar Mas IV No 2A RT 05/09 Kel. Pasirluyu Kec. Regol Kota Bandung,PERUMAHAN BESTARI MAS,26134,1,26134,648.11/1343 - SP/TR 20 Maret 2025;647/1565 - SP/TR 25 April 2025;648.1/1543 - SP/TR 17 April 2025,12042310213204004 Tanggal 12 April 2023;23122410113204057 Tanggal 23 Desember 2024;400.7.22/1047-KRK/42/Bid.Taru Tanggal 21 Februari 2025,1204230043635 Tanggal 12 April 2023;500.10.29.7/964/Bid. TL/2025 Tgl. 19 Februari 2025;9120304390956 Tanggal 05 Desember 2023;0307230050445 Tanggal 03 Juli 2023,Fungsi Hunian,Fungsi Hunian,2
|
||||
57 / 1372,TENNI DIANA No Telpon : 082116106640,Cikaahuripan RT 002/005 Kec. Neglasari Kota Tangerang,PERADAGANGAN KHUSUS KARPET PERMADANI DAN PENUTUP DINDING DN LANTAI DI TOKO,1290,1,1290,644/1372 - SP/TR 26 Maret 2025;647/1557 - SP/TR 22 April 2025;640/1559 - SP/TR 22 April 2025;644/1371 - SP/TR 26 Maret 2025;644/1375 - SP/TR 26 Maret 2025;644/1373 - SP/TR 26 Maret 2025,500.10.29.15/4355-REKTEK/TR Tanggal 25 Agustus 2023;500.10.29.15/1073-REKTEK/TR Tanggal 04 Maret 2024;400.7.22/759-KRK/34/Bid.Taru Tanggal 11 Februari 2025;500.10.29.15/4654-REKTEK/Bid. Taru Tanggal 08 September 2023;500.10.29.15/4160-REKTEK/Bid. Taru Tanggal 15 Nopember 2024;500.10.29.15/2500-REKTEK/Bid. Taru Tanggal 19 Juni 2024,9120306652149 Tanggal 26 Juni 2019;0220107462528 Tanggal 08 September 2022;1283000240318 Tanggal 01 Februari 2021;600.4.5/071-KTR/Bid.TL/2025 Tgl. 05 Mei 2025;0220301251224 Tanggal 11 Agustus 2023;1263000210596 Tanggal 09 Februari 2021;1405240295325 5 Mei 2024,Tidak Diketahui,Tidak Diketahui,1
|
||||
63 / 1567,Ir. BERSIH TARIGANT No Telpon : 081322777581,Jl Multatuli No 3 rt 001/001 Ds/Kel. Lebakgede Kec. Coblong Kota Bandung,PERUMAHAN TUSCANY HILL,23796,0.2428,5777.6688,648.11/1567 - SP/TR 25 April 2025,50.10.29.15/3829-REKTEK/Bid. Taru Tanggal 15 Oktober 2024,2007220014068 Tanggal 20 Juli 2025;500.10.29.7/Kep.429-DLH/2025 Tgl. 10 Maret 2025,Fungsi Hunian,Fungsi Hunian,2
|
||||
64 / 1682,PT CIPTA BERKAT PROPERTI No Telpon : 081910661980,Ruko Matahari Cigado No 25 Jalan Anggadireja Kel. Baleendah Kec. Baleendah Kab. Bandung,PERUMAHAN GARDEN VIEW CICALENGKA,45666,1,45666,648.11/1682 - SP/TR 06 Mei 2025,50.10.29.15/3156-REKTEK/Bid. Taru Tanggal 05 September 2024,2310210017936 Tanggal 23 Oktober 2021;500.10.29.7/Kep.477-DLH/2025 Tgl. 17 April 2025,Fungsi Hunian,Fungsi Hunian,2
|
||||
64 / 1682,PT CIPTA BERKAT PROPERTI No Telpon : 081910661980,Ruko Matahari Cigado No 25 Jalan Anggadireja Kel. Baleendah Kec. Baleendah Kab. Bandung,C. KWT PERUMAHAN,0,1,0,,,,Fungsi Hunian,Fungsi Hunian,1
|
||||
65 / 1644,PT ARGUNA JAYA PROPERTY No Telpon : 081214211164,Jl Pidada IV/4 Denpasar BR/Link Sedana metra Kel. Ubung Kec. Denpasar Utara Kota Denpasar,REVISI 1 PERUMAHAN ARGUNA SINDANGPANON,8553,0.53,4533.09,648.11/1644 - SP/TR 29 April 2025;644/1558 - SP/TR 22 April 2025,503/009/II-DPMPTSP/2020 Tanggal 25 Februari 2020;50.10.29.15/4063-REKTEK/Bid. Taru Tanggal 25 Nopember 2025,9120004890734 Tanggal 03 Agustus 2019;667/2081/TL Tgl. 08 Juni 2020;0609240042720001 Tanggal 06 September 2024,Fungsi Hunian,Fungsi Hunian,1
|
||||
67/ 1370,NENENG FATIMAH No Telpon : 08977980040,Jalan Batu Indah I No 26 RT 002/003 Kel. Batununggal Kec. Bandung Kidul Kota Bandung,PERGUDANGAN DAN PENYIMPANAN,2034,1,2034,644/1370 - SP/TR 26 Maret 2025,50.10.29.15/3340-REKTEK/Bid. Taru Tanggal 18 September 2025,1009240038601 Tanggal 10 September 2024,Usaha Besar (Non-Mikro),Fungsi Usaha,1
|
||||
68/ 1374,LILI JOJON No Telpon : 082219855556,Kp. Cibisoro RT 004/008 Desa Nanjung Kec. Mrgaasih Kab. Bandung,PERGUDANGAN DAN PENYIMPANAN,1654,1,1654,644/1374 - SP/TR 26 Maret 2025;644.2/1683 - SP/TR 06 Mei 2025;645/1730 - SP/TR 07 Mei 2025,50.10.29.15/3210-REKTEK/Bid. Taru Tanggal 10 September 2024;400.7.22/1254-KRK/58/Bid. Taru Tanggal 10 Maret 2025;50.10.29.15/3443-REKTEK/Bid. Taru Tanggal 26 September 2024,1220000361065 Tanggal 16 Maret 2021;1712240056863 Tanggal 17 Desember 2024;0103230081544 Tanggal 01 Maret 2023;600.4.5/071/1503-Ktr/Bid. TL/2025 Tanggal 17 April 2025,Usaha Besar (Non-Mikro),Fungsi Usaha,1
|
||||
71/ 1845,PT SANGKURIANG KRAMAT ABADI No Telpon : 082116602145,Kp. Rancakasiat Desa Rancamulya Kec. Pameungpeuk Kab. Bandung,PERUMAHAN CLUSTER GAHARU EMERALD,19889,1,19889,648.11 /1845 - SP/TR 19 Mei 2025,400.7.22/1526-KRK/96/Bid. Taru Tanggal 22 April 2025,0811230037204 Tanggal 08 Nopember 2023;500.10.29.7/Kep. 489-DLH/2025 Tanggal 15 Mei 2025,Fungsi Hunian,Fungsi Hunian,2
|
||||
72/ 1731,PT MARGA TIRTA KENCANA No Telpon : 082130000146,Jlan BKR Lingkar selatan No 140 Kota Bandung,PERUMAHAN TAMAN CIBADUYUT INDAH 2 DAN 3,205344,1,205344,648.11 /1731 - SP/TR 07 Mei 2025;648.11 /1946 - SP/TR 22 Mei 2025;643 /1881 - SP/TR 20 Mei 2025;644 /1684 - SP/TR 06 Mei 2025;648.12 /1825 - SP/TR 16 Mei 2025,591.4/002-BPMP/2010 Tanggal 10 Januari 2010;500.10.29.15/3108-REKTEK/Bid.taru Tanggal 29 Agustus 2024;500.10.29.15/3319-REKTEK/TR Tanggal 14 Juli 2023;12092410313204101 Tanggal 12 September 2024;400.7.22/1231-KRK/Bid.Taru Tanggal 06 Maret 2025,667/3803/DLH Tanggal 22 Desember 2016;9120204202309 Tanggal 20 Februari 2019;2002230028317 Tanggal 20 Februari 2023;600.4.5/073/1562-Ktr/Bid.TL/2025 Tanggal 23 April 2025;8120006931402 Tanggal 28 September 2022;12710006324580004 Tanggal 05 Maret 2025,Fungsi Hunian,Fungsi Hunian,2
|
||||
77/ 1879,PT PUTRA RAHMAN PRADANA No Telpon : 081312214962,Perum Cemerlang Permai Blok C No 16 Kel. Sukakarya Kec. Warudoyongf Kota Sukabumi,REVISI 1 PERUMAHAN BUKIT PINUS BANIARAN,90711,1,90711,648.11 /1879 - SP/TR 20 Mei 2025,22122310313204009 Tanggal 22 Desember 2023,500.10.29.7/Kep.482-DLH/2025 Tanggal 24 April 2025,Fungsi Hunian,Fungsi Hunian,2
|
||||
|
56
Dockerfile
Normal file
56
Dockerfile
Normal file
@@ -0,0 +1,56 @@
|
||||
FROM node:18 AS node-base
|
||||
|
||||
# Development stage
|
||||
FROM node-base AS development
|
||||
WORKDIR /var/www
|
||||
COPY package*.json ./
|
||||
RUN npm install
|
||||
COPY . .
|
||||
EXPOSE 5173
|
||||
CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"]
|
||||
|
||||
# Production stage
|
||||
FROM php:8.2-fpm AS production
|
||||
WORKDIR /var/www
|
||||
|
||||
# Install PHP extensions
|
||||
RUN apt-get update && apt-get install -y \
|
||||
git curl zip unzip libpng-dev libonig-dev libxml2-dev libzip-dev \
|
||||
&& docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd zip
|
||||
|
||||
# Install Node.js
|
||||
RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \
|
||||
&& apt-get install -y nodejs
|
||||
|
||||
# Install Composer
|
||||
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
|
||||
|
||||
# Copy application files
|
||||
COPY . .
|
||||
|
||||
# Install dependencies
|
||||
RUN composer install --no-dev --optimize-autoloader
|
||||
|
||||
# Install and build frontend assets
|
||||
RUN npm install \
|
||||
&& npm run build \
|
||||
&& ls -la public/build \
|
||||
&& mkdir -p public/assets \
|
||||
&& cp -r public/build/* public/assets/ \
|
||||
&& ls -la public/assets \
|
||||
&& rm -rf node_modules \
|
||||
&& rm -rf public/build
|
||||
|
||||
# Laravel caches
|
||||
RUN php artisan config:clear \
|
||||
&& php artisan route:clear \
|
||||
&& php artisan view:clear \
|
||||
&& php artisan optimize
|
||||
|
||||
RUN php artisan storage:link
|
||||
|
||||
# Permissions
|
||||
RUN chown -R www-data:www-data /var/www && chmod -R 755 /var/www/storage /var/www/public
|
||||
|
||||
EXPOSE 9000
|
||||
CMD ["php-fpm"]
|
||||
210
OPTIMIZED_TABLE_STRUCTURE.md
Normal file
210
OPTIMIZED_TABLE_STRUCTURE.md
Normal file
@@ -0,0 +1,210 @@
|
||||
# Struktur Tabel Retribusi PBG yang Dioptimalkan
|
||||
|
||||
## Ringkasan Optimasi
|
||||
|
||||
Struktur tabel baru ini **lebih sederhana**, **fokus pada perhitungan**, dan **menghilangkan redundansi** dari struktur sebelumnya.
|
||||
|
||||
## Perbandingan Struktur
|
||||
|
||||
### SEBELUM (Kompleks)
|
||||
|
||||
- `building_functions` - 8 kolom + relationship kompleks
|
||||
- `building_function_parameters` - 12 kolom dengan mismatch model/migration
|
||||
- `retribution_formulas` - Menyimpan formula sebagai string
|
||||
- `retribution_proposals` - 15+ kolom dengan banyak redundansi
|
||||
- `floor_height_indices` - OK, tidak berubah
|
||||
|
||||
### SESUDAH (Sederhana)
|
||||
|
||||
- `building_types` - **7 kolom**, hierarki sederhana
|
||||
- `retribution_indices` - **6 kolom**, parameter calculation saja
|
||||
- `height_indices` - **3 kolom**, sama seperti sebelumnya
|
||||
- `retribution_configs` - **5 kolom**, konfigurasi global
|
||||
- `retribution_calculations` - **8 kolom**, hasil perhitungan saja
|
||||
|
||||
---
|
||||
|
||||
## Detail Struktur Tabel Baru
|
||||
|
||||
### 1. `building_types`
|
||||
|
||||
**Fungsi:** Menyimpan jenis fungsi bangunan dengan hierarki sederhana
|
||||
|
||||
| Kolom | Tipe | Keterangan |
|
||||
| ------------- | ------------ | ---------------------------------- |
|
||||
| `id` | bigint | Primary key |
|
||||
| `code` | varchar(10) | Kode unik (UMKM, KEAGAMAAN, dll) |
|
||||
| `name` | varchar(100) | Nama fungsi bangunan |
|
||||
| `parent_id` | bigint | ID parent (untuk hierarki) |
|
||||
| `level` | tinyint | Level hierarki (1=parent, 2=child) |
|
||||
| `coefficient` | decimal(8,4) | **Koefisien untuk perhitungan** |
|
||||
| `is_free` | boolean | **Apakah gratis (keagamaan, MBR)** |
|
||||
|
||||
### 2. `retribution_indices`
|
||||
|
||||
**Fungsi:** Menyimpan parameter indeks untuk perhitungan (1:1 dengan building_types)
|
||||
|
||||
| Kolom | Tipe | Keterangan |
|
||||
| ----------------------- | ------------ | ---------------------------------- |
|
||||
| `id` | bigint | Primary key |
|
||||
| `building_type_id` | bigint | FK ke building_types |
|
||||
| `ip_permanent` | decimal(8,4) | **Indeks Permanensi** |
|
||||
| `ip_complexity` | decimal(8,4) | **Indeks Kompleksitas** |
|
||||
| `locality_index` | decimal(8,4) | **Indeks Lokalitas** |
|
||||
| `infrastructure_factor` | decimal(8,4) | **Faktor prasarana (default 50%)** |
|
||||
|
||||
### 3. `height_indices`
|
||||
|
||||
**Fungsi:** Indeks ketinggian per lantai (sama seperti sebelumnya)
|
||||
|
||||
| Kolom | Tipe | Keterangan |
|
||||
| -------------- | ------------ | ---------------------------------------- |
|
||||
| `id` | bigint | Primary key |
|
||||
| `floor_number` | tinyint | Nomor lantai (1,2,3,4,5,6) |
|
||||
| `height_index` | decimal(8,6) | **IP Ketinggian (1.0, 1.09, 1.12, dst)** |
|
||||
|
||||
### 4. `retribution_configs`
|
||||
|
||||
**Fungsi:** Konfigurasi global untuk perhitungan (menggantikan hard-coded values)
|
||||
|
||||
| Kolom | Tipe | Keterangan |
|
||||
| ------------- | ------------- | --------------------- |
|
||||
| `id` | bigint | Primary key |
|
||||
| `key` | varchar(50) | Kunci konfigurasi |
|
||||
| `value` | decimal(15,2) | **Nilai konfigurasi** |
|
||||
| `description` | varchar(200) | Deskripsi |
|
||||
|
||||
**Data yang disimpan:**
|
||||
|
||||
- `BASE_VALUE` = 70350 (nilai dasar)
|
||||
- `INFRASTRUCTURE_MULTIPLIER` = 0.5 (50% prasarana)
|
||||
- `HEIGHT_MULTIPLIER` = 0.5 (pengali indeks ketinggian)
|
||||
|
||||
### 5. `retribution_calculations`
|
||||
|
||||
**Fungsi:** Hasil perhitungan retribusi (history)
|
||||
|
||||
| Kolom | Tipe | Keterangan |
|
||||
| -------------------- | ------------- | -------------------------------- |
|
||||
| `id` | bigint | Primary key |
|
||||
| `calculation_id` | varchar(20) | ID unik perhitungan |
|
||||
| `building_type_id` | bigint | FK ke building_types |
|
||||
| `floor_number` | tinyint | Lantai yang dipilih |
|
||||
| `building_area` | decimal(12,2) | **Luas bangunan input** |
|
||||
| `retribution_amount` | decimal(15,2) | **Hasil perhitungan** |
|
||||
| `calculation_detail` | json | **Detail breakdown perhitungan** |
|
||||
| `calculated_at` | timestamp | Waktu perhitungan |
|
||||
|
||||
---
|
||||
|
||||
## Formula Perhitungan
|
||||
|
||||
### Formula Excel yang Diimplementasikan:
|
||||
|
||||
```
|
||||
H13 = coefficient * (ip_permanent + ip_complexity + (0.5 * height_index))
|
||||
|
||||
Main Calculation = building_area * (locality_index * BASE_VALUE * H13)
|
||||
|
||||
Infrastructure = INFRASTRUCTURE_MULTIPLIER * Main Calculation
|
||||
|
||||
Total Retribution = Main Calculation + Infrastructure
|
||||
```
|
||||
|
||||
### Implementasi dalam Service:
|
||||
|
||||
```php
|
||||
// Step 1: Calculate H13 coefficient
|
||||
$h13 = $buildingType->coefficient * (
|
||||
$indices->ip_permanent +
|
||||
$indices->ip_complexity +
|
||||
(0.5 * $heightIndex)
|
||||
);
|
||||
|
||||
// Step 2: Main calculation
|
||||
$mainCalculation = $buildingArea * ($indices->locality_index * $baseValue * $h13);
|
||||
|
||||
// Step 3: Infrastructure (50% additional)
|
||||
$infrastructureCalculation = 0.5 * $mainCalculation;
|
||||
|
||||
// Step 4: Total
|
||||
$totalRetribution = $mainCalculation + $infrastructureCalculation;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Keuntungan Struktur Baru
|
||||
|
||||
### ✅ **Simplicity**
|
||||
|
||||
- **5 tabel** vs 8+ tabel sebelumnya
|
||||
- **Kolom minimal** hanya yang diperlukan untuk perhitungan
|
||||
- **No redundant data** seperti ip_ketinggian di proposals
|
||||
|
||||
### ✅ **Performance**
|
||||
|
||||
- **Proper indexes** untuk query yang sering digunakan
|
||||
- **Normalized structure** mengurangi storage
|
||||
- **Cached configs** untuk values yang jarang berubah
|
||||
|
||||
### ✅ **Maintainability**
|
||||
|
||||
- **Clear separation** antara master data dan calculation results
|
||||
- **Configurable values** tidak hard-coded lagi
|
||||
- **Single responsibility** setiap tabel punya tujuan jelas
|
||||
|
||||
### ✅ **Flexibility**
|
||||
|
||||
- **Easy to extend** untuk fungsi bangunan baru
|
||||
- **Configurable formulas** lewat RetributionConfig
|
||||
- **Audit trail** lewat calculation history
|
||||
|
||||
### ✅ **Data Integrity**
|
||||
|
||||
- **Proper constraints** untuk validasi data
|
||||
- **Foreign key relationships** yang benar
|
||||
- **No model-migration mismatch**
|
||||
|
||||
---
|
||||
|
||||
## Migration Guide
|
||||
|
||||
### Langkah Implementasi:
|
||||
|
||||
1. **Run Migration:** `php artisan migrate` untuk tabel baru
|
||||
2. **Seed Data:** Data master berdasarkan Excel akan otomatis ter-seed
|
||||
3. **Update Code:** Ganti penggunaan model lama dengan model baru
|
||||
4. **Test Calculation:** Verifikasi hasil perhitungan sama dengan Excel
|
||||
5. **Deploy:** Struktur siap production
|
||||
|
||||
### Data Migration (Optional):
|
||||
|
||||
Jika ada data existing di tabel lama yang perlu dipindahkan, buat script migration untuk transfer data dari struktur lama ke struktur baru.
|
||||
|
||||
---
|
||||
|
||||
## Usage Example
|
||||
|
||||
```php
|
||||
// Initialize service
|
||||
$calculator = new RetributionCalculatorService();
|
||||
|
||||
// Calculate retribution
|
||||
$result = $calculator->calculate(
|
||||
buildingTypeId: 8, // UMKM
|
||||
floorNumber: 2, // 2 lantai
|
||||
buildingArea: 100.50, // 100.5 m2
|
||||
saveResult: true // Simpan ke database
|
||||
);
|
||||
|
||||
// Result structure
|
||||
[
|
||||
'building_type' => [...],
|
||||
'total_retribution' => 31658.25,
|
||||
'formatted_amount' => 'Rp 31,658.25',
|
||||
'calculation_steps' => [...],
|
||||
'calculation_id' => 'RTB-20250130140530-123'
|
||||
]
|
||||
```
|
||||
|
||||
Struktur ini **jauh lebih clean**, **mudah dipahami**, dan **optimal untuk perhitungan retribusi PBG**!
|
||||
467
app/Console/Commands/AssignSpatialPlanningsToCalculation.php
Normal file
467
app/Console/Commands/AssignSpatialPlanningsToCalculation.php
Normal file
@@ -0,0 +1,467 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\SpatialPlanning;
|
||||
use App\Models\RetributionCalculation;
|
||||
use App\Models\BuildingType;
|
||||
use App\Services\RetributionCalculatorService;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class AssignSpatialPlanningsToCalculation extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'spatial-planning:assign-calculations
|
||||
{--force : Force assign even if already has calculation}
|
||||
{--recalculate : Recalculate existing calculations with new values}
|
||||
{--chunk=100 : Process in chunks}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Assign retribution calculations to spatial plannings (supports recalculate for existing calculations)';
|
||||
|
||||
protected $calculatorService;
|
||||
|
||||
public function __construct(RetributionCalculatorService $calculatorService)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->calculatorService = $calculatorService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$this->info('🏗️ Starting spatial planning calculation assignment...');
|
||||
|
||||
// Get processing options
|
||||
$force = $this->option('force');
|
||||
$recalculate = $this->option('recalculate');
|
||||
$chunkSize = (int) $this->option('chunk');
|
||||
|
||||
// Get spatial plannings query
|
||||
$query = SpatialPlanning::query();
|
||||
|
||||
if ($recalculate) {
|
||||
// Recalculate mode: only process those WITH active calculations
|
||||
$query->whereHas('retributionCalculations', function ($q) {
|
||||
$q->where('is_active', true);
|
||||
});
|
||||
$this->info('🔄 Recalculate mode: Processing spatial plannings with existing calculations');
|
||||
} elseif (!$force) {
|
||||
// Normal mode: only process those without active calculations
|
||||
$query->whereDoesntHave('retributionCalculations', function ($q) {
|
||||
$q->where('is_active', true);
|
||||
});
|
||||
$this->info('➕ Normal mode: Processing spatial plannings without calculations');
|
||||
} else {
|
||||
// Force mode: process all
|
||||
$this->info('🔥 Force mode: Processing ALL spatial plannings');
|
||||
}
|
||||
|
||||
$totalRecords = $query->count();
|
||||
|
||||
if ($totalRecords === 0) {
|
||||
$this->warn('No spatial plannings found to process.');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$this->info("Found {$totalRecords} spatial planning(s) to process");
|
||||
|
||||
if (!$this->confirm('Do you want to continue?')) {
|
||||
$this->info('Operation cancelled.');
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Process in chunks
|
||||
$processed = 0;
|
||||
$errors = 0;
|
||||
$reused = 0;
|
||||
$created = 0;
|
||||
$buildingTypeStats = [];
|
||||
|
||||
$progressBar = $this->output->createProgressBar($totalRecords);
|
||||
$progressBar->start();
|
||||
|
||||
$recalculated = 0;
|
||||
|
||||
$query->chunk($chunkSize, function ($spatialPlannings) use (&$processed, &$errors, &$reused, &$created, &$recalculated, &$buildingTypeStats, $progressBar, $recalculate) {
|
||||
foreach ($spatialPlannings as $spatialPlanning) {
|
||||
try {
|
||||
$result = $this->assignCalculationToSpatialPlanning($spatialPlanning, $recalculate);
|
||||
|
||||
if ($result['reused']) {
|
||||
$reused++;
|
||||
} elseif (isset($result['recalculated']) && $result['recalculated']) {
|
||||
$recalculated++;
|
||||
} else {
|
||||
$created++;
|
||||
}
|
||||
|
||||
// Track building type statistics
|
||||
$buildingTypeName = $result['building_type_name'] ?? 'Unknown';
|
||||
if (!isset($buildingTypeStats[$buildingTypeName])) {
|
||||
$buildingTypeStats[$buildingTypeName] = 0;
|
||||
}
|
||||
$buildingTypeStats[$buildingTypeName]++;
|
||||
|
||||
$processed++;
|
||||
} catch (\Exception $e) {
|
||||
$errors++;
|
||||
$this->error("Error processing ID {$spatialPlanning->id}: " . $e->getMessage());
|
||||
}
|
||||
|
||||
$progressBar->advance();
|
||||
}
|
||||
});
|
||||
|
||||
$progressBar->finish();
|
||||
|
||||
// Show summary
|
||||
$this->newLine(2);
|
||||
$this->info('✅ Assignment completed!');
|
||||
|
||||
if ($recalculate) {
|
||||
$this->table(
|
||||
['Metric', 'Count'],
|
||||
[
|
||||
['Total Processed', $processed],
|
||||
['Recalculated (Changed)', $recalculated],
|
||||
['Unchanged', $reused],
|
||||
['Errors', $errors],
|
||||
]
|
||||
);
|
||||
} else {
|
||||
$this->table(
|
||||
['Metric', 'Count'],
|
||||
[
|
||||
['Total Processed', $processed],
|
||||
['Calculations Created', $created],
|
||||
['Calculations Reused', $reused],
|
||||
['Errors', $errors],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
// Show building type statistics
|
||||
if (!empty($buildingTypeStats)) {
|
||||
$this->newLine();
|
||||
$this->info('📊 Building Type Distribution:');
|
||||
$statsRows = [];
|
||||
arsort($buildingTypeStats); // Sort by count descending
|
||||
foreach ($buildingTypeStats as $typeName => $count) {
|
||||
$percentage = round(($count / $processed) * 100, 1);
|
||||
$statsRows[] = [$typeName, $count, $percentage . '%'];
|
||||
}
|
||||
$this->table(['Building Type', 'Count', 'Percentage'], $statsRows);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign calculation to a spatial planning
|
||||
*/
|
||||
private function assignCalculationToSpatialPlanning(SpatialPlanning $spatialPlanning, bool $recalculate = false): array
|
||||
{
|
||||
// 1. Detect building type
|
||||
$buildingType = $this->detectBuildingType($spatialPlanning->building_function);
|
||||
|
||||
// 2. Get calculation parameters (round to 2 decimal places)
|
||||
$floorNumber = $spatialPlanning->number_of_floors ?: 1;
|
||||
$buildingArea = round($spatialPlanning->getCalculationArea(), 2);
|
||||
|
||||
if ($buildingArea <= 0) {
|
||||
throw new \Exception("Invalid building area: {$buildingArea}");
|
||||
}
|
||||
|
||||
$reused = false;
|
||||
$isRecalculated = false;
|
||||
|
||||
if ($recalculate) {
|
||||
// Recalculate mode: Always create new calculation
|
||||
$calculationResult = $this->performCalculation($spatialPlanning, $buildingType);
|
||||
|
||||
// Check if spatial planning has existing active calculation
|
||||
$currentActiveCalculation = $spatialPlanning->activeRetributionCalculation;
|
||||
|
||||
if ($currentActiveCalculation) {
|
||||
$oldAmount = $currentActiveCalculation->retributionCalculation->retribution_amount;
|
||||
$oldArea = $currentActiveCalculation->retributionCalculation->building_area;
|
||||
$newAmount = $calculationResult['amount'];
|
||||
|
||||
// Check if there's a significant difference (more than 1 rupiah)
|
||||
if (abs($oldAmount - $newAmount) > 1) {
|
||||
// Create new calculation
|
||||
$calculation = RetributionCalculation::create([
|
||||
'building_type_id' => $buildingType->id,
|
||||
'floor_number' => $floorNumber,
|
||||
'building_area' => $buildingArea,
|
||||
'retribution_amount' => $calculationResult['amount'],
|
||||
'calculation_detail' => $calculationResult['detail'],
|
||||
]);
|
||||
|
||||
// Assign new calculation
|
||||
$spatialPlanning->assignRetributionCalculation(
|
||||
$calculation,
|
||||
"Recalculated: Area {$oldArea}→{$buildingArea}, Amount {$oldAmount}→{$newAmount}"
|
||||
);
|
||||
|
||||
$isRecalculated = true;
|
||||
} else {
|
||||
// No significant difference, keep existing
|
||||
$calculation = $currentActiveCalculation->retributionCalculation;
|
||||
$reused = true;
|
||||
}
|
||||
} else {
|
||||
// No existing calculation, create new
|
||||
$calculation = RetributionCalculation::create([
|
||||
'building_type_id' => $buildingType->id,
|
||||
'floor_number' => $floorNumber,
|
||||
'building_area' => $buildingArea,
|
||||
'retribution_amount' => $calculationResult['amount'],
|
||||
'calculation_detail' => $calculationResult['detail'],
|
||||
]);
|
||||
|
||||
$spatialPlanning->assignRetributionCalculation(
|
||||
$calculation,
|
||||
'Recalculated (new calculation)'
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Normal mode: Check if calculation already exists with same parameters
|
||||
$existingCalculation = RetributionCalculation::where([
|
||||
'building_type_id' => $buildingType->id,
|
||||
'floor_number' => $floorNumber,
|
||||
])
|
||||
->whereBetween('building_area', [
|
||||
$buildingArea * 0.99, // 1% tolerance
|
||||
$buildingArea * 1.01
|
||||
])
|
||||
->first();
|
||||
|
||||
if ($existingCalculation) {
|
||||
// Reuse existing calculation
|
||||
$calculation = $existingCalculation;
|
||||
$reused = true;
|
||||
} else {
|
||||
// Create new calculation
|
||||
$calculationResult = $this->performCalculation($spatialPlanning, $buildingType);
|
||||
|
||||
$calculation = RetributionCalculation::create([
|
||||
'building_type_id' => $buildingType->id,
|
||||
'floor_number' => $floorNumber,
|
||||
'building_area' => $buildingArea,
|
||||
'retribution_amount' => $calculationResult['amount'],
|
||||
'calculation_detail' => $calculationResult['detail'],
|
||||
]);
|
||||
}
|
||||
|
||||
// Assign to spatial planning
|
||||
$spatialPlanning->assignRetributionCalculation(
|
||||
$calculation,
|
||||
$reused ? 'Auto-assigned (reused calculation)' : 'Auto-assigned (new calculation)'
|
||||
);
|
||||
}
|
||||
|
||||
return [
|
||||
'calculation' => $calculation,
|
||||
'reused' => $reused,
|
||||
'recalculated' => $isRecalculated,
|
||||
'building_type_name' => $buildingType->name,
|
||||
'building_type_code' => $buildingType->code,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect building type based on building function using database
|
||||
*/
|
||||
private function detectBuildingType(string $buildingFunction = null): BuildingType
|
||||
{
|
||||
$function = strtolower($buildingFunction ?? '');
|
||||
|
||||
// Mapping building functions to building type codes from database
|
||||
$mappings = [
|
||||
// Religious
|
||||
'masjid' => 'KEAGAMAAN',
|
||||
'gereja' => 'KEAGAMAAN',
|
||||
'vihara' => 'KEAGAMAAN',
|
||||
'pura' => 'KEAGAMAAN',
|
||||
'keagamaan' => 'KEAGAMAAN',
|
||||
'religious' => 'KEAGAMAAN',
|
||||
|
||||
// Residential/Housing
|
||||
'rumah' => 'HUN_SEDH', // Default to simple housing
|
||||
'perumahan' => 'HUN_SEDH',
|
||||
'hunian' => 'HUN_SEDH',
|
||||
'residential' => 'HUN_SEDH',
|
||||
'tinggal' => 'HUN_SEDH',
|
||||
'mbr' => 'MBR', // Specifically for MBR
|
||||
'masyarakat berpenghasilan rendah' => 'MBR',
|
||||
|
||||
// Commercial/Business - default to UMKM
|
||||
'toko' => 'UMKM',
|
||||
'warung' => 'UMKM',
|
||||
'perdagangan' => 'UMKM',
|
||||
'dagang' => 'UMKM',
|
||||
'usaha' => 'UMKM',
|
||||
'komersial' => 'UMKM',
|
||||
'commercial' => 'UMKM',
|
||||
'pasar' => 'UMKM',
|
||||
'kios' => 'UMKM',
|
||||
|
||||
// Large commercial
|
||||
'mall' => 'USH_BESAR',
|
||||
'plaza' => 'USH_BESAR',
|
||||
'supermarket' => 'USH_BESAR',
|
||||
'department' => 'USH_BESAR',
|
||||
'hotel' => 'USH_BESAR',
|
||||
'resort' => 'USH_BESAR',
|
||||
|
||||
// Office
|
||||
'kantor' => 'UMKM', // Can be UMKM or USH_BESAR depending on size
|
||||
'perkantoran' => 'UMKM',
|
||||
'office' => 'UMKM',
|
||||
|
||||
// Industry (usually big business)
|
||||
'industri' => 'USH_BESAR',
|
||||
'pabrik' => 'USH_BESAR',
|
||||
'gudang' => 'USH_BESAR',
|
||||
'warehouse' => 'USH_BESAR',
|
||||
'manufacturing' => 'USH_BESAR',
|
||||
|
||||
// Social/Cultural
|
||||
'sekolah' => 'SOSBUDAYA',
|
||||
'pendidikan' => 'SOSBUDAYA',
|
||||
'universitas' => 'SOSBUDAYA',
|
||||
'kampus' => 'SOSBUDAYA',
|
||||
'rumah sakit' => 'SOSBUDAYA',
|
||||
'klinik' => 'SOSBUDAYA',
|
||||
'kesehatan' => 'SOSBUDAYA',
|
||||
'puskesmas' => 'SOSBUDAYA',
|
||||
'museum' => 'SOSBUDAYA',
|
||||
'perpustakaan' => 'SOSBUDAYA',
|
||||
'gedung olahraga' => 'SOSBUDAYA',
|
||||
|
||||
// Mixed use
|
||||
'campuran' => 'CAMP_KECIL', // Default to small mixed
|
||||
'mixed' => 'CAMP_KECIL',
|
||||
];
|
||||
|
||||
// Try to match building function
|
||||
$detectedCode = null;
|
||||
foreach ($mappings as $keyword => $code) {
|
||||
if (str_contains($function, $keyword)) {
|
||||
$detectedCode = $code;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Find building type in database by code
|
||||
if ($detectedCode) {
|
||||
$buildingType = BuildingType::where('code', $detectedCode)
|
||||
->whereHas('indices') // Only types with indices
|
||||
->first();
|
||||
|
||||
if ($buildingType) {
|
||||
return $buildingType;
|
||||
}
|
||||
}
|
||||
|
||||
// Default to "UMKM" type if not detected (most common business type)
|
||||
$defaultType = BuildingType::where('code', 'UMKM')
|
||||
->whereHas('indices')
|
||||
->first();
|
||||
|
||||
if ($defaultType) {
|
||||
return $defaultType;
|
||||
}
|
||||
|
||||
// Fallback to any available type with indices
|
||||
$fallbackType = BuildingType::whereHas('indices')
|
||||
->where('is_active', true)
|
||||
->first();
|
||||
|
||||
if (!$fallbackType) {
|
||||
throw new \Exception('No building types with indices found in database. Please run: php artisan db:seed --class=RetributionDataSeeder');
|
||||
}
|
||||
|
||||
return $fallbackType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform calculation using RetributionCalculatorService
|
||||
*/
|
||||
private function performCalculation(SpatialPlanning $spatialPlanning, BuildingType $buildingType): array
|
||||
{
|
||||
// Round area to 2 decimal places to match database storage format
|
||||
$buildingArea = round($spatialPlanning->getCalculationArea(), 2);
|
||||
$floorNumber = $spatialPlanning->number_of_floors ?: 1;
|
||||
|
||||
try {
|
||||
// Use the same calculation service as TestRetributionCalculation
|
||||
$result = $this->calculatorService->calculate(
|
||||
$buildingType->id,
|
||||
$floorNumber,
|
||||
$buildingArea,
|
||||
false // Don't save to database, we'll handle that separately
|
||||
);
|
||||
|
||||
return [
|
||||
'amount' => $result['total_retribution'],
|
||||
'detail' => [
|
||||
'building_type_id' => $buildingType->id,
|
||||
'building_type_name' => $buildingType->name,
|
||||
'building_type_code' => $buildingType->code,
|
||||
'coefficient' => $result['indices']['coefficient'],
|
||||
'ip_permanent' => $result['indices']['ip_permanent'],
|
||||
'ip_complexity' => $result['indices']['ip_complexity'],
|
||||
'locality_index' => $result['indices']['locality_index'],
|
||||
'height_index' => $result['input_parameters']['height_index'],
|
||||
'infrastructure_factor' => $result['indices']['infrastructure_factor'],
|
||||
'building_area' => $buildingArea,
|
||||
'floor_number' => $floorNumber,
|
||||
'building_function' => $spatialPlanning->building_function,
|
||||
'calculation_steps' => $result['calculation_detail'],
|
||||
'base_value' => $result['input_parameters']['base_value'],
|
||||
'is_free' => $buildingType->is_free,
|
||||
'calculation_date' => now()->toDateTimeString(),
|
||||
'total' => $result['total_retribution'],
|
||||
]
|
||||
];
|
||||
|
||||
} catch (\Exception $e) {
|
||||
// Fallback to basic calculation if service fails
|
||||
$this->warn("Calculation service failed for {$spatialPlanning->name}: {$e->getMessage()}. Using fallback calculation.");
|
||||
|
||||
// Basic fallback calculation
|
||||
$totalAmount = $buildingType->is_free ? 0 : ($buildingArea * 50000);
|
||||
|
||||
return [
|
||||
'amount' => $totalAmount,
|
||||
'detail' => [
|
||||
'building_type_id' => $buildingType->id,
|
||||
'building_type_name' => $buildingType->name,
|
||||
'building_type_code' => $buildingType->code,
|
||||
'building_area' => $buildingArea,
|
||||
'floor_number' => $floorNumber,
|
||||
'building_function' => $spatialPlanning->building_function,
|
||||
'calculation_method' => 'fallback',
|
||||
'error_message' => $e->getMessage(),
|
||||
'is_free' => $buildingType->is_free,
|
||||
'calculation_date' => now()->toDateTimeString(),
|
||||
'total' => $totalAmount,
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
317
app/Console/Commands/InitSpatialPlanningDatas.php
Normal file
317
app/Console/Commands/InitSpatialPlanningDatas.php
Normal file
@@ -0,0 +1,317 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\SpatialPlanning;
|
||||
use Illuminate\Console\Command;
|
||||
use Maatwebsite\Excel\Facades\Excel;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Exception;
|
||||
|
||||
class InitSpatialPlanningDatas extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'spatial:init {file? : Path to the CSV/Excel file} {--truncate : Clear existing data before import} {--safe-truncate : Clear only spatial plannings without retribution proposals} {--force-truncate : Force truncate by disabling foreign key checks}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Import spatial planning data from CSV/Excel file for retribution';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$filePath = $this->argument('file') ?? 'public/templates/Data_2025___Estimasi_Jumlah_Lantai.csv';
|
||||
$fullPath = storage_path('app/' . $filePath);
|
||||
|
||||
// Check if file exists
|
||||
if (!file_exists($fullPath)) {
|
||||
$this->error("File not found: {$fullPath}");
|
||||
$this->info("Available files in templates:");
|
||||
$this->listAvailableFiles();
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Handle truncate options
|
||||
if (($this->option('truncate') && $this->option('safe-truncate')) ||
|
||||
($this->option('truncate') && $this->option('force-truncate')) ||
|
||||
($this->option('safe-truncate') && $this->option('force-truncate'))) {
|
||||
$this->error('Cannot use multiple truncate options together. Choose only one.');
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Confirm truncate if requested
|
||||
if ($this->option('truncate')) {
|
||||
if ($this->confirm('This will delete all existing spatial planning data and related retribution proposals. Continue?')) {
|
||||
$this->info('Truncating tables...');
|
||||
|
||||
try {
|
||||
// First delete retribution proposals that reference spatial plannings
|
||||
$deletedProposals = DB::table('retribution_proposals')
|
||||
->whereNotNull('spatial_planning_id')
|
||||
->count();
|
||||
|
||||
if ($deletedProposals > 0) {
|
||||
DB::table('retribution_proposals')
|
||||
->whereNotNull('spatial_planning_id')
|
||||
->delete();
|
||||
$this->info("Deleted {$deletedProposals} retribution proposals linked to spatial plannings.");
|
||||
}
|
||||
|
||||
// Method 1: Try truncate with disabled foreign key checks
|
||||
try {
|
||||
DB::statement('SET FOREIGN_KEY_CHECKS = 0');
|
||||
DB::table('spatial_plannings')->truncate();
|
||||
DB::statement('SET FOREIGN_KEY_CHECKS = 1');
|
||||
$this->info('Spatial plannings table truncated successfully.');
|
||||
} catch (\Exception $truncateError) {
|
||||
// Method 2: Fallback to delete if truncate fails
|
||||
$this->warn('Truncate failed, using delete method...');
|
||||
$deletedSpatial = DB::table('spatial_plannings')->delete();
|
||||
$this->info("Deleted {$deletedSpatial} spatial planning records.");
|
||||
|
||||
// Reset auto increment
|
||||
DB::statement('ALTER TABLE spatial_plannings AUTO_INCREMENT = 1');
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$this->error('Failed to truncate tables: ' . $e->getMessage());
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
$this->info('Operation cancelled.');
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Force truncate - disable foreign key checks and truncate everything
|
||||
if ($this->option('force-truncate')) {
|
||||
if ($this->confirm('This will FORCE truncate ALL spatial planning data by disabling foreign key checks. This is risky! Continue?')) {
|
||||
$this->info('Force truncating with disabled foreign key checks...');
|
||||
|
||||
try {
|
||||
DB::beginTransaction();
|
||||
|
||||
// Disable foreign key checks
|
||||
DB::statement('SET FOREIGN_KEY_CHECKS = 0');
|
||||
|
||||
// Truncate both tables
|
||||
DB::table('retribution_proposals')->truncate();
|
||||
DB::table('spatial_plannings')->truncate();
|
||||
|
||||
// Re-enable foreign key checks
|
||||
DB::statement('SET FOREIGN_KEY_CHECKS = 1');
|
||||
|
||||
$this->info('Force truncate completed successfully.');
|
||||
|
||||
DB::commit();
|
||||
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
// Make sure to re-enable foreign key checks even on error
|
||||
try {
|
||||
DB::statement('SET FOREIGN_KEY_CHECKS = 1');
|
||||
} catch (\Exception $fkError) {
|
||||
$this->error('Failed to re-enable foreign key checks: ' . $fkError->getMessage());
|
||||
}
|
||||
$this->error('Failed to force truncate: ' . $e->getMessage());
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
$this->info('Operation cancelled.');
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Safe truncate - only delete spatial plannings without retribution proposals
|
||||
if ($this->option('safe-truncate')) {
|
||||
if ($this->confirm('This will delete only spatial planning data that have no retribution proposals. Continue?')) {
|
||||
$this->info('Safe truncating spatial plannings...');
|
||||
|
||||
try {
|
||||
DB::beginTransaction();
|
||||
|
||||
// Count spatial plannings with retribution proposals
|
||||
$withProposals = DB::table('spatial_plannings')
|
||||
->whereExists(function ($query) {
|
||||
$query->select(DB::raw(1))
|
||||
->from('retribution_proposals')
|
||||
->whereColumn('retribution_proposals.spatial_planning_id', 'spatial_plannings.id');
|
||||
})
|
||||
->count();
|
||||
|
||||
// Delete spatial plannings without retribution proposals
|
||||
$deletedCount = DB::table('spatial_plannings')
|
||||
->whereNotExists(function ($query) {
|
||||
$query->select(DB::raw(1))
|
||||
->from('retribution_proposals')
|
||||
->whereColumn('retribution_proposals.spatial_planning_id', 'spatial_plannings.id');
|
||||
})
|
||||
->delete();
|
||||
|
||||
$this->info("Deleted {$deletedCount} spatial plannings without retribution proposals.");
|
||||
$this->info("Kept {$withProposals} spatial plannings that have retribution proposals.");
|
||||
|
||||
DB::commit();
|
||||
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
$this->error('Failed to safe truncate: ' . $e->getMessage());
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
$this->info('Operation cancelled.');
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
$this->info("Starting import from: {$filePath}");
|
||||
$this->info("Full path: {$fullPath}");
|
||||
|
||||
try {
|
||||
DB::beginTransaction();
|
||||
|
||||
$data = Excel::toArray([], $fullPath);
|
||||
|
||||
if (empty($data) || empty($data[0])) {
|
||||
$this->error('No data found in the file.');
|
||||
return 1;
|
||||
}
|
||||
|
||||
$rows = $data[0]; // Get first sheet
|
||||
$headers = array_shift($rows); // Remove header row
|
||||
|
||||
$this->info("Found " . count($rows) . " data rows to import.");
|
||||
$this->info("Headers: " . implode(', ', $headers));
|
||||
|
||||
$progressBar = $this->output->createProgressBar(count($rows));
|
||||
$progressBar->start();
|
||||
|
||||
$imported = 0;
|
||||
$skipped = 0;
|
||||
|
||||
foreach ($rows as $index => $row) {
|
||||
try {
|
||||
// Skip empty rows
|
||||
if (empty(array_filter($row))) {
|
||||
$skipped++;
|
||||
$progressBar->advance();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Map CSV columns to model attributes
|
||||
$spatialData = [
|
||||
'name' => $this->cleanString($row[1] ?? ''), // pemohon
|
||||
'location' => $this->cleanString($row[2] ?? ''), // alamat
|
||||
'activities' => $this->cleanString($row[3] ?? ''), // activities
|
||||
'land_area' => $this->cleanNumber($row[4] ?? 0), // luas_lahan
|
||||
'site_bcr' => $this->cleanNumber($row[5] ?? 0), // bcr_kawasan
|
||||
'area' => $this->cleanNumber($row[6] ?? 0), // area
|
||||
'no_tapak' => $this->cleanString($row[7] ?? ''), // no_tapak
|
||||
'no_skkl' => $this->cleanString($row[8] ?? ''), // no_skkl
|
||||
'no_ukl' => $this->cleanString($row[9] ?? ''), // no_ukl
|
||||
'building_function' => $this->cleanString($row[10] ?? ''), // fungsi_bangunan
|
||||
'sub_building_function' => $this->cleanString($row[11] ?? ''), // sub_fungsi_bangunan
|
||||
'number_of_floors' => $this->cleanNumber($row[12] ?? 1), // jumlah_lantai
|
||||
'number' => $this->cleanString($row[0] ?? ''), // no
|
||||
'date' => now(), // Set current date
|
||||
'kbli' => null, // Not in CSV, set as null
|
||||
];
|
||||
|
||||
// Validate required fields
|
||||
if (empty($spatialData['name']) && empty($spatialData['activities'])) {
|
||||
$skipped++;
|
||||
$progressBar->advance();
|
||||
continue;
|
||||
}
|
||||
|
||||
SpatialPlanning::create($spatialData);
|
||||
$imported++;
|
||||
|
||||
} catch (Exception $e) {
|
||||
$this->newLine();
|
||||
$this->error("Error importing row " . ($index + 2) . ": " . $e->getMessage());
|
||||
$skipped++;
|
||||
}
|
||||
|
||||
$progressBar->advance();
|
||||
}
|
||||
|
||||
$progressBar->finish();
|
||||
$this->newLine(2);
|
||||
|
||||
DB::commit();
|
||||
|
||||
$this->info("Import completed successfully!");
|
||||
$this->info("Imported: {$imported} records");
|
||||
$this->info("Skipped: {$skipped} records");
|
||||
|
||||
return 0;
|
||||
|
||||
} catch (Exception $e) {
|
||||
DB::rollBack();
|
||||
$this->error("Import failed: " . $e->getMessage());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean string data
|
||||
*/
|
||||
private function cleanString($value)
|
||||
{
|
||||
if (is_null($value)) return null;
|
||||
return trim(str_replace(["\n", "\r", "\t"], ' ', $value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean numeric data
|
||||
*/
|
||||
private function cleanNumber($value)
|
||||
{
|
||||
if (is_null($value) || $value === '') return 0;
|
||||
|
||||
// Remove non-numeric characters except decimal point
|
||||
$cleaned = preg_replace('/[^0-9.]/', '', $value);
|
||||
|
||||
return is_numeric($cleaned) ? (float) $cleaned : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* List available template files
|
||||
*/
|
||||
private function listAvailableFiles()
|
||||
{
|
||||
$templatesPath = storage_path('app/public/templates');
|
||||
if (is_dir($templatesPath)) {
|
||||
$this->info("Files in storage/app/public/templates:");
|
||||
$extensions = ['csv', 'xlsx', 'xls'];
|
||||
foreach ($extensions as $ext) {
|
||||
$files = glob($templatesPath . '/*.' . $ext);
|
||||
foreach ($files as $file) {
|
||||
$this->line(' - ' . basename($file));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$publicTemplatesPath = public_path('templates');
|
||||
if (is_dir($publicTemplatesPath)) {
|
||||
$this->info("Files in public/templates:");
|
||||
$extensions = ['csv', 'xlsx', 'xls'];
|
||||
foreach ($extensions as $ext) {
|
||||
$files = glob($publicTemplatesPath . '/*.' . $ext);
|
||||
foreach ($files as $file) {
|
||||
$this->line(' - ' . basename($file));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,54 +42,4 @@ class ScrapingData extends Command
|
||||
|
||||
$this->info("Scraping job dispatched successfully");
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
// public function handle()
|
||||
// {
|
||||
|
||||
// try {
|
||||
// // Create a record with "processing" status
|
||||
// $import_datasource = ImportDatasource::create([
|
||||
// 'message' => 'Initiating scraping...',
|
||||
// 'response_body' => null,
|
||||
// 'status' => 'processing',
|
||||
// 'start_time' => now()
|
||||
// ]);
|
||||
|
||||
// // Run the service
|
||||
// $service_google_sheet = new ServiceGoogleSheet();
|
||||
// $service_google_sheet->run_service();
|
||||
|
||||
// // Run the ServicePbgTask with injected Guzzle Client
|
||||
// $this->service_pbg_task->run_service();
|
||||
|
||||
// // run the service pbg task assignments
|
||||
// $this->service_tab_pbg_task->run_service();
|
||||
|
||||
// // Update the record status to "success" after completion
|
||||
// $import_datasource->update([
|
||||
// 'status' => 'success',
|
||||
// 'message' => 'Scraping completed successfully.',
|
||||
// 'finish_time' => now()
|
||||
// ]);
|
||||
|
||||
// } catch (\Exception $e) {
|
||||
|
||||
// // Log the error for debugging
|
||||
// Log::error('Scraping failed: ' . $e->getMessage(), ['trace' => $e->getTraceAsString()]);
|
||||
|
||||
// // Handle errors by updating the status to "failed"
|
||||
// if (isset($import_datasource)) {
|
||||
// $import_datasource->update([
|
||||
// 'status' => 'failed',
|
||||
// 'response_body' => 'Error: ' . $e->getMessage(),
|
||||
// 'finish_time' => now()
|
||||
// ]);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
}
|
||||
|
||||
33
app/Console/Commands/ScrapingLeaderData.php
Normal file
33
app/Console/Commands/ScrapingLeaderData.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Services\ServiceGoogleSheet;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class ScrapingLeaderData extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'app:scraping-leader-data';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Scraping leader data from google spreadsheet and save to database';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$service_google_sheet = app(ServiceGoogleSheet::class);
|
||||
$service_google_sheet->sync_leader_data();
|
||||
$this->info('Leader data synced successfully');
|
||||
}
|
||||
}
|
||||
265
app/Console/Commands/TestRetributionCalculation.php
Normal file
265
app/Console/Commands/TestRetributionCalculation.php
Normal file
@@ -0,0 +1,265 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use App\Services\RetributionCalculatorService;
|
||||
use App\Models\BuildingType;
|
||||
|
||||
class TestRetributionCalculation extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'retribution:test
|
||||
{--area= : Luas bangunan dalam m2}
|
||||
{--floor= : Jumlah lantai (1-6)}
|
||||
{--type= : ID atau kode building type}
|
||||
{--all : Test semua building types}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Test perhitungan retribusi PBG dengan input luas bangunan dan tinggi lantai';
|
||||
|
||||
protected $calculatorService;
|
||||
|
||||
public function __construct(RetributionCalculatorService $calculatorService)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->calculatorService = $calculatorService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$this->info('🏢 SISTEM TEST PERHITUNGAN RETRIBUSI PBG');
|
||||
$this->info('=' . str_repeat('=', 50));
|
||||
|
||||
// Test all building types if --all flag is used
|
||||
if ($this->option('all')) {
|
||||
return $this->testAllBuildingTypes();
|
||||
}
|
||||
|
||||
// Get input parameters
|
||||
$area = $this->getArea();
|
||||
$floor = $this->getFloor();
|
||||
$buildingTypeId = $this->getBuildingType();
|
||||
|
||||
if (!$area || !$floor || !$buildingTypeId) {
|
||||
$this->error('❌ Parameter tidak lengkap!');
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Perform calculation
|
||||
$this->performCalculation($buildingTypeId, $floor, $area);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected function getArea()
|
||||
{
|
||||
$area = $this->option('area');
|
||||
|
||||
if (!$area) {
|
||||
$area = $this->ask('📐 Masukkan luas bangunan (m²)');
|
||||
}
|
||||
|
||||
if (!is_numeric($area) || $area <= 0) {
|
||||
$this->error('❌ Luas bangunan harus berupa angka positif!');
|
||||
return null;
|
||||
}
|
||||
|
||||
return (float) $area;
|
||||
}
|
||||
|
||||
protected function getFloor()
|
||||
{
|
||||
$floor = $this->option('floor');
|
||||
|
||||
if (!$floor) {
|
||||
$floor = $this->ask('🏗️ Masukkan jumlah lantai (1-6)');
|
||||
}
|
||||
|
||||
if (!is_numeric($floor) || $floor < 1 || $floor > 6) {
|
||||
$this->error('❌ Jumlah lantai harus antara 1-6!');
|
||||
return null;
|
||||
}
|
||||
|
||||
return (int) $floor;
|
||||
}
|
||||
|
||||
protected function getBuildingType()
|
||||
{
|
||||
$type = $this->option('type');
|
||||
|
||||
if (!$type) {
|
||||
$this->showBuildingTypes();
|
||||
$type = $this->ask('🏢 Masukkan ID atau kode building type');
|
||||
}
|
||||
|
||||
// Try to find by ID first, then by code
|
||||
$buildingType = null;
|
||||
|
||||
if (is_numeric($type)) {
|
||||
$buildingType = BuildingType::find($type);
|
||||
} else {
|
||||
$buildingType = BuildingType::where('code', strtoupper($type))->first();
|
||||
}
|
||||
|
||||
if (!$buildingType) {
|
||||
$this->error('❌ Building type tidak ditemukan!');
|
||||
return null;
|
||||
}
|
||||
|
||||
return $buildingType->id;
|
||||
}
|
||||
|
||||
protected function showBuildingTypes()
|
||||
{
|
||||
$this->info('📋 DAFTAR BUILDING TYPES:');
|
||||
$this->line('');
|
||||
|
||||
$buildingTypes = BuildingType::with('indices')
|
||||
->whereHas('indices') // Only types that have indices
|
||||
->get();
|
||||
|
||||
$headers = ['ID', 'Kode', 'Nama', 'Coefficient', 'Free'];
|
||||
$rows = [];
|
||||
|
||||
foreach ($buildingTypes as $type) {
|
||||
$rows[] = [
|
||||
$type->id,
|
||||
$type->code,
|
||||
$type->name,
|
||||
$type->indices ? number_format($type->indices->coefficient, 4) : 'N/A',
|
||||
$type->is_free ? '✅' : '❌'
|
||||
];
|
||||
}
|
||||
|
||||
$this->table($headers, $rows);
|
||||
$this->line('');
|
||||
}
|
||||
|
||||
protected function performCalculation($buildingTypeId, $floor, $area)
|
||||
{
|
||||
try {
|
||||
// Round area to 2 decimal places to match database storage format
|
||||
$roundedArea = round($area, 2);
|
||||
$result = $this->calculatorService->calculate($buildingTypeId, $floor, $roundedArea, false);
|
||||
|
||||
$this->displayResults($result, $roundedArea, $floor);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$this->error('❌ Error: ' . $e->getMessage());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
protected function displayResults($result, $area, $floor)
|
||||
{
|
||||
$this->info('');
|
||||
$this->info('📊 HASIL PERHITUNGAN RETRIBUSI');
|
||||
$this->info('=' . str_repeat('=', 40));
|
||||
|
||||
// Building info
|
||||
$this->line('🏢 <fg=cyan>Building Type:</> ' . $result['building_type']['name']);
|
||||
$this->line('📐 <fg=cyan>Luas Bangunan:</> ' . number_format($area, 0) . ' m²');
|
||||
$this->line('🏗️ <fg=cyan>Jumlah Lantai:</> ' . $floor);
|
||||
|
||||
if (isset($result['building_type']['is_free']) && $result['building_type']['is_free']) {
|
||||
$this->line('');
|
||||
$this->info('🎉 GRATIS - Building type ini tidak dikenakan retribusi');
|
||||
$this->line('💰 <fg=green>Total Retribusi: Rp 0</fg=green>');
|
||||
return;
|
||||
}
|
||||
|
||||
$this->line('');
|
||||
|
||||
// Parameters
|
||||
$this->info('📋 PARAMETER PERHITUNGAN:');
|
||||
$indices = $result['indices'];
|
||||
$this->line('• Coefficient: ' . number_format($indices['coefficient'], 4));
|
||||
$this->line('• IP Permanent: ' . number_format($indices['ip_permanent'], 4));
|
||||
$this->line('• IP Complexity: ' . number_format($indices['ip_complexity'], 4));
|
||||
$this->line('• Locality Index: ' . number_format($indices['locality_index'], 4));
|
||||
$this->line('• Height Index: ' . number_format($result['input_parameters']['height_index'], 4));
|
||||
|
||||
$this->line('');
|
||||
|
||||
// Calculation steps
|
||||
$this->info('🔢 LANGKAH PERHITUNGAN:');
|
||||
$detail = $result['calculation_detail'];
|
||||
$this->line('1. H5 Raw: ' . number_format($detail['h5_raw'], 6));
|
||||
$this->line('2. H5 Rounded: ' . number_format($detail['h5'], 4));
|
||||
$this->line('3. Main Calculation: Rp ' . number_format($detail['main'], 2));
|
||||
$this->line('4. Infrastructure (50%): Rp ' . number_format($detail['infrastructure'], 2));
|
||||
|
||||
$this->line('');
|
||||
|
||||
// Final result
|
||||
$this->info('💰 <fg=green>TOTAL RETRIBUSI: ' . $result['formatted_amount'] . '</fg=green>');
|
||||
$this->line('📈 <fg=yellow>Per m²: Rp ' . number_format($result['total_retribution'] / $area, 2) . '</fg=yellow>');
|
||||
}
|
||||
|
||||
protected function testAllBuildingTypes()
|
||||
{
|
||||
$area = round($this->option('area') ?: 100, 2);
|
||||
$floor = $this->option('floor') ?: 2;
|
||||
|
||||
$this->info("🧪 TESTING SEMUA BUILDING TYPES");
|
||||
$this->info("📐 Luas: {$area} m² | 🏗️ Lantai: {$floor}");
|
||||
$this->info('=' . str_repeat('=', 60));
|
||||
|
||||
$buildingTypes = BuildingType::with('indices')
|
||||
->whereHas('indices') // Only types that have indices
|
||||
->orderBy('level')
|
||||
->orderBy('name')
|
||||
->get();
|
||||
|
||||
$headers = ['Kode', 'Nama', 'Coefficient', 'Total Retribusi', 'Per m²'];
|
||||
$rows = [];
|
||||
|
||||
foreach ($buildingTypes as $type) {
|
||||
try {
|
||||
$result = $this->calculatorService->calculate($type->id, $floor, $area, false);
|
||||
|
||||
if ($type->is_free) {
|
||||
$rows[] = [
|
||||
$type->code,
|
||||
$type->name,
|
||||
'FREE',
|
||||
'Rp 0',
|
||||
'Rp 0'
|
||||
];
|
||||
} else {
|
||||
$rows[] = [
|
||||
$type->code,
|
||||
$type->name,
|
||||
number_format($result['indices']['coefficient'], 4),
|
||||
'Rp ' . number_format($result['total_retribution'], 0),
|
||||
'Rp ' . number_format($result['total_retribution'] / $area, 0)
|
||||
];
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$rows[] = [
|
||||
$type->code,
|
||||
$type->name,
|
||||
'ERROR',
|
||||
$e->getMessage(),
|
||||
'-'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$this->table($headers, $rows);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -25,12 +25,13 @@ class BigDataResumeController extends Controller
|
||||
$filterDate = $request->get("filterByDate");
|
||||
|
||||
if (!$filterDate || $filterDate === "latest") {
|
||||
$big_data_resume = BigdataResume::where('year', now()->year)->latest()->first();
|
||||
$big_data_resume = BigdataResume::where('year', 'leader')->latest()->first();
|
||||
if (!$big_data_resume) {
|
||||
return $this->response_empty_resume();
|
||||
}
|
||||
} else {
|
||||
$big_data_resume = BigdataResume::whereDate('created_at', $filterDate)
|
||||
$big_data_resume = BigdataResume::where('year', 'leader')
|
||||
->whereDate('created_at', $filterDate)
|
||||
->orderBy('id', 'desc')
|
||||
->first();
|
||||
|
||||
|
||||
@@ -23,19 +23,25 @@ class GrowthReportAPIController extends Controller
|
||||
$defaultEnd = $today;
|
||||
|
||||
// Use request values if provided, else use defaults
|
||||
$startDate = $request->input('start_date', $defaultStart->toDateString());
|
||||
$endDate = $request->input('end_date', $defaultEnd->toDateString());
|
||||
// $startDate = $request->input('start_date', $defaultStart->toDateString());
|
||||
// $endDate = $request->input('end_date', $defaultEnd->toDateString());
|
||||
|
||||
// Optional year filter (used if specified)
|
||||
$year = $request->input('year', now()->year);
|
||||
|
||||
// $query = BigdataResume::selectRaw("
|
||||
// DATE(created_at) as date,
|
||||
// SUM(potention_sum) as potention_sum,
|
||||
// SUM(verified_sum) as verified_sum,
|
||||
// SUM(non_verified_sum) as non_verified_sum
|
||||
// ")
|
||||
// ->whereBetween('created_at', [$startDate, $endDate]);
|
||||
$query = BigdataResume::selectRaw("
|
||||
DATE(created_at) as date,
|
||||
SUM(potention_sum) as potention_sum,
|
||||
SUM(verified_sum) as verified_sum,
|
||||
SUM(non_verified_sum) as non_verified_sum
|
||||
")
|
||||
->whereBetween('created_at', [$startDate, $endDate]);
|
||||
");
|
||||
|
||||
$query->whereNotNull('year')
|
||||
->where('year', '!=', 'all');
|
||||
|
||||
@@ -17,12 +17,16 @@ class LackOfPotentialController extends Controller
|
||||
$total_reklame = Advertisement::count();
|
||||
$total_pdam = Customer::count();
|
||||
$total_tata_ruang = SpatialPlanning::count();
|
||||
$total_tata_ruang_usaha = SpatialPlanning::where('building_function','like', '%usaha%')->count();
|
||||
$total_tata_ruang_non_usaha = SpatialPlanning::where('building_function','like', '%hunian%')->count();
|
||||
$data_report_tourism = TourismBasedKBLI::all();
|
||||
|
||||
return response()->json([
|
||||
'total_reklame' => $total_reklame,
|
||||
'total_pdam' => $total_pdam,
|
||||
'total_tata_ruang' => $total_tata_ruang,
|
||||
'total_tata_ruang_usaha' => $total_tata_ruang_usaha,
|
||||
'total_tata_ruang_non_usaha' => $total_tata_ruang_non_usaha,
|
||||
'data_report' => $data_report_tourism,
|
||||
], 200);
|
||||
}catch(\Exception $e){
|
||||
|
||||
59
app/Http/Controllers/Auth/AuthenticatedSessionController.php
Executable file → Normal file
59
app/Http/Controllers/Auth/AuthenticatedSessionController.php
Executable file → Normal file
@@ -36,13 +36,68 @@ class AuthenticatedSessionController extends Controller
|
||||
// Ambil user yang sedang login
|
||||
$user = Auth::user();
|
||||
|
||||
// Buat token untuk API
|
||||
$token = $user->createToken(env('APP_KEY'))->plainTextToken;
|
||||
// Hapus token lama jika ada
|
||||
$user->tokens()->delete();
|
||||
|
||||
// Buat token untuk API dengan scope dan expiration
|
||||
$tokenName = config('app.name', 'Laravel') . '-' . $user->id . '-' . time();
|
||||
|
||||
// Token dengan scope (opsional)
|
||||
$token = $user->createToken($tokenName, ['*'], now()->addDays(30))->plainTextToken;
|
||||
|
||||
// Simpan token di session untuk digunakan di frontend
|
||||
session(['api_token' => $token]);
|
||||
|
||||
return redirect()->intended(RouteServiceProvider::HOME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate API token for authenticated user
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function generateApiToken(Request $request)
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
if (!$user) {
|
||||
return response()->json(['error' => 'Unauthorized'], 401);
|
||||
}
|
||||
|
||||
// Delete existing tokens
|
||||
$user->tokens()->delete();
|
||||
|
||||
// Generate new token
|
||||
$tokenName = config('app.name', 'Laravel') . '-' . $user->id . '-' . time();
|
||||
$token = $user->createToken($tokenName, ['*'], now()->addDays(30))->plainTextToken;
|
||||
|
||||
return response()->json([
|
||||
'token' => $token,
|
||||
'token_type' => 'Bearer',
|
||||
'expires_in' => 30 * 24 * 60 * 60, // 30 days in seconds
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Revoke API token for authenticated user
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function revokeApiToken(Request $request)
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
if (!$user) {
|
||||
return response()->json(['error' => 'Unauthorized'], 401);
|
||||
}
|
||||
|
||||
$user->tokens()->delete();
|
||||
|
||||
return response()->json(['message' => 'All tokens revoked successfully']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy an authenticated session.
|
||||
*
|
||||
|
||||
0
app/Http/Controllers/Auth/ConfirmablePasswordController.php
Executable file → Normal file
0
app/Http/Controllers/Auth/ConfirmablePasswordController.php
Executable file → Normal file
0
app/Http/Controllers/Auth/EmailVerificationNotificationController.php
Executable file → Normal file
0
app/Http/Controllers/Auth/EmailVerificationNotificationController.php
Executable file → Normal file
0
app/Http/Controllers/Auth/EmailVerificationPromptController.php
Executable file → Normal file
0
app/Http/Controllers/Auth/EmailVerificationPromptController.php
Executable file → Normal file
0
app/Http/Controllers/Auth/NewPasswordController.php
Executable file → Normal file
0
app/Http/Controllers/Auth/NewPasswordController.php
Executable file → Normal file
0
app/Http/Controllers/Auth/PasswordResetLinkController.php
Executable file → Normal file
0
app/Http/Controllers/Auth/PasswordResetLinkController.php
Executable file → Normal file
0
app/Http/Controllers/Auth/RegisteredUserController.php
Executable file → Normal file
0
app/Http/Controllers/Auth/RegisteredUserController.php
Executable file → Normal file
0
app/Http/Controllers/Auth/VerifyEmailController.php
Executable file → Normal file
0
app/Http/Controllers/Auth/VerifyEmailController.php
Executable file → Normal file
0
app/Http/Controllers/Controller.php
Executable file → Normal file
0
app/Http/Controllers/Controller.php
Executable file → Normal file
@@ -21,4 +21,13 @@ class BigDataController extends Controller
|
||||
{
|
||||
return view('dashboards.pbg');
|
||||
}
|
||||
|
||||
public function leader()
|
||||
{
|
||||
$latest_import_datasource = ImportDatasource::latest()->first();
|
||||
$latest_created = $latest_import_datasource ?
|
||||
$latest_import_datasource->created_at->format("j F Y H:i:s") : null;
|
||||
$menus = Menu::all();
|
||||
return view('dashboards.leader', compact('latest_created', 'menus'));
|
||||
}
|
||||
}
|
||||
|
||||
0
app/Http/Controllers/RoutingController.php
Executable file → Normal file
0
app/Http/Controllers/RoutingController.php
Executable file → Normal file
0
app/Http/Requests/Auth/LoginRequest.php
Executable file → Normal file
0
app/Http/Requests/Auth/LoginRequest.php
Executable file → Normal file
@@ -19,8 +19,8 @@ class DataSettingResource extends JsonResource
|
||||
'key' => $this->key,
|
||||
'value' => $this->value,
|
||||
'type' => $this->type,
|
||||
'created_at' => $this->created_at->toDateTimeString(),
|
||||
'updated_at' => $this->updated_at->toDateTimeString(),
|
||||
'created_at' => $this->created_at ? $this->created_at->toDateTimeString() : null,
|
||||
'updated_at' => $this->updated_at ? $this->updated_at->toDateTimeString() : null,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,10 +60,10 @@ class ScrapingDataJob implements ShouldQueue
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$data_setting_result = $service_google_sheet->get_big_resume_data();
|
||||
// $data_setting_result = $service_google_sheet->get_big_resume_data();
|
||||
|
||||
BigdataResume::generateResumeData($import_datasource->id, "all", $data_setting_result);
|
||||
BigdataResume::generateResumeData($import_datasource->id, now()->year, $data_setting_result);
|
||||
// BigdataResume::generateResumeData($import_datasource->id, "all", $data_setting_result);
|
||||
// BigdataResume::generateResumeData($import_datasource->id, now()->year, $data_setting_result);
|
||||
|
||||
// Update status to success
|
||||
$import_datasource->update([
|
||||
@@ -79,7 +79,7 @@ class ScrapingDataJob implements ShouldQueue
|
||||
if (isset($import_datasource)) {
|
||||
$import_datasource->update([
|
||||
'status' => 'failed',
|
||||
'response_body' => 'Error: ' . $e->getMessage(),
|
||||
'response_body' => 'Terjadi kesalahan, Syncronize tidak selesai',
|
||||
'finish_time' => now(),
|
||||
'failed_uuid' => $failed_uuid,
|
||||
]);
|
||||
|
||||
131
app/Models/BuildingType.php
Normal file
131
app/Models/BuildingType.php
Normal file
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||
|
||||
class BuildingType extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'code',
|
||||
'name',
|
||||
'parent_id',
|
||||
'level',
|
||||
'is_free',
|
||||
'is_active'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'level' => 'integer',
|
||||
'is_free' => 'boolean',
|
||||
'is_active' => 'boolean'
|
||||
];
|
||||
|
||||
/**
|
||||
* Parent relationship
|
||||
*/
|
||||
public function parent(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(BuildingType::class, 'parent_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Children relationship
|
||||
*/
|
||||
public function children(): HasMany
|
||||
{
|
||||
return $this->hasMany(BuildingType::class, 'parent_id')
|
||||
->where('is_active', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retribution indices relationship
|
||||
*/
|
||||
public function indices(): HasOne
|
||||
{
|
||||
return $this->hasOne(RetributionIndex::class, 'building_type_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculations relationship
|
||||
*/
|
||||
public function calculations(): HasMany
|
||||
{
|
||||
return $this->hasMany(RetributionCalculation::class, 'building_type_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope: Active only
|
||||
*/
|
||||
public function scopeActive($query)
|
||||
{
|
||||
return $query->where('is_active', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope: Parents only
|
||||
*/
|
||||
public function scopeParents($query)
|
||||
{
|
||||
return $query->whereNull('parent_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope: Children only
|
||||
*/
|
||||
public function scopeChildren($query)
|
||||
{
|
||||
return $query->whereNotNull('parent_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope: Non-free types
|
||||
*/
|
||||
public function scopeChargeable($query)
|
||||
{
|
||||
return $query->where('is_free', false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if building type is free
|
||||
*/
|
||||
public function isFree(): bool
|
||||
{
|
||||
return $this->is_free;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this is a parent type
|
||||
*/
|
||||
public function isParent(): bool
|
||||
{
|
||||
return $this->parent_id === null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this is a child type
|
||||
*/
|
||||
public function isChild(): bool
|
||||
{
|
||||
return $this->parent_id !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get complete data for calculation
|
||||
*/
|
||||
public function getCalculationData(): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'code' => $this->code,
|
||||
'name' => $this->name,
|
||||
'coefficient' => $this->coefficient,
|
||||
'is_free' => $this->is_free,
|
||||
'indices' => $this->indices?->toArray(),
|
||||
'parent' => $this->parent?->only(['id', 'code', 'name'])
|
||||
];
|
||||
}
|
||||
}
|
||||
64
app/Models/CalculableRetribution.php
Normal file
64
app/Models/CalculableRetribution.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
|
||||
class CalculableRetribution extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'retribution_calculation_id',
|
||||
'calculable_id',
|
||||
'calculable_type',
|
||||
'is_active',
|
||||
'assigned_at',
|
||||
'notes',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'is_active' => 'boolean',
|
||||
'assigned_at' => 'timestamp',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the owning calculable model (polymorphic)
|
||||
*/
|
||||
public function calculable(): MorphTo
|
||||
{
|
||||
return $this->morphTo();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the retribution calculation
|
||||
*/
|
||||
public function retributionCalculation(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(RetributionCalculation::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope: Only active assignments
|
||||
*/
|
||||
public function scopeActive($query)
|
||||
{
|
||||
return $query->where('is_active', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope: Only inactive assignments
|
||||
*/
|
||||
public function scopeInactive($query)
|
||||
{
|
||||
return $query->where('is_active', false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope: For specific calculable type
|
||||
*/
|
||||
public function scopeForType($query, string $type)
|
||||
{
|
||||
return $query->where('calculable_type', $type);
|
||||
}
|
||||
}
|
||||
55
app/Models/HeightIndex.php
Normal file
55
app/Models/HeightIndex.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class HeightIndex extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'floor_number',
|
||||
'height_index'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'floor_number' => 'integer',
|
||||
'height_index' => 'decimal:6'
|
||||
];
|
||||
|
||||
/**
|
||||
* Get height index by floor number
|
||||
*/
|
||||
public static function getByFloor(int $floorNumber): ?HeightIndex
|
||||
{
|
||||
return self::where('floor_number', $floorNumber)->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get height index value by floor number
|
||||
*/
|
||||
public static function getHeightIndexByFloor(int $floorNumber): float
|
||||
{
|
||||
$index = self::getByFloor($floorNumber);
|
||||
return $index ? (float) $index->height_index : 1.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all height indices as array
|
||||
*/
|
||||
public static function getAllMapping(): array
|
||||
{
|
||||
return self::orderBy('floor_number')
|
||||
->pluck('height_index', 'floor_number')
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available floor numbers
|
||||
*/
|
||||
public static function getAvailableFloors(): array
|
||||
{
|
||||
return self::orderBy('floor_number')
|
||||
->pluck('floor_number')
|
||||
->toArray();
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,10 @@ class PbgTask extends Model
|
||||
return $this->hasOne(PbgTaskIndexIntegrations::class, 'pbg_task_uid', 'uuid');
|
||||
}
|
||||
|
||||
public function pbg_task_detail(){
|
||||
return $this->hasOne(PbgTaskDetail::class, 'pbg_task_uid', 'uuid');
|
||||
}
|
||||
|
||||
public function googleSheet(){
|
||||
return $this->hasOne(PbgTaskGoogleSheet::class, 'no_registrasi', 'registration_number');
|
||||
}
|
||||
|
||||
252
app/Models/PbgTaskDetail.php
Normal file
252
app/Models/PbgTaskDetail.php
Normal file
@@ -0,0 +1,252 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class PbgTaskDetail extends Model
|
||||
{
|
||||
protected $table = 'pbg_task_details';
|
||||
|
||||
protected $fillable = [
|
||||
'pbg_task_uid',
|
||||
'uid',
|
||||
'nik',
|
||||
'type_card',
|
||||
'ownership',
|
||||
'owner_name',
|
||||
'ward_id',
|
||||
'ward_name',
|
||||
'district_id',
|
||||
'district_name',
|
||||
'regency_id',
|
||||
'regency_name',
|
||||
'province_id',
|
||||
'province_name',
|
||||
'address',
|
||||
'owner_email',
|
||||
'owner_phone',
|
||||
'user',
|
||||
'name',
|
||||
'email',
|
||||
'phone',
|
||||
'user_nik',
|
||||
'user_province_id',
|
||||
'user_province_name',
|
||||
'user_regency_id',
|
||||
'user_regency_name',
|
||||
'user_district_id',
|
||||
'user_district_name',
|
||||
'user_address',
|
||||
'status',
|
||||
'status_name',
|
||||
'slf_status',
|
||||
'slf_status_name',
|
||||
'sppst_status',
|
||||
'sppst_file',
|
||||
'sppst_status_name',
|
||||
'file_pbg',
|
||||
'file_pbg_date',
|
||||
'due_date',
|
||||
'start_date',
|
||||
'document_number',
|
||||
'registration_number',
|
||||
'function_type',
|
||||
'application_type',
|
||||
'application_type_name',
|
||||
'consultation_type',
|
||||
'condition',
|
||||
'prototype',
|
||||
'permanency',
|
||||
'building_type',
|
||||
'building_type_name',
|
||||
'building_purpose',
|
||||
'building_use',
|
||||
'occupancy',
|
||||
'name_building',
|
||||
'total_area',
|
||||
'area',
|
||||
'area_type',
|
||||
'height',
|
||||
'floor',
|
||||
'floor_area',
|
||||
'basement',
|
||||
'basement_height',
|
||||
'basement_area',
|
||||
'unit',
|
||||
'prev_retribution',
|
||||
'prev_pbg',
|
||||
'prev_total_area',
|
||||
'koefisien_dasar_bangunan',
|
||||
'koefisien_lantai_bangunan',
|
||||
'koefisien_lantai_hijau',
|
||||
'koefisien_tapak_basement',
|
||||
'ketinggian_bangunan',
|
||||
'jalan_arteri',
|
||||
'jalan_kolektor',
|
||||
'jalan_bangunan',
|
||||
'gsb',
|
||||
'kkr_number',
|
||||
'unit_data',
|
||||
'is_mbr',
|
||||
'code',
|
||||
'building_ward_id',
|
||||
'building_ward_name',
|
||||
'building_district_id',
|
||||
'building_district_name',
|
||||
'building_regency_id',
|
||||
'building_regency_name',
|
||||
'building_province_id',
|
||||
'building_province_name',
|
||||
'building_address',
|
||||
'latitude',
|
||||
'longitude',
|
||||
'building_photo',
|
||||
'pbg_parent',
|
||||
'api_created_at',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'unit_data' => 'array',
|
||||
'is_mbr' => 'boolean',
|
||||
'total_area' => 'decimal:2',
|
||||
'area' => 'decimal:2',
|
||||
'height' => 'decimal:2',
|
||||
'floor_area' => 'decimal:2',
|
||||
'basement_height' => 'decimal:2',
|
||||
'basement_area' => 'decimal:2',
|
||||
'prev_retribution' => 'decimal:2',
|
||||
'prev_total_area' => 'decimal:2',
|
||||
'koefisien_dasar_bangunan' => 'decimal:4',
|
||||
'koefisien_lantai_bangunan' => 'decimal:4',
|
||||
'koefisien_lantai_hijau' => 'decimal:4',
|
||||
'koefisien_tapak_basement' => 'decimal:4',
|
||||
'ketinggian_bangunan' => 'decimal:2',
|
||||
'gsb' => 'decimal:2',
|
||||
'latitude' => 'decimal:8',
|
||||
'longitude' => 'decimal:8',
|
||||
'file_pbg_date' => 'date',
|
||||
'due_date' => 'date',
|
||||
'start_date' => 'date',
|
||||
'api_created_at' => 'datetime',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the PBG task that owns this detail
|
||||
*/
|
||||
public function pbgTask(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(PbgTask::class, 'pbg_task_uid', 'uuid');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create or update PbgTaskDetail from API response
|
||||
*/
|
||||
public static function createFromApiResponse(array $data, string $pbgTaskUuid): self
|
||||
{
|
||||
$detailData = [
|
||||
'pbg_task_uid' => $pbgTaskUuid,
|
||||
'uid' => $data['uid'] ?? null,
|
||||
'nik' => $data['nik'] ?? null,
|
||||
'type_card' => $data['type_card'] ?? null,
|
||||
'ownership' => $data['ownership'] ?? null,
|
||||
'owner_name' => $data['owner_name'] ?? null,
|
||||
'ward_id' => $data['ward_id'] ?? null,
|
||||
'ward_name' => $data['ward_name'] ?? null,
|
||||
'district_id' => $data['district_id'] ?? null,
|
||||
'district_name' => $data['district_name'] ?? null,
|
||||
'regency_id' => $data['regency_id'] ?? null,
|
||||
'regency_name' => $data['regency_name'] ?? null,
|
||||
'province_id' => $data['province_id'] ?? null,
|
||||
'province_name' => $data['province_name'] ?? null,
|
||||
'address' => $data['address'] ?? null,
|
||||
'owner_email' => $data['owner_email'] ?? null,
|
||||
'owner_phone' => $data['owner_phone'] ?? null,
|
||||
'user' => $data['user'] ?? null,
|
||||
'name' => $data['name'] ?? null,
|
||||
'email' => $data['email'] ?? null,
|
||||
'phone' => $data['phone'] ?? null,
|
||||
'user_nik' => $data['user_nik'] ?? null,
|
||||
'user_province_id' => $data['user_province_id'] ?? null,
|
||||
'user_province_name' => $data['user_province_name'] ?? null,
|
||||
'user_regency_id' => $data['user_regency_id'] ?? null,
|
||||
'user_regency_name' => $data['user_regency_name'] ?? null,
|
||||
'user_district_id' => $data['user_district_id'] ?? null,
|
||||
'user_district_name' => $data['user_district_name'] ?? null,
|
||||
'user_address' => $data['user_address'] ?? null,
|
||||
'status' => $data['status'] ?? null,
|
||||
'status_name' => $data['status_name'] ?? null,
|
||||
'slf_status' => $data['slf_status'] ?? null,
|
||||
'slf_status_name' => $data['slf_status_name'] ?? null,
|
||||
'sppst_status' => $data['sppst_status'] ?? null,
|
||||
'sppst_file' => $data['sppst_file'] ?? null,
|
||||
'sppst_status_name' => $data['sppst_status_name'] ?? null,
|
||||
'file_pbg' => $data['file_pbg'] ?? null,
|
||||
'file_pbg_date' => isset($data['file_pbg_date']) ? Carbon::parse($data['file_pbg_date'])->format('Y-m-d') : null,
|
||||
'due_date' => isset($data['due_date']) ? Carbon::parse($data['due_date'])->format('Y-m-d') : null,
|
||||
'start_date' => isset($data['start_date']) ? Carbon::parse($data['start_date'])->format('Y-m-d') : null,
|
||||
'document_number' => $data['document_number'] ?? null,
|
||||
'registration_number' => $data['registration_number'] ?? null,
|
||||
'function_type' => $data['function_type'] ?? null,
|
||||
'application_type' => $data['application_type'] ?? null,
|
||||
'application_type_name' => $data['application_type_name'] ?? null,
|
||||
'consultation_type' => $data['consultation_type'] ?? null,
|
||||
'condition' => $data['condition'] ?? null,
|
||||
'prototype' => $data['prototype'] ?? null,
|
||||
'permanency' => $data['permanency'] ?? null,
|
||||
'building_type' => $data['building_type'] ?? null,
|
||||
'building_type_name' => $data['building_type_name'] ?? null,
|
||||
'building_purpose' => $data['building_purpose'] ?? null,
|
||||
'building_use' => $data['building_use'] ?? null,
|
||||
'occupancy' => $data['occupancy'] ?? null,
|
||||
'name_building' => $data['name_building'] ?? null,
|
||||
'total_area' => $data['total_area'] ?? null,
|
||||
'area' => $data['area'] ?? null,
|
||||
'area_type' => $data['area_type'] ?? null,
|
||||
'height' => $data['height'] ?? null,
|
||||
'floor' => $data['floor'] ?? null,
|
||||
'floor_area' => $data['floor_area'] ?? null,
|
||||
'basement' => $data['basement'] ?? null,
|
||||
'basement_height' => $data['basement_height'] ?? null,
|
||||
'basement_area' => $data['basement_area'] ?? null,
|
||||
'unit' => $data['unit'] ?? null,
|
||||
'prev_retribution' => $data['prev_retribution'] ?? null,
|
||||
'prev_pbg' => $data['prev_pbg'] ?? null,
|
||||
'prev_total_area' => $data['prev_total_area'] ?? null,
|
||||
'koefisien_dasar_bangunan' => $data['koefisien_dasar_bangunan'] ?? null,
|
||||
'koefisien_lantai_bangunan' => $data['koefisien_lantai_bangunan'] ?? null,
|
||||
'koefisien_lantai_hijau' => $data['koefisien_lantai_hijau'] ?? null,
|
||||
'koefisien_tapak_basement' => $data['koefisien_tapak_basement'] ?? null,
|
||||
'ketinggian_bangunan' => $data['ketinggian_bangunan'] ?? null,
|
||||
'jalan_arteri' => $data['jalan_arteri'] ?? null,
|
||||
'jalan_kolektor' => $data['jalan_kolektor'] ?? null,
|
||||
'jalan_bangunan' => $data['jalan_bangunan'] ?? null,
|
||||
'gsb' => $data['gsb'] ?? null,
|
||||
'kkr_number' => $data['kkr_number'] ?? null,
|
||||
'unit_data' => $data['unit_data'] ?? null,
|
||||
'is_mbr' => $data['is_mbr'] ?? false,
|
||||
'code' => $data['code'] ?? null,
|
||||
'building_ward_id' => $data['building_ward_id'] ?? null,
|
||||
'building_ward_name' => $data['building_ward_name'] ?? null,
|
||||
'building_district_id' => $data['building_district_id'] ?? null,
|
||||
'building_district_name' => $data['building_district_name'] ?? null,
|
||||
'building_regency_id' => $data['building_regency_id'] ?? null,
|
||||
'building_regency_name' => $data['building_regency_name'] ?? null,
|
||||
'building_province_id' => $data['building_province_id'] ?? null,
|
||||
'building_province_name' => $data['building_province_name'] ?? null,
|
||||
'building_address' => $data['building_address'] ?? null,
|
||||
'latitude' => $data['latitude'] ?? null,
|
||||
'longitude' => $data['longitude'] ?? null,
|
||||
'building_photo' => $data['building_photo'] ?? null,
|
||||
'pbg_parent' => $data['pbg_parent'] ?? null,
|
||||
'api_created_at' => isset($data['created_at']) ? Carbon::parse($data['created_at'])->format('Y-m-d H:i:s') : null,
|
||||
];
|
||||
|
||||
return static::updateOrCreate(
|
||||
['uid' => $data['uid']],
|
||||
$detailData
|
||||
);
|
||||
}
|
||||
}
|
||||
139
app/Models/RetributionCalculation.php
Normal file
139
app/Models/RetributionCalculation.php
Normal file
@@ -0,0 +1,139 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class RetributionCalculation extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'calculation_id',
|
||||
'building_type_id',
|
||||
'floor_number',
|
||||
'building_area',
|
||||
'retribution_amount',
|
||||
'calculation_detail',
|
||||
'calculated_at',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'building_area' => 'decimal:2',
|
||||
'retribution_amount' => 'decimal:2',
|
||||
'calculation_detail' => 'array',
|
||||
'calculated_at' => 'timestamp',
|
||||
'floor_number' => 'integer',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the building type
|
||||
*/
|
||||
public function buildingType(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(BuildingType::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all calculable assignments
|
||||
*/
|
||||
public function calculableRetributions(): HasMany
|
||||
{
|
||||
return $this->hasMany(CalculableRetribution::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get active assignments only
|
||||
*/
|
||||
public function activeAssignments(): HasMany
|
||||
{
|
||||
return $this->hasMany(CalculableRetribution::class)->where('is_active', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate unique calculation ID
|
||||
*/
|
||||
public static function generateCalculationId(): string
|
||||
{
|
||||
return 'CALC-' . date('Ymd') . '-' . str_pad(mt_rand(1, 9999), 4, '0', STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Boot method to auto-generate calculation_id
|
||||
*/
|
||||
protected static function boot()
|
||||
{
|
||||
parent::boot();
|
||||
|
||||
static::creating(function ($model) {
|
||||
if (empty($model->calculation_id)) {
|
||||
$model->calculation_id = self::generateCalculationId();
|
||||
}
|
||||
if (empty($model->calculated_at)) {
|
||||
$model->calculated_at = now();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if calculation is being used
|
||||
*/
|
||||
public function isInUse(): bool
|
||||
{
|
||||
return $this->activeAssignments()->exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get calculation summary
|
||||
*/
|
||||
public function getSummary(): array
|
||||
{
|
||||
return [
|
||||
'calculation_id' => $this->calculation_id,
|
||||
'building_type' => $this->buildingType->name ?? 'Unknown',
|
||||
'floor_number' => $this->floor_number,
|
||||
'building_area' => $this->building_area,
|
||||
'retribution_amount' => $this->retribution_amount,
|
||||
'calculated_at' => $this->calculated_at->format('Y-m-d H:i:s'),
|
||||
'in_use' => $this->isInUse(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new calculation
|
||||
*/
|
||||
public static function createCalculation(
|
||||
int $buildingTypeId,
|
||||
int $floorNumber,
|
||||
float $buildingArea,
|
||||
float $retributionAmount,
|
||||
array $calculationDetail
|
||||
): self {
|
||||
return self::create([
|
||||
'calculation_id' => self::generateCalculationId(),
|
||||
'building_type_id' => $buildingTypeId,
|
||||
'floor_number' => $floorNumber,
|
||||
'building_area' => $buildingArea,
|
||||
'retribution_amount' => $retributionAmount,
|
||||
'calculation_detail' => $calculationDetail,
|
||||
'calculated_at' => Carbon::now()
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get formatted retribution amount
|
||||
*/
|
||||
public function getFormattedAmount(): string
|
||||
{
|
||||
return 'Rp ' . number_format($this->retribution_amount, 2, ',', '.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get calculation breakdown
|
||||
*/
|
||||
public function getCalculationBreakdown(): array
|
||||
{
|
||||
return $this->calculation_detail ?? [];
|
||||
}
|
||||
}
|
||||
50
app/Models/RetributionConfig.php
Normal file
50
app/Models/RetributionConfig.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class RetributionConfig extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'key',
|
||||
'value',
|
||||
'description',
|
||||
'is_active'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'value' => 'decimal:2',
|
||||
'is_active' => 'boolean'
|
||||
];
|
||||
|
||||
/**
|
||||
* Get config value by key
|
||||
*/
|
||||
public static function getValue(string $key, float $default = 0.0): float
|
||||
{
|
||||
$config = self::where('key', $key)->where('is_active', true)->first();
|
||||
return $config ? (float) $config->value : $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all active configs as array
|
||||
*/
|
||||
public static function getAllActive(): array
|
||||
{
|
||||
return self::where('is_active', true)
|
||||
->pluck('value', 'key')
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update config value
|
||||
*/
|
||||
public static function updateValue(string $key, float $value): bool
|
||||
{
|
||||
return self::updateOrCreate(
|
||||
['key' => $key],
|
||||
['value' => $value, 'is_active' => true]
|
||||
);
|
||||
}
|
||||
}
|
||||
57
app/Models/RetributionIndex.php
Normal file
57
app/Models/RetributionIndex.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class RetributionIndex extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'building_type_id',
|
||||
'coefficient',
|
||||
'ip_permanent',
|
||||
'ip_complexity',
|
||||
'locality_index',
|
||||
'infrastructure_factor',
|
||||
'is_active'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'coefficient' => 'decimal:4',
|
||||
'ip_permanent' => 'decimal:4',
|
||||
'ip_complexity' => 'decimal:4',
|
||||
'locality_index' => 'decimal:4',
|
||||
'infrastructure_factor' => 'decimal:4',
|
||||
'is_active' => 'boolean'
|
||||
];
|
||||
|
||||
/**
|
||||
* Building type relationship
|
||||
*/
|
||||
public function buildingType(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(BuildingType::class, 'building_type_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope: Active only
|
||||
*/
|
||||
public function scopeActive($query)
|
||||
{
|
||||
return $query->where('is_active', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all indices as array
|
||||
*/
|
||||
public function getIndicesArray(): array
|
||||
{
|
||||
return [
|
||||
'ip_permanent' => $this->ip_permanent,
|
||||
'ip_complexity' => $this->ip_complexity,
|
||||
'locality_index' => $this->locality_index,
|
||||
'infrastructure_factor' => $this->infrastructure_factor
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,10 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Traits\HasRetributionCalculation;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
|
||||
/**
|
||||
* Class SpatialPlanning
|
||||
*
|
||||
@@ -23,6 +25,7 @@ use Illuminate\Database\Eloquent\Model;
|
||||
*/
|
||||
class SpatialPlanning extends Model
|
||||
{
|
||||
use HasRetributionCalculation;
|
||||
|
||||
protected $perPage = 20;
|
||||
|
||||
@@ -31,7 +34,35 @@ class SpatialPlanning extends Model
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $fillable = ['name', 'kbli', 'activities', 'area', 'location', 'number', 'date'];
|
||||
protected $fillable = ['name', 'kbli', 'activities', 'area', 'location', 'number', 'date', 'no_tapak', 'no_skkl', 'no_ukl', 'building_function', 'sub_building_function', 'number_of_floors', 'land_area', 'site_bcr'];
|
||||
|
||||
protected $casts = [
|
||||
'area' => 'decimal:6',
|
||||
'land_area' => 'decimal:6',
|
||||
'site_bcr' => 'decimal:6',
|
||||
'number_of_floors' => 'integer',
|
||||
'date' => 'date'
|
||||
];
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Get building function text for detection
|
||||
*/
|
||||
public function getBuildingFunctionText(): string
|
||||
{
|
||||
return $this->building_function ?? $this->activities ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get area for calculation (prioritize area, fallback to land_area)
|
||||
*/
|
||||
public function getCalculationArea(): float
|
||||
{
|
||||
return (float) ($this->area ?? $this->land_area ?? 0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
0
app/Models/User.php
Executable file → Normal file
0
app/Models/User.php
Executable file → Normal file
0
app/Providers/AppServiceProvider.php
Executable file → Normal file
0
app/Providers/AppServiceProvider.php
Executable file → Normal file
0
app/Providers/AuthServiceProvider.php
Executable file → Normal file
0
app/Providers/AuthServiceProvider.php
Executable file → Normal file
0
app/Providers/BroadcastServiceProvider.php
Executable file → Normal file
0
app/Providers/BroadcastServiceProvider.php
Executable file → Normal file
0
app/Providers/EventServiceProvider.php
Executable file → Normal file
0
app/Providers/EventServiceProvider.php
Executable file → Normal file
0
app/Providers/RouteServiceProvider.php
Executable file → Normal file
0
app/Providers/RouteServiceProvider.php
Executable file → Normal file
254
app/Services/RetributionCalculatorService.php
Normal file
254
app/Services/RetributionCalculatorService.php
Normal file
@@ -0,0 +1,254 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\BuildingType;
|
||||
use App\Models\HeightIndex;
|
||||
use App\Models\RetributionConfig;
|
||||
use App\Models\RetributionCalculation;
|
||||
|
||||
class RetributionCalculatorService
|
||||
{
|
||||
/**
|
||||
* Calculate retribution for given parameters
|
||||
*/
|
||||
public function calculate(
|
||||
int $buildingTypeId,
|
||||
int $floorNumber,
|
||||
float $buildingArea,
|
||||
bool $saveResult = true,
|
||||
bool $excelCompatibleMode = false
|
||||
): array {
|
||||
// Get building type with indices
|
||||
$buildingType = BuildingType::with('indices')->findOrFail($buildingTypeId);
|
||||
|
||||
// Check if building type is free
|
||||
if ($buildingType->isFree()) {
|
||||
return $this->createFreeResult($buildingType, $floorNumber, $buildingArea, $saveResult);
|
||||
}
|
||||
|
||||
// Get height index
|
||||
$heightIndex = HeightIndex::getHeightIndexByFloor($floorNumber);
|
||||
|
||||
// Get configuration values
|
||||
$baseValue = RetributionConfig::getValue('BASE_VALUE', 70350);
|
||||
$infrastructureMultiplier = RetributionConfig::getValue('INFRASTRUCTURE_MULTIPLIER', 0.5);
|
||||
$heightMultiplier = RetributionConfig::getValue('HEIGHT_MULTIPLIER', 0.5);
|
||||
|
||||
// Get indices
|
||||
$indices = $buildingType->indices;
|
||||
if (!$indices) {
|
||||
throw new \Exception("Indices not found for building type: {$buildingType->name}");
|
||||
}
|
||||
|
||||
// Calculate using Excel formula
|
||||
$result = $this->executeCalculation(
|
||||
$buildingType,
|
||||
$indices,
|
||||
$heightIndex,
|
||||
$baseValue,
|
||||
$infrastructureMultiplier,
|
||||
$heightMultiplier,
|
||||
$floorNumber,
|
||||
$buildingArea,
|
||||
$excelCompatibleMode
|
||||
);
|
||||
|
||||
// Save result if requested
|
||||
if ($saveResult) {
|
||||
$calculation = RetributionCalculation::createCalculation(
|
||||
$buildingTypeId,
|
||||
$floorNumber,
|
||||
$buildingArea,
|
||||
$result['total_retribution'],
|
||||
$result['calculation_detail']
|
||||
);
|
||||
$result['calculation_id'] = $calculation->calculation_id;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the main calculation logic
|
||||
*/
|
||||
protected function executeCalculation(
|
||||
BuildingType $buildingType,
|
||||
$indices,
|
||||
float $heightIndex,
|
||||
float $baseValue,
|
||||
float $infrastructureMultiplier,
|
||||
float $heightMultiplier,
|
||||
int $floorNumber,
|
||||
float $buildingArea,
|
||||
bool $excelCompatibleMode = false
|
||||
): array {
|
||||
// Step 1: Calculate H5 coefficient (Excel formula: RUNDOWN(($E5*($F5+$G5+(0.5*H$3))),4))
|
||||
// H5 = coefficient * (ip_permanent + ip_complexity + (height_multiplier * height_index))
|
||||
$h5Raw = $indices->coefficient * (
|
||||
$indices->ip_permanent +
|
||||
$indices->ip_complexity +
|
||||
($heightMultiplier * $heightIndex)
|
||||
);
|
||||
|
||||
// Apply RUNDOWN (floor to 4 decimal places)
|
||||
$h5 = floor($h5Raw * 10000) / 10000;
|
||||
|
||||
// Step 2: Main calculation (Excel: 1*D5*(N5*base_value*H5*1))
|
||||
// Main = building_area * locality_index * base_value * h5
|
||||
$mainCalculation = $buildingArea * $indices->locality_index * $baseValue * $h5;
|
||||
|
||||
// Step 3: Infrastructure calculation (Excel: O3*(1*D5*(N5*base_value*H5*1)))
|
||||
// Additional = infrastructure_multiplier * main_calculation
|
||||
$infrastructureCalculation = $infrastructureMultiplier * $mainCalculation;
|
||||
|
||||
// Step 4: Total retribution (Main + Infrastructure)
|
||||
if ($excelCompatibleMode) {
|
||||
// Try to match Excel exactly - round intermediate calculations
|
||||
$mainCalculation = round($mainCalculation, 0);
|
||||
$infrastructureCalculation = round($infrastructureCalculation, 0);
|
||||
$totalRetribution = $mainCalculation + $infrastructureCalculation;
|
||||
} else {
|
||||
// Apply standard rounding to match Excel results more closely
|
||||
$totalRetribution = round($mainCalculation + $infrastructureCalculation, 0);
|
||||
}
|
||||
|
||||
return [
|
||||
'building_type' => [
|
||||
'id' => $buildingType->id,
|
||||
'code' => $buildingType->code,
|
||||
'name' => $buildingType->name,
|
||||
'is_free' => $buildingType->is_free
|
||||
],
|
||||
'input_parameters' => [
|
||||
'building_area' => $buildingArea,
|
||||
'floor_number' => $floorNumber,
|
||||
'height_index' => $heightIndex,
|
||||
'base_value' => $baseValue,
|
||||
'infrastructure_multiplier' => $infrastructureMultiplier,
|
||||
'height_multiplier' => $heightMultiplier
|
||||
],
|
||||
'indices' => [
|
||||
'coefficient' => $indices->coefficient,
|
||||
'ip_permanent' => $indices->ip_permanent,
|
||||
'ip_complexity' => $indices->ip_complexity,
|
||||
'locality_index' => $indices->locality_index,
|
||||
'infrastructure_factor' => $indices->infrastructure_factor
|
||||
],
|
||||
'calculation_steps' => [
|
||||
'h5_coefficient' => [
|
||||
'formula' => 'RUNDOWN((coefficient * (ip_permanent + ip_complexity + (height_multiplier * height_index))), 4)',
|
||||
'calculation' => "RUNDOWN(({$indices->coefficient} * ({$indices->ip_permanent} + {$indices->ip_complexity} + ({$heightMultiplier} * {$heightIndex}))), 4)",
|
||||
'raw_result' => $h5Raw,
|
||||
'result' => $h5
|
||||
],
|
||||
'main_calculation' => [
|
||||
'formula' => 'building_area * locality_index * base_value * h5',
|
||||
'calculation' => "{$buildingArea} * {$indices->locality_index} * {$baseValue} * {$h5}",
|
||||
'result' => $mainCalculation
|
||||
],
|
||||
'infrastructure_calculation' => [
|
||||
'formula' => 'infrastructure_multiplier * main_calculation',
|
||||
'calculation' => "{$infrastructureMultiplier} * {$mainCalculation}",
|
||||
'result' => $infrastructureCalculation
|
||||
],
|
||||
'total_calculation' => [
|
||||
'formula' => 'main_calculation + infrastructure_calculation',
|
||||
'calculation' => "{$mainCalculation} + {$infrastructureCalculation}",
|
||||
'result' => $totalRetribution
|
||||
]
|
||||
],
|
||||
'total_retribution' => $totalRetribution,
|
||||
'formatted_amount' => 'Rp ' . number_format($totalRetribution, 2, ',', '.'),
|
||||
'calculation_detail' => [
|
||||
'h5_raw' => $h5Raw,
|
||||
'h5' => $h5,
|
||||
'main' => $mainCalculation,
|
||||
'infrastructure' => $infrastructureCalculation,
|
||||
'total' => $totalRetribution
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create result for free building types
|
||||
*/
|
||||
protected function createFreeResult(
|
||||
BuildingType $buildingType,
|
||||
int $floorNumber,
|
||||
float $buildingArea,
|
||||
bool $saveResult
|
||||
): array {
|
||||
$result = [
|
||||
'building_type' => [
|
||||
'id' => $buildingType->id,
|
||||
'code' => $buildingType->code,
|
||||
'name' => $buildingType->name,
|
||||
'is_free' => true
|
||||
],
|
||||
'input_parameters' => [
|
||||
'building_area' => $buildingArea,
|
||||
'floor_number' => $floorNumber
|
||||
],
|
||||
'total_retribution' => 0.0,
|
||||
'formatted_amount' => 'Rp 0 (Gratis)',
|
||||
'calculation_detail' => [
|
||||
'reason' => 'Building type is free of charge',
|
||||
'total' => 0.0
|
||||
]
|
||||
];
|
||||
|
||||
if ($saveResult) {
|
||||
$calculation = RetributionCalculation::createCalculation(
|
||||
$buildingType->id,
|
||||
$floorNumber,
|
||||
$buildingArea,
|
||||
0.0,
|
||||
$result['calculation_detail']
|
||||
);
|
||||
$result['calculation_id'] = $calculation->calculation_id;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get calculation by ID
|
||||
*/
|
||||
public function getCalculationById(string $calculationId): ?RetributionCalculation
|
||||
{
|
||||
return RetributionCalculation::with('buildingType')
|
||||
->where('calculation_id', $calculationId)
|
||||
->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all available building types for calculation
|
||||
*/
|
||||
public function getAvailableBuildingTypes(): array
|
||||
{
|
||||
return BuildingType::with('indices')
|
||||
->active()
|
||||
->children() // Only child types can be used for calculation
|
||||
->get()
|
||||
->map(function ($type) {
|
||||
return [
|
||||
'id' => $type->id,
|
||||
'code' => $type->code,
|
||||
'name' => $type->name,
|
||||
'is_free' => $type->is_free,
|
||||
'has_indices' => $type->indices !== null,
|
||||
'coefficient' => $type->indices ? $type->indices->coefficient : null
|
||||
];
|
||||
})
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all available floor numbers
|
||||
*/
|
||||
public function getAvailableFloors(): array
|
||||
{
|
||||
return HeightIndex::getAvailableFloors();
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\BigdataResume;
|
||||
use App\Models\DataSetting;
|
||||
use App\Models\ImportDatasource;
|
||||
use App\Models\PbgTaskGoogleSheet;
|
||||
use App\Models\SpatialPlanning;
|
||||
use App\Models\RetributionCalculation;
|
||||
use Carbon\Carbon;
|
||||
use Exception;
|
||||
use Google_Client;
|
||||
use Google_Service_Sheets;
|
||||
use Log;
|
||||
use Google\Client as Google_Client;
|
||||
use Google\Service\Sheets as Google_Service_Sheets;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
class ServiceGoogleSheet
|
||||
{
|
||||
protected $client;
|
||||
@@ -107,7 +111,7 @@ class ServiceGoogleSheet
|
||||
'tanggal_skrd' => $this->convertToDate($cleanValue($row[33] ?? null)),
|
||||
'ptsp' => $cleanValue($row[34] ?? null),
|
||||
'selesai_terbit' => $cleanValue($row[35] ?? null),
|
||||
'tanggal_pembayaran' => $cleanValue($row[36] ?? null),
|
||||
'tanggal_pembayaran' => $this->convertToDate($cleanValue($row[36] ?? null)),
|
||||
'format_sts' => $cleanValue($row[37] ?? null),
|
||||
'tahun_terbit' => (int) $cleanValue($row[38] ?? null),
|
||||
'tahun_berjalan' => (int) $cleanValue($row[39] ?? null),
|
||||
@@ -210,6 +214,110 @@ class ServiceGoogleSheet
|
||||
}
|
||||
}
|
||||
|
||||
public function sync_leader_data(){
|
||||
$import_datasource = ImportDatasource::create([
|
||||
'message' => 'Processing leader data',
|
||||
'status' => 'processing',
|
||||
'start_time' => now(),
|
||||
'failed_uuid' => null
|
||||
]);
|
||||
try {
|
||||
$sections = [
|
||||
'TARGET_PAD' => "TARGET PAD 2024",
|
||||
'KEKURANGAN_POTENSI' => "DEVIASI TARGET DENGAN POTENSI TOTAL BERKAS",
|
||||
'TOTAL_POTENSI_BERKAS' => "•TOTAL BERKAS 2025",
|
||||
'BELUM_TERVERIFIKASI' => "•BERKAS AKTUAL BELUM TERVERIFIKASI (POTENSI):",
|
||||
'TERVERIFIKASI' => "•BERKAS AKTUAL TERVERIFIKASI DINAS TEKNIS 2025:",
|
||||
'NON_USAHA' => "•NON USAHA: HUNIAN, SOSBUD, KEAGAMAAN",
|
||||
'USAHA' => "•USAHA: USAHA, CAMPURAN, KOLEKTIF, PRASARANA",
|
||||
'PROSES_DINAS_TEKNIS' => "•TERPROSES DI DPUTR: belum selesai rekomtek'",
|
||||
'WAITING_KLIK_DPMPTSP' => "•TERPROSES DI PTSP: Pengiriman SKRD/ Validasi di PTSP",
|
||||
'REALISASI_TERBIT_PBG' => "•BERKAS YANG TERBIT PBG 2025:"
|
||||
];
|
||||
|
||||
$result = [];
|
||||
|
||||
foreach ($sections as $key => $identifier) {
|
||||
$values = $this->get_values_from_section($identifier, [10, 11], 2);
|
||||
|
||||
if (!empty($values)) {
|
||||
$result[$key] = [
|
||||
'identifier' => $identifier,
|
||||
'total' => $values[0] ?? null, // index 0 untuk total/jumlah
|
||||
'nominal' => $values[1] ?? null // index 1 untuk nominal
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
BigdataResume::create([
|
||||
'import_datasource_id' => $import_datasource->id,
|
||||
'year' => 'leader',
|
||||
// USAHA
|
||||
'business_count' => $this->convertToInteger($result['USAHA']['total'] ?? null) ?? 0,
|
||||
'business_sum' => $this->convertToDecimal($result['USAHA']['nominal'] ?? null) ?? 0,
|
||||
// NON USAHA
|
||||
'non_business_count' => $this->convertToInteger($result['NON_USAHA']['total'] ?? null) ?? 0,
|
||||
'non_business_sum' => $this->convertToDecimal($result['NON_USAHA']['nominal'] ?? null) ?? 0,
|
||||
// TERVERIFIKASI
|
||||
'verified_count' => $this->convertToInteger($result['TERVERIFIKASI']['total'] ?? null) ?? 0,
|
||||
'verified_sum' => $this->convertToDecimal($result['TERVERIFIKASI']['nominal'] ?? null) ?? 0,
|
||||
// BELUM TERVERIFIKASI
|
||||
'non_verified_count' => $this->convertToInteger($result['BELUM_TERVERIFIKASI']['total'] ?? null) ?? 0,
|
||||
'non_verified_sum' => $this->convertToDecimal($result['BELUM_TERVERIFIKASI']['nominal'] ?? null) ?? 0,
|
||||
// TOTAL POTENSI BERKAS
|
||||
'potention_count' => $this->convertToInteger($result['TOTAL_POTENSI_BERKAS']['total'] ?? null) ?? 0,
|
||||
'potention_sum' => $this->convertToDecimal($result['TOTAL_POTENSI_BERKAS']['nominal'] ?? null) ?? 0,
|
||||
// REALISASI TERBIT PBG
|
||||
'issuance_realization_pbg_count' => $this->convertToInteger($result['REALISASI_TERBIT_PBG']['total'] ?? null) ?? 0,
|
||||
'issuance_realization_pbg_sum' => $this->convertToDecimal($result['REALISASI_TERBIT_PBG']['nominal'] ?? null) ?? 0,
|
||||
// WAITING KLIK DPMPTSP
|
||||
'waiting_click_dpmptsp_count' => $this->convertToInteger($result['WAITING_KLIK_DPMPTSP']['total'] ?? null) ?? 0,
|
||||
'waiting_click_dpmptsp_sum' => $this->convertToDecimal($result['WAITING_KLIK_DPMPTSP']['nominal'] ?? null) ?? 0,
|
||||
// PROSES DINAS TEKNIS
|
||||
'process_in_technical_office_count' => $this->convertToInteger($result['PROSES_DINAS_TEKNIS']['total'] ?? null) ?? 0,
|
||||
'process_in_technical_office_sum' => $this->convertToDecimal($result['PROSES_DINAS_TEKNIS']['nominal'] ?? null) ?? 0,
|
||||
// TATA RUANG
|
||||
'spatial_count' => $this->getSpatialPlanningWithCalculationCount(),
|
||||
'spatial_sum' => $this->getSpatialPlanningCalculationSum()
|
||||
]);
|
||||
|
||||
// Save data settings
|
||||
$dataSettings = [
|
||||
'TARGET_PAD' => $result['TARGET_PAD']['nominal'] ?? null,
|
||||
'KEKURANGAN_POTENSI' => $result['KEKURANGAN_POTENSI']['nominal'] ?? null,
|
||||
'REALISASI_TERBIT_PBG_COUNT' => $result['REALISASI_TERBIT_PBG']['total'] ?? null,
|
||||
'REALISASI_TERBIT_PBG_SUM' => $result['REALISASI_TERBIT_PBG']['nominal'] ?? null,
|
||||
'MENUNGGU_KLIK_DPMPTSP_COUNT' => $result['WAITING_KLIK_DPMPTSP']['total'] ?? null,
|
||||
'MENUNGGU_KLIK_DPMPTSP_SUM' => $result['WAITING_KLIK_DPMPTSP']['nominal'] ?? null,
|
||||
'PROSES_DINAS_TEKNIS_COUNT' => $result['PROSES_DINAS_TEKNIS']['total'] ?? null,
|
||||
'PROSES_DINAS_TEKNIS_SUM' => $result['PROSES_DINAS_TEKNIS']['nominal'] ?? null,
|
||||
];
|
||||
|
||||
foreach ($dataSettings as $key => $value) {
|
||||
DataSetting::updateOrInsert(
|
||||
['key' => $key],
|
||||
['value' => $this->convertToInteger($value) ?? 0]
|
||||
);
|
||||
}
|
||||
|
||||
$import_datasource->update([
|
||||
'status' => 'success',
|
||||
'response_body' => json_encode($result),
|
||||
'message' => 'Leader data synced',
|
||||
'finish_time' => now()
|
||||
]);
|
||||
return $result;
|
||||
} catch (\Exception $e) {
|
||||
Log::error("Error syncing leader data", ['error' => $e->getMessage()]);
|
||||
$import_datasource->update([
|
||||
'status' => 'failed',
|
||||
'message' => 'Leader data sync failed',
|
||||
'finish_time' => now()
|
||||
]);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
public function get_big_resume_data(){
|
||||
try {
|
||||
$sheet_big_data = $this->get_data_by_sheet();
|
||||
@@ -261,9 +369,71 @@ class ServiceGoogleSheet
|
||||
return!empty($values)? $values : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get specific values from a row that contains a specific text/section identifier
|
||||
* @param string $section_identifier Text to search for in the row
|
||||
* @param array $column_indices Array of column indices to extract values from
|
||||
* @param int $no_sheet Sheet number (0-based)
|
||||
* @return array Array of values from specified columns, or empty array if section not found
|
||||
*/
|
||||
private function get_values_from_section(string $section_identifier, array $column_indices = [], int $no_sheet = 1) {
|
||||
try {
|
||||
$sheet_data = $this->get_data_by_sheet($no_sheet);
|
||||
|
||||
if (empty($sheet_data)) {
|
||||
Log::warning("No data found in sheet", ['sheet' => $no_sheet]);
|
||||
return [];
|
||||
}
|
||||
|
||||
// Search for the row containing the section identifier
|
||||
$target_row = null;
|
||||
foreach ($sheet_data as $row_index => $row) {
|
||||
if (is_array($row)) {
|
||||
foreach ($row as $cell) {
|
||||
if (is_string($cell) && strpos($cell, $section_identifier) !== false) {
|
||||
$target_row = $row;
|
||||
break 2; // Break out of both loops
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($target_row === null) {
|
||||
Log::warning("Section not found", ['section_identifier' => $section_identifier]);
|
||||
return [];
|
||||
}
|
||||
|
||||
// Extract values from specified column indices
|
||||
$extracted_values = [];
|
||||
foreach ($column_indices as $col_index) {
|
||||
if (isset($target_row[$col_index])) {
|
||||
$value = trim($target_row[$col_index]);
|
||||
$extracted_values[] = $value !== '' ? $value : null;
|
||||
} else {
|
||||
$extracted_values[] = null;
|
||||
}
|
||||
}
|
||||
|
||||
Log::info("Values extracted from section", [
|
||||
'section_identifier' => $section_identifier,
|
||||
'column_indices' => $column_indices,
|
||||
'extracted_values' => $extracted_values
|
||||
]);
|
||||
|
||||
return $extracted_values;
|
||||
} catch (\Exception $e) {
|
||||
Log::error("Error getting values from section", [
|
||||
'error' => $e->getMessage(),
|
||||
'section_identifier' => $section_identifier,
|
||||
'sheet' => $no_sheet
|
||||
]);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
private function convertToInteger($value) {
|
||||
// Check if the value is an empty string, and return null if true
|
||||
if (trim($value) === "") {
|
||||
// Check if the value is null or empty string, and return null if true
|
||||
if ($value === null || trim($value) === "") {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -300,6 +470,48 @@ class ServiceGoogleSheet
|
||||
return is_numeric($value) ? (float) number_format((float) $value, 2, '.', '') : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get count of spatial plannings that have active retribution calculations
|
||||
*/
|
||||
public function getSpatialPlanningWithCalculationCount(): int
|
||||
{
|
||||
try {
|
||||
return SpatialPlanning::whereHas('retributionCalculations', function ($query) {
|
||||
$query->where('is_active', true);
|
||||
})->count();
|
||||
} catch (\Exception $e) {
|
||||
Log::error("Error getting spatial planning with calculation count", ['error' => $e->getMessage()]);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get total sum of retribution amounts for spatial plannings with active calculations
|
||||
*/
|
||||
public function getSpatialPlanningCalculationSum(): float
|
||||
{
|
||||
try {
|
||||
// Get all spatial plannings that have active calculations
|
||||
$spatialPlannings = SpatialPlanning::whereHas('retributionCalculations', function ($query) {
|
||||
$query->where('is_active', true);
|
||||
})->with(['retributionCalculations.retributionCalculation'])
|
||||
->get();
|
||||
|
||||
$totalSum = 0;
|
||||
foreach ($spatialPlannings as $spatialPlanning) {
|
||||
$activeCalculation = $spatialPlanning->activeRetributionCalculation;
|
||||
if ($activeCalculation && $activeCalculation->retributionCalculation) {
|
||||
$totalSum += $activeCalculation->retributionCalculation->retribution_amount;
|
||||
}
|
||||
}
|
||||
|
||||
return (float) $totalSum;
|
||||
} catch (\Exception $e) {
|
||||
Log::error("Error getting spatial planning calculation sum", ['error' => $e->getMessage()]);
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
private function convertToDate($dateString)
|
||||
{
|
||||
try {
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Services;
|
||||
|
||||
use App\Models\GlobalSetting;
|
||||
use App\Models\PbgTask;
|
||||
use App\Models\PbgTaskDetail;
|
||||
use App\Models\PbgTaskIndexIntegrations;
|
||||
use App\Models\PbgTaskPrasarana;
|
||||
use App\Models\PbgTaskRetributions;
|
||||
@@ -53,6 +54,7 @@ class ServiceTabPbgTask
|
||||
}
|
||||
try{
|
||||
$this->current_uuid = $pbg_task->uuid;
|
||||
$this->scraping_task_details($pbg_task->uuid);
|
||||
$this->scraping_task_assignments($pbg_task->uuid);
|
||||
$this->scraping_task_retributions($pbg_task->uuid);
|
||||
$this->scraping_task_integrations($pbg_task->uuid);
|
||||
@@ -71,6 +73,75 @@ class ServiceTabPbgTask
|
||||
return $this->current_uuid;
|
||||
}
|
||||
|
||||
private function scraping_task_details($uuid)
|
||||
{
|
||||
$url = "{$this->simbg_host}/api/pbg/v1/detail/{$uuid}/";
|
||||
$options = [
|
||||
'headers' => [
|
||||
'Authorization' => "Bearer {$this->user_token}",
|
||||
'Content-Type' => 'application/json'
|
||||
]
|
||||
];
|
||||
|
||||
$maxRetries = 3;
|
||||
$initialDelay = 1;
|
||||
$retriedAfter401 = false;
|
||||
|
||||
for ($retryCount = 0; $retryCount < $maxRetries; $retryCount++) {
|
||||
try {
|
||||
$response = $this->client->get($url, $options);
|
||||
$responseData = json_decode($response->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR);
|
||||
|
||||
if (empty($responseData['data']) || !is_array($responseData['data'])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$data = $responseData['data'];
|
||||
|
||||
// Use the static method from PbgTaskDetail model to create/update
|
||||
PbgTaskDetail::createFromApiResponse($data, $uuid);
|
||||
|
||||
return $responseData;
|
||||
} catch (\GuzzleHttp\Exception\ClientException $e) {
|
||||
if ($e->getCode() === 401 && !$retriedAfter401) {
|
||||
Log::warning("401 Unauthorized - Refreshing token and retrying...");
|
||||
try{
|
||||
$this->refreshToken();
|
||||
$options['headers']['Authorization'] = "Bearer {$this->user_token}";
|
||||
$retriedAfter401 = true;
|
||||
continue;
|
||||
}catch(\Exception $refreshError){
|
||||
Log::error("Token refresh and login failed: " . $refreshError->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
} catch (\GuzzleHttp\Exception\ServerException | \GuzzleHttp\Exception\ConnectException $e) {
|
||||
if ($e->getCode() === 502) {
|
||||
Log::warning("502 Bad Gateway - Retrying in {$initialDelay} seconds...");
|
||||
} else {
|
||||
Log::error("Network error ({$e->getCode()}) - Retrying in {$initialDelay} seconds...");
|
||||
}
|
||||
|
||||
sleep($initialDelay);
|
||||
$initialDelay *= 2;
|
||||
} catch (\GuzzleHttp\Exception\RequestException $e) {
|
||||
Log::error("Request error ({$e->getCode()}): " . $e->getMessage());
|
||||
return false;
|
||||
} catch (\JsonException $e) {
|
||||
Log::error("JSON decoding error: " . $e->getMessage());
|
||||
return false;
|
||||
} catch (\Throwable $e) {
|
||||
Log::critical("Unhandled error: " . $e->getMessage(), ['trace' => $e->getTraceAsString()]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Log::error("Failed to fetch task details for UUID {$uuid} after {$maxRetries} retries.");
|
||||
throw new \Exception("Failed to fetch task details for UUID {$uuid} after retries.");
|
||||
}
|
||||
|
||||
private function scraping_task_assignments($uuid)
|
||||
{
|
||||
$url = "{$this->simbg_host}/api/pbg/v1/list-tim-penilai/{$uuid}/?page=1&size=10";
|
||||
|
||||
79
app/Traits/HasRetributionCalculation.php
Normal file
79
app/Traits/HasRetributionCalculation.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace App\Traits;
|
||||
|
||||
use App\Models\RetributionCalculation;
|
||||
use App\Models\CalculableRetribution;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphMany;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphOne;
|
||||
|
||||
trait HasRetributionCalculation
|
||||
{
|
||||
/**
|
||||
* Get all retribution calculations for this model (polymorphic many-to-many)
|
||||
*/
|
||||
public function retributionCalculations(): MorphMany
|
||||
{
|
||||
return $this->morphMany(CalculableRetribution::class, 'calculable');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get active retribution calculation
|
||||
*/
|
||||
public function activeRetributionCalculation(): MorphOne
|
||||
{
|
||||
return $this->morphOne(CalculableRetribution::class, 'calculable')
|
||||
->where('is_active', true)
|
||||
->latest('assigned_at');
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign calculation to this model
|
||||
*/
|
||||
public function assignRetributionCalculation(RetributionCalculation $calculation, string $notes = null): CalculableRetribution
|
||||
{
|
||||
// Deactivate previous active calculation
|
||||
$this->retributionCalculations()
|
||||
->where('is_active', true)
|
||||
->update(['is_active' => false]);
|
||||
|
||||
// Create new assignment
|
||||
return $this->retributionCalculations()->create([
|
||||
'retribution_calculation_id' => $calculation->id,
|
||||
'is_active' => true,
|
||||
'assigned_at' => now(),
|
||||
'notes' => $notes,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current retribution amount
|
||||
*/
|
||||
public function getCurrentRetributionAmount(): float
|
||||
{
|
||||
$activeCalculation = $this->activeRetributionCalculation;
|
||||
|
||||
return $activeCalculation
|
||||
? $activeCalculation->retributionCalculation->retribution_amount
|
||||
: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if has active calculation
|
||||
*/
|
||||
public function hasActiveRetributionCalculation(): bool
|
||||
{
|
||||
return $this->activeRetributionCalculation()->exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get calculation history
|
||||
*/
|
||||
public function getRetributionCalculationHistory()
|
||||
{
|
||||
return $this->retributionCalculations()
|
||||
->with('retributionCalculation')
|
||||
->orderBy('assigned_at', 'desc')
|
||||
->get();
|
||||
}
|
||||
}
|
||||
0
bootstrap/app.php
Executable file → Normal file
0
bootstrap/app.php
Executable file → Normal file
0
bootstrap/providers.php
Executable file → Normal file
0
bootstrap/providers.php
Executable file → Normal file
34
build-and-zip.sh
Executable file
34
build-and-zip.sh
Executable file
@@ -0,0 +1,34 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Build and Zip Script for Laravel Vite Project
|
||||
echo "🚀 Starting build process..."
|
||||
|
||||
# Clean previous build
|
||||
echo "🧹 Cleaning previous build..."
|
||||
rm -rf public/build
|
||||
rm -f build.zip
|
||||
|
||||
# Run npm build
|
||||
echo "📦 Building assets with Vite..."
|
||||
npm run build
|
||||
|
||||
# Check if build was successful
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✅ Build completed successfully!"
|
||||
|
||||
# Create zip file
|
||||
echo "📁 Creating build.zip..."
|
||||
cd public && zip -r ../build.zip build/
|
||||
cd ..
|
||||
|
||||
echo "✅ build.zip created successfully!"
|
||||
echo "📊 Build folder size:"
|
||||
du -sh public/build
|
||||
echo "📊 Zip file size:"
|
||||
du -sh build.zip
|
||||
|
||||
echo "🎉 Process completed! You can now upload build.zip to your server."
|
||||
else
|
||||
echo "❌ Build failed! Please check the errors above."
|
||||
exit 1
|
||||
fi
|
||||
0
composer.json
Executable file → Normal file
0
composer.json
Executable file → Normal file
0
config/app.php
Executable file → Normal file
0
config/app.php
Executable file → Normal file
0
config/auth.php
Executable file → Normal file
0
config/auth.php
Executable file → Normal file
0
config/cache.php
Executable file → Normal file
0
config/cache.php
Executable file → Normal file
0
config/database.php
Executable file → Normal file
0
config/database.php
Executable file → Normal file
0
config/filesystems.php
Executable file → Normal file
0
config/filesystems.php
Executable file → Normal file
0
config/logging.php
Executable file → Normal file
0
config/logging.php
Executable file → Normal file
0
config/mail.php
Executable file → Normal file
0
config/mail.php
Executable file → Normal file
0
config/queue.php
Executable file → Normal file
0
config/queue.php
Executable file → Normal file
0
config/services.php
Executable file → Normal file
0
config/services.php
Executable file → Normal file
0
config/session.php
Executable file → Normal file
0
config/session.php
Executable file → Normal file
0
database/.gitignore
vendored
Executable file → Normal file
0
database/.gitignore
vendored
Executable file → Normal file
0
database/factories/UserFactory.php
Executable file → Normal file
0
database/factories/UserFactory.php
Executable file → Normal file
0
database/migrations/0001_01_01_000000_create_users_table.php
Executable file → Normal file
0
database/migrations/0001_01_01_000000_create_users_table.php
Executable file → Normal file
0
database/migrations/0001_01_01_000001_create_cache_table.php
Executable file → Normal file
0
database/migrations/0001_01_01_000001_create_cache_table.php
Executable file → Normal file
0
database/migrations/0001_01_01_000002_create_jobs_table.php
Executable file → Normal file
0
database/migrations/0001_01_01_000002_create_jobs_table.php
Executable file → Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('pbg_task', function(Blueprint $table){
|
||||
$table->text('address')->nullable()->change();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('spatial_plannings', function (Blueprint $table) {
|
||||
$table->text('no_tapak')->nullable();
|
||||
$table->text('no_skkl')->nullable();
|
||||
$table->text('no_ukl')->nullable();
|
||||
$table->string('building_function')->nullable();
|
||||
$table->string('sub_building_function')->nullable();
|
||||
$table->integer('number_of_floors')->default(1);
|
||||
$table->decimal('land_area', 18, 6)->nullable();
|
||||
$table->decimal('site_bcr', 10, 6)->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('spatial_plannings', function (Blueprint $table) {
|
||||
$table->dropColumn([
|
||||
'no_tapak',
|
||||
'no_skkl',
|
||||
'no_ukl',
|
||||
'building_function',
|
||||
'sub_building_function',
|
||||
'number_of_floors',
|
||||
'land_area',
|
||||
'site_bcr',
|
||||
]);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('building_functions', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('code')->unique()->comment('Kode unik fungsi bangunan');
|
||||
$table->string('name', 255)->comment('Nama fungsi bangunan');
|
||||
$table->text('description')->nullable()->comment('Deskripsi detail fungsi bangunan');
|
||||
$table->unsignedBigInteger('parent_id')->nullable()->comment('ID parent untuk hierarki');
|
||||
$table->foreign('parent_id')->references('id')->on('building_functions')->onDelete('cascade');
|
||||
$table->integer('level')->default(0)->comment('Level hierarki (0=root, 1=child, dst)');
|
||||
$table->integer('sort_order')->default(0)->comment('Urutan tampilan');
|
||||
$table->decimal('base_tariff', 15, 2)->nullable()->comment('Tarif dasar per m2');
|
||||
|
||||
// Indexes untuk performa
|
||||
$table->index(['parent_id', 'level']);
|
||||
$table->index(['level', 'sort_order']);
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('building_functions');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('floor_height_indices', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->integer('floor_number')->comment('Nomor lantai');
|
||||
$table->decimal('ip_ketinggian', 10, 6)->comment('Indeks ketinggian per lantai');
|
||||
$table->text('description')->nullable()->comment('Deskripsi indeks ketinggian');
|
||||
$table->timestamps();
|
||||
|
||||
// Unique constraint untuk floor_number
|
||||
$table->unique('floor_number');
|
||||
$table->index('floor_number');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('floor_height_indices');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('building_function_parameters', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('building_function_id')->constrained('building_functions')->onDelete('cascade');
|
||||
$table->decimal('fungsi_bangunan', 10, 6)->nullable()->comment('Parameter fungsi bangunan');
|
||||
$table->decimal('ip_permanen', 10, 6)->nullable()->comment('Parameter IP permanen');
|
||||
$table->decimal('ip_kompleksitas', 10, 6)->nullable()->comment('Parameter IP kompleksitas');
|
||||
$table->decimal('indeks_lokalitas', 10, 6)->nullable()->comment('Parameter indeks lokalitas');
|
||||
$table->decimal('asumsi_prasarana', 8, 6)->nullable()->comment('Parameter asumsi prasarana untuk perhitungan retribusi');
|
||||
$table->decimal('koefisien_dasar', 15, 6)->nullable()->comment('Koefisien dasar perhitungan');
|
||||
$table->timestamps();
|
||||
|
||||
// Unique constraint untuk 1:1 relationship
|
||||
$table->unique('building_function_id');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('building_function_parameters');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('retribution_formulas', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('building_function_id')->constrained('building_functions')->onDelete('cascade');
|
||||
$table->string('name', 255)->comment('Nama formula');
|
||||
$table->integer('floor_number')->comment('Nomor lantai (1, 2, 3, dst, 0=semua lantai)');
|
||||
$table->text('formula_expression')->comment('Rumus matematika untuk perhitungan');
|
||||
$table->timestamps();
|
||||
|
||||
// Indexes untuk performa
|
||||
$table->index(['building_function_id', 'floor_number'], 'idx_building_floor');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('retribution_formulas');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('retribution_proposals', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('spatial_planning_id')->nullable()->constrained('spatial_plannings')->onDelete('cascade');
|
||||
$table->foreignId('building_function_id')->constrained('building_functions')->onDelete('cascade');
|
||||
$table->foreignId('retribution_formula_id')->constrained('retribution_formulas')->onDelete('cascade');
|
||||
$table->string('proposal_number')->unique()->comment('Nomor usulan retribusi');
|
||||
$table->integer('floor_number')->comment('Nomor lantai (1, 2, 3, dst)');
|
||||
$table->decimal('floor_area', 15, 6)->comment('Luas lantai ini (m2)');
|
||||
$table->decimal('total_building_area', 15, 6)->comment('Total luas bangunan (m2)');
|
||||
$table->decimal('ip_ketinggian', 10, 6)->comment('IP ketinggian untuk lantai ini');
|
||||
$table->decimal('floor_retribution_amount', 15, 2)->comment('Jumlah retribusi untuk lantai ini');
|
||||
$table->decimal('total_retribution_amount', 15, 2)->comment('Total retribusi keseluruhan');
|
||||
$table->json('calculation_parameters')->nullable()->comment('Parameter yang digunakan dalam perhitungan');
|
||||
$table->json('calculation_breakdown')->nullable()->comment('Breakdown detail perhitungan');
|
||||
$table->text('notes')->nullable()->comment('Catatan tambahan');
|
||||
$table->datetime('calculated_at')->comment('Waktu perhitungan dilakukan');
|
||||
$table->timestamps();
|
||||
|
||||
// Indexes untuk performa
|
||||
$table->index(['spatial_planning_id', 'floor_number'], 'idx_spatial_floor');
|
||||
$table->index(['building_function_id'], 'idx_function');
|
||||
$table->index('calculated_at');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('retribution_proposals');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,166 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('pbg_task_details', function (Blueprint $table) {
|
||||
$table->id();
|
||||
|
||||
// Foreign key relationship
|
||||
$table->string('pbg_task_uid')->index();
|
||||
|
||||
// Basic information
|
||||
$table->string('uid')->unique();
|
||||
$table->string('nik');
|
||||
$table->string('type_card');
|
||||
$table->string('ownership')->nullable();
|
||||
$table->string('owner_name');
|
||||
|
||||
// Owner location information
|
||||
$table->bigInteger('ward_id');
|
||||
$table->string('ward_name');
|
||||
$table->integer('district_id');
|
||||
$table->string('district_name');
|
||||
$table->integer('regency_id');
|
||||
$table->string('regency_name');
|
||||
$table->integer('province_id');
|
||||
$table->string('province_name');
|
||||
$table->text('address');
|
||||
|
||||
// Owner contact information
|
||||
$table->string('owner_email');
|
||||
$table->string('owner_phone');
|
||||
|
||||
// User information
|
||||
$table->integer('user');
|
||||
$table->string('name');
|
||||
$table->string('email');
|
||||
$table->string('phone');
|
||||
$table->string('user_nik');
|
||||
|
||||
// User location information
|
||||
$table->integer('user_province_id');
|
||||
$table->string('user_province_name');
|
||||
$table->integer('user_regency_id');
|
||||
$table->string('user_regency_name');
|
||||
$table->integer('user_district_id');
|
||||
$table->string('user_district_name');
|
||||
$table->text('user_address');
|
||||
|
||||
// Status information
|
||||
$table->integer('status');
|
||||
$table->string('status_name');
|
||||
$table->integer('slf_status')->nullable();
|
||||
$table->string('slf_status_name')->nullable();
|
||||
$table->integer('sppst_status');
|
||||
$table->string('sppst_file')->nullable();
|
||||
$table->string('sppst_status_name');
|
||||
|
||||
// Files and documents
|
||||
$table->string('file_pbg')->nullable();
|
||||
$table->date('file_pbg_date')->nullable();
|
||||
$table->date('due_date')->nullable();
|
||||
$table->date('start_date');
|
||||
$table->string('document_number')->nullable();
|
||||
$table->string('registration_number');
|
||||
|
||||
// Application information
|
||||
$table->string('function_type')->nullable();
|
||||
$table->string('application_type')->nullable();
|
||||
$table->string('application_type_name')->nullable();
|
||||
$table->string('consultation_type')->nullable();
|
||||
$table->string('condition')->nullable();
|
||||
$table->string('prototype')->nullable();
|
||||
$table->string('permanency')->nullable();
|
||||
|
||||
// Building information
|
||||
$table->integer('building_type')->nullable();
|
||||
$table->string('building_type_name')->nullable();
|
||||
$table->string('building_purpose')->nullable();
|
||||
$table->string('building_use')->nullable();
|
||||
$table->string('occupancy')->nullable();
|
||||
$table->string('name_building')->nullable();
|
||||
|
||||
// Building dimensions and specifications
|
||||
$table->decimal('total_area', 10, 2);
|
||||
$table->decimal('area', 10, 2)->nullable();
|
||||
$table->string('area_type')->nullable();
|
||||
$table->decimal('height', 8, 2);
|
||||
$table->integer('floor');
|
||||
$table->decimal('floor_area', 10, 2)->nullable();
|
||||
$table->integer('basement');
|
||||
$table->decimal('basement_height', 8, 2)->nullable();
|
||||
$table->decimal('basement_area', 10, 2);
|
||||
$table->integer('unit')->nullable();
|
||||
|
||||
// Previous information
|
||||
$table->decimal('prev_retribution', 15, 2)->nullable();
|
||||
$table->string('prev_pbg')->nullable();
|
||||
$table->decimal('prev_total_area', 10, 2)->nullable();
|
||||
|
||||
// Coefficients
|
||||
$table->decimal('koefisien_dasar_bangunan', 8, 4)->nullable();
|
||||
$table->decimal('koefisien_lantai_bangunan', 8, 4)->nullable();
|
||||
$table->decimal('koefisien_lantai_hijau', 8, 4)->nullable();
|
||||
$table->decimal('koefisien_tapak_basement', 8, 4)->nullable();
|
||||
$table->decimal('ketinggian_bangunan', 8, 2)->nullable();
|
||||
|
||||
// Road information
|
||||
$table->string('jalan_arteri')->nullable();
|
||||
$table->string('jalan_kolektor')->nullable();
|
||||
$table->string('jalan_bangunan')->nullable();
|
||||
$table->decimal('gsb', 8, 2)->nullable();
|
||||
$table->string('kkr_number')->nullable();
|
||||
|
||||
// Unit data as JSON
|
||||
$table->json('unit_data')->nullable();
|
||||
|
||||
// Additional flags
|
||||
$table->boolean('is_mbr')->default(false);
|
||||
$table->string('code');
|
||||
|
||||
// Building location information
|
||||
$table->bigInteger('building_ward_id');
|
||||
$table->string('building_ward_name');
|
||||
$table->integer('building_district_id');
|
||||
$table->string('building_district_name');
|
||||
$table->integer('building_regency_id');
|
||||
$table->string('building_regency_name');
|
||||
$table->integer('building_province_id');
|
||||
$table->string('building_province_name');
|
||||
$table->text('building_address');
|
||||
|
||||
// Coordinates
|
||||
$table->decimal('latitude', 10, 8)->nullable();
|
||||
$table->decimal('longitude', 11, 8)->nullable();
|
||||
|
||||
// Additional files
|
||||
$table->string('building_photo')->nullable();
|
||||
$table->string('pbg_parent')->nullable();
|
||||
|
||||
// Original created_at from API
|
||||
$table->timestamp('api_created_at')->nullable();
|
||||
|
||||
$table->timestamps();
|
||||
|
||||
// Add foreign key constraint
|
||||
$table->foreign('pbg_task_uid')->references('uuid')->on('pbg_task')->onDelete('cascade');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('pbg_task_details');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
// 1. Tabel Fungsi Bangunan (Simplified)
|
||||
Schema::create('building_types', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('code', 10)->unique()->comment('Kode fungsi bangunan');
|
||||
$table->string('name', 100)->comment('Nama fungsi bangunan');
|
||||
$table->unsignedBigInteger('parent_id')->nullable()->comment('Parent ID untuk hierarki');
|
||||
$table->tinyInteger('level')->default(1)->comment('Level hierarki (1=parent, 2=child)');
|
||||
$table->boolean('is_free')->default(false)->comment('Apakah gratis (keagamaan, MBR)');
|
||||
$table->boolean('is_active')->default(true);
|
||||
$table->timestamps();
|
||||
|
||||
$table->index(['parent_id', 'level']);
|
||||
$table->index('is_active');
|
||||
$table->foreign('parent_id')->references('id')->on('building_types')->onDelete('cascade');
|
||||
});
|
||||
|
||||
// 2. Tabel Parameter Indeks (Simplified)
|
||||
Schema::create('retribution_indices', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('building_type_id');
|
||||
$table->decimal('coefficient', 8, 4)->comment('Koefisien fungsi bangunan');
|
||||
$table->decimal('ip_permanent', 8, 4)->comment('Indeks Permanensi');
|
||||
$table->decimal('ip_complexity', 8, 4)->comment('Indeks Kompleksitas');
|
||||
$table->decimal('locality_index', 8, 4)->comment('Indeks Lokalitas');
|
||||
$table->decimal('infrastructure_factor', 8, 4)->default(0.5)->comment('Faktor prasarana (default 50%)');
|
||||
$table->boolean('is_active')->default(true);
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique('building_type_id');
|
||||
$table->foreign('building_type_id')->references('id')->on('building_types')->onDelete('cascade');
|
||||
});
|
||||
|
||||
// 3. Tabel Indeks Ketinggian (Simplified)
|
||||
Schema::create('height_indices', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->tinyInteger('floor_number')->unique()->comment('Nomor lantai');
|
||||
$table->decimal('height_index', 8, 6)->comment('Indeks ketinggian');
|
||||
$table->timestamps();
|
||||
|
||||
$table->index('floor_number');
|
||||
});
|
||||
|
||||
// 4. Tabel Konfigurasi Global
|
||||
Schema::create('retribution_configs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('key', 50)->unique()->comment('Kunci konfigurasi');
|
||||
$table->decimal('value', 15, 2)->comment('Nilai konfigurasi');
|
||||
$table->string('description', 200)->comment('Deskripsi konfigurasi');
|
||||
$table->boolean('is_active')->default(true);
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
// 5. Tabel Hasil Perhitungan (Simplified)
|
||||
Schema::create('retribution_calculations', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('calculation_id', 20)->unique()->comment('ID unik perhitungan');
|
||||
$table->unsignedBigInteger('building_type_id');
|
||||
$table->tinyInteger('floor_number');
|
||||
$table->decimal('building_area', 12, 2)->comment('Luas bangunan (m2)');
|
||||
$table->decimal('retribution_amount', 15, 2)->comment('Jumlah retribusi');
|
||||
$table->json('calculation_detail')->comment('Detail perhitungan');
|
||||
$table->timestamp('calculated_at');
|
||||
$table->timestamps();
|
||||
|
||||
$table->index(['building_type_id', 'floor_number']);
|
||||
$table->index('calculated_at');
|
||||
$table->foreign('building_type_id')->references('id')->on('building_types');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('retribution_calculations');
|
||||
Schema::dropIfExists('retribution_configs');
|
||||
Schema::dropIfExists('height_indices');
|
||||
Schema::dropIfExists('retribution_indices');
|
||||
Schema::dropIfExists('building_types');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('calculable_retributions', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('retribution_calculation_id');
|
||||
$table->morphs('calculable'); // calculable_id & calculable_type (automatically creates index)
|
||||
$table->boolean('is_active')->default(true)->comment('Status aktif calculation');
|
||||
$table->timestamp('assigned_at')->useCurrent()->comment('Kapan calculation di-assign');
|
||||
$table->text('notes')->nullable()->comment('Catatan assignment');
|
||||
$table->timestamps();
|
||||
|
||||
// Additional indexes for better performance
|
||||
$table->index('is_active');
|
||||
$table->index('assigned_at');
|
||||
|
||||
// Foreign key constraint
|
||||
$table->foreign('retribution_calculation_id')
|
||||
->references('id')
|
||||
->on('retribution_calculations')
|
||||
->onDelete('cascade');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('calculable_retributions');
|
||||
}
|
||||
};
|
||||
1
database/seeders/DatabaseSeeder.php
Executable file → Normal file
1
database/seeders/DatabaseSeeder.php
Executable file → Normal file
@@ -50,6 +50,7 @@ class DatabaseSeeder extends Seeder
|
||||
MenuSeeder::class,
|
||||
UsersRoleMenuSeeder::class,
|
||||
GlobalSettingSeeder::class,
|
||||
RetributionDataSeeder::class,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ class MenuSeeder extends Seeder
|
||||
"sort_order" => 2,
|
||||
"children" => [
|
||||
[
|
||||
"name" => "Dashboard Pimpinan",
|
||||
"name" => "Dashboard Pimpinan SIMBG",
|
||||
"url" => "dashboard.home",
|
||||
"icon" => null,
|
||||
"sort_order" => 1,
|
||||
@@ -67,6 +67,12 @@ class MenuSeeder extends Seeder
|
||||
"icon" => null,
|
||||
"sort_order" => 4,
|
||||
],
|
||||
[
|
||||
"name" => "Dashboard Pimpinan",
|
||||
"url" => "dashboard.leader",
|
||||
"icon" => null,
|
||||
"sort_order" => 5,
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
|
||||
71
database/seeders/RetributionDataSeeder.php
Normal file
71
database/seeders/RetributionDataSeeder.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class RetributionDataSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
// Seed Building Types berdasarkan Excel (without coefficient)
|
||||
DB::table('building_types')->insert([
|
||||
// Parent Functions
|
||||
['id' => 1, 'code' => 'KEAGAMAAN', 'name' => 'Fungsi Keagamaan', 'parent_id' => null, 'level' => 1, 'is_free' => true, 'is_active' => true, 'created_at' => now(), 'updated_at' => now()],
|
||||
['id' => 2, 'code' => 'SOSBUDAYA', 'name' => 'Fungsi Sosial Budaya', 'parent_id' => null, 'level' => 1, 'is_free' => false, 'is_active' => true, 'created_at' => now(), 'updated_at' => now()],
|
||||
['id' => 3, 'code' => 'CAMPURAN', 'name' => 'Fungsi Campuran (lebih dari 1)', 'parent_id' => null, 'level' => 1, 'is_free' => false, 'is_active' => true, 'created_at' => now(), 'updated_at' => now()],
|
||||
['id' => 4, 'code' => 'USAHA', 'name' => 'Fungsi Usaha', 'parent_id' => null, 'level' => 1, 'is_free' => false, 'is_active' => true, 'created_at' => now(), 'updated_at' => now()],
|
||||
['id' => 5, 'code' => 'HUNIAN', 'name' => 'Fungsi Hunian', 'parent_id' => null, 'level' => 1, 'is_free' => false, 'is_active' => true, 'created_at' => now(), 'updated_at' => now()],
|
||||
|
||||
// Child Functions
|
||||
['id' => 6, 'code' => 'CAMP_KECIL', 'name' => 'Campuran Kecil', 'parent_id' => 3, 'level' => 2, 'is_free' => false, 'is_active' => true, 'created_at' => now(), 'updated_at' => now()],
|
||||
['id' => 7, 'code' => 'CAMP_BESAR', 'name' => 'Campuran Besar', 'parent_id' => 3, 'level' => 2, 'is_free' => false, 'is_active' => true, 'created_at' => now(), 'updated_at' => now()],
|
||||
['id' => 8, 'code' => 'UMKM', 'name' => 'Fungsi Usaha (UMKM)', 'parent_id' => 4, 'level' => 2, 'is_free' => false, 'is_active' => true, 'created_at' => now(), 'updated_at' => now()],
|
||||
['id' => 9, 'code' => 'USH_BESAR', 'name' => 'Usaha Besar (Non-Mikro)', 'parent_id' => 4, 'level' => 2, 'is_free' => false, 'is_active' => true, 'created_at' => now(), 'updated_at' => now()],
|
||||
['id' => 10, 'code' => 'HUN_SEDH', 'name' => 'Hunian Sederhana <100', 'parent_id' => 5, 'level' => 2, 'is_free' => false, 'is_active' => true, 'created_at' => now(), 'updated_at' => now()],
|
||||
['id' => 11, 'code' => 'HUN_TSEDH', 'name' => 'Hunian Tidak Sederhana >100', 'parent_id' => 5, 'level' => 2, 'is_free' => false, 'is_active' => true, 'created_at' => now(), 'updated_at' => now()],
|
||||
['id' => 12, 'code' => 'MBR', 'name' => 'Rumah Tinggal MBR', 'parent_id' => 5, 'level' => 2, 'is_free' => true, 'is_active' => true, 'created_at' => now(), 'updated_at' => now()],
|
||||
]);
|
||||
|
||||
// Seed Retribution Indices berdasarkan Excel (with coefficient moved here)
|
||||
DB::table('retribution_indices')->insert([
|
||||
['building_type_id' => 1, 'coefficient' => 0.0000, 'ip_permanent' => 0.4000, 'ip_complexity' => 0.0000, 'locality_index' => 0.0000, 'infrastructure_factor' => 0.5000, 'is_active' => true, 'created_at' => now(), 'updated_at' => now()], // Keagamaan
|
||||
['building_type_id' => 2, 'coefficient' => 0.3000, 'ip_permanent' => 0.4000, 'ip_complexity' => 0.6000, 'locality_index' => 0.0030, 'infrastructure_factor' => 0.5000, 'is_active' => true, 'created_at' => now(), 'updated_at' => now()], // Sosial Budaya
|
||||
['building_type_id' => 6, 'coefficient' => 0.6000, 'ip_permanent' => 0.4000, 'ip_complexity' => 0.6000, 'locality_index' => 0.0050, 'infrastructure_factor' => 0.5000, 'is_active' => true, 'created_at' => now(), 'updated_at' => now()], // Campuran Kecil
|
||||
['building_type_id' => 7, 'coefficient' => 0.8000, 'ip_permanent' => 0.4000, 'ip_complexity' => 0.6000, 'locality_index' => 0.0050, 'infrastructure_factor' => 0.5000, 'is_active' => true, 'created_at' => now(), 'updated_at' => now()], // Campuran Besar
|
||||
['building_type_id' => 8, 'coefficient' => 0.5000, 'ip_permanent' => 0.4000, 'ip_complexity' => 0.6000, 'locality_index' => 0.0040, 'infrastructure_factor' => 0.5000, 'is_active' => true, 'created_at' => now(), 'updated_at' => now()], // UMKM
|
||||
['building_type_id' => 9, 'coefficient' => 0.7000, 'ip_permanent' => 0.4000, 'ip_complexity' => 0.6000, 'locality_index' => 0.0050, 'infrastructure_factor' => 0.5000, 'is_active' => true, 'created_at' => now(), 'updated_at' => now()], // Usaha Besar
|
||||
['building_type_id' => 10, 'coefficient' => 0.1500, 'ip_permanent' => 0.4000, 'ip_complexity' => 0.3000, 'locality_index' => 0.0040, 'infrastructure_factor' => 0.5000, 'is_active' => true, 'created_at' => now(), 'updated_at' => now()], // Hunian Sederhana
|
||||
['building_type_id' => 11, 'coefficient' => 0.1700, 'ip_permanent' => 0.4000, 'ip_complexity' => 0.6000, 'locality_index' => 0.0040, 'infrastructure_factor' => 0.5000, 'is_active' => true, 'created_at' => now(), 'updated_at' => now()], // Hunian Tidak Sederhana
|
||||
['building_type_id' => 12, 'coefficient' => 0.0000, 'ip_permanent' => 0.4000, 'ip_complexity' => 0.0000, 'locality_index' => 0.0000, 'infrastructure_factor' => 0.5000, 'is_active' => true, 'created_at' => now(), 'updated_at' => now()], // MBR
|
||||
]);
|
||||
|
||||
// Seed Height Indices berdasarkan Excel
|
||||
DB::table('height_indices')->insert([
|
||||
['floor_number' => 1, 'height_index' => 1.0000, 'created_at' => now(), 'updated_at' => now()],
|
||||
['floor_number' => 2, 'height_index' => 1.0900, 'created_at' => now(), 'updated_at' => now()],
|
||||
['floor_number' => 3, 'height_index' => 1.1200, 'created_at' => now(), 'updated_at' => now()],
|
||||
['floor_number' => 4, 'height_index' => 1.1350, 'created_at' => now(), 'updated_at' => now()],
|
||||
['floor_number' => 5, 'height_index' => 1.1620, 'created_at' => now(), 'updated_at' => now()],
|
||||
['floor_number' => 6, 'height_index' => 1.1970, 'created_at' => now(), 'updated_at' => now()],
|
||||
]);
|
||||
|
||||
// Seed Retribution Configs
|
||||
DB::table('retribution_configs')->insert([
|
||||
['key' => 'BASE_VALUE', 'value' => 7035000.00, 'description' => 'Nilai dasar perhitungan retribusi', 'is_active' => true, 'created_at' => now(), 'updated_at' => now()],
|
||||
['key' => 'INFRASTRUCTURE_MULTIPLIER', 'value' => 0.50, 'description' => 'Pengali asumsi prasarana (50%)', 'is_active' => true, 'created_at' => now(), 'updated_at' => now()],
|
||||
['key' => 'HEIGHT_MULTIPLIER', 'value' => 0.50, 'description' => 'Pengali indeks ketinggian dalam formula', 'is_active' => true, 'created_at' => now(), 'updated_at' => now()],
|
||||
]);
|
||||
|
||||
$this->command->info('✅ Retribution data seeded successfully!');
|
||||
$this->command->info('📊 Building Types: 12 records');
|
||||
$this->command->info('📊 Retribution Indices: 9 records');
|
||||
$this->command->info('📊 Height Indices: 6 records');
|
||||
$this->command->info('📊 Retribution Configs: 3 records');
|
||||
}
|
||||
}
|
||||
@@ -68,9 +68,9 @@ class UsersRoleMenuSeeder extends Seeder
|
||||
// Attach User to role super admin
|
||||
$accountSuperadmin = User::where('email', 'superadmin@sibedas.com')->first();
|
||||
$accountUser = User::where('email', 'user@sibedas.com')->first();
|
||||
$accountDefault = User::where('email','user@demo.com')->first();
|
||||
// $accountDefault = User::where('email','user@demo.com')->first();
|
||||
$accountSuperadmin->roles()->sync([$roles['superadmin']->id]);
|
||||
$accountUser->roles()->sync([$roles['user']->id]);
|
||||
$accountDefault->roles()->sync([$roles['user']->id]);
|
||||
// $accountDefault->roles()->sync([$roles['user']->id]);
|
||||
}
|
||||
}
|
||||
|
||||
39
deploy.sh
39
deploy.sh
@@ -1,39 +0,0 @@
|
||||
GIT_BRANCH="dev"
|
||||
PHP_VERSION="php8.3"
|
||||
|
||||
echo "🚀 Starting deployment..."
|
||||
php artisan down
|
||||
|
||||
echo "📥 Pulling latest changes from Git..."
|
||||
git fetch origin $GIT_BRANCH
|
||||
git reset --hard origin/$GIT_BRANCH
|
||||
git pull origin $GIT_BRANCH
|
||||
|
||||
echo "⚡ Installing NPM dependencies and building assets..."
|
||||
npm ci --no-audit --no-fund
|
||||
npm run build
|
||||
|
||||
echo "📦 Installing composer dependencies..."
|
||||
COMPOSER_ALLOW_SUPERUSER=1 composer install --no-interaction --optimize-autoloader
|
||||
|
||||
echo "🗄️ Running migrations..."
|
||||
php artisan migrate --force
|
||||
|
||||
echo "Running seeders..."
|
||||
php artisan db:seed --force
|
||||
|
||||
echo "⚡ Optimizing application..."
|
||||
php artisan optimize:clear
|
||||
|
||||
echo "🔄 Restarting PHP service..."
|
||||
systemctl reload $PHP_VERSION-fpm
|
||||
|
||||
echo "🔁 Restarting Supervisor queue workers..."
|
||||
php artisan queue:restart
|
||||
|
||||
supervisorctl reread
|
||||
supervisorctl update
|
||||
supervisorctl restart all
|
||||
|
||||
php artisan up
|
||||
echo "🚀 Deployment completed successfully!"
|
||||
80
docker-compose.yml
Normal file
80
docker-compose.yml
Normal file
@@ -0,0 +1,80 @@
|
||||
services:
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
target: production
|
||||
container_name: sibedas_app
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
APP_ENV: local
|
||||
APP_DEBUG: true
|
||||
APP_URL: http://localhost
|
||||
VITE_APP_URL: http://localhost
|
||||
DB_CONNECTION: mariadb
|
||||
DB_HOST: db
|
||||
DB_PORT: 3306
|
||||
DB_DATABASE: sibedas_db
|
||||
DB_USERNAME: root
|
||||
DB_PASSWORD: root
|
||||
volumes:
|
||||
- .:/var/www
|
||||
depends_on:
|
||||
- db
|
||||
networks:
|
||||
- sibedas_net
|
||||
|
||||
nginx:
|
||||
image: nginx:alpine
|
||||
container_name: sibedas_nginx
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "8000:80"
|
||||
volumes:
|
||||
- .:/var/www
|
||||
- ./docker/nginx/conf.d/app.conf:/etc/nginx/conf.d/default.conf
|
||||
depends_on:
|
||||
- app
|
||||
networks:
|
||||
- sibedas_net
|
||||
|
||||
db:
|
||||
image: mariadb:10.6
|
||||
container_name: sibedas_db
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: root
|
||||
MYSQL_DATABASE: sibedas_db
|
||||
MYSQL_USER: root
|
||||
MYSQL_PASSWORD: root
|
||||
ports:
|
||||
- "3306:3306"
|
||||
volumes:
|
||||
- dbdata:/var/lib/mysql
|
||||
- ./sibedas.sql:/docker-entrypoint-initdb.d/sibedas.sql
|
||||
networks:
|
||||
- sibedas_net
|
||||
|
||||
vite:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
target: development
|
||||
container_name: sibedas_vite
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
VITE_APP_URL: http://localhost
|
||||
volumes:
|
||||
- .:/var/www
|
||||
- /var/www/node_modules
|
||||
ports:
|
||||
- "5173:5173"
|
||||
networks:
|
||||
- sibedas_net
|
||||
|
||||
volumes:
|
||||
dbdata:
|
||||
|
||||
networks:
|
||||
sibedas_net:
|
||||
driver: bridge
|
||||
50
docker/nginx/conf.d/app.conf
Normal file
50
docker/nginx/conf.d/app.conf
Normal file
@@ -0,0 +1,50 @@
|
||||
server {
|
||||
listen 80;
|
||||
index index.php index.html;
|
||||
server_name localhost;
|
||||
|
||||
root /var/www/public;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.php?$query_string;
|
||||
}
|
||||
|
||||
# Proxy Vite requests in development
|
||||
location /@vite {
|
||||
proxy_pass http://vite:5173;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Host $host;
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
}
|
||||
|
||||
location /build/ {
|
||||
alias /var/www/public/build/;
|
||||
access_log off;
|
||||
expires max;
|
||||
}
|
||||
|
||||
location /assets/ {
|
||||
alias /var/www/public/assets/;
|
||||
access_log off;
|
||||
expires max;
|
||||
}
|
||||
|
||||
location ~ \.php$ {
|
||||
fastcgi_pass app:9000;
|
||||
fastcgi_index index.php;
|
||||
include fastcgi_params;
|
||||
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
|
||||
fastcgi_param DOCUMENT_ROOT $realpath_root;
|
||||
}
|
||||
|
||||
location ~ /\.ht {
|
||||
deny all;
|
||||
}
|
||||
|
||||
location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2|ttf|eot|otf)$ {
|
||||
expires max;
|
||||
log_not_found off;
|
||||
}
|
||||
}
|
||||
59
docker/startup.sh
Normal file
59
docker/startup.sh
Normal file
@@ -0,0 +1,59 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Enable error reporting
|
||||
set -e
|
||||
set -x
|
||||
|
||||
# Create necessary directories with proper permissions as root
|
||||
mkdir -p /var/log/supervisor
|
||||
mkdir -p /var/run/supervisor
|
||||
chown -R www-data:www-data /var/log/supervisor
|
||||
chown -R www-data:www-data /var/run/supervisor
|
||||
chmod -R 775 /var/log/supervisor
|
||||
chmod -R 775 /var/run/supervisor
|
||||
|
||||
# Create storage directories with proper permissions
|
||||
mkdir -p /var/www/storage/logs
|
||||
chown -R www-data:www-data /var/www/storage
|
||||
chmod -R 775 /var/www/storage
|
||||
|
||||
# Ensure Laravel storage and cache directories are writable
|
||||
chown -R www-data:www-data /var/www/bootstrap/cache
|
||||
chmod -R 775 /var/www/bootstrap/cache
|
||||
|
||||
# Wait for database to be ready (with increased timeout and better error handling)
|
||||
max_tries=30
|
||||
count=0
|
||||
echo "Waiting for database connection..."
|
||||
|
||||
# First, wait a bit for the database container to fully initialize
|
||||
sleep 10
|
||||
|
||||
while ! php artisan db:monitor > /dev/null 2>&1; do
|
||||
count=$((count + 1))
|
||||
if [ $count -gt $max_tries ]; then
|
||||
echo "Database connection timeout after $max_tries attempts"
|
||||
echo "Checking database container status..."
|
||||
# Try to connect directly to MySQL to get more detailed error
|
||||
mysql -h db -u admindb_arifal -parifal201 -e "SELECT 1" || true
|
||||
exit 1
|
||||
fi
|
||||
echo "Waiting for database... ($count/$max_tries)"
|
||||
sleep 5
|
||||
done
|
||||
|
||||
echo "Database connection established!"
|
||||
|
||||
# Run database-dependent commands
|
||||
echo "Running database migrations..."
|
||||
php artisan migrate --force
|
||||
|
||||
echo "Running database seeders..."
|
||||
php artisan db:seed --force
|
||||
|
||||
echo "Optimizing Laravel..."
|
||||
php artisan optimize:clear
|
||||
php artisan optimize
|
||||
|
||||
# Start supervisor (which will manage PHP-FPM)
|
||||
exec /usr/bin/supervisord -n -c /etc/supervisor/conf.d/supervisord.conf
|
||||
41
docker/supervisor/laravel-production.conf
Normal file
41
docker/supervisor/laravel-production.conf
Normal file
@@ -0,0 +1,41 @@
|
||||
[supervisord]
|
||||
nodaemon=true
|
||||
user=root
|
||||
logfile=/var/log/supervisor/supervisord.log
|
||||
pidfile=/var/run/supervisord.pid
|
||||
|
||||
[program:laravel-queue-worker]
|
||||
process_name=%(program_name)s_%(process_num)02d
|
||||
command=php /var/www/pupr/artisan queue:work --queue=default --timeout=82800 --tries=3 --memory=512 --sleep=3
|
||||
directory=/var/www/pupr
|
||||
autostart=true
|
||||
autorestart=true
|
||||
numprocs=2
|
||||
redirect_stderr=true
|
||||
stdout_logfile=/var/www/pupr/storage/logs/queue-worker.log
|
||||
stdout_logfile_maxbytes=10MB
|
||||
stdout_logfile_backups=5
|
||||
stopasgroup=true
|
||||
killasgroup=true
|
||||
user=www-data
|
||||
priority=10
|
||||
|
||||
[program:laravel-scheduler]
|
||||
process_name=%(program_name)s_%(process_num)02d
|
||||
command=php /var/www/pupr/artisan schedule:work
|
||||
directory=/var/www/pupr
|
||||
autostart=true
|
||||
autorestart=true
|
||||
numprocs=1
|
||||
redirect_stderr=true
|
||||
stdout_logfile=/var/www/pupr/storage/logs/scheduler.log
|
||||
stdout_logfile_maxbytes=10MB
|
||||
stdout_logfile_backups=5
|
||||
stopasgroup=true
|
||||
killasgroup=true
|
||||
user=www-data
|
||||
priority=20
|
||||
|
||||
[group:laravel]
|
||||
programs=laravel-queue-worker,laravel-scheduler
|
||||
priority=999
|
||||
31
docker/supervisor/supervisord.conf
Normal file
31
docker/supervisor/supervisord.conf
Normal file
@@ -0,0 +1,31 @@
|
||||
[supervisord]
|
||||
nodaemon=true
|
||||
user=root
|
||||
logfile=/var/log/supervisor/supervisord.log
|
||||
pidfile=/var/run/supervisord.pid
|
||||
|
||||
[program:laravel-worker]
|
||||
process_name=%(program_name)s_%(process_num)02d
|
||||
command=php /var/www/artisan queue:work --queue=default --timeout=82800 --tries=1
|
||||
autostart=true
|
||||
autorestart=true
|
||||
numprocs=1
|
||||
redirect_stderr=true
|
||||
stdout_logfile=/var/www/storage/logs/worker.log
|
||||
stopasgroup=true
|
||||
killasgroup=true
|
||||
user=www-data
|
||||
priority=10
|
||||
|
||||
[program:laravel-scheduler]
|
||||
process_name=%(program_name)s_%(process_num)02d
|
||||
command=php /var/www/artisan schedule:work
|
||||
autostart=true
|
||||
autorestart=true
|
||||
numprocs=1
|
||||
redirect_stderr=true
|
||||
stdout_logfile=/var/www/storage/logs/scheduler.log
|
||||
stopasgroup=true
|
||||
killasgroup=true
|
||||
user=www-data
|
||||
priority=20
|
||||
4
package.json
Executable file → Normal file
4
package.json
Executable file → Normal file
@@ -3,6 +3,10 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
"build:prod": "NODE_OPTIONS='--max-old-space-size=2048 --max-semi-space-size=1024' vite build --config vite.config.production.js",
|
||||
"build:chunked": "./build-chunked.sh",
|
||||
"build:local": "vite build && echo 'Build completed! Now run: ./deploy.sh to upload to server'",
|
||||
"build:zip": "./build-and-zip.sh",
|
||||
"dev": "vite"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
0
phpunit.xml
Executable file → Normal file
0
phpunit.xml
Executable file → Normal file
0
public/.htaccess
Executable file → Normal file
0
public/.htaccess
Executable file → Normal file
@@ -1 +0,0 @@
|
||||
var u=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{};function f(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}function l(e){if(e.__esModule)return e;var r=e.default;if(typeof r=="function"){var t=function o(){return this instanceof o?Reflect.construct(r,arguments,this.constructor):r.apply(this,arguments)};t.prototype=r.prototype}else t={};return Object.defineProperty(t,"__esModule",{value:!0}),Object.keys(e).forEach(function(o){var n=Object.getOwnPropertyDescriptor(e,o);Object.defineProperty(t,o,n.get?n:{enumerable:!0,get:function(){return e[o]}})}),t}export{f as a,u as c,l as g};
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 1.2 MiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user