Fix: Data inconsistency pada transisi tahun/bulan dan setup API lokal
- Implementasi fallback mechanism untuk daily_summary (threshold 5%) - Auto-detect base path untuk subdirectory installation - Perbaikan query dengan CAST(? AS DATE) untuk semua tanggal - Script utilities: check_daily_summary.php dan check_and_fix_hourly_summary.php - Setup .htaccess untuk routing Slim Framework - Test script untuk verifikasi API lokal - Dokumentasi SETUP_LOCAL_API.md
This commit is contained in:
124
bin/check_and_fix_hourly_summary.php
Normal file
124
bin/check_and_fix_hourly_summary.php
Normal file
@@ -0,0 +1,124 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
/**
|
||||
* Script untuk check dan fix data hourly_summary yang tidak valid
|
||||
*
|
||||
* Usage:
|
||||
* php bin/check_and_fix_hourly_summary.php [date]
|
||||
*
|
||||
* Examples:
|
||||
* php bin/check_and_fix_hourly_summary.php 2026-01-01
|
||||
* php bin/check_and_fix_hourly_summary.php # Check semua tanggal
|
||||
*/
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
use App\Config\AppConfig;
|
||||
use App\Support\Database;
|
||||
use App\Modules\Retribusi\Summary\HourlySummaryService;
|
||||
|
||||
AppConfig::loadEnv(__DIR__ . '/..');
|
||||
|
||||
$db = Database::getConnection(
|
||||
AppConfig::get('DB_HOST'),
|
||||
AppConfig::get('DB_NAME'),
|
||||
AppConfig::get('DB_USER'),
|
||||
AppConfig::get('DB_PASS')
|
||||
);
|
||||
|
||||
$service = new HourlySummaryService($db);
|
||||
|
||||
// Get date from command line or check all dates
|
||||
$dateInput = $argv[1] ?? null;
|
||||
|
||||
if ($dateInput) {
|
||||
// Validate date format
|
||||
$dateTime = DateTime::createFromFormat('Y-m-d', $dateInput);
|
||||
if ($dateTime === false || $dateTime->format('Y-m-d') !== $dateInput) {
|
||||
echo "Error: Invalid date format. Expected Y-m-d (e.g., 2026-01-01)\n";
|
||||
exit(1);
|
||||
}
|
||||
$dates = [$dateInput];
|
||||
} else {
|
||||
// Get all unique dates from hourly_summary
|
||||
$stmt = $db->query("SELECT DISTINCT summary_date FROM hourly_summary ORDER BY summary_date DESC LIMIT 30");
|
||||
$dates = [];
|
||||
foreach ($stmt->fetchAll() as $row) {
|
||||
$dates[] = $row['summary_date'];
|
||||
}
|
||||
|
||||
if (empty($dates)) {
|
||||
echo "No dates found in hourly_summary table.\n";
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
echo "=== Checking and Fixing hourly_summary ===\n\n";
|
||||
|
||||
foreach ($dates as $date) {
|
||||
echo "--- Date: $date ---\n";
|
||||
|
||||
// Check entry_events count
|
||||
$stmt = $db->prepare('
|
||||
SELECT COUNT(*) as total_count
|
||||
FROM entry_events e
|
||||
INNER JOIN locations l ON e.location_code = l.code AND l.is_active = 1
|
||||
INNER JOIN gates g ON e.location_code = g.location_code
|
||||
AND e.gate_code = g.gate_code
|
||||
AND g.is_active = 1
|
||||
WHERE DATE(e.event_time) = CAST(? AS DATE)
|
||||
');
|
||||
$stmt->execute([$date]);
|
||||
$entryResult = $stmt->fetch();
|
||||
$entryCount = (int) ($entryResult['total_count'] ?? 0);
|
||||
|
||||
// Check hourly_summary count
|
||||
$stmt = $db->prepare('
|
||||
SELECT SUM(total_count) as total_count
|
||||
FROM hourly_summary
|
||||
WHERE summary_date = CAST(? AS DATE)
|
||||
');
|
||||
$stmt->execute([$date]);
|
||||
$summaryResult = $stmt->fetch();
|
||||
$summaryCount = (int) ($summaryResult['total_count'] ?? 0);
|
||||
|
||||
echo " entry_events: $entryCount events\n";
|
||||
echo " hourly_summary: $summaryCount events\n";
|
||||
|
||||
if ($entryCount > 0 && $summaryCount == 0) {
|
||||
echo " ❌ PROBLEM: entry_events has data but hourly_summary is empty!\n";
|
||||
echo " → Running aggregation...\n";
|
||||
try {
|
||||
$result = $service->aggregateForDate($date);
|
||||
echo " ✓ Aggregation completed: {$result['rows_processed']} rows processed\n";
|
||||
} catch (Exception $e) {
|
||||
echo " ✗ Error: " . $e->getMessage() . "\n";
|
||||
}
|
||||
} elseif ($entryCount > 0 && $summaryCount > 0 && abs($entryCount - $summaryCount) > ($entryCount * 0.1)) {
|
||||
// Perbedaan lebih dari 10%
|
||||
$diff = abs($entryCount - $summaryCount);
|
||||
$diffPercent = ($diff / max($entryCount, $summaryCount)) * 100;
|
||||
echo " ⚠️ WARNING: Count mismatch! Difference: $diff ($diffPercent%)\n";
|
||||
|
||||
if ($diffPercent > 50) {
|
||||
echo " → Re-running aggregation to fix...\n";
|
||||
try {
|
||||
$result = $service->aggregateForDate($date);
|
||||
echo " ✓ Re-aggregation completed: {$result['rows_processed']} rows processed\n";
|
||||
} catch (Exception $e) {
|
||||
echo " ✗ Error: " . $e->getMessage() . "\n";
|
||||
}
|
||||
}
|
||||
} elseif ($entryCount > 0 && $summaryCount > 0) {
|
||||
echo " ✓ OK: Counts match\n";
|
||||
} elseif ($entryCount == 0 && $summaryCount == 0) {
|
||||
echo " ℹ️ No data for this date\n";
|
||||
} elseif ($entryCount == 0 && $summaryCount > 0) {
|
||||
echo " ⚠️ WARNING: hourly_summary has data but entry_events is empty (orphaned data)\n";
|
||||
}
|
||||
|
||||
echo "\n";
|
||||
}
|
||||
|
||||
echo "Done!\n";
|
||||
|
||||
107
bin/check_daily_summary.php
Normal file
107
bin/check_daily_summary.php
Normal file
@@ -0,0 +1,107 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
/**
|
||||
* Script untuk check data daily_summary
|
||||
*
|
||||
* Usage:
|
||||
* php bin/check_daily_summary.php [date]
|
||||
*/
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
use App\Config\AppConfig;
|
||||
use App\Support\Database;
|
||||
|
||||
AppConfig::loadEnv(__DIR__ . '/..');
|
||||
|
||||
$db = Database::getConnection(
|
||||
AppConfig::get('DB_HOST'),
|
||||
AppConfig::get('DB_NAME'),
|
||||
AppConfig::get('DB_USER'),
|
||||
AppConfig::get('DB_PASS')
|
||||
);
|
||||
|
||||
$dateInput = $argv[1] ?? '2026-01-01';
|
||||
|
||||
// Validate date format
|
||||
$dateTime = DateTime::createFromFormat('Y-m-d', $dateInput);
|
||||
if ($dateTime === false || $dateTime->format('Y-m-d') !== $dateInput) {
|
||||
echo "Error: Invalid date format. Expected Y-m-d (e.g., 2026-01-01)\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$date = $dateInput;
|
||||
|
||||
echo "=== Checking daily_summary for date: $date ===\n\n";
|
||||
|
||||
// Check entry_events count
|
||||
$stmt = $db->prepare('
|
||||
SELECT COUNT(*) as total_count
|
||||
FROM entry_events e
|
||||
INNER JOIN locations l ON e.location_code = l.code AND l.is_active = 1
|
||||
INNER JOIN gates g ON e.location_code = g.location_code
|
||||
AND e.gate_code = g.gate_code
|
||||
AND g.is_active = 1
|
||||
WHERE DATE(e.event_time) = CAST(? AS DATE)
|
||||
');
|
||||
$stmt->execute([$date]);
|
||||
$entryResult = $stmt->fetch();
|
||||
$entryCount = (int) ($entryResult['total_count'] ?? 0);
|
||||
|
||||
// Check daily_summary count
|
||||
$stmt = $db->prepare('
|
||||
SELECT SUM(total_count) as total_count, SUM(total_amount) as total_amount
|
||||
FROM daily_summary
|
||||
WHERE summary_date = CAST(? AS DATE)
|
||||
');
|
||||
$stmt->execute([$date]);
|
||||
$summaryResult = $stmt->fetch();
|
||||
$summaryCount = (int) ($summaryResult['total_count'] ?? 0);
|
||||
$summaryAmount = (int) ($summaryResult['total_amount'] ?? 0);
|
||||
|
||||
// Check detail per location/gate/category
|
||||
$stmt = $db->prepare('
|
||||
SELECT location_code, gate_code, category, total_count, total_amount
|
||||
FROM daily_summary
|
||||
WHERE summary_date = CAST(? AS DATE)
|
||||
ORDER BY total_count DESC
|
||||
');
|
||||
$stmt->execute([$date]);
|
||||
$details = $stmt->fetchAll();
|
||||
|
||||
echo "entry_events: $entryCount events\n";
|
||||
echo "daily_summary: $summaryCount events (Rp " . number_format($summaryAmount, 0, ',', '.') . ")\n\n";
|
||||
|
||||
if ($entryCount > 0 && $summaryCount == 0) {
|
||||
echo "❌ PROBLEM: entry_events has data but daily_summary is empty!\n";
|
||||
echo "Run: php bin/daily_summary.php $date\n";
|
||||
} elseif ($entryCount > 0 && $summaryCount > 0 && abs($entryCount - $summaryCount) > ($entryCount * 0.1)) {
|
||||
$diff = abs($entryCount - $summaryCount);
|
||||
$diffPercent = ($diff / max($entryCount, $summaryCount)) * 100;
|
||||
echo "⚠️ WARNING: Count mismatch! Difference: $diff ($diffPercent%)\n";
|
||||
echo "Run: php bin/daily_summary.php $date\n";
|
||||
} elseif ($entryCount > 0 && $summaryCount > 0) {
|
||||
echo "✓ OK: Counts match\n";
|
||||
}
|
||||
|
||||
if (!empty($details)) {
|
||||
echo "\nDetails (top 10):\n";
|
||||
$count = 0;
|
||||
foreach ($details as $row) {
|
||||
if ($count++ >= 10) break;
|
||||
echo sprintf(
|
||||
" %s / %s / %s: %d events (Rp %s)\n",
|
||||
$row['location_code'],
|
||||
$row['gate_code'],
|
||||
$row['category'],
|
||||
$row['total_count'],
|
||||
number_format($row['total_amount'], 0, ',', '.')
|
||||
);
|
||||
}
|
||||
if (count($details) > 10) {
|
||||
echo " ... and " . (count($details) - 10) . " more\n";
|
||||
}
|
||||
}
|
||||
|
||||
echo "\n";
|
||||
|
||||
Reference in New Issue
Block a user