316 lines
13 KiB
PHP
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()]);
|
|
} |