Files
D1041231027-WebGIS/03/api/verifikasi.php
T
2026-06-10 18:54:25 +07:00

316 lines
13 KiB
PHP

<?php
// api/verifikasi.php
// Fitur 2: Keterbaruan Data & Verifikasi Berkala
// Method: GET (list warga perlu update), POST (catat verifikasi), GET?action=config, PUT (update config)
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST, PUT, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type');
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { http_response_code(200); exit; }
require_once '../config/db.php';
require_once 'middleware.php';
/* ─────────────────────────────────────────────
HELPER
───────────────────────────────────────────── */
function getKonfig(PDO $pdo, string $kunci, $default = null) {
$stmt = $pdo->prepare("SELECT nilai, tipe FROM konfigurasi_sistem WHERE kunci = ?");
$stmt->execute([$kunci]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$row) return $default;
return match($row['tipe']) {
'integer' => (int)$row['nilai'],
'decimal' => (float)$row['nilai'],
'boolean' => filter_var($row['nilai'], FILTER_VALIDATE_BOOLEAN),
default => $row['nilai'],
};
}
/* ─────────────────────────────────────────────
CORE: Hitung skor kepercayaan data
Formula: mulai 100, kurangi seiring waktu
Decay rate: linear -100/interval_bulan per bulan
───────────────────────────────────────────── */
function hitungSkorKepercayaan(
?string $tanggalVerifikasi,
int $intervalBulan,
int $minSkor = 40
): array {
if (!$tanggalVerifikasi) {
return ['skor' => 0, 'hari_sejak' => null, 'status' => 'kadaluarsa'];
}
$tglVerif = new DateTime($tanggalVerifikasi);
$today = new DateTime();
// Jika tanggal verifikasi di masa depan (karena beda timezone / lag server), anggap 0 hari
if ($tglVerif > $today) {
$hariSejak = 0;
} else {
$diff = $today->diff($tglVerif);
$hariSejak = $diff->days;
}
$intervalHari = $intervalBulan * 30;
// Skor linear: 100 di hari 0, turun ke 0 di hari intervalHari
$skor = max(0, (int)round(100 - ($hariSejak / $intervalHari * 100)));
$status = match(true) {
$skor >= 80 => 'terverifikasi',
$skor >= $minSkor => 'perlu_update',
default => 'kadaluarsa',
};
// Berapa hari lagi sampai perlu_update dan kadaluarsa
$hariMenujuPerluUpdate = max(0, (int)($intervalHari * 0.2) - $hariSejak);
$hariMenujuKadaluarsa = max(0, (int)($intervalHari * ((100 - $minSkor) / 100)) - $hariSejak);
return [
'skor' => $skor,
'hari_sejak_verifikasi' => $hariSejak,
'interval_hari' => $intervalHari,
'status' => $status,
'hari_menuju_perlu_update' => $hariMenujuPerluUpdate,
'hari_menuju_kadaluarsa' => $hariMenujuKadaluarsa,
'tanggal_kadaluarsa' => (clone $tglVerif)->modify("+{$intervalHari} days")->format('Y-m-d'),
];
}
/* ─────────────────────────────────────────────
UPDATE skor semua warga secara batch
Formula: mulai 100, kurangi seiring waktu
───────────────────────────────────────────── */
function updateSkopBatch(PDO $pdo): int {
$interval = getKonfig($pdo, 'interval_verifikasi_bulan', 6);
$minSkor = getKonfig($pdo, 'skor_data_kadaluarsa', 40);
$wargaList = $pdo->query("SELECT id, tanggal_verifikasi FROM rumah_warga")->fetchAll(PDO::FETCH_ASSOC);
$updated = 0;
$stmt = $pdo->prepare("
UPDATE rumah_warga SET skor_kepercayaan = ?, status_verifikasi = ? WHERE id = ?
");
foreach ($wargaList as $w) {
$calc = hitungSkorKepercayaan($w['tanggal_verifikasi'], $interval, $minSkor);
$stmt->execute([$calc['skor'], $calc['status'], (int)$w['id']]);
$updated++;
}
return $updated;
}
/* ─────────────────────────────────────────────
ROUTING
───────────────────────────────────────────── */
try {
$method = $_SERVER['REQUEST_METHOD'];
$action = $_GET['action'] ?? 'list';
if ($method === 'GET') {
// Hanya Admin dan Petugas Lapangan yang boleh melihat data verifikasi
requireAuth(['admin', 'petugas']);
if ($action === 'list') {
// Daftar warga yang perlu diverifikasi
$statusFilter = $_GET['status'] ?? null; // terverifikasi|perlu_update|kadaluarsa
$limit = (int)($_GET['limit'] ?? 50);
$offset = (int)($_GET['offset'] ?? 0);
// Update skor dulu secara batch
updateSkopBatch($pdo);
if ($statusFilter === 'semua') {
$where = "";
} elseif ($statusFilter === 'terverifikasi' || $statusFilter === 'perlu_update' || $statusFilter === 'kadaluarsa') {
$where = "WHERE rw.status_verifikasi = " . $pdo->quote($statusFilter);
} else {
// Default: Hanya warga yang perlu verifikasi (perlu_update atau kadaluarsa)
$where = "WHERE rw.status_verifikasi IN ('perlu_update', 'kadaluarsa')";
}
$sql = "
SELECT rw.id, rw.nama_kepala_keluarga, rw.no_kk,
rw.kelurahan, rw.kecamatan,
rw.tanggal_verifikasi, rw.petugas_verifikasi,
rw.skor_kepercayaan, rw.status_verifikasi,
rw.kategori_kemiskinan, rw.status_bantuan,
rw.latitude, rw.longitude
FROM rumah_warga rw
$where
ORDER BY rw.skor_kepercayaan ASC, rw.tanggal_verifikasi ASC
LIMIT $limit OFFSET $offset
";
$rows = $pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC);
// Enrichment: hitung detail decay per row
$interval = getKonfig($pdo, 'interval_verifikasi_bulan', 6);
$minSkor = getKonfig($pdo, 'skor_data_kadaluarsa', 40);
foreach ($rows as &$r) {
$r['detail_skor'] = hitungSkorKepercayaan(
$r['tanggal_verifikasi'], $interval, $minSkor
);
}
// Summary count
$summary = $pdo->query("
SELECT status_verifikasi, COUNT(*) as jumlah
FROM rumah_warga
GROUP BY status_verifikasi
")->fetchAll(PDO::FETCH_KEY_PAIR);
echo json_encode([
'success' => true,
'data' => $rows,
'summary' => $summary,
'total' => count($rows),
]);
} elseif ($action === 'config') {
// Ambil semua konfigurasi
$rows = $pdo->query("SELECT * FROM konfigurasi_sistem ORDER BY id")->fetchAll(PDO::FETCH_ASSOC);
echo json_encode(['success' => true, 'data' => $rows]);
} elseif ($action === 'riwayat') {
// Riwayat verifikasi untuk 1 warga
$wargaId = (int)($_GET['warga_id'] ?? 0);
$rows = $pdo->prepare("
SELECT * FROM riwayat_verifikasi
WHERE rumah_warga_id = ?
ORDER BY tanggal DESC LIMIT 20
");
$rows->execute([$wargaId]);
echo json_encode(['success' => true, 'data' => $rows->fetchAll(PDO::FETCH_ASSOC)]);
} elseif ($action === 'statistik') {
updateSkopBatch($pdo);
$data = [
'per_status' => $pdo->query("
SELECT status_verifikasi, COUNT(*) as jumlah,
ROUND(AVG(skor_kepercayaan)) as avg_skor
FROM rumah_warga GROUP BY status_verifikasi
")->fetchAll(PDO::FETCH_ASSOC),
'avg_skor_global' => (float)$pdo->query("SELECT ROUND(AVG(skor_kepercayaan),1) FROM rumah_warga")->fetchColumn(),
'paling_lama' => $pdo->query("
SELECT nama_kepala_keluarga, tanggal_verifikasi, skor_kepercayaan
FROM rumah_warga ORDER BY skor_kepercayaan ASC LIMIT 5
")->fetchAll(PDO::FETCH_ASSOC),
];
echo json_encode(['success' => true, 'data' => $data]);
}
} elseif ($method === 'POST') {
// Catat verifikasi baru untuk 1 warga
// Hanya Admin, Petugas Lapangan, dan Koordinator
$currentUser = requireAuth(['admin', 'petugas', 'koordinator']);
$body = json_decode(file_get_contents('php://input'), true);
$wargaId = (int)($body['warga_id'] ?? 0);
$petugas = trim($body['petugas'] ?? '');
$catatan = $body['catatan'] ?? null;
$tanggal = $body['tanggal'] ?? date('Y-m-d');
if (!$wargaId) {
http_response_code(400);
echo json_encode(['success' => false, 'message' => 'warga_id wajib diisi']);
exit;
}
// Jika koordinator, lakukan pengecekan wilayah
if ($currentUser['role'] === 'koordinator') {
$koorIbadahId = (int)$currentUser['rumah_ibadah_id'];
$wilayahRes = $pdo->prepare("SELECT kelurahan, kecamatan FROM rumah_ibadah WHERE id = ?");
$wilayahRes->execute([$koorIbadahId]);
if ($wil = $wilayahRes->fetch(PDO::FETCH_ASSOC)) {
$kel = $wil['kelurahan'];
$kec = $wil['kecamatan'];
$wargaRes = $pdo->prepare("SELECT kelurahan, kecamatan FROM rumah_warga WHERE id = ?");
$wargaRes->execute([$wargaId]);
if ($wargaRow = $wargaRes->fetch(PDO::FETCH_ASSOC)) {
$wKel = strtolower(trim($wargaRow['kelurahan'] ?? ''));
$wKec = strtolower(trim($wargaRow['kecamatan'] ?? ''));
if (strpos($wKel, strtolower($kel)) === false && strpos($wKec, strtolower($kec)) === false) {
http_response_code(403);
echo json_encode(['success' => false, 'message' => 'Forbidden: Warga di luar wilayah tugas Anda.']);
exit;
}
} else {
http_response_code(404);
echo json_encode(['success' => false, 'message' => 'Warga tidak ditemukan.']);
exit;
}
} else {
http_response_code(403);
echo json_encode(['success' => false, 'message' => 'Forbidden: Wilayah tugas tidak terkonfigurasi.']);
exit;
}
}
// Ambil skor lama
$oldRow = $pdo->prepare("SELECT skor_kepercayaan, status_verifikasi FROM rumah_warga WHERE id = ?");
$oldRow->execute([$wargaId]);
$old = $oldRow->fetch(PDO::FETCH_ASSOC);
$skorLama = $old ? (int)$old['skor_kepercayaan'] : null;
// Reset skor ke 100 & update tanggal verifikasi
$stmt = $pdo->prepare("
UPDATE rumah_warga
SET tanggal_verifikasi = ?,
petugas_verifikasi = ?,
skor_kepercayaan = 100,
status_verifikasi = 'terverifikasi',
updated_at = NOW()
WHERE id = ?
");
$stmt->execute([$tanggal, $petugas ?: null, $wargaId]);
// Simpan riwayat
$riwayat = $pdo->prepare("
INSERT INTO riwayat_verifikasi
(rumah_warga_id, tanggal, petugas, skor_sebelum, skor_sesudah, catatan)
VALUES (?, ?, ?, ?, 100, ?)
");
$riwayat->execute([$wargaId, $tanggal, $petugas ?: null, $skorLama, $catatan]);
echo json_encode([
'success' => true,
'message' => 'Verifikasi berhasil dicatat. Skor kepercayaan data dikembalikan ke 100.',
'skor_lama' => $skorLama,
'skor_baru' => 100,
]);
} elseif ($method === 'PUT') {
// Update konfigurasi sistem
// HANYA Admin yang boleh merubah konfigurasi sistem
requireAuth(['admin']);
$body = json_decode(file_get_contents('php://input'), true);
if (!is_array($body)) {
http_response_code(400);
echo json_encode(['success' => false, 'message' => 'Body tidak valid']);
exit;
}
$stmt = $pdo->prepare("UPDATE konfigurasi_sistem SET nilai = ? WHERE kunci = ?");
$updated = 0;
foreach ($body as $kunci => $nilai) {
$stmt->execute([(string)$nilai, $kunci]);
$updated += $stmt->rowCount();
}
// Setelah konfigurasi diupdate, recalculate semua skor
updateSkopBatch($pdo);
echo json_encode([
'success' => true,
'message' => "$updated konfigurasi berhasil diperbarui. Skor kepercayaan seluruh data telah dihitung ulang.",
]);
}
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
}