initial commit
parent
d78647ce46
commit
266632ec35
|
@ -18,6 +18,11 @@ yarn-error.log
|
|||
/auth.json
|
||||
/.fleet
|
||||
/.idea
|
||||
|
||||
# Production logs (but keep the structure)
|
||||
/storage/logs/*.log
|
||||
!/storage/logs/.gitkeep
|
||||
|
||||
/.nova
|
||||
/.vscode
|
||||
/.zed
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
# PERUBAHAN SISTEM TERAKHIR TERBIT
|
||||
|
||||
## Masalah yang Diperbaiki
|
||||
|
||||
- Menghapus kolom `rank_order` yang statis dan akan bermasalah ketika ada data baru
|
||||
- Mengubah sistem ordering menggunakan `tanggal_terbit` yang lebih dinamis dan logis
|
||||
|
||||
## Perubahan Yang Dilakukan
|
||||
|
||||
### 1. Database Migration (2025_07_14_033547_create_terakhir_terbit_table.php)
|
||||
|
||||
- ❌ Dihapus: `rank_order` column
|
||||
- ❌ Dihapus: unique constraint berdasarkan rank_order
|
||||
- ✅ Ditambah: index pada `kategori` dan `tanggal_terbit`
|
||||
- ✅ Ditambah: index pada `kategori` dan `sync_date`
|
||||
|
||||
### 2. Model TerakhirTerbit (app/Models/TerakhirTerbit.php)
|
||||
|
||||
- ❌ Dihapus: `rank_order` dari fillable dan casts
|
||||
- ✅ Diubah: Query method `getLatestByKategori()` menggunakan `ORDER BY tanggal_terbit DESC` + `LIMIT 5`
|
||||
- ✅ Diubah: Query method `getByKategoriAndDate()` menggunakan `ORDER BY tanggal_terbit DESC` + `LIMIT 5`
|
||||
|
||||
### 3. API Service (app/Services/PerizinanApiService.php)
|
||||
|
||||
- ❌ Dihapus: Logic penambahan `rank_order` dalam `syncTerakhirTerbitToDatabase()`
|
||||
- ✅ Simplified: Data disimpan tanpa manual ranking, biarkan database yang mengurutkan
|
||||
|
||||
### 4. Dashboard Helper (app/Helpers/DashboardHelper.php)
|
||||
|
||||
- ✅ Diubah: Generate `rank_order` secara dinamis hanya untuk display (tidak disimpan di DB)
|
||||
- ✅ Data tetap diurutkan berdasarkan `tanggal_terbit` terbaru dari database
|
||||
|
||||
## Keuntungan Sistem Baru
|
||||
|
||||
### ✅ Dinamis
|
||||
|
||||
- Data selalu diurutkan berdasarkan tanggal terbit terbaru
|
||||
- Tidak ada masalah ketika ada data baru
|
||||
|
||||
### ✅ Consistent
|
||||
|
||||
- Urutan data konsisten berdasarkan logical field (`tanggal_terbit`)
|
||||
- Tidak bergantung pada urutan API response
|
||||
|
||||
### ✅ Scalable
|
||||
|
||||
- Bisa handle unlimited data tanpa konflik ranking
|
||||
- Index database optimal untuk performa query
|
||||
|
||||
### ✅ Maintainable
|
||||
|
||||
- Code lebih sederhana tanpa logic manual ranking
|
||||
- Ranking hanya untuk display purpose
|
||||
|
||||
## Contoh Output Sistem Baru
|
||||
|
||||
```
|
||||
PERTEK - Top 5 Terakhir Terbit:
|
||||
#1: SUMANTO TJAHAJA (02 Jul 2025) ← Terbaru
|
||||
#2: Eka Ferdiyus Supatmo (16 Jun 2025) ← Kedua terbaru
|
||||
#3: Adrian Mara Maulana (13 Jun 2025) ← Ketiga terbaru
|
||||
#4: AHMAD SYARIF (04 Jun 2025) ← Keempat terbaru
|
||||
#5: drg. WWM Diah Arimbi (28 May 2025) ← Kelima terbaru
|
||||
```
|
||||
|
||||
## Migration Command
|
||||
|
||||
```bash
|
||||
php artisan migrate:fresh # ← Sudah dijalankan
|
||||
php artisan perizinan:sync --all # ← Test dengan data baru
|
||||
```
|
||||
|
||||
## Status: ✅ COMPLETED
|
||||
|
||||
Sistem terakhir terbit sekarang menggunakan ordering dinamis berdasarkan tanggal terbit terbaru.
|
|
@ -0,0 +1,150 @@
|
|||
# Fastest Permohonan API Integration
|
||||
|
||||
## Overview
|
||||
|
||||
Integration telah selesai untuk endpoint fastest permohonan API Jakarta:
|
||||
|
||||
- **URL**: `https://wsdev.jakarta.go.id/gateway/DataPerizinanLingkungan/1.0/fastest_permohonan`
|
||||
- **Parameters**: `kategori` (pertek/amdal), `limit` (default: 5)
|
||||
- **Headers**: `Authorization: Bearer <token>`, `x-Gateway-APIKey: <key>`
|
||||
|
||||
## Database Schema
|
||||
|
||||
### Table: `fastest_permohonan`
|
||||
|
||||
```sql
|
||||
- id (bigint, primary key)
|
||||
- kategori (varchar) - pertek, amdal
|
||||
- nama (varchar) - nama izin dari API
|
||||
- total (integer) - jumlah total izin
|
||||
- durasi_pemohon (varchar) - durasi rata-rata pemohon
|
||||
- durasi_petugas (varchar) - durasi rata-rata petugas
|
||||
- rank_order (integer) - urutan ranking 1-5
|
||||
- api_last_updated (datetime) - dari field API last_updated
|
||||
- sync_date (date) - tanggal sync data
|
||||
- created_at, updated_at (timestamps)
|
||||
```
|
||||
|
||||
**Indexes:**
|
||||
|
||||
- `(kategori, sync_date, rank_order)`
|
||||
- Unique: `(kategori, rank_order, sync_date)`
|
||||
|
||||
## API Response Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"success": 1,
|
||||
"message": "Fastest permohonan retrieved successfully.",
|
||||
"data": [
|
||||
{
|
||||
"nama": "SURAT PERNYATAAN PENGELOLAAN LINGKUNGAN (SPPL)",
|
||||
"total": 7174,
|
||||
"durasi_pemohon": "5 Hari 06 Jam 09 Menit 01 Detik",
|
||||
"durasi_petugas": "4 Hari 03 Jam 12 Menit 18 Detik"
|
||||
}
|
||||
],
|
||||
"last_updated": "2025-07-14 09:50:07"
|
||||
}
|
||||
```
|
||||
|
||||
## Files Modified/Created
|
||||
|
||||
### New Files:
|
||||
|
||||
1. `database/migrations/2025_07_14_024445_create_fastest_permohonan_table.php`
|
||||
2. `app/Models/FastestPermohonan.php`
|
||||
|
||||
### Modified Files:
|
||||
|
||||
1. `app/Services/PerizinanApiService.php`
|
||||
|
||||
- Added `fetchFastestPermohonan()` method
|
||||
- Added `syncFastestDataToDatabase()` method
|
||||
- Modified `syncAllCategories()` to include fastest data
|
||||
|
||||
2. `app/Console/Commands/SyncPerizinanData.php`
|
||||
|
||||
- Added `--fastest` option
|
||||
- Updated to sync both status and fastest data
|
||||
|
||||
3. `routes/console.php`
|
||||
|
||||
- Updated schedule to include `--fastest` flag
|
||||
|
||||
4. `app/Helpers/DashboardHelper.php`
|
||||
- Added `getFastestPermohonanByType()` method
|
||||
- Added `getAllFastestData()` method
|
||||
- Added helper methods for formatting and display
|
||||
|
||||
## Commands
|
||||
|
||||
### Sync Status Data Only:
|
||||
|
||||
```bash
|
||||
php artisan perizinan:sync # All categories
|
||||
php artisan perizinan:sync pertek # Specific category
|
||||
```
|
||||
|
||||
### Sync Status + Fastest Data:
|
||||
|
||||
```bash
|
||||
php artisan perizinan:sync --fastest # All categories
|
||||
php artisan perizinan:sync pertek --fastest # Specific category
|
||||
```
|
||||
|
||||
### Check Data:
|
||||
|
||||
```bash
|
||||
php artisan perizinan:check
|
||||
```
|
||||
|
||||
## Scheduled Tasks
|
||||
|
||||
Daily at midnight (00:00 WIB):
|
||||
|
||||
```bash
|
||||
php artisan perizinan:sync --fastest
|
||||
```
|
||||
|
||||
## Data Access Examples
|
||||
|
||||
### Using DashboardHelper:
|
||||
|
||||
```php
|
||||
// Get fastest data for dashboard
|
||||
$fastestData = DashboardHelper::getAllFastestData();
|
||||
|
||||
// Get fastest data for specific type
|
||||
$pertekFastest = DashboardHelper::getFastestPermohonanByType('pertek');
|
||||
```
|
||||
|
||||
### Using Model Directly:
|
||||
|
||||
```php
|
||||
// Get today's data for PERTEK
|
||||
$pertek = FastestPermohonan::getLatestByKategori('pertek');
|
||||
|
||||
// Get data for specific date
|
||||
$data = FastestPermohonan::getByKategoriAndDate('amdal', '2025-07-14');
|
||||
```
|
||||
|
||||
## Data Flow
|
||||
|
||||
1. **API Call**: Service fetches data from Jakarta API
|
||||
2. **Data Validation**: Check API response success
|
||||
3. **Database Sync**:
|
||||
- Delete existing data for today (to ensure fresh ranking)
|
||||
- Insert new data with ranking based on API order
|
||||
4. **Dashboard Display**: Helper methods format data for frontend
|
||||
|
||||
## Production Deployment
|
||||
|
||||
Data akan tersync otomatis setiap hari jam 12 malam melalui Laravel Scheduler di production server.
|
||||
|
||||
## Notes
|
||||
|
||||
- Data di-refresh setiap hari untuk memastikan ranking terbaru
|
||||
- Ranking ditentukan berdasarkan urutan dari API response
|
||||
- Fallback data tersedia jika API tidak tersedia
|
||||
- Support untuk kategori 'pertek' dan 'amdal' sesuai endpoint yang tersedia
|
|
@ -0,0 +1,191 @@
|
|||
# FINAL SYSTEM IMPROVEMENTS - SEMUA TABEL SUDAH OPTIMAL
|
||||
|
||||
## Ringkasan Perubahan
|
||||
|
||||
✅ **FASTEST_PERMOHONAN** - Dihapus rank_order, gunakan API ordering
|
||||
✅ **TERAKHIR_TERBIT** - Dihapus rank_order, gunakan tanggal_terbit ordering
|
||||
✅ **PERIZINAN_STATUS** - Sudah optimal (tidak perlu rank_order)
|
||||
|
||||
## 1. FASTEST_PERMOHONAN Table
|
||||
|
||||
### ❌ Masalah Sebelumnya:
|
||||
|
||||
```php
|
||||
// MASALAH: rank_order statis, konflik saat ada data baru
|
||||
'rank_order' => $index + 1 // Static ranking!
|
||||
```
|
||||
|
||||
### ✅ Solusi Sekarang:
|
||||
|
||||
```php
|
||||
// OPTIMAL: Simpan data tanpa ranking, biarkan API order alami
|
||||
FastestPermohonan::create([
|
||||
'kategori' => $kategori,
|
||||
'nama' => $item['nama'],
|
||||
'total' => $item['total'],
|
||||
'durasi_pemohon' => $item['durasi_pemohon'],
|
||||
'durasi_petugas' => $item['durasi_petugas',
|
||||
// ❌ DIHAPUS: 'rank_order' => $index + 1
|
||||
'api_last_updated' => $apiLastUpdated,
|
||||
'sync_date' => $syncDate
|
||||
]);
|
||||
|
||||
// Query menggunakan insertion order (id) untuk preserve API order
|
||||
->orderBy('id')->limit(5)
|
||||
```
|
||||
|
||||
### 🎯 Keuntungan:
|
||||
|
||||
- **API Order Preserved**: Data tetap sesuai urutan tercepat dari API
|
||||
- **No Conflicts**: Tidak ada masalah ranking duplicate
|
||||
- **Dynamic Display**: Ranking (#1-#5) generate saat display
|
||||
|
||||
## 2. TERAKHIR_TERBIT Table
|
||||
|
||||
### ❌ Masalah Sebelumnya:
|
||||
|
||||
```php
|
||||
// MASALAH: rank_order berdasarkan API response, bukan tanggal terbaru
|
||||
'rank_order' => $index + 1 // Static dari API!
|
||||
```
|
||||
|
||||
### ✅ Solusi Sekarang:
|
||||
|
||||
```php
|
||||
// OPTIMAL: Urutkan berdasarkan tanggal terbit terbaru
|
||||
TerakhirTerbit::create([
|
||||
'kategori' => $kategori,
|
||||
'nama_izin' => $item['nama_izin'],
|
||||
'pemohon' => $item['pemohon'],
|
||||
'tanggal_terbit' => Carbon::parse($item['tanggal_terbit']),
|
||||
// ❌ DIHAPUS: 'rank_order' => $index + 1
|
||||
'api_last_updated' => $apiLastUpdated,
|
||||
'sync_date' => $syncDate
|
||||
]);
|
||||
|
||||
// Query menggunakan logical order (tanggal terbaru)
|
||||
->orderBy('tanggal_terbit', 'desc')->limit(5)
|
||||
```
|
||||
|
||||
### 🎯 Keuntungan:
|
||||
|
||||
- **Always Latest**: Selalu tampilkan izin dengan tanggal terbit terbaru
|
||||
- **Logical Order**: Urutan berdasarkan field yang relevan (tanggal_terbit)
|
||||
- **Future Proof**: Data baru otomatis masuk urutan yang benar
|
||||
|
||||
## 3. PERIZINAN_STATUS Table
|
||||
|
||||
### ✅ Sudah Optimal:
|
||||
|
||||
```php
|
||||
// BAGUS: Cek existing data untuk hari yang sama, no deletion
|
||||
$exists = PerizinanStatus::where('kategori', $kategori)
|
||||
->where('status_id', $item['id'])
|
||||
->whereDate('sync_date', $syncDate)
|
||||
->exists();
|
||||
|
||||
if (!$exists) {
|
||||
// Simpan data baru
|
||||
}
|
||||
```
|
||||
|
||||
### 🎯 Tidak Perlu Perubahan:
|
||||
|
||||
- **Smart Duplication Check**: Hanya cegah data duplicate untuk hari yang sama
|
||||
- **Historical Data**: Simpan semua data historis untuk analisis
|
||||
- **No Ranking**: Status data tidak perlu ranking system
|
||||
|
||||
## Database Schema Changes
|
||||
|
||||
### FASTEST_PERMOHONAN Migration:
|
||||
|
||||
```php
|
||||
// ❌ DIHAPUS:
|
||||
$table->integer('rank_order');
|
||||
$table->unique(['kategori', 'rank_order', 'sync_date']);
|
||||
|
||||
// ✅ DITAMBAH:
|
||||
$table->index(['kategori', 'total']); // untuk sorting performance
|
||||
```
|
||||
|
||||
### TERAKHIR_TERBIT Migration:
|
||||
|
||||
```php
|
||||
// ❌ DIHAPUS:
|
||||
$table->integer('rank_order');
|
||||
$table->unique(['kategori', 'rank_order', 'sync_date']);
|
||||
|
||||
// ✅ DITAMBAH:
|
||||
$table->index(['kategori', 'tanggal_terbit']); // untuk sorting performance
|
||||
```
|
||||
|
||||
## Model Changes
|
||||
|
||||
### FastestPermohonan Model:
|
||||
|
||||
```php
|
||||
// ✅ Query Method:
|
||||
public static function getLatestByKategori($kategori)
|
||||
{
|
||||
return self::where('kategori', $kategori)
|
||||
->whereDate('sync_date', Carbon::today())
|
||||
->orderBy('id') // Preserve API order
|
||||
->limit(5)
|
||||
->get();
|
||||
}
|
||||
```
|
||||
|
||||
### TerakhirTerbit Model:
|
||||
|
||||
```php
|
||||
// ✅ Query Method:
|
||||
public static function getLatestByKategori($kategori)
|
||||
{
|
||||
return self::where('kategori', $kategori)
|
||||
->whereDate('sync_date', Carbon::today())
|
||||
->orderBy('tanggal_terbit', 'desc') // Latest first
|
||||
->limit(5)
|
||||
->get();
|
||||
}
|
||||
```
|
||||
|
||||
## Dashboard Helper Changes
|
||||
|
||||
### Dynamic Ranking Generation:
|
||||
|
||||
```php
|
||||
// ✅ Generate ranking hanya untuk display
|
||||
$index = 1;
|
||||
foreach ($dbData as $item) {
|
||||
$data[] = [
|
||||
'nama' => $item->nama,
|
||||
// ...data lainnya...
|
||||
'rank_order' => $index++ // Dynamic display ranking
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
## Hasil Test Final
|
||||
|
||||
```
|
||||
FASTEST DATA (API ORDER):
|
||||
#1: SPPL UNTUK KEPENTINGAN BANGUNAN MILIK PEMERINTAH (6 total, 3j54m) ← Tercepat
|
||||
#2: SPPL (7,174 total, 5h6j9m) ← Kedua tercepat
|
||||
#3: Izin Tempat Uji Emisi (592 total, 5h1j57m) ← Ketiga tercepat
|
||||
|
||||
TERAKHIR TERBIT (TANGGAL TERBARU):
|
||||
#1: SUMANTO TJAHAJA (02 Jul 2025) ← Terbaru
|
||||
#2: Eka Ferdiyus Supatmo (16 Jun 2025) ← Kedua terbaru
|
||||
#3: Adrian Mara Maulana (13 Jun 2025) ← Ketiga terbaru
|
||||
```
|
||||
|
||||
## ✅ STATUS: SYSTEM FULLY OPTIMIZED
|
||||
|
||||
Semua tabel sekarang menggunakan:
|
||||
|
||||
- **NO Static rank_order** yang bermasalah
|
||||
- **Logical ordering** berdasarkan data relevance
|
||||
- **Dynamic ranking** untuk display purposes
|
||||
- **Scalable architecture** untuk data masa depan
|
||||
|
||||
🎉 **System ready for production!**
|
|
@ -0,0 +1,164 @@
|
|||
# Setup Perizinan API Integration
|
||||
|
||||
## Overview
|
||||
|
||||
Sistem ini mengintegrasikan data perizinan dari API Jakarta dengan database lokal menggunakan cron job yang berjalan setiap hari pada jam 12 malam.
|
||||
|
||||
## Setup Instructions
|
||||
|
||||
### 1. Environment Configuration
|
||||
|
||||
Update file `.env` dengan credentials API:
|
||||
|
||||
```env
|
||||
# Perizinan API Configuration
|
||||
PERIZINAN_API_BASE_URL=https://wsdev.jakarta.go.id/gateway/DataPerizinanLingkungan/1.0
|
||||
PERIZINAN_API_BEARER_TOKEN=your_actual_bearer_token
|
||||
PERIZINAN_API_KEY=your_actual_api_key
|
||||
```
|
||||
|
||||
### 2. Database Migration
|
||||
|
||||
Jalankan migration untuk membuat tabel:
|
||||
|
||||
```bash
|
||||
php artisan migrate
|
||||
```
|
||||
|
||||
### 3. Manual Sync (Testing)
|
||||
|
||||
Untuk test sync manual:
|
||||
|
||||
```bash
|
||||
# Sync semua kategori
|
||||
php artisan perizinan:sync
|
||||
|
||||
# Sync kategori tertentu
|
||||
php artisan perizinan:sync pertek
|
||||
php artisan perizinan:sync amdal
|
||||
```
|
||||
|
||||
### 4. Cron Job Setup (Linux Production Server)
|
||||
|
||||
Sistem sudah dikonfigurasi untuk berjalan otomatis setiap hari jam 12 malam. Untuk aktivasi cron job di server Linux:
|
||||
|
||||
#### Opsi 1: Laravel Scheduler (Recommended)
|
||||
|
||||
```bash
|
||||
# Edit crontab
|
||||
crontab -e
|
||||
|
||||
# Add this line (runs every minute):
|
||||
* * * * * cd /var/www/perling && ./run-scheduler.sh >/dev/null 2>&1
|
||||
```
|
||||
|
||||
#### Opsi 2: Direct Sync
|
||||
|
||||
```bash
|
||||
# Edit crontab
|
||||
crontab -e
|
||||
|
||||
# Add this line (runs daily at midnight):
|
||||
0 0 * * * cd /var/www/perling && ./sync-perizinan.sh >/dev/null 2>&1
|
||||
```
|
||||
|
||||
### 5. Production Deployment
|
||||
|
||||
Untuk deployment ke production server, lihat file `PRODUCTION_DEPLOY.md` untuk panduan lengkap.
|
||||
|
||||
### 6. Monitoring
|
||||
|
||||
Cek log file untuk monitoring sync process:
|
||||
|
||||
```bash
|
||||
# Application logs
|
||||
tail -f storage/logs/laravel.log
|
||||
|
||||
# Cron job logs
|
||||
tail -f storage/logs/cron.log
|
||||
|
||||
# Scheduler logs (if using Laravel Scheduler)
|
||||
tail -f storage/logs/scheduler.log
|
||||
|
||||
# Check cron service status
|
||||
sudo systemctl status cron
|
||||
|
||||
# View cron job list
|
||||
crontab -l
|
||||
```
|
||||
|
||||
## API Response Format
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Summary by status retrieved successfully.",
|
||||
"data": [
|
||||
{
|
||||
"id": "ditolak",
|
||||
"label": "Izin Ditolak",
|
||||
"value": 1443
|
||||
},
|
||||
{
|
||||
"id": "selesai",
|
||||
"label": "Izin Selesai",
|
||||
"value": 8379
|
||||
},
|
||||
{
|
||||
"id": "proses",
|
||||
"label": "Dalam Proses",
|
||||
"value": 143
|
||||
},
|
||||
{
|
||||
"id": "total",
|
||||
"label": "Total Pengajuan",
|
||||
"value": 9965
|
||||
}
|
||||
],
|
||||
"last_updated": "2025-07-14 08:55:04"
|
||||
}
|
||||
```
|
||||
|
||||
## Database Schema
|
||||
|
||||
Table: `perizinan_status`
|
||||
|
||||
- `id` - Primary key
|
||||
- `kategori` - Category (pertek, amdal)
|
||||
- `status_id` - Status ID (ditolak, selesai, proses, total)
|
||||
- `label` - Human readable label
|
||||
- `value` - Count value
|
||||
- `api_last_updated` - Last updated timestamp from API
|
||||
- `sync_date` - Date when data was synced
|
||||
- `created_at` / `updated_at` - Laravel timestamps
|
||||
|
||||
## Features
|
||||
|
||||
- ✅ Daily automatic sync at midnight
|
||||
- ✅ Duplicate prevention (same data per day)
|
||||
- ✅ Comprehensive logging
|
||||
- ✅ Fallback to static data if API fails
|
||||
- ✅ Manual sync commands for testing
|
||||
- ✅ Database storage with indexing for performance
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Command not found
|
||||
|
||||
```bash
|
||||
php artisan optimize:clear
|
||||
composer dump-autoload
|
||||
```
|
||||
|
||||
### API Connection Issues
|
||||
|
||||
1. Verify credentials in `.env`
|
||||
2. Check network connectivity
|
||||
3. Review logs for detailed error messages
|
||||
|
||||
### Database Issues
|
||||
|
||||
```bash
|
||||
php artisan migrate:fresh
|
||||
php artisan migrate
|
||||
```
|
|
@ -0,0 +1,191 @@
|
|||
# Production Deployment Guide - Perizinan API Integration
|
||||
|
||||
## 🚀 Quick Deployment
|
||||
|
||||
### 1. Upload Files to Server
|
||||
|
||||
Upload semua files ke server Linux di directory `/var/www/perling` (atau sesuai setup Anda).
|
||||
|
||||
### 2. Run Deployment Script
|
||||
|
||||
```bash
|
||||
cd /var/www/perling
|
||||
chmod +x deploy-production.sh
|
||||
./deploy-production.sh
|
||||
```
|
||||
|
||||
### 3. Update Environment Variables
|
||||
|
||||
Edit file `.env` dan update:
|
||||
|
||||
```env
|
||||
APP_ENV=production
|
||||
APP_DEBUG=false
|
||||
|
||||
# Database production
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=3306
|
||||
DB_DATABASE=perling_production
|
||||
DB_USERNAME=your_db_user
|
||||
DB_PASSWORD=your_db_password
|
||||
|
||||
# API Credentials (yang sudah ada)
|
||||
PERIZINAN_API_BEARER_TOKEN=f633c4f8e8a84708895bf31c0afdfa68c9079613d18d4edfb1fc6a0019b5f832
|
||||
PERIZINAN_API_KEY=b06c8ef1-06db-443d-996b-80e8c3374923
|
||||
```
|
||||
|
||||
### 4. Setup Cron Job
|
||||
|
||||
#### Option 1: Laravel Scheduler (Recommended)
|
||||
|
||||
```bash
|
||||
# Edit crontab
|
||||
sudo crontab -e
|
||||
|
||||
# Add this line (runs every minute):
|
||||
* * * * * cd /var/www/perling && ./run-scheduler.sh >/dev/null 2>&1
|
||||
```
|
||||
|
||||
#### Option 2: Direct Sync
|
||||
|
||||
```bash
|
||||
# Edit crontab
|
||||
sudo crontab -e
|
||||
|
||||
# Add this line (runs daily at midnight):
|
||||
0 0 * * * cd /var/www/perling && ./sync-perizinan.sh >/dev/null 2>&1
|
||||
```
|
||||
|
||||
## 🔧 Manual Commands
|
||||
|
||||
### Test API Connection
|
||||
|
||||
```bash
|
||||
cd /var/www/perling
|
||||
php artisan perizinan:sync pertek
|
||||
```
|
||||
|
||||
### Check Synced Data
|
||||
|
||||
```bash
|
||||
php artisan perizinan:check
|
||||
```
|
||||
|
||||
### View Logs
|
||||
|
||||
```bash
|
||||
# Cron logs
|
||||
tail -f storage/logs/cron.log
|
||||
|
||||
# Laravel application logs
|
||||
tail -f storage/logs/laravel.log
|
||||
|
||||
# Scheduler logs (if using Option 1)
|
||||
tail -f storage/logs/scheduler.log
|
||||
```
|
||||
|
||||
### View Cron Job Status
|
||||
|
||||
```bash
|
||||
# View current crontab
|
||||
crontab -l
|
||||
|
||||
# View cron service logs
|
||||
sudo journalctl -u cron -f
|
||||
```
|
||||
|
||||
## 🐛 Troubleshooting
|
||||
|
||||
### Permission Issues
|
||||
|
||||
```bash
|
||||
# Fix storage permissions
|
||||
sudo chown -R www-data:www-data storage/
|
||||
sudo chmod -R 775 storage/
|
||||
|
||||
# Fix cache permissions
|
||||
sudo chown -R www-data:www-data bootstrap/cache/
|
||||
sudo chmod -R 775 bootstrap/cache/
|
||||
```
|
||||
|
||||
### Database Issues
|
||||
|
||||
```bash
|
||||
# Clear config cache
|
||||
php artisan config:clear
|
||||
|
||||
# Run migrations
|
||||
php artisan migrate --force
|
||||
|
||||
# Check database connection
|
||||
php artisan tinker --execute="DB::connection()->getPdo();"
|
||||
```
|
||||
|
||||
### API Connection Issues
|
||||
|
||||
```bash
|
||||
# Test API manually
|
||||
curl -X GET "https://wsdev.jakarta.go.id/gateway/DataPerizinanLingkungan/1.0/summary_by_status?kategori=pertek" \
|
||||
-H "Authorization: Bearer f633c4f8e8a84708895bf31c0afdfa68c9079613d18d4edfb1fc6a0019b5f832" \
|
||||
-H "x-Gateway-APIKey: b06c8ef1-06db-443d-996b-80e8c3374923"
|
||||
```
|
||||
|
||||
### Cron Job Not Running
|
||||
|
||||
```bash
|
||||
# Check if cron service is running
|
||||
sudo systemctl status cron
|
||||
|
||||
# Start cron service if stopped
|
||||
sudo systemctl start cron
|
||||
|
||||
# Check cron logs
|
||||
sudo tail -f /var/log/cron.log
|
||||
```
|
||||
|
||||
## 📊 Monitoring
|
||||
|
||||
### Daily Health Check
|
||||
|
||||
```bash
|
||||
# Check if data was synced today
|
||||
php artisan perizinan:check
|
||||
|
||||
# Check latest logs
|
||||
tail -20 storage/logs/cron.log
|
||||
```
|
||||
|
||||
### Weekly Review
|
||||
|
||||
```bash
|
||||
# Check database records count
|
||||
php artisan tinker --execute="echo App\Models\PerizinanStatus::count() . ' total records';"
|
||||
|
||||
# Check logs for errors
|
||||
grep -i error storage/logs/laravel.log | tail -10
|
||||
```
|
||||
|
||||
## 🔐 Security Notes
|
||||
|
||||
1. **File Permissions**: Ensure proper permissions for Laravel
|
||||
2. **Database**: Use strong password and limit access
|
||||
3. **API Keys**: Keep credentials secure, don't commit to version control
|
||||
4. **Logs**: Regularly rotate and clean up log files
|
||||
5. **SSL**: Use HTTPS in production
|
||||
|
||||
## 📈 Performance Tips
|
||||
|
||||
1. **Database Indexing**: Already added in migration
|
||||
2. **Log Rotation**: Setup logrotate for Laravel logs
|
||||
3. **Caching**: Enable OPcache for PHP
|
||||
4. **Queue**: Consider using Redis for Laravel queues if needed
|
||||
|
||||
## 🎯 Success Criteria
|
||||
|
||||
✅ Cron job runs without errors
|
||||
✅ Data syncs daily at midnight
|
||||
✅ Dashboard shows real API data
|
||||
✅ Logs are clean without errors
|
||||
✅ Database grows with daily records
|
||||
✅ API connection is stable
|
|
@ -0,0 +1,73 @@
|
|||
# Manual Task Scheduler Setup Instructions
|
||||
|
||||
## Cara Setup Task Scheduler Manual di Windows
|
||||
|
||||
### Langkah 1: Buka Task Scheduler
|
||||
|
||||
1. Tekan `Win + R`
|
||||
2. Ketik `taskschd.msc` dan tekan Enter
|
||||
3. Atau search "Task Scheduler" di Start Menu
|
||||
|
||||
### Langkah 2: Create Basic Task
|
||||
|
||||
1. Di panel kanan, klik **"Create Basic Task..."**
|
||||
2. **Name**: `PerizinanDataSync`
|
||||
3. **Description**: `Daily sync of perizinan data from Jakarta API at midnight`
|
||||
4. Klik **Next**
|
||||
|
||||
### Langkah 3: Trigger (Kapan berjalan)
|
||||
|
||||
1. Pilih **"Daily"**
|
||||
2. Klik **Next**
|
||||
3. **Start date**: Hari ini
|
||||
4. **Start time**: `12:00:00 AM` (midnight)
|
||||
5. **Recur every**: `1 days`
|
||||
6. Klik **Next**
|
||||
|
||||
### Langkah 4: Action (Apa yang dijalankan)
|
||||
|
||||
1. Pilih **"Start a program"**
|
||||
2. Klik **Next**
|
||||
3. **Program/script**: `C:\laragon\www\perling\sync-perizinan.bat`
|
||||
4. **Start in**: `C:\laragon\www\perling`
|
||||
5. Klik **Next**
|
||||
|
||||
### Langkah 5: Finish
|
||||
|
||||
1. Review semua setting
|
||||
2. ✅ Centang **"Open the Properties dialog for this task when I click Finish"**
|
||||
3. Klik **Finish**
|
||||
|
||||
### Langkah 6: Advanced Settings (Optional)
|
||||
|
||||
Di Properties dialog:
|
||||
|
||||
1. Tab **General**:
|
||||
|
||||
- ✅ Centang "Run whether user is logged on or not"
|
||||
- ✅ Centang "Run with highest privileges"
|
||||
|
||||
2. Tab **Conditions**:
|
||||
|
||||
- ❌ Uncheck "Start the task only if the computer is on AC power"
|
||||
|
||||
3. Tab **Settings**:
|
||||
|
||||
- ✅ Centang "Allow task to be run on demand"
|
||||
- ✅ Centang "Run task as soon as possible after a scheduled start is missed"
|
||||
|
||||
4. Klik **OK**
|
||||
|
||||
## Test Task
|
||||
|
||||
Untuk test task manual:
|
||||
|
||||
1. Klik kanan pada task **"PerizinanDataSync"**
|
||||
2. Pilih **"Run"**
|
||||
3. Check log di: `C:\laragon\www\perling\storage\logs\cron.log`
|
||||
|
||||
## Monitoring
|
||||
|
||||
- Task history bisa dilihat di tab **History** pada task properties
|
||||
- Log aplikasi di: `storage\logs\cron.log`
|
||||
- Log Laravel di: `storage\logs\laravel.log`
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use App\Models\PerizinanStatus;
|
||||
|
||||
class CheckPerizinanData extends Command
|
||||
{
|
||||
protected $signature = 'perizinan:check';
|
||||
protected $description = 'Check synced perizinan data';
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$this->info('📊 Checking synced perizinan data...');
|
||||
|
||||
$data = PerizinanStatus::orderBy('kategori')->orderBy('status_id')->get();
|
||||
|
||||
if ($data->isEmpty()) {
|
||||
$this->warn('No data found in database');
|
||||
return;
|
||||
}
|
||||
|
||||
$groupedData = $data->groupBy('kategori');
|
||||
|
||||
foreach ($groupedData as $kategori => $items) {
|
||||
$this->newLine();
|
||||
$this->info("=== {$kategori} ===");
|
||||
|
||||
foreach ($items as $item) {
|
||||
$this->line(" {$item->status_id}: {$item->label} = {$item->value}");
|
||||
}
|
||||
}
|
||||
|
||||
$this->newLine();
|
||||
$this->info("Total records: " . $data->count());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,154 @@
|
|||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use App\Services\PerizinanApiService;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class SyncPerizinanData extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'perizinan:sync {kategori?} {--fastest : Include fastest permohonan data sync} {--terakhir-terbit : Include terakhir terbit data sync} {--all : Include all data types (fastest + terakhir terbit)}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Sync perizinan data from API to database (status, fastest permohonan, and terakhir terbit)';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$startTime = Carbon::now();
|
||||
$kategori = $this->argument('kategori');
|
||||
$includeFastest = $this->option('fastest');
|
||||
$includeTerakhirTerbit = $this->option('terakhir-terbit');
|
||||
$includeAll = $this->option('all');
|
||||
|
||||
// If --all is specified, include both fastest and terakhir terbit
|
||||
if ($includeAll) {
|
||||
$includeFastest = true;
|
||||
$includeTerakhirTerbit = true;
|
||||
}
|
||||
|
||||
Log::info('Starting perizinan data sync', [
|
||||
'kategori' => $kategori,
|
||||
'include_fastest' => $includeFastest,
|
||||
'include_terakhir_terbit' => $includeTerakhirTerbit,
|
||||
'start_time' => $startTime->format('Y-m-d H:i:s')
|
||||
]);
|
||||
|
||||
$this->info('🚀 Starting perizinan data synchronization...');
|
||||
if ($includeFastest) {
|
||||
$this->info('⚡ Including fastest permohonan data sync');
|
||||
}
|
||||
if ($includeTerakhirTerbit) {
|
||||
$this->info('📋 Including terakhir terbit data sync');
|
||||
}
|
||||
|
||||
$service = new PerizinanApiService();
|
||||
|
||||
try {
|
||||
if ($kategori) {
|
||||
// Sync specific category
|
||||
$this->info("📥 Syncing status data for kategori: {$kategori}");
|
||||
$statusResult = $service->syncToDatabase($kategori);
|
||||
|
||||
if ($statusResult) {
|
||||
$this->info("✅ Successfully synced status data for kategori: {$kategori}");
|
||||
} else {
|
||||
$this->error("❌ Failed to sync status data for kategori: {$kategori}");
|
||||
}
|
||||
|
||||
// Sync fastest data if requested
|
||||
if ($includeFastest) {
|
||||
$this->info("⚡ Syncing fastest permohonan data for kategori: {$kategori}");
|
||||
$fastestResult = $service->syncFastestDataToDatabase($kategori, 5);
|
||||
|
||||
if ($fastestResult) {
|
||||
$this->info("✅ Successfully synced fastest data for kategori: {$kategori}");
|
||||
} else {
|
||||
$this->error("❌ Failed to sync fastest data for kategori: {$kategori}");
|
||||
}
|
||||
}
|
||||
|
||||
// Sync terakhir terbit data if requested
|
||||
if ($includeTerakhirTerbit) {
|
||||
$this->info("📋 Syncing terakhir terbit data for kategori: {$kategori}");
|
||||
$terakhirTerbitResult = $service->syncTerakhirTerbitToDatabase($kategori, 5);
|
||||
|
||||
if ($terakhirTerbitResult) {
|
||||
$this->info("✅ Successfully synced terakhir terbit data for kategori: {$kategori}");
|
||||
} else {
|
||||
$this->error("❌ Failed to sync terakhir terbit data for kategori: {$kategori}");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Sync all categories
|
||||
$this->info("📥 Syncing data for all categories...");
|
||||
$results = $service->syncAllCategories($includeFastest, $includeTerakhirTerbit);
|
||||
|
||||
foreach ($results as $cat => $result) {
|
||||
if (isset($result['status']) && $result['status']) {
|
||||
$this->info("✅ Successfully synced status data for kategori: {$cat}");
|
||||
} else {
|
||||
$this->error("❌ Failed to sync status data for kategori: {$cat}");
|
||||
}
|
||||
|
||||
if ($includeFastest) {
|
||||
if (isset($result['fastest']) && $result['fastest']) {
|
||||
$this->info("✅ Successfully synced fastest data for kategori: {$cat}");
|
||||
} else {
|
||||
$this->error("❌ Failed to sync fastest data for kategori: {$cat}");
|
||||
}
|
||||
}
|
||||
|
||||
if ($includeTerakhirTerbit) {
|
||||
if (isset($result['terakhir_terbit']) && $result['terakhir_terbit']) {
|
||||
$this->info("✅ Successfully synced terakhir terbit data for kategori: {$cat}");
|
||||
} else {
|
||||
$this->error("❌ Failed to sync terakhir terbit data for kategori: {$cat}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$endTime = Carbon::now();
|
||||
$duration = $endTime->diffInSeconds($startTime);
|
||||
|
||||
$this->info("🎉 Data synchronization completed in {$duration} seconds");
|
||||
|
||||
Log::info('Perizinan data sync completed', [
|
||||
'kategori' => $kategori ?: 'all',
|
||||
'include_fastest' => $includeFastest,
|
||||
'include_terakhir_terbit' => $includeTerakhirTerbit,
|
||||
'duration_seconds' => $duration,
|
||||
'end_time' => $endTime->format('Y-m-d H:i:s')
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$this->error("💥 An error occurred: " . $e->getMessage());
|
||||
|
||||
Log::error('Perizinan data sync failed', [
|
||||
'kategori' => $kategori ?: 'all',
|
||||
'include_fastest' => $includeFastest,
|
||||
'include_terakhir_terbit' => $includeTerakhirTerbit,
|
||||
'error' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use App\Models\PerizinanStatus;
|
||||
use App\Services\PerizinanApiService;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class TestProductionSetup extends Command
|
||||
{
|
||||
protected $signature = 'perizinan:test-production';
|
||||
protected $description = 'Test production setup readiness';
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$this->info('🔍 Testing Production Setup Readiness...');
|
||||
$this->newLine();
|
||||
|
||||
$allPassed = true;
|
||||
|
||||
// Test 1: Database Connection
|
||||
$this->info('1. Testing Database Connection...');
|
||||
try {
|
||||
DB::connection()->getPdo();
|
||||
$this->line(' ✅ Database connection successful');
|
||||
} catch (\Exception $e) {
|
||||
$this->error(' ❌ Database connection failed: ' . $e->getMessage());
|
||||
$allPassed = false;
|
||||
}
|
||||
|
||||
// Test 2: Database Table
|
||||
$this->info('2. Testing Database Table...');
|
||||
try {
|
||||
$count = PerizinanStatus::count();
|
||||
$this->line(" ✅ perizinan_status table exists with {$count} records");
|
||||
} catch (\Exception $e) {
|
||||
$this->error(' ❌ Database table issue: ' . $e->getMessage());
|
||||
$allPassed = false;
|
||||
}
|
||||
|
||||
// Test 3: API Configuration
|
||||
$this->info('3. Testing API Configuration...');
|
||||
$bearerToken = config('services.perizinan.bearer_token');
|
||||
$apiKey = config('services.perizinan.api_key');
|
||||
|
||||
if ($bearerToken && $bearerToken !== 'your_actual_bearer_token') {
|
||||
$this->line(' ✅ Bearer token configured');
|
||||
} else {
|
||||
$this->error(' ❌ Bearer token not configured properly');
|
||||
$allPassed = false;
|
||||
}
|
||||
|
||||
if ($apiKey && $apiKey !== 'your_actual_api_key') {
|
||||
$this->line(' ✅ API key configured');
|
||||
} else {
|
||||
$this->error(' ❌ API key not configured properly');
|
||||
$allPassed = false;
|
||||
}
|
||||
|
||||
// Test 4: API Connection
|
||||
$this->info('4. Testing API Connection...');
|
||||
try {
|
||||
$service = new PerizinanApiService();
|
||||
$response = $service->fetchSummaryByStatus('pertek');
|
||||
|
||||
if ($response && ($response['success'] ?? false)) {
|
||||
$this->line(' ✅ API connection successful');
|
||||
$dataCount = count($response['data'] ?? []);
|
||||
$this->line(" 📊 Retrieved {$dataCount} data items");
|
||||
} else {
|
||||
$this->error(' ❌ API response invalid');
|
||||
$allPassed = false;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$this->error(' ❌ API connection failed: ' . $e->getMessage());
|
||||
$allPassed = false;
|
||||
}
|
||||
|
||||
// Test 5: Storage Permissions
|
||||
$this->info('5. Testing Storage Permissions...');
|
||||
$logPath = storage_path('logs/test-' . time() . '.log');
|
||||
try {
|
||||
file_put_contents($logPath, 'test');
|
||||
unlink($logPath);
|
||||
$this->line(' ✅ Storage directory writable');
|
||||
} catch (\Exception $e) {
|
||||
$this->error(' ❌ Storage permission issue: ' . $e->getMessage());
|
||||
$allPassed = false;
|
||||
}
|
||||
|
||||
// Test 6: Schedule Configuration
|
||||
$this->info('6. Testing Schedule Configuration...');
|
||||
try {
|
||||
Artisan::call('schedule:list');
|
||||
$this->line(' ✅ Laravel scheduler configured');
|
||||
$this->line(' 📅 Run "php artisan schedule:list" to see scheduled commands');
|
||||
} catch (\Exception $e) {
|
||||
$this->error(' ❌ Scheduler issue: ' . $e->getMessage());
|
||||
$allPassed = false;
|
||||
}
|
||||
|
||||
$this->newLine();
|
||||
|
||||
if ($allPassed) {
|
||||
$this->info('🎉 All tests passed! Production setup is ready.');
|
||||
$this->newLine();
|
||||
$this->info('Next steps:');
|
||||
$this->line('1. Deploy to production server');
|
||||
$this->line('2. Run: chmod +x *.sh');
|
||||
$this->line('3. Run: ./deploy-production.sh');
|
||||
$this->line('4. Setup cron job as per PRODUCTION_DEPLOY.md');
|
||||
} else {
|
||||
$this->error('❌ Some tests failed. Please fix the issues above.');
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
|
@ -2,12 +2,17 @@
|
|||
|
||||
namespace App\Helpers;
|
||||
|
||||
use App\Models\PerizinanStatus;
|
||||
use App\Models\FastestPermohonan;
|
||||
use App\Models\TerakhirTerbit;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class DashboardHelper
|
||||
{
|
||||
/**
|
||||
* Data master untuk setiap jenis izin
|
||||
* Data master untuk setiap jenis izin - fallback untuk data yang belum ada di API
|
||||
*/
|
||||
public static function getStatusDataByType(string $type): array
|
||||
public static function getFallbackDataByType(string $type): array
|
||||
{
|
||||
$data = [
|
||||
'pertek' => [
|
||||
|
@ -15,16 +20,16 @@ class DashboardHelper
|
|||
['id' => 'selesai', 'label' => 'Izin Selesai', 'value' => 45, 'color' => 'success'],
|
||||
['id' => 'proses', 'label' => 'Dalam Proses', 'value' => 12, 'color' => 'info'],
|
||||
],
|
||||
'rintek' => [
|
||||
['id' => 'ditolak', 'label' => 'Izin Ditolak', 'value' => 5, 'color' => 'danger'],
|
||||
['id' => 'selesai', 'label' => 'Izin Selesai', 'value' => 28, 'color' => 'success'],
|
||||
['id' => 'proses', 'label' => 'Dalam Proses', 'value' => 7, 'color' => 'info'],
|
||||
],
|
||||
'amdal' => [
|
||||
['id' => 'ditolak', 'label' => 'Izin Ditolak', 'value' => 3, 'color' => 'danger'],
|
||||
['id' => 'selesai', 'label' => 'Izin Selesai', 'value' => 15, 'color' => 'success'],
|
||||
['id' => 'proses', 'label' => 'Dalam Proses', 'value' => 9, 'color' => 'info'],
|
||||
],
|
||||
'rintek' => [
|
||||
['id' => 'ditolak', 'label' => 'Izin Ditolak', 'value' => 5, 'color' => 'danger'],
|
||||
['id' => 'selesai', 'label' => 'Izin Selesai', 'value' => 28, 'color' => 'success'],
|
||||
['id' => 'proses', 'label' => 'Dalam Proses', 'value' => 7, 'color' => 'info'],
|
||||
],
|
||||
'izin_angkut' => [
|
||||
['id' => 'ditolak', 'label' => 'Izin Ditolak', 'value' => 12, 'color' => 'danger'],
|
||||
['id' => 'selesai', 'label' => 'Izin Selesai', 'value' => 67, 'color' => 'success'],
|
||||
|
@ -40,11 +45,48 @@ class DashboardHelper
|
|||
return $data[$type] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get status data by type from database or fallback
|
||||
*/
|
||||
public static function getStatusDataByType(string $type): array
|
||||
{
|
||||
// Cek apakah ada data di database untuk hari ini
|
||||
$dbData = PerizinanStatus::getLatestByKategori($type);
|
||||
|
||||
if ($dbData->isNotEmpty()) {
|
||||
$data = [];
|
||||
$colorMap = [
|
||||
'ditolak' => 'danger',
|
||||
'selesai' => 'success',
|
||||
'proses' => 'info',
|
||||
'total' => 'primary'
|
||||
];
|
||||
|
||||
foreach ($dbData as $item) {
|
||||
$data[] = [
|
||||
'id' => $item->status_id,
|
||||
'label' => $item->label,
|
||||
'value' => $item->value,
|
||||
'color' => $colorMap[$item->status_id] ?? 'secondary'
|
||||
];
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
// Fallback ke data statis jika tidak ada data di database
|
||||
return self::getFallbackDataByType($type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Menambahkan total ke data statuses
|
||||
*/
|
||||
public static function addTotalToStatuses(array $statuses): array
|
||||
{
|
||||
// Cek apakah sudah ada total di data
|
||||
$hasTotal = collect($statuses)->contains('id', 'total');
|
||||
|
||||
if (!$hasTotal) {
|
||||
$total = array_sum(array_column($statuses, 'value'));
|
||||
$statuses[] = [
|
||||
'id' => 'total',
|
||||
|
@ -52,6 +94,7 @@ class DashboardHelper
|
|||
'value' => $total,
|
||||
'color' => 'primary'
|
||||
];
|
||||
}
|
||||
|
||||
return $statuses;
|
||||
}
|
||||
|
@ -224,4 +267,181 @@ class DashboardHelper
|
|||
|
||||
return $colors[$statusId] ?? '#6b7280';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get fastest permohonan data from database or fallback
|
||||
*/
|
||||
public static function getFastestPermohonanByType(string $type): array
|
||||
{
|
||||
// Try to get data from database first
|
||||
$dbData = FastestPermohonan::getLatestByKategori($type);
|
||||
|
||||
if ($dbData->isNotEmpty()) {
|
||||
$data = [];
|
||||
$index = 1;
|
||||
foreach ($dbData as $item) {
|
||||
$data[] = [
|
||||
'nama' => $item->nama,
|
||||
'total' => $item->total,
|
||||
'durasi_pemohon' => $item->durasi_pemohon,
|
||||
'durasi_petugas' => $item->durasi_petugas,
|
||||
'rank_order' => $index++ // Generate rank_order dinamis untuk display
|
||||
];
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
// Fallback to static data if no database data available
|
||||
return self::getFallbackFastestData($type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fallback static data for fastest permohonan
|
||||
*/
|
||||
private static function getFallbackFastestData(string $type): array
|
||||
{
|
||||
$fallbackData = [
|
||||
'pertek' => [
|
||||
['nama' => 'Izin Pengelolaan Limbah B3', 'total' => 45, 'durasi_pemohon' => '2 Hari 3 Jam', 'durasi_petugas' => '5 Hari 2 Jam', 'rank_order' => 1],
|
||||
['nama' => 'Izin Emisi Kendaraan', 'total' => 67, 'durasi_pemohon' => '3 Hari 1 Jam', 'durasi_petugas' => '7 Hari 4 Jam', 'rank_order' => 2],
|
||||
['nama' => 'SPPL Industri', 'total' => 23, 'durasi_pemohon' => '4 Hari 2 Jam', 'durasi_petugas' => '8 Hari 1 Jam', 'rank_order' => 3],
|
||||
['nama' => 'Izin Penyimpanan B3', 'total' => 34, 'durasi_pemohon' => '5 Hari', 'durasi_petugas' => '9 Hari 3 Jam', 'rank_order' => 4],
|
||||
['nama' => 'Izin Pengolahan Limbah', 'total' => 19, 'durasi_pemohon' => '6 Hari 4 Jam', 'durasi_petugas' => '10 Hari 2 Jam', 'rank_order' => 5],
|
||||
],
|
||||
'amdal' => [
|
||||
['nama' => 'AMDAL Bangunan Tinggi', 'total' => 12, 'durasi_pemohon' => '15 Hari', 'durasi_petugas' => '30 Hari', 'rank_order' => 1],
|
||||
['nama' => 'AMDAL Infrastruktur', 'total' => 8, 'durasi_pemohon' => '18 Hari', 'durasi_petugas' => '35 Hari', 'rank_order' => 2],
|
||||
['nama' => 'AMDAL Industri', 'total' => 15, 'durasi_pemohon' => '20 Hari', 'durasi_petugas' => '40 Hari', 'rank_order' => 3],
|
||||
['nama' => 'AMDAL Perumahan', 'total' => 25, 'durasi_pemohon' => '22 Hari', 'durasi_petugas' => '42 Hari', 'rank_order' => 4],
|
||||
['nama' => 'AMDAL Komersial', 'total' => 18, 'durasi_pemohon' => '25 Hari', 'durasi_petugas' => '45 Hari', 'rank_order' => 5],
|
||||
]
|
||||
];
|
||||
|
||||
return $fallbackData[$type] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get fastest data for all supported types
|
||||
*/
|
||||
public static function getAllFastestData(): array
|
||||
{
|
||||
$supportedTypes = ['pertek', 'amdal']; // Only types with API endpoints
|
||||
$allData = [];
|
||||
|
||||
foreach ($supportedTypes as $type) {
|
||||
$allData[$type] = [
|
||||
'type' => $type,
|
||||
'label' => self::getTypeLabel($type),
|
||||
'data' => self::getFastestPermohonanByType($type)
|
||||
];
|
||||
}
|
||||
|
||||
return $allData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format duration text for display
|
||||
*/
|
||||
public static function formatDuration(string $duration): string
|
||||
{
|
||||
// API returns format like "03 Jam 54 Menit 13 Detik" or "2 Hari 01 Jam 02 Menit 04 Detik"
|
||||
return $duration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get rank badge color based on ranking
|
||||
*/
|
||||
public static function getRankBadgeColor(int $rank): string
|
||||
{
|
||||
$colors = [
|
||||
1 => 'success',
|
||||
2 => 'info',
|
||||
3 => 'warning',
|
||||
4 => 'secondary',
|
||||
5 => 'dark'
|
||||
];
|
||||
|
||||
return $colors[$rank] ?? 'secondary';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get terakhir terbit data from database or fallback
|
||||
*/
|
||||
public static function getTerakhirTerbitByType(string $type): array
|
||||
{
|
||||
// Try to get data from database first
|
||||
$dbData = TerakhirTerbit::getLatestByKategori($type);
|
||||
|
||||
if ($dbData->isNotEmpty()) {
|
||||
$data = [];
|
||||
$index = 1;
|
||||
foreach ($dbData as $item) {
|
||||
$data[] = [
|
||||
'nama_izin' => $item->nama_izin,
|
||||
'pemohon' => $item->pemohon,
|
||||
'tanggal_terbit' => $item->tanggal_terbit,
|
||||
'rank_order' => $index++ // Generate rank_order dinamis untuk display
|
||||
];
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
// Fallback to static data if no database data available
|
||||
return self::getFallbackTerakhirTerbitData($type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fallback static data for terakhir terbit
|
||||
*/
|
||||
private static function getFallbackTerakhirTerbitData(string $type): array
|
||||
{
|
||||
$fallbackData = [
|
||||
'pertek' => [
|
||||
['nama_izin' => 'SERTIFIKAT LAIK OPERASI - PEMENUHAN BAKU MUTU EMISI', 'pemohon' => 'PT. MAJU BERSAMA', 'tanggal_terbit' => Carbon::parse('2025-07-15'), 'rank_order' => 1],
|
||||
['nama_izin' => 'PERSETUJUAN TEKNIS - PEMENUHAN BAKU MUTU AIR LIMBAH', 'pemohon' => 'CV. KARYA MANDIRI', 'tanggal_terbit' => Carbon::parse('2025-07-14'), 'rank_order' => 2],
|
||||
['nama_izin' => 'SERTIFIKAT LAIK OPERASI - PEMENUHAN BAKU MUTU AIR LIMBAH', 'pemohon' => 'PT. INDUSTRI SEJAHTERA', 'tanggal_terbit' => Carbon::parse('2025-07-13'), 'rank_order' => 3],
|
||||
['nama_izin' => 'PERSETUJUAN TEKNIS - PEMENUHAN BAKU MUTU AIR LIMBAH', 'pemohon' => 'PT. TEKNOLOGI MODERN', 'tanggal_terbit' => Carbon::parse('2025-07-12'), 'rank_order' => 4],
|
||||
['nama_izin' => 'SERTIFIKAT LAIK OPERASI - PEMENUHAN BAKU MUTU EMISI', 'pemohon' => 'CV. BERKAH JAYA', 'tanggal_terbit' => Carbon::parse('2025-07-11'), 'rank_order' => 5],
|
||||
],
|
||||
'amdal' => [
|
||||
['nama_izin' => 'AMDAL BANGUNAN TINGGI', 'pemohon' => 'PT. KONSTRUKSI PRIMA', 'tanggal_terbit' => Carbon::parse('2025-07-10'), 'rank_order' => 1],
|
||||
['nama_izin' => 'AMDAL INFRASTRUKTUR', 'pemohon' => 'CV. INFRATEK', 'tanggal_terbit' => Carbon::parse('2025-07-09'), 'rank_order' => 2],
|
||||
['nama_izin' => 'AMDAL INDUSTRI', 'pemohon' => 'PT. INDUSTRI MODERN', 'tanggal_terbit' => Carbon::parse('2025-07-08'), 'rank_order' => 3],
|
||||
['nama_izin' => 'AMDAL PERUMAHAN', 'pemohon' => 'PT. PROPERTI SEJAHTERA', 'tanggal_terbit' => Carbon::parse('2025-07-07'), 'rank_order' => 4],
|
||||
['nama_izin' => 'AMDAL KOMERSIAL', 'pemohon' => 'CV. KOMERSIAL JAYA', 'tanggal_terbit' => Carbon::parse('2025-07-06'), 'rank_order' => 5],
|
||||
]
|
||||
];
|
||||
|
||||
return $fallbackData[$type] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get terakhir terbit data for all supported types
|
||||
*/
|
||||
public static function getAllTerakhirTerbitData(): array
|
||||
{
|
||||
$supportedTypes = ['pertek', 'amdal']; // Only types with API endpoints
|
||||
$allData = [];
|
||||
|
||||
foreach ($supportedTypes as $type) {
|
||||
$allData[$type] = [
|
||||
'type' => $type,
|
||||
'label' => self::getTypeLabel($type),
|
||||
'data' => self::getTerakhirTerbitByType($type)
|
||||
];
|
||||
}
|
||||
|
||||
return $allData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format tanggal terbit for display
|
||||
*/
|
||||
public static function formatTanggalTerbit($tanggal): string
|
||||
{
|
||||
if ($tanggal instanceof Carbon) {
|
||||
return $tanggal->format('d M Y');
|
||||
}
|
||||
return Carbon::parse($tanggal)->format('d M Y');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,14 +8,23 @@ use Illuminate\Support\Facades\Log;
|
|||
use App\Helpers\DashboardHelper;
|
||||
|
||||
class DashboardController extends Controller
|
||||
{ public function index()
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
// Get all statistics for all types
|
||||
$allStatistics = DashboardHelper::getAllStatistics();
|
||||
|
||||
// Get fastest data for types that have API endpoints
|
||||
$allFastestData = DashboardHelper::getAllFastestData();
|
||||
|
||||
// Get terakhir terbit data for types that have API endpoints
|
||||
$allTerakhirTerbitData = DashboardHelper::getAllTerakhirTerbitData();
|
||||
|
||||
// Default data for main view (PERTEK)
|
||||
$statuses = DashboardHelper::getStatusDataByType('pertek');
|
||||
$statuses = DashboardHelper::addTotalToStatuses($statuses);
|
||||
$type = 'pertek';
|
||||
|
||||
return view('dashboard/index', compact('statuses', 'type'));
|
||||
return view('dashboard/index', compact('statuses', 'type', 'allStatistics', 'allFastestData', 'allTerakhirTerbitData'));
|
||||
}
|
||||
|
||||
public function pertek()
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class FastestPermohonan extends Model
|
||||
{
|
||||
protected $table = 'fastest_permohonan';
|
||||
|
||||
protected $fillable = [
|
||||
'kategori',
|
||||
'nama',
|
||||
'total',
|
||||
'durasi_pemohon',
|
||||
'durasi_petugas',
|
||||
'api_last_updated',
|
||||
'sync_date'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'api_last_updated' => 'datetime',
|
||||
'sync_date' => 'date',
|
||||
'total' => 'integer'
|
||||
];
|
||||
|
||||
/**
|
||||
* Get fastest permohonan by kategori for today, ordered by API response order
|
||||
* Since API already provides fastest data in correct order
|
||||
*/
|
||||
public static function getLatestByKategori($kategori)
|
||||
{
|
||||
return self::where('kategori', $kategori)
|
||||
->whereDate('sync_date', Carbon::today())
|
||||
->orderBy('id') // Keep API order by using insertion order
|
||||
->limit(5)
|
||||
->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get data for specific kategori and date
|
||||
*/
|
||||
public static function getByKategoriAndDate($kategori, $date = null)
|
||||
{
|
||||
$date = $date ?: Carbon::today();
|
||||
|
||||
return self::where('kategori', $kategori)
|
||||
->whereDate('sync_date', $date)
|
||||
->orderBy('id') // Keep API order by using insertion order
|
||||
->limit(5)
|
||||
->get();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class PerizinanStatus extends Model
|
||||
{
|
||||
protected $table = 'perizinan_status';
|
||||
|
||||
protected $fillable = [
|
||||
'kategori',
|
||||
'status_id',
|
||||
'label',
|
||||
'value',
|
||||
'api_last_updated',
|
||||
'sync_date'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'api_last_updated' => 'datetime',
|
||||
'sync_date' => 'date',
|
||||
'value' => 'integer'
|
||||
];
|
||||
|
||||
/**
|
||||
* Get latest data by kategori
|
||||
*/
|
||||
public static function getLatestByKategori($kategori)
|
||||
{
|
||||
return self::where('kategori', $kategori)
|
||||
->whereDate('sync_date', Carbon::today())
|
||||
->get()
|
||||
->keyBy('status_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get data for specific kategori and date
|
||||
*/
|
||||
public static function getByKategoriAndDate($kategori, $date = null)
|
||||
{
|
||||
$date = $date ?: Carbon::today();
|
||||
|
||||
return self::where('kategori', $kategori)
|
||||
->whereDate('sync_date', $date)
|
||||
->get()
|
||||
->keyBy('status_id');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class TerakhirTerbit extends Model
|
||||
{
|
||||
protected $table = 'terakhir_terbit';
|
||||
|
||||
protected $fillable = [
|
||||
'kategori',
|
||||
'nama_izin',
|
||||
'pemohon',
|
||||
'tanggal_terbit',
|
||||
'api_last_updated',
|
||||
'sync_date'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'tanggal_terbit' => 'date',
|
||||
'api_last_updated' => 'datetime',
|
||||
'sync_date' => 'date'
|
||||
];
|
||||
|
||||
/**
|
||||
* Get latest data by kategori for today, ordered by tanggal_terbit DESC
|
||||
*/
|
||||
public static function getLatestByKategori($kategori)
|
||||
{
|
||||
return self::where('kategori', $kategori)
|
||||
->whereDate('sync_date', Carbon::today())
|
||||
->orderBy('tanggal_terbit', 'desc')
|
||||
->limit(5)
|
||||
->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get data by kategori and specific date, ordered by tanggal_terbit DESC
|
||||
*/
|
||||
public static function getByKategoriAndDate($kategori, $date)
|
||||
{
|
||||
return self::where('kategori', $kategori)
|
||||
->whereDate('sync_date', $date)
|
||||
->orderBy('tanggal_terbit', 'desc')
|
||||
->limit(5)
|
||||
->get();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,358 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use App\Models\PerizinanStatus;
|
||||
use App\Models\FastestPermohonan;
|
||||
use App\Models\TerakhirTerbit;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class PerizinanApiService
|
||||
{
|
||||
private $baseUrl;
|
||||
private $bearerToken;
|
||||
private $apiKey;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->baseUrl = config('services.perizinan.base_url', 'https://wsdev.jakarta.go.id/gateway/DataPerizinanLingkungan/1.0');
|
||||
$this->bearerToken = config('services.perizinan.bearer_token');
|
||||
$this->apiKey = config('services.perizinan.api_key');
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch summary by status from API
|
||||
*/
|
||||
public function fetchSummaryByStatus($kategori)
|
||||
{
|
||||
try {
|
||||
$url = "{$this->baseUrl}/summary_by_status";
|
||||
|
||||
Log::info("Fetching data from API", [
|
||||
'url' => $url,
|
||||
'kategori' => $kategori
|
||||
]);
|
||||
|
||||
$response = Http::timeout(60) // Increase timeout to 60 seconds
|
||||
->retry(3, 100) // Retry 3 times with 100ms delay
|
||||
->withHeaders([
|
||||
'Authorization' => 'Bearer ' . $this->bearerToken,
|
||||
'x-Gateway-APIKey' => $this->apiKey,
|
||||
'Accept' => 'application/json',
|
||||
'Content-Type' => 'application/json'
|
||||
])->get($url, [
|
||||
'kategori' => $kategori
|
||||
]);
|
||||
|
||||
if ($response->successful()) {
|
||||
$data = $response->json();
|
||||
|
||||
Log::info("API Response received", [
|
||||
'kategori' => $kategori,
|
||||
'success' => $data['success'] ?? false,
|
||||
'data_count' => count($data['data'] ?? [])
|
||||
]);
|
||||
|
||||
return $data;
|
||||
} else {
|
||||
Log::error("API request failed", [
|
||||
'kategori' => $kategori,
|
||||
'status' => $response->status(),
|
||||
'body' => $response->body()
|
||||
]);
|
||||
|
||||
return null;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Log::error("Exception occurred while fetching API data", [
|
||||
'kategori' => $kategori,
|
||||
'error' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync data to database
|
||||
*/
|
||||
public function syncToDatabase($kategori)
|
||||
{
|
||||
$apiData = $this->fetchSummaryByStatus($kategori);
|
||||
|
||||
if (!$apiData || !($apiData['success'] ?? false)) {
|
||||
Log::warning("Failed to fetch data for kategori: {$kategori}");
|
||||
return false;
|
||||
}
|
||||
|
||||
$syncDate = Carbon::today();
|
||||
$apiLastUpdated = Carbon::parse($apiData['last_updated']);
|
||||
$syncedCount = 0;
|
||||
|
||||
foreach ($apiData['data'] as $item) {
|
||||
// Check if data already exists for today
|
||||
$exists = PerizinanStatus::where('kategori', $kategori)
|
||||
->where('status_id', $item['id'])
|
||||
->whereDate('sync_date', $syncDate)
|
||||
->exists();
|
||||
|
||||
if (!$exists) {
|
||||
PerizinanStatus::create([
|
||||
'kategori' => $kategori,
|
||||
'status_id' => $item['id'],
|
||||
'label' => $item['label'],
|
||||
'value' => $item['value'],
|
||||
'api_last_updated' => $apiLastUpdated,
|
||||
'sync_date' => $syncDate
|
||||
]);
|
||||
|
||||
$syncedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
Log::info("Data synced successfully", [
|
||||
'kategori' => $kategori,
|
||||
'synced_count' => $syncedCount,
|
||||
'sync_date' => $syncDate->format('Y-m-d'),
|
||||
'api_last_updated' => $apiLastUpdated->format('Y-m-d H:i:s')
|
||||
]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch fastest permohonan from API
|
||||
*/
|
||||
public function fetchFastestPermohonan($kategori, $limit = 5)
|
||||
{
|
||||
try {
|
||||
$url = "{$this->baseUrl}/fastest_permohonan";
|
||||
|
||||
Log::info("Fetching fastest permohonan data from API", [
|
||||
'url' => $url,
|
||||
'kategori' => $kategori,
|
||||
'limit' => $limit
|
||||
]);
|
||||
|
||||
$response = Http::timeout(60) // Increase timeout to 60 seconds
|
||||
->retry(3, 100) // Retry 3 times with 100ms delay
|
||||
->withHeaders([
|
||||
'Authorization' => 'Bearer ' . $this->bearerToken,
|
||||
'x-Gateway-APIKey' => $this->apiKey,
|
||||
'Accept' => 'application/json',
|
||||
'Content-Type' => 'application/json'
|
||||
])->get($url, [
|
||||
'kategori' => $kategori,
|
||||
'limit' => $limit
|
||||
]);
|
||||
|
||||
if ($response->successful()) {
|
||||
$data = $response->json();
|
||||
|
||||
Log::info("Fastest permohonan API Response received", [
|
||||
'kategori' => $kategori,
|
||||
'limit' => $limit,
|
||||
'success' => $data['success'] ?? false,
|
||||
'data_count' => count($data['data'] ?? [])
|
||||
]);
|
||||
|
||||
return $data;
|
||||
} else {
|
||||
Log::error("Fastest permohonan API request failed", [
|
||||
'kategori' => $kategori,
|
||||
'limit' => $limit,
|
||||
'status' => $response->status(),
|
||||
'body' => $response->body()
|
||||
]);
|
||||
|
||||
return null;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Log::error("Exception occurred while fetching fastest permohonan data", [
|
||||
'kategori' => $kategori,
|
||||
'limit' => $limit,
|
||||
'error' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync fastest permohonan data to database
|
||||
*/
|
||||
public function syncFastestDataToDatabase($kategori, $limit = 5)
|
||||
{
|
||||
$apiData = $this->fetchFastestPermohonan($kategori, $limit);
|
||||
|
||||
if (!$apiData || !($apiData['success'] ?? false)) {
|
||||
Log::warning("Failed to fetch fastest permohonan data for kategori: {$kategori}");
|
||||
return false;
|
||||
}
|
||||
|
||||
$syncDate = Carbon::today();
|
||||
$apiLastUpdated = Carbon::parse($apiData['last_updated']);
|
||||
$syncedCount = 0;
|
||||
|
||||
// Delete existing data for today to ensure we have fresh data
|
||||
FastestPermohonan::where('kategori', $kategori)
|
||||
->whereDate('sync_date', $syncDate)
|
||||
->delete();
|
||||
|
||||
foreach ($apiData['data'] as $index => $item) {
|
||||
FastestPermohonan::create([
|
||||
'kategori' => $kategori,
|
||||
'nama' => $item['nama'],
|
||||
'total' => $item['total'],
|
||||
'durasi_pemohon' => $item['durasi_pemohon'],
|
||||
'durasi_petugas' => $item['durasi_petugas'],
|
||||
'api_last_updated' => $apiLastUpdated,
|
||||
'sync_date' => $syncDate
|
||||
]);
|
||||
|
||||
$syncedCount++;
|
||||
}
|
||||
|
||||
Log::info("Fastest permohonan data synced successfully", [
|
||||
'kategori' => $kategori,
|
||||
'synced_count' => $syncedCount,
|
||||
'sync_date' => $syncDate->format('Y-m-d'),
|
||||
'api_last_updated' => $apiLastUpdated->format('Y-m-d H:i:s')
|
||||
]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch terakhir terbit from API
|
||||
*/
|
||||
public function fetchTerakhirTerbit($kategori, $limit = 5)
|
||||
{
|
||||
try {
|
||||
$url = "{$this->baseUrl}/terakhir_terbit";
|
||||
|
||||
Log::info("Fetching terakhir terbit data from API", [
|
||||
'url' => $url,
|
||||
'kategori' => $kategori,
|
||||
'limit' => $limit
|
||||
]);
|
||||
|
||||
$response = Http::timeout(60) // Increase timeout to 60 seconds
|
||||
->retry(3, 100) // Retry 3 times with 100ms delay
|
||||
->withHeaders([
|
||||
'Authorization' => 'Bearer ' . $this->bearerToken,
|
||||
'x-Gateway-APIKey' => $this->apiKey,
|
||||
'Accept' => 'application/json',
|
||||
'Content-Type' => 'application/json'
|
||||
])->get($url, [
|
||||
'kategori' => $kategori,
|
||||
'limit' => $limit
|
||||
]);
|
||||
|
||||
if ($response->successful()) {
|
||||
$data = $response->json();
|
||||
|
||||
Log::info("Terakhir terbit API Response received", [
|
||||
'kategori' => $kategori,
|
||||
'limit' => $limit,
|
||||
'success' => $data['success'] ?? false,
|
||||
'data_count' => count($data['data'] ?? [])
|
||||
]);
|
||||
|
||||
return $data;
|
||||
} else {
|
||||
Log::error("Terakhir terbit API request failed", [
|
||||
'kategori' => $kategori,
|
||||
'limit' => $limit,
|
||||
'status' => $response->status(),
|
||||
'body' => $response->body()
|
||||
]);
|
||||
|
||||
return null;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Log::error("Exception occurred while fetching terakhir terbit data", [
|
||||
'kategori' => $kategori,
|
||||
'limit' => $limit,
|
||||
'error' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync terakhir terbit data to database
|
||||
*/
|
||||
public function syncTerakhirTerbitToDatabase($kategori, $limit = 5)
|
||||
{
|
||||
$apiData = $this->fetchTerakhirTerbit($kategori, $limit);
|
||||
|
||||
if (!$apiData || !($apiData['success'] ?? false)) {
|
||||
Log::warning("Failed to fetch terakhir terbit data for kategori: {$kategori}");
|
||||
return false;
|
||||
}
|
||||
|
||||
$syncDate = Carbon::today();
|
||||
$apiLastUpdated = Carbon::parse($apiData['last_updated']);
|
||||
$syncedCount = 0;
|
||||
|
||||
// Delete existing data for today to ensure we have fresh ranking
|
||||
TerakhirTerbit::where('kategori', $kategori)
|
||||
->whereDate('sync_date', $syncDate)
|
||||
->delete();
|
||||
|
||||
foreach ($apiData['data'] as $index => $item) {
|
||||
TerakhirTerbit::create([
|
||||
'kategori' => $kategori,
|
||||
'nama_izin' => $item['nama_izin'],
|
||||
'pemohon' => $item['pemohon'],
|
||||
'tanggal_terbit' => Carbon::parse($item['tanggal_terbit']),
|
||||
'api_last_updated' => $apiLastUpdated,
|
||||
'sync_date' => $syncDate
|
||||
]);
|
||||
|
||||
$syncedCount++;
|
||||
}
|
||||
|
||||
Log::info("Terakhir terbit data synced successfully", [
|
||||
'kategori' => $kategori,
|
||||
'synced_count' => $syncedCount,
|
||||
'sync_date' => $syncDate->format('Y-m-d'),
|
||||
'api_last_updated' => $apiLastUpdated->format('Y-m-d H:i:s')
|
||||
]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync all categories
|
||||
*/
|
||||
public function syncAllCategories($includeFastest = true, $includeTerakhirTerbit = true)
|
||||
{
|
||||
$categories = ['pertek', 'amdal'];
|
||||
$results = [];
|
||||
|
||||
foreach ($categories as $kategori) {
|
||||
// Sync status data
|
||||
$results[$kategori]['status'] = $this->syncToDatabase($kategori);
|
||||
|
||||
// Sync fastest data if requested
|
||||
if ($includeFastest) {
|
||||
$results[$kategori]['fastest'] = $this->syncFastestDataToDatabase($kategori, 5);
|
||||
}
|
||||
|
||||
// Sync terakhir terbit data if requested
|
||||
if ($includeTerakhirTerbit) {
|
||||
$results[$kategori]['terakhir_terbit'] = $this->syncTerakhirTerbitToDatabase($kategori, 5);
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Environment Check Script for Production Server
|
||||
# Run this to verify server requirements
|
||||
|
||||
echo "🔍 Checking Production Environment..."
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
# Check PHP version
|
||||
echo -e "\n${YELLOW}📋 PHP Version:${NC}"
|
||||
php_version=$(php -v | head -n 1)
|
||||
echo "$php_version"
|
||||
|
||||
# Check PHP extensions
|
||||
echo -e "\n${YELLOW}📦 Required PHP Extensions:${NC}"
|
||||
extensions=("curl" "json" "mbstring" "openssl" "pdo" "tokenizer" "xml" "zip")
|
||||
|
||||
for ext in "${extensions[@]}"; do
|
||||
if php -m | grep -q "$ext"; then
|
||||
echo -e "${GREEN}✅ $ext${NC}"
|
||||
else
|
||||
echo -e "${RED}❌ $ext (missing)${NC}"
|
||||
fi
|
||||
done
|
||||
|
||||
# Check Composer
|
||||
echo -e "\n${YELLOW}🎵 Composer:${NC}"
|
||||
if command -v composer &> /dev/null; then
|
||||
composer_version=$(composer --version)
|
||||
echo -e "${GREEN}✅ $composer_version${NC}"
|
||||
else
|
||||
echo -e "${RED}❌ Composer not found${NC}"
|
||||
fi
|
||||
|
||||
# Check cron service
|
||||
echo -e "\n${YELLOW}⏰ Cron Service:${NC}"
|
||||
if systemctl is-active --quiet cron; then
|
||||
echo -e "${GREEN}✅ Cron service is running${NC}"
|
||||
else
|
||||
echo -e "${RED}❌ Cron service is not running${NC}"
|
||||
fi
|
||||
|
||||
# Check disk space
|
||||
echo -e "\n${YELLOW}💾 Disk Space:${NC}"
|
||||
df -h | head -n 1
|
||||
df -h | grep -E '^/dev/'
|
||||
|
||||
# Check memory
|
||||
echo -e "\n${YELLOW}🧠 Memory:${NC}"
|
||||
free -h
|
||||
|
||||
# Check network connectivity to API
|
||||
echo -e "\n${YELLOW}🌐 API Connectivity:${NC}"
|
||||
if curl -s --connect-timeout 5 https://wsdev.jakarta.go.id > /dev/null; then
|
||||
echo -e "${GREEN}✅ Can reach Jakarta API${NC}"
|
||||
else
|
||||
echo -e "${RED}❌ Cannot reach Jakarta API${NC}"
|
||||
fi
|
||||
|
||||
# Check write permissions
|
||||
echo -e "\n${YELLOW}🔐 Directory Permissions:${NC}"
|
||||
dirs=("storage" "storage/logs" "bootstrap/cache")
|
||||
|
||||
for dir in "${dirs[@]}"; do
|
||||
if [ -w "$dir" ]; then
|
||||
echo -e "${GREEN}✅ $dir is writable${NC}"
|
||||
else
|
||||
echo -e "${RED}❌ $dir is not writable${NC}"
|
||||
fi
|
||||
done
|
||||
|
||||
echo -e "\n${GREEN}🏁 Environment check completed!${NC}"
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
$app = require_once __DIR__ . '/bootstrap/app.php';
|
||||
|
||||
$app->make(\Illuminate\Contracts\Console\Kernel::class)->bootstrap();
|
||||
|
||||
use App\Models\FastestPermohonan;
|
||||
|
||||
echo "=== FASTEST PERMOHONAN DATA ===\n\n";
|
||||
|
||||
echo "PERTEK Category:\n";
|
||||
$pertekData = FastestPermohonan::where('kategori', 'pertek')->orderBy('rank_order')->get();
|
||||
foreach ($pertekData as $item) {
|
||||
echo "#{$item->rank_order}: {$item->nama}\n";
|
||||
echo " Total: {$item->total}, Durasi Pemohon: {$item->durasi_pemohon}\n";
|
||||
echo " Durasi Petugas: {$item->durasi_petugas}\n\n";
|
||||
}
|
||||
|
||||
echo "\nAMDAL Category:\n";
|
||||
$amdalData = FastestPermohonan::where('kategori', 'amdal')->orderBy('rank_order')->get();
|
||||
foreach ($amdalData as $item) {
|
||||
echo "#{$item->rank_order}: {$item->nama}\n";
|
||||
echo " Total: {$item->total}, Durasi Pemohon: {$item->durasi_pemohon}\n";
|
||||
echo " Durasi Petugas: {$item->durasi_petugas}\n\n";
|
||||
}
|
||||
|
||||
echo "Total Records: " . FastestPermohonan::count() . "\n";
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
$app = require_once __DIR__ . '/bootstrap/app.php';
|
||||
|
||||
$app->make(\Illuminate\Contracts\Console\Kernel::class)->bootstrap();
|
||||
|
||||
use App\Models\TerakhirTerbit;
|
||||
|
||||
echo "=== TERAKHIR TERBIT DATA ===\n\n";
|
||||
|
||||
echo "PERTEK Category:\n";
|
||||
$pertekData = TerakhirTerbit::where('kategori', 'pertek')->orderBy('rank_order')->get();
|
||||
foreach ($pertekData as $item) {
|
||||
echo "#{$item->rank_order}: {$item->nama_izin}\n";
|
||||
echo " Pemohon: {$item->pemohon}\n";
|
||||
echo " Tanggal Terbit: {$item->tanggal_terbit->format('d M Y')}\n\n";
|
||||
}
|
||||
|
||||
echo "\nAMDAL Category:\n";
|
||||
$amdalData = TerakhirTerbit::where('kategori', 'amdal')->orderBy('rank_order')->get();
|
||||
foreach ($amdalData as $item) {
|
||||
echo "#{$item->rank_order}: {$item->nama_izin}\n";
|
||||
echo " Pemohon: {$item->pemohon}\n";
|
||||
echo " Tanggal Terbit: {$item->tanggal_terbit->format('d M Y')}\n\n";
|
||||
}
|
||||
|
||||
echo "Total Records: " . TerakhirTerbit::count() . "\n";
|
|
@ -35,4 +35,10 @@ return [
|
|||
],
|
||||
],
|
||||
|
||||
'perizinan' => [
|
||||
'base_url' => env('PERIZINAN_API_BASE_URL', 'https://wsdev.jakarta.go.id/gateway/DataPerizinanLingkungan/1.0'),
|
||||
'bearer_token' => env('PERIZINAN_API_BEARER_TOKEN'),
|
||||
'api_key' => env('PERIZINAN_API_KEY'),
|
||||
],
|
||||
|
||||
];
|
||||
|
|
|
@ -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('perizinan_status', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('kategori'); // pertek, amdal, etc
|
||||
$table->string('status_id'); // ditolak, selesai, proses, total
|
||||
$table->string('label'); // Izin Ditolak, Izin Selesai, etc
|
||||
$table->integer('value'); // count value
|
||||
$table->datetime('api_last_updated'); // from API last_updated field
|
||||
$table->date('sync_date'); // date when this data was synced
|
||||
$table->timestamps();
|
||||
|
||||
// Index untuk performa
|
||||
$table->index(['kategori', 'status_id', 'sync_date']);
|
||||
$table->unique(['kategori', 'status_id', 'sync_date']); // prevent duplicate data per day
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('perizinan_status');
|
||||
}
|
||||
};
|
|
@ -0,0 +1,38 @@
|
|||
<?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('fastest_permohonan', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('kategori'); // pertek, amdal, etc
|
||||
$table->string('nama'); // nama izin from API
|
||||
$table->integer('total'); // jumlah total izin from API
|
||||
$table->string('durasi_pemohon')->nullable(); // durasi rata-rata pemohon from API
|
||||
$table->string('durasi_petugas')->nullable(); // durasi rata-rata petugas from API
|
||||
$table->datetime('api_last_updated'); // from API last_updated field
|
||||
$table->date('sync_date'); // date when this data was synced
|
||||
$table->timestamps();
|
||||
|
||||
// Index untuk performa - urutkan berdasarkan durasi dan total
|
||||
$table->index(['kategori', 'sync_date']);
|
||||
$table->index(['kategori', 'total']); // untuk sorting fastest
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('fastest_permohonan');
|
||||
}
|
||||
};
|
|
@ -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('terakhir_terbit', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('kategori'); // pertek, amdal, etc
|
||||
$table->string('nama_izin'); // nama izin dari API
|
||||
$table->string('pemohon'); // nama pemohon dari API
|
||||
$table->date('tanggal_terbit'); // tanggal terbit dari API
|
||||
$table->datetime('api_last_updated'); // from API last_updated field
|
||||
$table->date('sync_date'); // date when this data was synced
|
||||
$table->timestamps();
|
||||
|
||||
// Index untuk performa
|
||||
$table->index(['kategori', 'tanggal_terbit']);
|
||||
$table->index(['kategori', 'sync_date']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('terakhir_terbit');
|
||||
}
|
||||
};
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
$app = require_once __DIR__ . '/bootstrap/app.php';
|
||||
|
||||
$app->make(\Illuminate\Contracts\Console\Kernel::class)->bootstrap();
|
||||
|
||||
use App\Services\PerizinanApiService;
|
||||
|
||||
$service = new PerizinanApiService();
|
||||
$data = $service->fetchFastestPermohonan('pertek', 5);
|
||||
|
||||
echo "API Response:\n";
|
||||
print_r($data);
|
||||
|
||||
if ($data && isset($data['data']) && is_array($data['data'])) {
|
||||
echo "\nFirst item structure:\n";
|
||||
if (count($data['data']) > 0) {
|
||||
print_r($data['data'][0]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Production Deployment Script for Perizinan System
|
||||
# Run this script after deploying to production server
|
||||
|
||||
echo "🚀 Setting up Perizinan API Integration for Production..."
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Get current directory
|
||||
CURRENT_DIR=$(pwd)
|
||||
echo -e "${BLUE}Current directory: $CURRENT_DIR${NC}"
|
||||
|
||||
# Check if we're in Laravel project
|
||||
if [ ! -f "artisan" ]; then
|
||||
echo -e "${RED}❌ Error: artisan file not found. Make sure you're in Laravel project directory.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 1. Make shell scripts executable
|
||||
echo -e "${YELLOW}📁 Making shell scripts executable...${NC}"
|
||||
chmod +x sync-perizinan.sh
|
||||
chmod +x run-scheduler.sh
|
||||
echo -e "${GREEN}✅ Shell scripts are now executable${NC}"
|
||||
|
||||
# 2. Create log directories if they don't exist
|
||||
echo -e "${YELLOW}📁 Creating log directories...${NC}"
|
||||
mkdir -p storage/logs
|
||||
echo -e "${GREEN}✅ Log directories created${NC}"
|
||||
|
||||
# 3. Set proper permissions
|
||||
echo -e "${YELLOW}🔐 Setting proper permissions...${NC}"
|
||||
chmod -R 775 storage/
|
||||
chmod -R 775 bootstrap/cache/
|
||||
echo -e "${GREEN}✅ Permissions set${NC}"
|
||||
|
||||
# 4. Update .env for production
|
||||
echo -e "${YELLOW}⚙️ Checking .env configuration...${NC}"
|
||||
if [ ! -f ".env" ]; then
|
||||
echo -e "${RED}❌ .env file not found. Please create it first.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if API credentials are set
|
||||
if grep -q "PERIZINAN_API_BEARER_TOKEN=your_actual_bearer_token" .env; then
|
||||
echo -e "${RED}⚠️ Warning: Please update PERIZINAN_API_BEARER_TOKEN in .env${NC}"
|
||||
fi
|
||||
|
||||
if grep -q "PERIZINAN_API_KEY=your_actual_api_key" .env; then
|
||||
echo -e "${RED}⚠️ Warning: Please update PERIZINAN_API_KEY in .env${NC}"
|
||||
fi
|
||||
|
||||
# 5. Run database migrations
|
||||
echo -e "${YELLOW}🗄️ Running database migrations...${NC}"
|
||||
php artisan migrate --force
|
||||
echo -e "${GREEN}✅ Database migrations completed${NC}"
|
||||
|
||||
# 6. Test API connection
|
||||
echo -e "${YELLOW}🌐 Testing API connection...${NC}"
|
||||
php artisan perizinan:sync pertek
|
||||
if [ $? -eq 0 ]; then
|
||||
echo -e "${GREEN}✅ API connection test successful${NC}"
|
||||
else
|
||||
echo -e "${RED}❌ API connection test failed. Please check your credentials.${NC}"
|
||||
fi
|
||||
|
||||
# 7. Setup cron job options
|
||||
echo ""
|
||||
echo -e "${BLUE}📅 Cron Job Setup Options:${NC}"
|
||||
echo ""
|
||||
echo -e "${YELLOW}Option 1: Laravel Scheduler (Recommended)${NC}"
|
||||
echo "Add this line to crontab (crontab -e):"
|
||||
echo -e "${GREEN}* * * * * cd $CURRENT_DIR && ./run-scheduler.sh >/dev/null 2>&1${NC}"
|
||||
echo ""
|
||||
echo -e "${YELLOW}Option 2: Direct Sync${NC}"
|
||||
echo "Add this line to crontab (crontab -e):"
|
||||
echo -e "${GREEN}0 0 * * * cd $CURRENT_DIR && ./sync-perizinan.sh >/dev/null 2>&1${NC}"
|
||||
echo ""
|
||||
|
||||
# 8. Show commands for manual setup
|
||||
echo -e "${BLUE}📋 Manual Commands:${NC}"
|
||||
echo ""
|
||||
echo "To edit crontab:"
|
||||
echo -e "${GREEN}sudo crontab -e${NC}"
|
||||
echo ""
|
||||
echo "To view current crontab:"
|
||||
echo -e "${GREEN}crontab -l${NC}"
|
||||
echo ""
|
||||
echo "To test sync manually:"
|
||||
echo -e "${GREEN}php artisan perizinan:sync${NC}"
|
||||
echo ""
|
||||
echo "To check synced data:"
|
||||
echo -e "${GREEN}php artisan perizinan:check${NC}"
|
||||
echo ""
|
||||
echo "To view logs:"
|
||||
echo -e "${GREEN}tail -f storage/logs/cron.log${NC}"
|
||||
echo -e "${GREEN}tail -f storage/logs/laravel.log${NC}"
|
||||
echo ""
|
||||
|
||||
echo -e "${GREEN}🎉 Production setup completed!${NC}"
|
||||
echo -e "${YELLOW}📝 Don't forget to:${NC}"
|
||||
echo "1. Update API credentials in .env"
|
||||
echo "2. Setup cron job using one of the options above"
|
||||
echo "3. Monitor logs after deployment"
|
|
@ -112,7 +112,13 @@
|
|||
<div>
|
||||
<span class="badge text-bg-primary">PERTEK</span>
|
||||
<p class="fw-medium text-primary-light mb-1">Total Pengajuan</p>
|
||||
<h4 class="mb-1 fw-bold text-primary-600">{{ $statuses[3]['value'] ?? 65 }}</h4>
|
||||
<h4 class="mb-1 fw-bold text-primary-600">
|
||||
@php
|
||||
$pertekData = $allStatistics['pertek']['data'] ?? [];
|
||||
$totalData = collect($pertekData)->firstWhere('id', 'total');
|
||||
@endphp
|
||||
{{ number_format($totalData['value'] ?? 65) }}
|
||||
</h4>
|
||||
</div>
|
||||
<div class="w-50-px h-50-px bg-yellow rounded-circle d-flex justify-content-center align-items-center">
|
||||
<x-lucide-equal class="text-white w-32-px h-32-px mb-0"/>
|
||||
|
@ -130,7 +136,12 @@
|
|||
<div>
|
||||
<span class="badge text-bg-primary">PERTEK</span>
|
||||
<p class="fw-medium text-primary-light mb-1">Izin Disetujui</p>
|
||||
<h4 class="mb-1 fw-bold text-success-main">{{ $statuses[1]['value'] ?? 45 }}</h4>
|
||||
<h4 class="mb-1 fw-bold text-success-main">
|
||||
@php
|
||||
$selesaiData = collect($pertekData)->firstWhere('id', 'selesai');
|
||||
@endphp
|
||||
{{ number_format($selesaiData['value'] ?? 45) }}
|
||||
</h4>
|
||||
|
||||
</div>
|
||||
<div class="w-50-px h-50-px bg-green rounded-circle d-flex justify-content-center align-items-center">
|
||||
|
@ -149,7 +160,12 @@
|
|||
<div>
|
||||
<span class="badge text-bg-primary">PERTEK</span>
|
||||
<p class="fw-medium text-primary-light mb-1">Dalam Proses</p>
|
||||
<h4 class="mb-1 fw-bold text-info-main">{{ $statuses[2]['value'] ?? 12 }}</h4>
|
||||
<h4 class="mb-1 fw-bold text-info-main">
|
||||
@php
|
||||
$prosesData = collect($pertekData)->firstWhere('id', 'proses');
|
||||
@endphp
|
||||
{{ number_format($prosesData['value'] ?? 12) }}
|
||||
</h4>
|
||||
|
||||
</div>
|
||||
<div class="w-50-px h-50-px bg-info rounded-circle d-flex justify-content-center align-items-center">
|
||||
|
@ -168,7 +184,12 @@
|
|||
<div>
|
||||
<span class="badge text-bg-primary">PERTEK</span>
|
||||
<p class="fw-medium text-primary-light mb-1">Izin Ditolak</p>
|
||||
<h4 class="mb-1 fw-bold text-danger-main">{{ $statuses[0]['value'] ?? 8 }}</h4>
|
||||
<h4 class="mb-1 fw-bold text-danger-main">
|
||||
@php
|
||||
$ditolakData = collect($pertekData)->firstWhere('id', 'ditolak');
|
||||
@endphp
|
||||
{{ number_format($ditolakData['value'] ?? 8) }}
|
||||
</h4>
|
||||
|
||||
</div>
|
||||
<div class="w-50-px h-50-px bg-red rounded-circle d-flex justify-content-center align-items-center">
|
||||
|
@ -199,6 +220,18 @@
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@php
|
||||
$pertekFastestData = $allFastestData['pertek']['data'] ?? [];
|
||||
@endphp
|
||||
@forelse($pertekFastestData as $fastest)
|
||||
<tr>
|
||||
<td>{{ $fastest['rank_order'] }}</td>
|
||||
<td class="text-sm">{{ $fastest['nama'] }}</td>
|
||||
<td class="text-sm d-none d-md-table-cell">{{ number_format($fastest['total']) }}</td>
|
||||
<td class="text-sm d-none d-xl-table-cell">{{ $fastest['durasi_pemohon'] }}</td>
|
||||
<td class="text-sm d-none d-xl-table-cell">{{ $fastest['durasi_petugas'] }}</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td>1</td>
|
||||
<td class="text-sm">SERTIFIKAT LAIK OPERASI - PEMENUHAN BAKU MUTU EMISI</td>
|
||||
|
@ -234,6 +267,7 @@
|
|||
<td class="text-sm d-none d-xl-table-cell">55 Jam 12 Menit</td>
|
||||
<td class="text-sm d-none d-xl-table-cell">45 Jam 36 Menit</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
@ -257,6 +291,17 @@
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@php
|
||||
$pertekTerakhirTerbitData = $allTerakhirTerbitData['pertek']['data'] ?? [];
|
||||
@endphp
|
||||
@forelse($pertekTerakhirTerbitData as $terakhir)
|
||||
<tr>
|
||||
<td>{{ $terakhir['rank_order'] }}</td>
|
||||
<td class="text-sm">{{ $terakhir['nama_izin'] }}</td>
|
||||
<td class="text-sm d-none d-md-table-cell">{{ $terakhir['pemohon'] }}</td>
|
||||
<td class="text-sm d-none d-xl-table-cell">{{ \App\Helpers\DashboardHelper::formatTanggalTerbit($terakhir['tanggal_terbit']) }}</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td>1</td>
|
||||
<td class="text-sm">SERTIFIKAT LAIK OPERASI - PEMENUHAN BAKU MUTU EMISI</td>
|
||||
|
@ -287,6 +332,7 @@
|
|||
<td class="text-sm d-none d-md-table-cell">CV. BERKAH JAYA</td>
|
||||
<td class="text-sm d-none d-xl-table-cell">11 Des 2024</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
@ -377,7 +423,13 @@
|
|||
<div>
|
||||
<span class="badge text-bg-success">RINTEK</span>
|
||||
<p class="fw-medium text-primary-light mb-1">Total Pengajuan</p>
|
||||
<h4 class="mb-1 fw-bold text-primary-600">892</h4>
|
||||
<h4 class="mb-1 fw-bold text-primary-600">
|
||||
@php
|
||||
$rintekData = $allStatistics['rintek']['data'] ?? [];
|
||||
$rintekTotalData = collect($rintekData)->firstWhere('id', 'total');
|
||||
@endphp
|
||||
{{ number_format($rintekTotalData['value'] ?? 892) }}
|
||||
</h4>
|
||||
</div>
|
||||
<div class="w-50-px h-50-px bg-yellow rounded-circle d-flex justify-content-center align-items-center">
|
||||
<x-lucide-equal class="text-white w-32-px h-32-px mb-0"/>
|
||||
|
@ -395,7 +447,12 @@
|
|||
<div>
|
||||
<span class="badge text-bg-success">RINTEK</span>
|
||||
<p class="fw-medium text-primary-light mb-1">Izin Disetujui</p>
|
||||
<h4 class="mb-1 fw-bold text-success-main">675</h4>
|
||||
<h4 class="mb-1 fw-bold text-success-main">
|
||||
@php
|
||||
$rintekSelesaiData = collect($rintekData)->firstWhere('id', 'selesai');
|
||||
@endphp
|
||||
{{ number_format($rintekSelesaiData['value'] ?? 675) }}
|
||||
</h4>
|
||||
</div>
|
||||
<div class="w-50-px h-50-px bg-green rounded-circle d-flex justify-content-center align-items-center">
|
||||
<x-lucide-circle-check class="text-white w-32-px h-32-px mb-0"/>
|
||||
|
@ -413,7 +470,12 @@
|
|||
<div>
|
||||
<span class="badge text-bg-success">RINTEK</span>
|
||||
<p class="fw-medium text-primary-light mb-1">Dalam Proses</p>
|
||||
<h4 class="mb-1 fw-bold text-info-main">147</h4>
|
||||
<h4 class="mb-1 fw-bold text-info-main">
|
||||
@php
|
||||
$rintekProsesData = collect($rintekData)->firstWhere('id', 'proses');
|
||||
@endphp
|
||||
{{ number_format($rintekProsesData['value'] ?? 147) }}
|
||||
</h4>
|
||||
|
||||
</div>
|
||||
<div class="w-50-px h-50-px bg-info rounded-circle d-flex justify-content-center align-items-center">
|
||||
|
@ -432,7 +494,12 @@
|
|||
<div>
|
||||
<span class="badge text-bg-success">RINTEK</span>
|
||||
<p class="fw-medium text-primary-light mb-1">Izin Ditolak</p>
|
||||
<h4 class="mb-1 fw-bold text-danger-main">70</h4>
|
||||
<h4 class="mb-1 fw-bold text-danger-main">
|
||||
@php
|
||||
$rintekDitolakData = collect($rintekData)->firstWhere('id', 'ditolak');
|
||||
@endphp
|
||||
{{ number_format($rintekDitolakData['value'] ?? 70) }}
|
||||
</h4>
|
||||
|
||||
</div>
|
||||
<div class="w-50-px h-50-px bg-red rounded-circle d-flex justify-content-center align-items-center">
|
||||
|
@ -641,7 +708,13 @@
|
|||
<div>
|
||||
<span class="badge text-bg-info text-white">AMDAL</span>
|
||||
<p class="fw-medium text-primary-light mb-1">Total Pengajuan</p>
|
||||
<h4 class="mb-1 fw-bold text-primary-600">456</h4>
|
||||
<h4 class="mb-1 fw-bold text-primary-600">
|
||||
@php
|
||||
$amdalData = $allStatistics['amdal']['data'] ?? [];
|
||||
$amdalTotalData = collect($amdalData)->firstWhere('id', 'total');
|
||||
@endphp
|
||||
{{ number_format($amdalTotalData['value'] ?? 456) }}
|
||||
</h4>
|
||||
</div>
|
||||
<div class="w-50-px h-50-px bg-yellow rounded-circle d-flex justify-content-center align-items-center">
|
||||
<x-lucide-equal class="text-white w-32-px h-32-px mb-0"/>
|
||||
|
@ -659,7 +732,12 @@
|
|||
<div>
|
||||
<span class="badge text-bg-info text-white">AMDAL</span>
|
||||
<p class="fw-medium text-primary-light mb-1">Izin Disetujui</p>
|
||||
<h4 class="mb-1 fw-bold text-success-main">298</h4>
|
||||
<h4 class="mb-1 fw-bold text-success-main">
|
||||
@php
|
||||
$amdalSelesaiData = collect($amdalData)->firstWhere('id', 'selesai');
|
||||
@endphp
|
||||
{{ number_format($amdalSelesaiData['value'] ?? 298) }}
|
||||
</h4>
|
||||
</div>
|
||||
<div class="w-50-px h-50-px bg-green rounded-circle d-flex justify-content-center align-items-center">
|
||||
<x-lucide-circle-check class="text-white w-32-px h-32-px mb-0"/>
|
||||
|
@ -677,7 +755,12 @@
|
|||
<div>
|
||||
<span class="badge text-bg-info text-white">AMDAL</span>
|
||||
<p class="fw-medium text-primary-light mb-1">Dalam Proses</p>
|
||||
<h4 class="mb-1 fw-bold text-info-main">112</h4>
|
||||
<h4 class="mb-1 fw-bold text-info-main">
|
||||
@php
|
||||
$amdalProsesData = collect($amdalData)->firstWhere('id', 'proses');
|
||||
@endphp
|
||||
{{ number_format($amdalProsesData['value'] ?? 112) }}
|
||||
</h4>
|
||||
|
||||
</div>
|
||||
<div class="w-50-px h-50-px bg-info rounded-circle d-flex justify-content-center align-items-center">
|
||||
|
@ -696,7 +779,12 @@
|
|||
<div>
|
||||
<span class="badge text-bg-info text-white">AMDAL</span>
|
||||
<p class="fw-medium text-primary-light mb-1">Izin Ditolak</p>
|
||||
<h4 class="mb-1 fw-bold text-danger-main">46</h4>
|
||||
<h4 class="mb-1 fw-bold text-danger-main">
|
||||
@php
|
||||
$amdalDitolakData = collect($amdalData)->firstWhere('id', 'ditolak');
|
||||
@endphp
|
||||
{{ number_format($amdalDitolakData['value'] ?? 46) }}
|
||||
</h4>
|
||||
|
||||
</div>
|
||||
<div class="w-50-px h-50-px bg-red rounded-circle d-flex justify-content-center align-items-center">
|
||||
|
@ -727,6 +815,18 @@
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@php
|
||||
$amdalFastestData = $allFastestData['amdal']['data'] ?? [];
|
||||
@endphp
|
||||
@forelse($amdalFastestData as $fastest)
|
||||
<tr>
|
||||
<td>{{ $fastest['rank_order'] }}</td>
|
||||
<td class="text-sm">{{ $fastest['nama'] }}</td>
|
||||
<td class="text-sm">{{ number_format($fastest['total']) }}</td>
|
||||
<td class="text-sm d-none d-lg-table-cell">{{ $fastest['durasi_pemohon'] }}</td>
|
||||
<td class="text-sm d-none d-lg-table-cell">{{ $fastest['durasi_petugas'] }}</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td>1</td>
|
||||
<td class="text-sm">IZIN LINGKUNGAN KEGIATAN USAHA</td>
|
||||
|
@ -762,6 +862,7 @@
|
|||
<td class="text-sm d-none d-lg-table-cell">84 Jam 50 Menit</td>
|
||||
<td class="text-sm d-none d-lg-table-cell">58 Jam 25 Menit</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
@ -785,6 +886,17 @@
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@php
|
||||
$amdalTerakhirTerbitData = $allTerakhirTerbitData['amdal']['data'] ?? [];
|
||||
@endphp
|
||||
@forelse($amdalTerakhirTerbitData as $terakhir)
|
||||
<tr>
|
||||
<td>{{ $terakhir['rank_order'] }}</td>
|
||||
<td class="text-sm">{{ $terakhir['nama_izin'] }}</td>
|
||||
<td class="text-sm">{{ $terakhir['pemohon'] }}</td>
|
||||
<td class="text-sm d-none d-lg-table-cell">{{ \App\Helpers\DashboardHelper::formatTanggalTerbit($terakhir['tanggal_terbit']) }}</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td>1</td>
|
||||
<td class="text-sm">SERTIFIKAT LAIK OPERASI - PEMENUHAN BAKU MUTU EMISI</td>
|
||||
|
@ -815,6 +927,7 @@
|
|||
<td class="text-sm">CV. BERKAH JAYA</td>
|
||||
<td class="text-sm d-none d-lg-table-cell">11 Des 2024</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
|
|
@ -3,8 +3,16 @@
|
|||
use Illuminate\Foundation\Console\ClosureCommand;
|
||||
use Illuminate\Foundation\Inspiring;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\Schedule;
|
||||
|
||||
Artisan::command('inspire', function () {
|
||||
/** @var ClosureCommand $this */
|
||||
$this->comment(Inspiring::quote());
|
||||
})->purpose('Display an inspiring quote');
|
||||
|
||||
// Schedule perizinan data sync daily at midnight (including all data types)
|
||||
Schedule::command('perizinan:sync --all')
|
||||
->dailyAt('00:00')
|
||||
->timezone('Asia/Jakarta')
|
||||
->withoutOverlapping()
|
||||
->runInBackground();
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
@echo off
|
||||
REM Laravel Scheduler Runner
|
||||
REM This should run every minute via Task Scheduler
|
||||
|
||||
cd /d "C:\laragon\www\perling"
|
||||
|
||||
REM Run Laravel scheduler
|
||||
php artisan schedule:run >> storage\logs\scheduler.log 2>&1
|
|
@ -0,0 +1,13 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Laravel Scheduler Runner for Linux Server
|
||||
# This should run every minute via cron
|
||||
|
||||
# Set project directory (adjust this path for your production server)
|
||||
PROJECT_DIR="/var/www/perling"
|
||||
|
||||
# Change to project directory
|
||||
cd "$PROJECT_DIR"
|
||||
|
||||
# Run Laravel scheduler
|
||||
php artisan schedule:run >> storage/logs/scheduler.log 2>&1
|
|
@ -0,0 +1,52 @@
|
|||
# PowerShell Script to Create Windows Task Scheduler for Perizinan Data Sync
|
||||
# Run this script as Administrator
|
||||
|
||||
$TaskName = "PerizinanDataSync"
|
||||
$TaskDescription = "Daily sync of perizinan data from Jakarta API at midnight"
|
||||
$ScriptPath = "C:\laragon\www\perling\sync-perizinan.bat"
|
||||
$LogPath = "C:\laragon\www\perling\storage\logs\task-scheduler.log"
|
||||
|
||||
Write-Host "Creating Windows Task Scheduler for Perizinan Data Sync..." -ForegroundColor Green
|
||||
|
||||
# Check if task already exists
|
||||
$ExistingTask = Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue
|
||||
|
||||
if ($ExistingTask) {
|
||||
Write-Host "Task '$TaskName' already exists. Removing old task..." -ForegroundColor Yellow
|
||||
Unregister-ScheduledTask -TaskName $TaskName -Confirm:$false
|
||||
}
|
||||
|
||||
try {
|
||||
# Create Task Action
|
||||
$Action = New-ScheduledTaskAction -Execute $ScriptPath
|
||||
|
||||
# Create Task Trigger (Daily at midnight)
|
||||
$Trigger = New-ScheduledTaskTrigger -Daily -At "00:00"
|
||||
|
||||
# Create Task Settings
|
||||
$Settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable
|
||||
|
||||
# Create Task Principal (Run with highest privileges)
|
||||
$Principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest
|
||||
|
||||
# Register the Task
|
||||
Register-ScheduledTask -TaskName $TaskName -Action $Action -Trigger $Trigger -Settings $Settings -Principal $Principal -Description $TaskDescription
|
||||
|
||||
Write-Host "✅ Task Scheduler created successfully!" -ForegroundColor Green
|
||||
Write-Host "Task Name: $TaskName" -ForegroundColor Cyan
|
||||
Write-Host "Schedule: Daily at 00:00 (midnight)" -ForegroundColor Cyan
|
||||
Write-Host "Script Path: $ScriptPath" -ForegroundColor Cyan
|
||||
|
||||
# Show task info
|
||||
Get-ScheduledTask -TaskName $TaskName | Format-Table -AutoSize
|
||||
|
||||
} catch {
|
||||
Write-Host "❌ Error creating task scheduler: $($_.Exception.Message)" -ForegroundColor Red
|
||||
Write-Host "Please run this script as Administrator" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
Write-Host "`nTo manually run the task, use:" -ForegroundColor Cyan
|
||||
Write-Host "Start-ScheduledTask -TaskName '$TaskName'" -ForegroundColor White
|
||||
|
||||
Write-Host "`nTo view task history:" -ForegroundColor Cyan
|
||||
Write-Host "Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-TaskScheduler/Operational'; ID=102}" -ForegroundColor White
|
|
@ -0,0 +1,15 @@
|
|||
@echo off
|
||||
REM Perizinan Data Sync Script
|
||||
REM Runs daily at midnight to sync data from API
|
||||
|
||||
cd /d "C:\laragon\www\perling"
|
||||
|
||||
REM Log start time
|
||||
echo [%date% %time%] Starting perizinan data sync >> storage\logs\cron.log
|
||||
|
||||
REM Run the sync command
|
||||
php artisan perizinan:sync >> storage\logs\cron.log 2>&1
|
||||
|
||||
REM Log completion
|
||||
echo [%date% %time%] Perizinan data sync completed >> storage\logs\cron.log
|
||||
echo. >> storage\logs\cron.log
|
|
@ -0,0 +1,25 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Perizinan Data Sync Script for Linux Server
|
||||
# Runs daily at midnight to sync data from API
|
||||
|
||||
# Set project directory (adjust this path for your production server)
|
||||
PROJECT_DIR="/var/www/perling"
|
||||
|
||||
# Change to project directory
|
||||
cd "$PROJECT_DIR"
|
||||
|
||||
# Log start time
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Starting perizinan data sync" >> storage/logs/cron.log
|
||||
|
||||
# Run the sync command
|
||||
php artisan perizinan:sync >> storage/logs/cron.log 2>&1
|
||||
|
||||
# Log completion
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Perizinan data sync completed" >> storage/logs/cron.log
|
||||
echo "" >> storage/logs/cron.log
|
||||
|
||||
# Optional: Send notification if sync fails
|
||||
# if [ $? -ne 0 ]; then
|
||||
# echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: Perizinan sync failed" >> storage/logs/cron.log
|
||||
# fi
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
$app = require_once __DIR__ . '/bootstrap/app.php';
|
||||
|
||||
$app->make(\Illuminate\Contracts\Console\Kernel::class)->bootstrap();
|
||||
|
||||
use App\Helpers\DashboardHelper;
|
||||
|
||||
echo "=== TESTING ALL DATA TABLES AFTER RANK_ORDER REMOVAL ===\n\n";
|
||||
|
||||
// Test fastest data
|
||||
echo "FASTEST DATA (API ORDER WITHOUT RANK_ORDER):\n";
|
||||
$fastestData = DashboardHelper::getAllFastestData();
|
||||
foreach ($fastestData as $type => $data) {
|
||||
echo "{$data['label']} - Top 5 Tercepat:\n";
|
||||
foreach ($data['data'] as $item) {
|
||||
echo " #{$item['rank_order']}: {$item['nama']}\n";
|
||||
echo " Total: " . number_format($item['total']) . ", Durasi Pemohon: {$item['durasi_pemohon']}\n";
|
||||
}
|
||||
echo "\n";
|
||||
}
|
||||
|
||||
// Test terakhir terbit data
|
||||
echo "TERAKHIR TERBIT DATA (ORDERED BY tanggal_terbit):\n";
|
||||
$terakhirTerbitData = DashboardHelper::getAllTerakhirTerbitData();
|
||||
foreach ($terakhirTerbitData as $type => $data) {
|
||||
echo "{$data['label']} - Top 5 Terakhir Terbit:\n";
|
||||
foreach ($data['data'] as $item) {
|
||||
echo " #{$item['rank_order']}: {$item['nama_izin']}\n";
|
||||
echo " Pemohon: {$item['pemohon']}, Tanggal: " . DashboardHelper::formatTanggalTerbit($item['tanggal_terbit']) . "\n";
|
||||
}
|
||||
echo "\n";
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
$app = require_once __DIR__ . '/bootstrap/app.php';
|
||||
|
||||
$app->make(\Illuminate\Contracts\Console\Kernel::class)->bootstrap();
|
||||
|
||||
use App\Models\TerakhirTerbit;
|
||||
|
||||
echo "=== TERAKHIR TERBIT DATA (ORDERED BY tanggal_terbit DESC) ===\n\n";
|
||||
|
||||
echo "PERTEK:\n";
|
||||
$pertekData = TerakhirTerbit::getLatestByKategori('pertek');
|
||||
foreach ($pertekData as $index => $item) {
|
||||
$no = $index + 1;
|
||||
echo " {$no}. {$item->nama_izin}\n";
|
||||
echo " Pemohon: {$item->pemohon}\n";
|
||||
echo " Tanggal Terbit: {$item->tanggal_terbit->format('d M Y')}\n";
|
||||
echo " Sync Date: {$item->sync_date->format('Y-m-d')}\n\n";
|
||||
}
|
||||
|
||||
echo "AMDAL:\n";
|
||||
$amdalData = TerakhirTerbit::getLatestByKategori('amdal');
|
||||
foreach ($amdalData as $index => $item) {
|
||||
$no = $index + 1;
|
||||
echo " {$no}. {$item->nama_izin}\n";
|
||||
echo " Pemohon: {$item->pemohon}\n";
|
||||
echo " Tanggal Terbit: {$item->tanggal_terbit->format('d M Y')}\n";
|
||||
echo " Sync Date: {$item->sync_date->format('Y-m-d')}\n\n";
|
||||
}
|
Loading…
Reference in New Issue