Initial commit
This commit is contained in:
@@ -0,0 +1,316 @@
|
||||
<?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()]);
|
||||
}
|
||||
Reference in New Issue
Block a user