diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md index 166ccd6..3487486 100644 --- a/DEPLOYMENT.md +++ b/DEPLOYMENT.md @@ -200,14 +200,28 @@ openssl rand -hex 32 Setup di aaPanel → Cron: ```cron -# Daily summary (run at 1 AM every day) +# Daily summary (run at 1 AM every day, rekap kemarin) 0 1 * * * cd /www/wwwroot/api.btekno.cloud/api && /www/server/php/83/bin/php bin/daily_summary.php -# Hourly summary (run at 1 AM every day) -0 1 * * * cd /www/wwwroot/api.btekno.cloud/api && /www/server/php/83/bin/php bin/hourly_summary.php +# Hourly summary - REALTIME UPDATE (run every hour, update jam yang baru saja berlalu) +# Contoh: jam 2:00 update jam 1:00, jam 3:00 update jam 2:00, dst +0 * * * * cd /www/wwwroot/api.btekno.cloud/api && /www/server/php/83/bin/php bin/hourly_summary.php today $(date -d '1 hour ago' +\%H) + +# Hourly summary - FINAL RECAP (run at 1 AM every day, rekap semua jam kemarin) +# Opsional: untuk memastikan semua jam kemarin sudah ter-rekap dengan benar +0 1 * * * cd /www/wwwroot/api.btekno.cloud/api && /www/server/php/83/bin/php bin/hourly_summary.php yesterday ``` -**Note**: Ganti `/www/server/php/83/bin/php` dengan path PHP yang sesuai di server Anda. +**Penjelasan:** + +1. **Daily summary**: Rekap harian untuk kemarin (jalan jam 1 pagi) +2. **Hourly summary - REALTIME**: Update setiap jam untuk jam yang baru saja berlalu (untuk dashboard realtime) +3. **Hourly summary - FINAL RECAP**: Rekap final semua jam kemarin (opsional, untuk memastikan data lengkap) + +**Note**: +- Ganti `/www/server/php/83/bin/php` dengan path PHP yang sesuai di server Anda +- Untuk update realtime, cron harus jalan **setiap jam** (`0 * * * *`) +- Script default ke `today` jika tidak ada argumen, jadi cocok untuk update realtime ## ✅ Verification diff --git a/README.md b/README.md index 9078cdb..3ef32c7 100644 --- a/README.md +++ b/README.md @@ -185,17 +185,32 @@ php bin/daily_summary.php [date] ### Hourly Summary ```bash -php bin/hourly_summary.php [date] -# Default: yesterday +# Update hari ini (default, untuk realtime) +php bin/hourly_summary.php + +# Rekap kemarin +php bin/hourly_summary.php yesterday + +# Rekap tanggal tertentu +php bin/hourly_summary.php 2025-01-01 + +# Update jam tertentu saja (untuk efisiensi) +php bin/hourly_summary.php today 14 # Update jam 14 hari ini +php bin/hourly_summary.php 2025-01-01 13 # Update jam 13 tanggal tertentu ``` ### Cron Job Setup ```cron -# Daily summary (run at 1 AM) +# Daily summary (run at 1 AM, rekap kemarin) 0 1 * * * cd /path/to/api-btekno && php bin/daily_summary.php -# Hourly summary (run at 1 AM) -0 1 * * * cd /path/to/api-btekno && php bin/hourly_summary.php +# Hourly summary - REALTIME (run every hour, update jam yang baru saja berlalu) +# Contoh: jam 2:00 update jam 1:00, jam 3:00 update jam 2:00 +0 * * * * cd /path/to/api-btekno && php bin/hourly_summary.php today $(date -d '1 hour ago' +\%H) + +# Hourly summary - FINAL RECAP (run at 1 AM, rekap semua jam kemarin) +# Opsional: untuk memastikan semua jam kemarin sudah ter-rekap +0 1 * * * cd /path/to/api-btekno && php bin/hourly_summary.php yesterday ``` ## 🔒 Security diff --git a/bin/hourly_summary.php b/bin/hourly_summary.php index 319b802..48dbdae 100644 --- a/bin/hourly_summary.php +++ b/bin/hourly_summary.php @@ -7,17 +7,18 @@ declare(strict_types=1); * CLI script untuk generate hourly summary * * Usage: - * php bin/hourly_summary.php [date] + * php bin/hourly_summary.php [date] [hour] * * Examples: - * php bin/hourly_summary.php 2025-01-01 - * php bin/hourly_summary.php # default: yesterday + * php bin/hourly_summary.php 2025-01-01 # Rekap semua jam untuk tanggal tertentu + * php bin/hourly_summary.php # Default: hari ini (untuk update realtime) + * php bin/hourly_summary.php 2025-01-01 14 # Rekap jam 14 saja untuk tanggal tertentu + * php bin/hourly_summary.php today 13 # Rekap jam 13 hari ini * * Note: - * Default menggunakan yesterday karena: - * - Data hari ini mungkin belum lengkap (masih ada event yang masuk) - * - Cron biasanya jalan di akhir hari untuk rekap hari sebelumnya - * - Lebih aman untuk rekap data yang sudah final + * - Default: hari ini (untuk update realtime via cron setiap jam) + * - Untuk rekap final kemarin, gunakan: php bin/hourly_summary.php yesterday + * - Untuk update jam tertentu saja, tambahkan parameter hour (0-23) */ // Get project root directory @@ -33,18 +34,43 @@ use App\Support\Database; // Load environment variables AppConfig::loadEnv($rootPath); -// Get date from command line argument or use yesterday (deterministic) -$date = $argv[1] ?? date('Y-m-d', strtotime('-1 day')); +// Get date from command line argument or use today (for realtime updates) +$dateInput = $argv[1] ?? 'today'; + +// Handle special keywords +if ($dateInput === 'today') { + $date = date('Y-m-d'); +} elseif ($dateInput === 'yesterday') { + $date = date('Y-m-d', strtotime('-1 day')); +} else { + $date = $dateInput; +} // Validate date format $dateTime = DateTime::createFromFormat('Y-m-d', $date); if ($dateTime === false || $dateTime->format('Y-m-d') !== $date) { - echo "Error: Invalid date format. Expected Y-m-d (e.g., 2025-01-01)\n"; - echo "Usage: php bin/hourly_summary.php [date]\n"; - echo " If date is omitted, defaults to yesterday\n"; + echo "Error: Invalid date format. Expected Y-m-d (e.g., 2025-01-01) or 'today'/'yesterday'\n"; + echo "Usage: php bin/hourly_summary.php [date] [hour]\n"; + echo " date: Y-m-d format, 'today', or 'yesterday' (default: today)\n"; + echo " hour: 0-23 (optional, untuk update jam tertentu saja)\n"; + echo "Examples:\n"; + echo " php bin/hourly_summary.php # Update hari ini\n"; + echo " php bin/hourly_summary.php yesterday # Rekap kemarin\n"; + echo " php bin/hourly_summary.php 2025-01-01 14 # Rekap jam 14 tanggal tertentu\n"; exit(1); } +// Get hour from command line (optional, 0-23) +$hour = null; +if (isset($argv[2])) { + $hourInput = (int) $argv[2]; + if ($hourInput < 0 || $hourInput > 23) { + echo "Error: Hour must be between 0 and 23\n"; + exit(1); + } + $hour = $hourInput; +} + try { // Get database connection $dbHost = AppConfig::get('DB_HOST', 'localhost'); @@ -63,12 +89,20 @@ try { $service = new HourlySummaryService($db); // Run aggregation - echo "Processing hourly summary for date: {$date}\n"; - $result = $service->aggregateForDate($date); - - echo "Success!\n"; - echo "Date: {$result['date']}\n"; - echo "Rows processed: {$result['rows_processed']}\n"; + if ($hour !== null) { + echo "Processing hourly summary for date: {$date}, hour: {$hour}\n"; + $result = $service->aggregateForDate($date, $hour); + echo "Success!\n"; + echo "Date: {$result['date']}\n"; + echo "Hour: {$result['hour']}\n"; + echo "Rows processed: {$result['rows_processed']}\n"; + } else { + echo "Processing hourly summary for date: {$date} (all hours)\n"; + $result = $service->aggregateForDate($date); + echo "Success!\n"; + echo "Date: {$result['date']}\n"; + echo "Rows processed: {$result['rows_processed']}\n"; + } exit(0); diff --git a/src/Modules/Retribusi/Summary/HourlySummaryService.php b/src/Modules/Retribusi/Summary/HourlySummaryService.php index 90d8bd0..25b3514 100644 --- a/src/Modules/Retribusi/Summary/HourlySummaryService.php +++ b/src/Modules/Retribusi/Summary/HourlySummaryService.php @@ -20,10 +20,11 @@ class HourlySummaryService * Aggregate hourly summary for a specific date * * @param string $date Format: Y-m-d - * @return array ['rows_processed' => int, 'date' => string] + * @param int|null $hour Optional: 0-23, if provided only aggregate for this hour + * @return array ['rows_processed' => int, 'date' => string, 'hour' => int|null] * @throws PDOException */ - public function aggregateForDate(string $date): array + public function aggregateForDate(string $date, ?int $hour = null): array { // Validate date format $dateTime = \DateTime::createFromFormat('Y-m-d', $date); @@ -31,6 +32,11 @@ class HourlySummaryService throw new \InvalidArgumentException('Invalid date format. Expected Y-m-d'); } + // Validate hour if provided + if ($hour !== null && ($hour < 0 || $hour > 23)) { + throw new \InvalidArgumentException('Invalid hour. Must be between 0 and 23'); + } + $this->db->beginTransaction(); try { @@ -55,6 +61,17 @@ class HourlySummaryService AND e.gate_code = t.gate_code AND e.category = t.category WHERE DATE(e.event_time) = ? + "; + + $params = [$date]; + + // Add hour filter if provided + if ($hour !== null) { + $sql .= " AND HOUR(e.event_time) = ?"; + $params[] = $hour; + } + + $sql .= " GROUP BY DATE(e.event_time), HOUR(e.event_time), @@ -65,7 +82,7 @@ class HourlySummaryService "; $stmt = $this->db->prepare($sql); - $stmt->execute([$date]); + $stmt->execute($params); $aggregated = $stmt->fetchAll(); $rowsProcessed = 0; @@ -103,7 +120,8 @@ class HourlySummaryService return [ 'rows_processed' => $rowsProcessed, - 'date' => $date + 'date' => $date, + 'hour' => $hour ]; } catch (PDOException $e) {