Initial commit
This commit is contained in:
@@ -0,0 +1,163 @@
|
||||
<?php
|
||||
// api/anggota_keluarga.php
|
||||
header('Content-Type: application/json');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
|
||||
header('Access-Control-Allow-Headers: Content-Type');
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { http_response_code(204); exit; }
|
||||
|
||||
require_once '../config/db.php';
|
||||
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
|
||||
switch ($method) {
|
||||
case 'GET':
|
||||
require_once 'middleware.php';
|
||||
$currentUser = requireAuth(['admin', 'petugas', 'koordinator']);
|
||||
if ($currentUser['role'] === 'koordinator') {
|
||||
echo json_encode(['success' => true, 'data' => [], 'total' => 0]);
|
||||
break;
|
||||
}
|
||||
|
||||
$wargaId = (int)($_GET['rumah_warga_id'] ?? 0);
|
||||
if (!$wargaId) { echo json_encode(['success' => false, 'message' => 'rumah_warga_id diperlukan']); break; }
|
||||
|
||||
$res = $conn->query("SELECT * FROM anggota_keluarga WHERE rumah_warga_id=$wargaId ORDER BY tanggal_lahir ASC");
|
||||
$data = [];
|
||||
$today = new DateTime();
|
||||
while ($row = $res->fetch_assoc()) {
|
||||
// Hitung usia
|
||||
if ($row['tanggal_lahir']) {
|
||||
$lahir = new DateTime($row['tanggal_lahir']);
|
||||
$row['usia'] = (int)$today->diff($lahir)->y;
|
||||
} else {
|
||||
$row['usia'] = null;
|
||||
}
|
||||
$data[] = $row;
|
||||
}
|
||||
echo json_encode(['success' => true, 'data' => $data, 'total' => count($data)]);
|
||||
break;
|
||||
|
||||
case 'POST':
|
||||
$input = json_decode(file_get_contents('php://input'), true) ?: $_POST;
|
||||
$wargaId = (int)($input['rumah_warga_id'] ?? 0);
|
||||
$nama = $conn->real_escape_string(trim($input['nama'] ?? ''));
|
||||
$nik = $conn->real_escape_string($input['nik'] ?? '');
|
||||
$tl = !empty($input['tanggal_lahir']) ? "'" . $conn->real_escape_string($input['tanggal_lahir']) . "'" : 'NULL';
|
||||
$jk = $conn->real_escape_string($input['jenis_kelamin'] ?? 'laki_laki');
|
||||
$pend = $conn->real_escape_string($input['pendidikan'] ?? 'sd');
|
||||
$pek = $conn->real_escape_string($input['pekerjaan'] ?? '');
|
||||
$stsBk = $conn->real_escape_string($input['status_bekerja'] ?? 'tidak_bekerja');
|
||||
$kes = $conn->real_escape_string($input['kondisi_kesehatan'] ?? 'sehat');
|
||||
$bisa = (int)($input['dapat_bekerja'] ?? 1);
|
||||
|
||||
if (!$wargaId || !$nama) { echo json_encode(['success' => false, 'message' => 'ID warga dan nama wajib diisi']); break; }
|
||||
|
||||
$sql = "INSERT INTO anggota_keluarga (rumah_warga_id,nama,nik,tanggal_lahir,jenis_kelamin,pendidikan,pekerjaan,status_bekerja,kondisi_kesehatan,dapat_bekerja)
|
||||
VALUES ($wargaId,'$nama','$nik',$tl,'$jk','$pend','$pek','$stsBk','$kes',$bisa)";
|
||||
|
||||
if ($conn->query($sql)) {
|
||||
// Re-hitung rekomendasi induk
|
||||
rehitungRekomendasi($conn, $wargaId);
|
||||
echo json_encode(['success' => true, 'message' => 'Anggota berhasil ditambahkan', 'id' => $conn->insert_id]);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => 'Gagal: ' . $conn->error]);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'PUT':
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
$id = (int)($input['id'] ?? 0);
|
||||
$nama = $conn->real_escape_string(trim($input['nama'] ?? ''));
|
||||
$nik = $conn->real_escape_string($input['nik'] ?? '');
|
||||
$tl = !empty($input['tanggal_lahir']) ? "'" . $conn->real_escape_string($input['tanggal_lahir']) . "'" : 'NULL';
|
||||
$jk = $conn->real_escape_string($input['jenis_kelamin'] ?? 'laki_laki');
|
||||
$pend = $conn->real_escape_string($input['pendidikan'] ?? 'sd');
|
||||
$pek = $conn->real_escape_string($input['pekerjaan'] ?? '');
|
||||
$stsBk = $conn->real_escape_string($input['status_bekerja'] ?? 'tidak_bekerja');
|
||||
$kes = $conn->real_escape_string($input['kondisi_kesehatan'] ?? 'sehat');
|
||||
$bisa = (int)($input['dapat_bekerja'] ?? 1);
|
||||
|
||||
if (!$id) { echo json_encode(['success' => false, 'message' => 'ID tidak valid']); break; }
|
||||
|
||||
$sql = "UPDATE anggota_keluarga SET
|
||||
nama='$nama', nik='$nik', tanggal_lahir=$tl, jenis_kelamin='$jk',
|
||||
pendidikan='$pend', pekerjaan='$pek', status_bekerja='$stsBk',
|
||||
kondisi_kesehatan='$kes', dapat_bekerja=$bisa
|
||||
WHERE id=$id";
|
||||
|
||||
if ($conn->query($sql)) {
|
||||
// Ambil warga_id lalu re-hitung rekomendasi
|
||||
$r = $conn->query("SELECT rumah_warga_id FROM anggota_keluarga WHERE id=$id");
|
||||
if ($row = $r->fetch_assoc()) rehitungRekomendasi($conn, (int)$row['rumah_warga_id']);
|
||||
echo json_encode(['success' => true, 'message' => 'Anggota berhasil diperbarui']);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => 'Gagal: ' . $conn->error]);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'DELETE':
|
||||
$id = (int)($_GET['id'] ?? 0);
|
||||
if (!$id) { $input = json_decode(file_get_contents('php://input'), true); $id = (int)($input['id'] ?? 0); }
|
||||
|
||||
// Ambil warga_id sebelum hapus
|
||||
$r = $conn->query("SELECT rumah_warga_id FROM anggota_keluarga WHERE id=$id");
|
||||
$wargaId = ($row = $r->fetch_assoc()) ? (int)$row['rumah_warga_id'] : 0;
|
||||
|
||||
if ($conn->query("DELETE FROM anggota_keluarga WHERE id=$id")) {
|
||||
if ($wargaId) rehitungRekomendasi($conn, $wargaId);
|
||||
echo json_encode(['success' => true, 'message' => 'Anggota berhasil dihapus']);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => 'Gagal: ' . $conn->error]);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
http_response_code(405);
|
||||
echo json_encode(['error' => 'Method not allowed']);
|
||||
}
|
||||
$conn->close();
|
||||
|
||||
// ── Helper: re-hitung & simpan rekomendasi ────────────────────────────────
|
||||
function rehitungRekomendasi(mysqli $conn, int $wargaId): void
|
||||
{
|
||||
$res = $conn->query("SELECT penghasilan_bulanan, status_pekerjaan, is_rekomendasi_manual FROM rumah_warga WHERE id=$wargaId");
|
||||
if (!($row = $res->fetch_assoc())) return;
|
||||
if (isset($row['is_rekomendasi_manual']) && (int)$row['is_rekomendasi_manual'] === 1) return;
|
||||
|
||||
$penghasilan = (float)$row['penghasilan_bulanan'];
|
||||
$stsPekerjaan = $row['status_pekerjaan'];
|
||||
|
||||
$today = new DateTime();
|
||||
$resA = $conn->query("SELECT tanggal_lahir, status_bekerja, dapat_bekerja, kondisi_kesehatan FROM anggota_keluarga WHERE rumah_warga_id=$wargaId");
|
||||
$anggota = [];
|
||||
while ($a = $resA->fetch_assoc()) $anggota[] = $a;
|
||||
|
||||
$adaProduktif = false;
|
||||
$adaProduktifBisaKerja = false;
|
||||
$semuaProduktifTakBisa = true;
|
||||
|
||||
foreach ($anggota as $a) {
|
||||
$usia = null;
|
||||
if ($a['tanggal_lahir']) {
|
||||
$lahir = new DateTime($a['tanggal_lahir']);
|
||||
$usia = (int)$today->diff($lahir)->y;
|
||||
}
|
||||
if ($usia !== null && $usia >= 18 && $usia <= 55) {
|
||||
$adaProduktif = true;
|
||||
$bisaKerja = (int)$a['dapat_bekerja'] === 1 && in_array($a['kondisi_kesehatan'], ['sehat', 'sakit_ringan']);
|
||||
if ($bisaKerja) $semuaProduktifTakBisa = false;
|
||||
if ($bisaKerja && $a['status_bekerja'] !== 'bekerja') $adaProduktifBisaKerja = true;
|
||||
}
|
||||
}
|
||||
|
||||
$perluRutin = !$adaProduktif || ($adaProduktif && $semuaProduktifTakBisa) || $penghasilan < 500000 || $stsPekerjaan === 'tidak_bekerja';
|
||||
$perluPelatihan = $adaProduktifBisaKerja;
|
||||
|
||||
$rek = 'tidak_ada';
|
||||
if ($perluRutin && $perluPelatihan) $rek = 'keduanya';
|
||||
elseif ($perluRutin) $rek = 'bantuan_rutin';
|
||||
elseif ($perluPelatihan) $rek = 'bantuan_pelatihan';
|
||||
|
||||
$conn->query("UPDATE rumah_warga SET rekomendasi_bantuan='$rek' WHERE id=$wargaId");
|
||||
}
|
||||
+216
@@ -0,0 +1,216 @@
|
||||
<?php
|
||||
// api/auth.php
|
||||
header('Content-Type: application/json');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
|
||||
header('Access-Control-Allow-Headers: Content-Type, Authorization');
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
||||
http_response_code(200);
|
||||
exit;
|
||||
}
|
||||
|
||||
require_once '../config/db.php';
|
||||
require_once 'middleware.php';
|
||||
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
$action = $_GET['action'] ?? 'status';
|
||||
|
||||
try {
|
||||
switch ($method) {
|
||||
case 'GET':
|
||||
if ($action === 'status') {
|
||||
// Cek status login (Guest support)
|
||||
$user = optionalAuth();
|
||||
if ($user) {
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'logged_in' => true,
|
||||
'user' => [
|
||||
'id' => $user['id'],
|
||||
'username' => $user['username'],
|
||||
'role' => $user['role'],
|
||||
'rumah_ibadah_id' => $user['rumah_ibadah_id'],
|
||||
'no_telp' => $user['no_telp'] ?? ''
|
||||
]
|
||||
]);
|
||||
} else {
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'logged_in' => false,
|
||||
'user' => null,
|
||||
'message' => 'Guest Mode: Pengguna belum login.'
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'message' => 'Action GET tidak dikenal.']);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'POST':
|
||||
if ($action === 'login') {
|
||||
// Proses Login
|
||||
$body = json_decode(file_get_contents('php://input'), true) ?: $_POST;
|
||||
$username = trim($body['username'] ?? '');
|
||||
$password = trim($body['password'] ?? '');
|
||||
|
||||
if (!$username || !$password) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'message' => 'Username dan Password wajib diisi.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Cari user berdasarkan username
|
||||
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = ?");
|
||||
$stmt->execute([$username]);
|
||||
$user = $stmt->fetch();
|
||||
|
||||
if ($user && password_verify($password, $user['password'])) {
|
||||
// Simpan data penting ke session
|
||||
$_SESSION['user'] = [
|
||||
'id' => (int)$user['id'],
|
||||
'username' => $user['username'],
|
||||
'role' => $user['role'],
|
||||
'rumah_ibadah_id' => $user['rumah_ibadah_id'] ? (int)$user['rumah_ibadah_id'] : null,
|
||||
'no_telp' => $user['no_telp'] ?? ''
|
||||
];
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => 'Login berhasil! Selamat datang, ' . $user['username'],
|
||||
'user' => $_SESSION['user']
|
||||
]);
|
||||
} else {
|
||||
http_response_code(401);
|
||||
echo json_encode(['success' => false, 'message' => 'Username atau Password salah.']);
|
||||
}
|
||||
} elseif ($action === 'register') {
|
||||
// Proses Registrasi Mandiri (hanya untuk kontributor & koordinator)
|
||||
$body = json_decode(file_get_contents('php://input'), true) ?: $_POST;
|
||||
$username = trim($body['username'] ?? '');
|
||||
$email = trim($body['email'] ?? '');
|
||||
$password = trim($body['password'] ?? '');
|
||||
$noTelp = trim($body['no_telp'] ?? '');
|
||||
$role = trim($body['role'] ?? '');
|
||||
$rumahIbadahId = isset($body['rumah_ibadah_id']) && $body['rumah_ibadah_id'] !== '' ? (int)$body['rumah_ibadah_id'] : null;
|
||||
|
||||
// Validasi field wajib
|
||||
if (!$username || !$email || !$password || !$noTelp || !$role) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'message' => 'Semua field wajib diisi (username, email, password, no_telp, role).']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Validasi panjang & kekuatan password
|
||||
if (strlen($password) < 6) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'message' => 'Password minimal 6 karakter.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Validasi format email
|
||||
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'message' => 'Format email tidak valid.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Whitelist role — HANYA kontributor dan koordinator yang boleh mendaftar sendiri
|
||||
$allowedRoles = ['kontributor', 'koordinator'];
|
||||
if (!in_array($role, $allowedRoles)) {
|
||||
http_response_code(403);
|
||||
echo json_encode(['success' => false, 'message' => 'Peran "' . $role . '" tidak diizinkan untuk registrasi mandiri.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Koordinator wajib memilih rumah ibadah
|
||||
if ($role === 'koordinator' && !$rumahIbadahId) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'message' => 'Koordinator Rumah Ibadah wajib memilih rumah ibadah yang dikelola.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Kontributor tidak boleh terikat ke rumah ibadah
|
||||
if ($role === 'kontributor') {
|
||||
$rumahIbadahId = null;
|
||||
}
|
||||
|
||||
// Cek duplikasi username
|
||||
$stmtCheck = $pdo->prepare("SELECT id FROM users WHERE username = ? LIMIT 1");
|
||||
$stmtCheck->execute([$username]);
|
||||
if ($stmtCheck->fetch()) {
|
||||
http_response_code(409);
|
||||
echo json_encode(['success' => false, 'message' => 'Username "' . $username . '" sudah terdaftar. Gunakan username lain.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Cek duplikasi email
|
||||
$stmtCheck2 = $pdo->prepare("SELECT id FROM users WHERE email = ? LIMIT 1");
|
||||
$stmtCheck2->execute([$email]);
|
||||
if ($stmtCheck2->fetch()) {
|
||||
http_response_code(409);
|
||||
echo json_encode(['success' => false, 'message' => 'Email "' . $email . '" sudah terdaftar. Gunakan email lain.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Validasi rumah ibadah ada di database (untuk koordinator)
|
||||
if ($role === 'koordinator' && $rumahIbadahId) {
|
||||
$stmtIbadah = $pdo->prepare("SELECT id FROM rumah_ibadah WHERE id = ? LIMIT 1");
|
||||
$stmtIbadah->execute([$rumahIbadahId]);
|
||||
if (!$stmtIbadah->fetch()) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'message' => 'Rumah ibadah yang dipilih tidak valid atau tidak ditemukan.']);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
// Hash password dengan bcrypt
|
||||
$hashedPassword = password_hash($password, PASSWORD_BCRYPT);
|
||||
|
||||
// Simpan user baru ke database
|
||||
$stmtInsert = $pdo->prepare("INSERT INTO users (username, email, password, no_telp, role, rumah_ibadah_id) VALUES (?, ?, ?, ?, ?, ?)");
|
||||
$stmtInsert->execute([$username, $email, $hashedPassword, $noTelp, $role, $rumahIbadahId]);
|
||||
|
||||
$roleLabel = $role === 'koordinator' ? 'Koordinator Rumah Ibadah' : 'Kontributor';
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => 'Registrasi berhasil! Selamat bergabung sebagai ' . $roleLabel . '. Silakan login dengan akun Anda.',
|
||||
'username' => $username
|
||||
]);
|
||||
|
||||
} elseif ($action === 'logout') {
|
||||
// Proses Logout
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
// Hapus semua data session dan hancurkan
|
||||
$_SESSION = [];
|
||||
if (ini_get("session.use_cookies")) {
|
||||
$params = session_get_cookie_params();
|
||||
setcookie(session_name(), '', time() - 42000,
|
||||
$params["path"], $params["domain"],
|
||||
$params["secure"], $params["httponly"]
|
||||
);
|
||||
}
|
||||
session_destroy();
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => 'Logout berhasil. Anda sekarang masuk sebagai Guest.'
|
||||
]);
|
||||
} else {
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'message' => 'Action POST tidak dikenal.']);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
http_response_code(405);
|
||||
echo json_encode(['error' => 'Method not allowed']);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['success' => false, 'message' => 'Terjadi kesalahan sistem: ' . $e->getMessage()]);
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
<?php
|
||||
// api/kebutuhan.php
|
||||
header('Content-Type: application/json');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
|
||||
header('Access-Control-Allow-Headers: Content-Type, Authorization');
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
||||
http_response_code(200);
|
||||
exit;
|
||||
}
|
||||
|
||||
require_once '../config/db.php';
|
||||
require_once 'middleware.php';
|
||||
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
|
||||
try {
|
||||
switch ($method) {
|
||||
case 'GET':
|
||||
$currentUser = optionalAuth();
|
||||
$id = isset($_GET['id']) ? (int)$_GET['id'] : null;
|
||||
|
||||
if ($id) {
|
||||
$stmt = $pdo->prepare(
|
||||
"SELECT k.*, ri.nama AS nama_rumah_ibadah, ri.alamat, ri.latitude, ri.longitude,
|
||||
COALESCE(SUM(CASE WHEN ko.status = 'confirmed' THEN ko.jumlah ELSE 0 END), 0) AS jumlah_terkumpul
|
||||
FROM kebutuhan k
|
||||
JOIN rumah_ibadah ri ON k.rumah_ibadah_id = ri.id
|
||||
LEFT JOIN kontribusi ko ON k.id = ko.kebutuhan_id
|
||||
WHERE k.id = ?
|
||||
GROUP BY k.id"
|
||||
);
|
||||
$stmt->execute([$id]);
|
||||
$row = $stmt->fetch();
|
||||
|
||||
if ($row) {
|
||||
$row['sisa_kebutuhan'] = max(0, (float)$row['jumlah_dibutuhkan'] - (float)$row['jumlah_terkumpul']);
|
||||
echo json_encode(['success' => true, 'data' => $row, 'viewer_role' => $currentUser ? $currentUser['role'] : 'guest']);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => 'Kebutuhan tidak ditemukan']);
|
||||
}
|
||||
} else {
|
||||
$sql = "SELECT k.*, ri.nama AS nama_rumah_ibadah, ri.alamat, ri.latitude, ri.longitude,
|
||||
COALESCE(SUM(CASE WHEN ko.status = 'confirmed' THEN ko.jumlah ELSE 0 END), 0) AS jumlah_terkumpul
|
||||
FROM kebutuhan k
|
||||
JOIN rumah_ibadah ri ON k.rumah_ibadah_id = ri.id
|
||||
LEFT JOIN kontribusi ko ON k.id = ko.kebutuhan_id
|
||||
GROUP BY k.id
|
||||
ORDER BY k.created_at DESC";
|
||||
|
||||
$rows = $pdo->query($sql)->fetchAll();
|
||||
foreach ($rows as &$r) {
|
||||
$r['sisa_kebutuhan'] = max(0, (float)$r['jumlah_dibutuhkan'] - (float)$r['jumlah_terkumpul']);
|
||||
}
|
||||
|
||||
echo json_encode(['success' => true, 'data' => $rows, 'viewer_role' => $currentUser ? $currentUser['role'] : 'guest']);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'POST':
|
||||
$currentUser = requireAuth(['admin', 'koordinator']);
|
||||
$body = json_decode(file_get_contents('php://input'), true) ?: $_POST;
|
||||
|
||||
$rumahIbadahId = (int)($body['rumah_ibadah_id'] ?? 0);
|
||||
$jenisBantuan = trim($body['jenis_bantuan'] ?? '');
|
||||
$kategori = trim($body['kategori'] ?? 'barang');
|
||||
$satuan = trim($body['satuan'] ?? 'unit');
|
||||
$jumlahReq = (float)($body['jumlah_dibutuhkan'] ?? 0);
|
||||
|
||||
if ($currentUser['role'] === 'koordinator' && $currentUser['rumah_ibadah_id'] != $rumahIbadahId) {
|
||||
http_response_code(403);
|
||||
echo json_encode(['success' => false, 'message' => 'Koordinator hanya dapat mengelola kebutuhan rumah ibadah mereka sendiri.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$allowedSatuan = ['unit','orang','jam','shift','nominal'];
|
||||
if (!in_array($satuan, $allowedSatuan)) $satuan = 'unit';
|
||||
|
||||
if (!$rumahIbadahId || !$jenisBantuan || $jumlahReq <= 0) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'message' => 'Parameter input tidak valid atau jumlah harus lebih besar dari 0']);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$stmt = $pdo->prepare("INSERT INTO kebutuhan (rumah_ibadah_id, jenis_bantuan, kategori, jumlah_dibutuhkan, satuan) VALUES (?, ?, ?, ?, ?)");
|
||||
$stmt->execute([$rumahIbadahId, $jenisBantuan, $kategori, $jumlahReq, $satuan]);
|
||||
} catch (PDOException $e) {
|
||||
$stmt = $pdo->prepare("INSERT INTO kebutuhan (rumah_ibadah_id, jenis_bantuan, kategori, jumlah_dibutuhkan) VALUES (?, ?, ?, ?)");
|
||||
$stmt->execute([$rumahIbadahId, $jenisBantuan, $kategori, $jumlahReq]);
|
||||
}
|
||||
|
||||
echo json_encode(['success' => true, 'message' => 'Kebutuhan bantuan berhasil ditambahkan', 'id' => $pdo->lastInsertId()]);
|
||||
break;
|
||||
|
||||
case 'PUT':
|
||||
$currentUser = requireAuth(['admin', 'koordinator']);
|
||||
$body = json_decode(file_get_contents('php://input'), true) ?: $_POST;
|
||||
$id = (int)($body['id'] ?? 0);
|
||||
|
||||
$stmt = $pdo->prepare("SELECT rumah_ibadah_id FROM kebutuhan WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$kebutuhan = $stmt->fetch();
|
||||
|
||||
if (!$kebutuhan) {
|
||||
http_response_code(404);
|
||||
echo json_encode(['success' => false, 'message' => 'Kebutuhan tidak ditemukan']);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($currentUser['role'] === 'koordinator' && $currentUser['rumah_ibadah_id'] != $kebutuhan['rumah_ibadah_id']) {
|
||||
http_response_code(403);
|
||||
echo json_encode(['success' => false, 'message' => 'Koordinator tidak berhak merubah kebutuhan rumah ibadah lain.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$satuanIn = trim($body['satuan'] ?? 'unit');
|
||||
$allowedSatuan = ['unit','orang','jam','shift','nominal'];
|
||||
if (!in_array($satuanIn, $allowedSatuan)) $satuanIn = 'unit';
|
||||
|
||||
try {
|
||||
$stmt = $pdo->prepare("UPDATE kebutuhan SET jenis_bantuan = ?, kategori = ?, jumlah_dibutuhkan = ?, satuan = ? WHERE id = ?");
|
||||
$stmt->execute([
|
||||
trim($body['jenis_bantuan']),
|
||||
trim($body['kategori']),
|
||||
(float)$body['jumlah_dibutuhkan'],
|
||||
$satuanIn,
|
||||
$id
|
||||
]);
|
||||
} catch (PDOException $e) {
|
||||
$stmt = $pdo->prepare("UPDATE kebutuhan SET jenis_bantuan = ?, kategori = ?, jumlah_dibutuhkan = ? WHERE id = ?");
|
||||
$stmt->execute([
|
||||
trim($body['jenis_bantuan']),
|
||||
trim($body['kategori']),
|
||||
(float)$body['jumlah_dibutuhkan'],
|
||||
$id
|
||||
]);
|
||||
}
|
||||
|
||||
echo json_encode(['success' => true, 'message' => 'Kebutuhan berhasil diperbarui']);
|
||||
break;
|
||||
|
||||
case 'DELETE':
|
||||
$currentUser = requireAuth(['admin', 'koordinator']);
|
||||
$id = (int)($_GET['id'] ?? 0);
|
||||
|
||||
$stmt = $pdo->prepare("SELECT rumah_ibadah_id FROM kebutuhan WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$kebutuhan = $stmt->fetch();
|
||||
|
||||
if (!$kebutuhan) {
|
||||
http_response_code(404);
|
||||
echo json_encode(['success' => false, 'message' => 'Kebutuhan tidak ditemukan']);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($currentUser['role'] === 'koordinator' && $currentUser['rumah_ibadah_id'] != $kebutuhan['rumah_ibadah_id']) {
|
||||
http_response_code(403);
|
||||
echo json_encode(['success' => false, 'message' => 'Koordinator tidak berhak menghapus kebutuhan rumah ibadah lain.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("DELETE FROM kebutuhan WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
|
||||
echo json_encode(['success' => true, 'message' => 'Kebutuhan berhasil dihapus']);
|
||||
break;
|
||||
|
||||
default:
|
||||
http_response_code(405);
|
||||
echo json_encode(['error' => 'Method not allowed']);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
||||
}
|
||||
@@ -0,0 +1,445 @@
|
||||
<?php
|
||||
// api/kemandirian.php
|
||||
// Fitur 3: Potensi Kemandirian Warga
|
||||
// Hitung skor kemandirian berdasarkan: penghasilan, status kerja anggota, jumlah tanggungan
|
||||
// Method: GET (list/detail/statistik), POST (catat perkembangan manual), PUT (set exit plan)
|
||||
|
||||
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'],
|
||||
};
|
||||
}
|
||||
|
||||
function hitungUsia(?string $tanggalLahir): ?int {
|
||||
if (!$tanggalLahir) return null;
|
||||
$lahir = new DateTime($tanggalLahir);
|
||||
$today = new DateTime();
|
||||
return (int)$today->diff($lahir)->y;
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────
|
||||
CORE: Hitung Skor Kemandirian
|
||||
|
||||
Komponen:
|
||||
A) Skor Penghasilan (bobot configurable, default 40%)
|
||||
- 0% : penghasilan = 0
|
||||
- 50% : penghasilan = 50% threshold
|
||||
- 100%: penghasilan >= threshold UMR
|
||||
|
||||
B) Skor Status Kerja (default 35%)
|
||||
- Dihitung dari anggota usia produktif (18-55)
|
||||
- Poin per anggota: bekerja_tetap=100, tidak_tetap=60, tidak_bekerja=0, pelajar=30
|
||||
- Bonus: sudah bekerja tetap setelah ikut pelatihan
|
||||
|
||||
C) Skor Tanggungan (default 25%)
|
||||
- Tanggungan sedikit (1-2) = skor tinggi
|
||||
- Tanggungan banyak (> 6) = skor rendah
|
||||
|
||||
Status kemandirian:
|
||||
- 0-20 : sangat_bergantung
|
||||
- 21-40 : bergantung
|
||||
- 41-60 : berkembang
|
||||
- 61-79 : berpotensi_mandiri
|
||||
- 80+ : mandiri (siap exit)
|
||||
───────────────────────────────────────────── */
|
||||
function hitungSkorKemandirian(
|
||||
PDO $pdo,
|
||||
array $warga,
|
||||
array $anggotaList,
|
||||
float $thresholdPenghasilan,
|
||||
int $bobotPenghasilan,
|
||||
int $bobotStatusKerja,
|
||||
int $bobotTanggungan
|
||||
): array {
|
||||
$penghasilan = (float)$warga['penghasilan_bulanan'];
|
||||
$tanggungan = (int)$warga['jumlah_tanggungan'];
|
||||
$statusKerja = $warga['status_pekerjaan'];
|
||||
|
||||
/* ── A: Skor Penghasilan ── */
|
||||
$skorPenghasilan = 0;
|
||||
if ($thresholdPenghasilan > 0) {
|
||||
$skorPenghasilan = (int)min(100, round($penghasilan / $thresholdPenghasilan * 100));
|
||||
}
|
||||
|
||||
/* ── B: Skor Status Kerja ── */
|
||||
$bobotStatusKerjaMap = [
|
||||
'tetap' => 100,
|
||||
'tidak_tetap' => 60,
|
||||
'pensiun' => 50,
|
||||
'tidak_bekerja'=> 0,
|
||||
];
|
||||
|
||||
// Skor kepala keluarga
|
||||
$poinKK = $bobotStatusKerjaMap[$statusKerja] ?? 0;
|
||||
$anggotaProd = array_filter($anggotaList, function($a) {
|
||||
$usia = hitungUsia($a['tanggal_lahir']);
|
||||
return $usia !== null && $usia >= 18 && $usia <= 55;
|
||||
});
|
||||
|
||||
if (count($anggotaProd) > 0) {
|
||||
$totalPoin = $poinKK;
|
||||
foreach ($anggotaProd as $a) {
|
||||
$stsBekMap = [
|
||||
'bekerja' => 100,
|
||||
'pensiunan' => 50,
|
||||
'pelajar' => 30,
|
||||
'tidak_bekerja' => 0,
|
||||
];
|
||||
$poin = $stsBekMap[$a['status_bekerja']] ?? 0;
|
||||
// Diskon: tidak bisa bekerja / disabilitas
|
||||
if (!$a['dapat_bekerja'] || in_array($a['kondisi_kesehatan'], ['sakit_berat','disabilitas'])) {
|
||||
$poin = (int)($poin * 0.3);
|
||||
}
|
||||
$totalPoin += $poin;
|
||||
}
|
||||
$jumlahDitimbang = count($anggotaProd) + 1; // +1 untuk KK
|
||||
$skorStatusKerja = (int)round($totalPoin / ($jumlahDitimbang * 100) * 100);
|
||||
} else {
|
||||
// Tidak ada anggota produktif, skor bergantung murni dari KK
|
||||
$skorStatusKerja = $poinKK;
|
||||
}
|
||||
|
||||
/* ── C: Skor Tanggungan ── */
|
||||
// Makin sedikit tanggungan relatif terhadap yang bekerja = makin mandiri
|
||||
$anggotaBekerja = count(array_filter($anggotaList, fn($a) => $a['status_bekerja'] === 'bekerja'));
|
||||
$pencariNafkah = $anggotaBekerja + ($statusKerja !== 'tidak_bekerja' ? 1 : 0);
|
||||
|
||||
if ($tanggungan <= 0) {
|
||||
$skorTanggungan = 100;
|
||||
} elseif ($pencariNafkah >= $tanggungan) {
|
||||
$skorTanggungan = 100; // semua tanggungan punya pemasukan
|
||||
} else {
|
||||
$rasio = $pencariNafkah / $tanggungan;
|
||||
$skorTanggungan = (int)min(100, round($rasio * 100));
|
||||
}
|
||||
|
||||
/* ── Skor Total (weighted) ── */
|
||||
$totalBobot = $bobotPenghasilan + $bobotStatusKerja + $bobotTanggungan;
|
||||
if ($totalBobot <= 0) $totalBobot = 100;
|
||||
|
||||
$skorTotal = (int)round(
|
||||
($skorPenghasilan * $bobotPenghasilan
|
||||
+ $skorStatusKerja * $bobotStatusKerja
|
||||
+ $skorTanggungan * $bobotTanggungan) / $totalBobot
|
||||
);
|
||||
|
||||
/* ── Status ── */
|
||||
$status = match(true) {
|
||||
$skorTotal >= 80 => 'mandiri',
|
||||
$skorTotal >= 61 => 'berpotensi_mandiri',
|
||||
$skorTotal >= 41 => 'berkembang',
|
||||
$skorTotal >= 21 => 'bergantung',
|
||||
default => 'sangat_bergantung',
|
||||
};
|
||||
|
||||
/* ── Estimasi waktu menuju mandiri ── */
|
||||
$estimasiKenaikan = 5; // asumsi naik 5 poin/bulan dengan pelatihan
|
||||
$bulanMenujuMandiri = $skorTotal >= 80 ? 0 : (int)ceil((80 - $skorTotal) / $estimasiKenaikan);
|
||||
|
||||
/* ── Rekomendasi aksi ── */
|
||||
$rekomendasi = [];
|
||||
if ($skorPenghasilan < 50) $rekomendasi[] = 'Tingkatkan penghasilan melalui pelatihan vokasional';
|
||||
if ($skorStatusKerja < 50) $rekomendasi[] = 'Dukung anggota produktif untuk mendapat pekerjaan tetap';
|
||||
if ($skorTanggungan < 50) $rekomendasi[] = 'Pertimbangkan program KB/pemberdayaan tanggungan';
|
||||
if (count($rekomendasi) === 0) $rekomendasi[] = 'Pertahankan perkembangan yang ada, pantau secara berkala';
|
||||
|
||||
return [
|
||||
'skor_total' => $skorTotal,
|
||||
'skor_penghasilan' => $skorPenghasilan,
|
||||
'skor_status_kerja' => $skorStatusKerja,
|
||||
'skor_tanggungan' => $skorTanggungan,
|
||||
'status' => $status,
|
||||
'estimasi_bulan_mandiri' => $bulanMenujuMandiri,
|
||||
'rekomendasi' => $rekomendasi,
|
||||
'detail' => [
|
||||
'penghasilan' => $penghasilan,
|
||||
'threshold' => $thresholdPenghasilan,
|
||||
'jumlah_tanggungan' => $tanggungan,
|
||||
'pencari_nafkah' => $pencariNafkah,
|
||||
'anggota_produktif' => count($anggotaProd),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────
|
||||
UPDATE skor kemandirian batch + simpan riwayat
|
||||
───────────────────────────────────────────── */
|
||||
function updateKemandirianBatch(PDO $pdo, bool $simpanRiwayat = false): int {
|
||||
$thres = getKonfig($pdo, 'threshold_penghasilan_mandiri', 3500000);
|
||||
$bPeng = getKonfig($pdo, 'bobot_penghasilan', 40);
|
||||
$bKerja = getKonfig($pdo, 'bobot_status_kerja', 35);
|
||||
$bTangg = getKonfig($pdo, 'bobot_tanggungan', 25);
|
||||
|
||||
$wargaList = $pdo->query("
|
||||
SELECT id, penghasilan_bulanan, jumlah_tanggungan, status_pekerjaan,
|
||||
skor_kemandirian as skor_lama
|
||||
FROM rumah_warga
|
||||
")->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$stmtAnggota = $pdo->prepare("
|
||||
SELECT nama, tanggal_lahir, status_bekerja, kondisi_kesehatan, dapat_bekerja
|
||||
FROM anggota_keluarga WHERE rumah_warga_id = ?
|
||||
");
|
||||
$stmtUpdate = $pdo->prepare("
|
||||
UPDATE rumah_warga SET skor_kemandirian = ?, status_kemandirian = ? WHERE id = ?
|
||||
");
|
||||
$stmtRiwayat = $pdo->prepare("
|
||||
INSERT INTO riwayat_kemandirian
|
||||
(rumah_warga_id, tanggal, skor, status, penghasilan_saat_ini)
|
||||
VALUES (?, CURDATE(), ?, ?, ?)
|
||||
");
|
||||
|
||||
$updated = 0;
|
||||
foreach ($wargaList as $w) {
|
||||
$stmtAnggota->execute([(int)$w['id']]);
|
||||
$anggota = $stmtAnggota->fetchAll(PDO::FETCH_ASSOC);
|
||||
$calc = hitungSkorKemandirian($pdo, $w, $anggota, $thres, $bPeng, $bKerja, $bTangg);
|
||||
|
||||
$stmtUpdate->execute([$calc['skor_total'], $calc['status'], (int)$w['id']]);
|
||||
|
||||
if ($simpanRiwayat && $calc['skor_total'] !== (int)$w['skor_lama']) {
|
||||
$stmtRiwayat->execute([
|
||||
(int)$w['id'],
|
||||
$calc['skor_total'],
|
||||
$calc['status'],
|
||||
(float)$w['penghasilan_bulanan'],
|
||||
]);
|
||||
}
|
||||
$updated++;
|
||||
}
|
||||
return $updated;
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────
|
||||
ROUTING
|
||||
───────────────────────────────────────────── */
|
||||
try {
|
||||
// Hanya Admin dan Petugas Lapangan yang memiliki akses ke modul kemandirian warga
|
||||
requireAuth(['admin', 'petugas']);
|
||||
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
$action = $_GET['action'] ?? 'list';
|
||||
|
||||
if ($method === 'GET') {
|
||||
|
||||
if ($action === 'list') {
|
||||
// Recalculate dulu
|
||||
updateKemandirianBatch($pdo, true);
|
||||
|
||||
$statusFilter = $_GET['status'] ?? null;
|
||||
$minSkor = (int)($_GET['min_skor'] ?? 0);
|
||||
$limit = (int)($_GET['limit'] ?? 50);
|
||||
$thresholdSk = getKonfig($pdo, 'threshold_skor_mandiri', 70);
|
||||
|
||||
$where = [];
|
||||
if ($statusFilter) $where[] = "rw.status_kemandirian = " . $pdo->quote($statusFilter);
|
||||
if ($minSkor > 0) $where[] = "rw.skor_kemandirian >= $minSkor";
|
||||
$whereStr = $where ? "WHERE " . implode(" AND ", $where) : "";
|
||||
|
||||
$rows = $pdo->query("
|
||||
SELECT rw.id, rw.nama_kepala_keluarga, rw.no_kk,
|
||||
rw.penghasilan_bulanan, rw.jumlah_tanggungan,
|
||||
rw.status_pekerjaan, rw.kategori_kemiskinan,
|
||||
rw.skor_kemandirian, rw.status_kemandirian,
|
||||
rw.catatan_kemandirian, rw.tanggal_exit_plan,
|
||||
rw.rekomendasi_bantuan, rw.status_bantuan,
|
||||
rw.kelurahan, rw.kecamatan,
|
||||
rw.latitude, rw.longitude
|
||||
FROM rumah_warga rw
|
||||
$whereStr
|
||||
ORDER BY rw.skor_kemandirian DESC
|
||||
LIMIT $limit
|
||||
")->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// Tambah detail skor per row
|
||||
$thres = getKonfig($pdo, 'threshold_penghasilan_mandiri', 3500000);
|
||||
$bPeng = getKonfig($pdo, 'bobot_penghasilan', 40);
|
||||
$bKerja = getKonfig($pdo, 'bobot_status_kerja', 35);
|
||||
$bTangg = getKonfig($pdo, 'bobot_tanggungan', 25);
|
||||
$stmtA = $pdo->prepare("SELECT * FROM anggota_keluarga WHERE rumah_warga_id = ?");
|
||||
|
||||
foreach ($rows as &$r) {
|
||||
$stmtA->execute([(int)$r['id']]);
|
||||
$anggota = $stmtA->fetchAll(PDO::FETCH_ASSOC);
|
||||
$r['detail_skor'] = hitungSkorKemandirian($pdo, $r, $anggota, $thres, $bPeng, $bKerja, $bTangg);
|
||||
}
|
||||
|
||||
$summary = $pdo->query("
|
||||
SELECT status_kemandirian, COUNT(*) as jumlah, ROUND(AVG(skor_kemandirian)) as avg_skor
|
||||
FROM rumah_warga GROUP BY status_kemandirian
|
||||
")->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$siapExit = $pdo->query("
|
||||
SELECT COUNT(*) FROM rumah_warga
|
||||
WHERE skor_kemandirian >= $thresholdSk
|
||||
AND status_bantuan != 'sudah_terima'
|
||||
")->fetchColumn();
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'data' => $rows,
|
||||
'summary' => $summary,
|
||||
'siap_exit' => (int)$siapExit,
|
||||
]);
|
||||
|
||||
} elseif ($action === 'detail') {
|
||||
$wargaId = (int)($_GET['id'] ?? 0);
|
||||
|
||||
$warga = $pdo->prepare("SELECT * FROM rumah_warga WHERE id = ?");
|
||||
$warga->execute([$wargaId]);
|
||||
$w = $warga->fetch(PDO::FETCH_ASSOC);
|
||||
if (!$w) { echo json_encode(['success' => false, 'message' => 'Warga tidak ditemukan']); exit; }
|
||||
|
||||
$stmtA = $pdo->prepare("SELECT * FROM anggota_keluarga WHERE rumah_warga_id = ?");
|
||||
$stmtA->execute([$wargaId]);
|
||||
$anggota = $stmtA->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$thres = getKonfig($pdo, 'threshold_penghasilan_mandiri', 3500000);
|
||||
$bPeng = getKonfig($pdo, 'bobot_penghasilan', 40);
|
||||
$bKerja = getKonfig($pdo, 'bobot_status_kerja', 35);
|
||||
$bTangg = getKonfig($pdo, 'bobot_tanggungan', 25);
|
||||
|
||||
$skor = hitungSkorKemandirian($pdo, $w, $anggota, $thres, $bPeng, $bKerja, $bTangg);
|
||||
|
||||
// Riwayat
|
||||
$riwayat = $pdo->prepare("
|
||||
SELECT * FROM riwayat_kemandirian WHERE rumah_warga_id = ?
|
||||
ORDER BY tanggal DESC LIMIT 12
|
||||
");
|
||||
$riwayat->execute([$wargaId]);
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'data' => $w,
|
||||
'skor' => $skor,
|
||||
'anggota' => $anggota,
|
||||
'riwayat' => $riwayat->fetchAll(PDO::FETCH_ASSOC),
|
||||
]);
|
||||
|
||||
} elseif ($action === 'statistik') {
|
||||
updateKemandirianBatch($pdo, false);
|
||||
$thresholdSk = getKonfig($pdo, 'threshold_skor_mandiri', 70);
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'distribusi_status' => $pdo->query("
|
||||
SELECT status_kemandirian as status, COUNT(*) as jumlah,
|
||||
ROUND(AVG(skor_kemandirian)) as avg_skor,
|
||||
ROUND(AVG(penghasilan_bulanan)) as avg_penghasilan
|
||||
FROM rumah_warga GROUP BY status_kemandirian ORDER BY avg_skor DESC
|
||||
")->fetchAll(PDO::FETCH_ASSOC),
|
||||
'siap_exit_plan' => (int)$pdo->query("
|
||||
SELECT COUNT(*) FROM rumah_warga WHERE skor_kemandirian >= $thresholdSk
|
||||
")->fetchColumn(),
|
||||
'sudah_punya_exit_plan' => (int)$pdo->query("
|
||||
SELECT COUNT(*) FROM rumah_warga WHERE tanggal_exit_plan IS NOT NULL
|
||||
")->fetchColumn(),
|
||||
'avg_skor_global' => (float)$pdo->query("
|
||||
SELECT ROUND(AVG(skor_kemandirian),1) FROM rumah_warga
|
||||
")->fetchColumn(),
|
||||
'trend_bulan_ini' => $pdo->query("
|
||||
SELECT DATE(tanggal) as tgl, ROUND(AVG(skor)) as avg_skor, COUNT(*) as jumlah
|
||||
FROM riwayat_kemandirian
|
||||
WHERE tanggal >= DATE_SUB(NOW(), INTERVAL 30 DAY)
|
||||
GROUP BY DATE(tanggal) ORDER BY tgl
|
||||
")->fetchAll(PDO::FETCH_ASSOC),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
} elseif ($method === 'POST') {
|
||||
// Catat perkembangan/catatan manual dari petugas
|
||||
$body = json_decode(file_get_contents('php://input'), true);
|
||||
$wargaId = (int)($body['warga_id'] ?? 0);
|
||||
|
||||
if (!$wargaId) { echo json_encode(['success' => false, 'message' => 'warga_id wajib']); exit; }
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
UPDATE rumah_warga
|
||||
SET catatan_kemandirian = ?, tanggal_exit_plan = ?, updated_at = NOW()
|
||||
WHERE id = ?
|
||||
");
|
||||
$stmt->execute([
|
||||
$body['catatan'] ?? null,
|
||||
$body['tanggal_exit_plan'] ?? null,
|
||||
$wargaId,
|
||||
]);
|
||||
|
||||
// Hitung ulang dan simpan ke riwayat
|
||||
$warga = $pdo->prepare("SELECT * FROM rumah_warga WHERE id = ?");
|
||||
$warga->execute([$wargaId]);
|
||||
$w = $warga->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
$stmtA = $pdo->prepare("SELECT * FROM anggota_keluarga WHERE rumah_warga_id = ?");
|
||||
$stmtA->execute([$wargaId]);
|
||||
$anggota = $stmtA->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$thres = getKonfig($pdo, 'threshold_penghasilan_mandiri', 3500000);
|
||||
$bPeng = getKonfig($pdo, 'bobot_penghasilan', 40);
|
||||
$bKerja = getKonfig($pdo, 'bobot_status_kerja', 35);
|
||||
$bTangg = getKonfig($pdo, 'bobot_tanggungan', 25);
|
||||
$calc = hitungSkorKemandirian($pdo, $w, $anggota, $thres, $bPeng, $bKerja, $bTangg);
|
||||
|
||||
$pdo->prepare("
|
||||
INSERT INTO riwayat_kemandirian
|
||||
(rumah_warga_id, tanggal, skor, status, penghasilan_saat_ini, catatan)
|
||||
VALUES (?, CURDATE(), ?, ?, ?, ?)
|
||||
")->execute([
|
||||
$wargaId, $calc['skor_total'], $calc['status'],
|
||||
(float)$w['penghasilan_bulanan'], $body['catatan'] ?? null
|
||||
]);
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => 'Catatan kemandirian berhasil disimpan',
|
||||
'skor' => $calc,
|
||||
]);
|
||||
|
||||
} elseif ($method === 'PUT') {
|
||||
// Update status kemandirian manual oleh admin (override sistem)
|
||||
$body = json_decode(file_get_contents('php://input'), true);
|
||||
$wargaId = (int)($body['warga_id'] ?? 0);
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
UPDATE rumah_warga
|
||||
SET status_kemandirian = ?,
|
||||
catatan_kemandirian = ?,
|
||||
tanggal_exit_plan = ?,
|
||||
updated_at = NOW()
|
||||
WHERE id = ?
|
||||
");
|
||||
$stmt->execute([
|
||||
$body['status_kemandirian'] ?? null,
|
||||
$body['catatan'] ?? null,
|
||||
$body['tanggal_exit_plan'] ?? null,
|
||||
$wargaId,
|
||||
]);
|
||||
|
||||
echo json_encode(['success' => true, 'message' => 'Status kemandirian diperbarui']);
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
<?php
|
||||
// api/kontribusi.php
|
||||
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, Authorization');
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
||||
http_response_code(200);
|
||||
exit;
|
||||
}
|
||||
|
||||
require_once '../config/db.php';
|
||||
require_once 'middleware.php';
|
||||
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
|
||||
try {
|
||||
switch ($method) {
|
||||
case 'GET':
|
||||
// Wajib login untuk melihat riwayat kontribusi
|
||||
$currentUser = requireAuth(['admin', 'koordinator', 'kontributor']);
|
||||
|
||||
if ($currentUser['role'] === 'kontributor') {
|
||||
// Hanya riwayat kontribusi milik sendiri
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT ko.id, ko.user_id, ko.kebutuhan_id, ko.status, ko.created_at, ko.updated_at,
|
||||
ko.jumlah,
|
||||
ko.jenis_kontribusi AS tipe_kontribusi,
|
||||
ko.detail_barang_jasa AS catatan,
|
||||
k.jenis_bantuan,
|
||||
ri.nama AS nama_rumah_ibadah,
|
||||
ri.nama AS nama_ibadah
|
||||
FROM kontribusi ko
|
||||
JOIN kebutuhan k ON ko.kebutuhan_id = k.id
|
||||
JOIN rumah_ibadah ri ON k.rumah_ibadah_id = ri.id
|
||||
WHERE ko.user_id = ?
|
||||
ORDER BY ko.created_at DESC
|
||||
");
|
||||
$stmt->execute([$currentUser['id']]);
|
||||
$rows = $stmt->fetchAll();
|
||||
} else {
|
||||
// Admin atau Koordinator melihat kontribusi
|
||||
$where = "";
|
||||
$params = [];
|
||||
if ($currentUser['role'] === 'koordinator') {
|
||||
$where = "WHERE k.rumah_ibadah_id = ?";
|
||||
$params[] = $currentUser['rumah_ibadah_id'];
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT ko.id, ko.user_id, ko.kebutuhan_id, ko.status, ko.created_at, ko.updated_at,
|
||||
ko.jumlah,
|
||||
ko.jenis_kontribusi AS tipe_kontribusi,
|
||||
ko.detail_barang_jasa AS catatan,
|
||||
k.jenis_bantuan,
|
||||
ri.nama AS nama_rumah_ibadah,
|
||||
ri.nama AS nama_ibadah,
|
||||
u.username AS nama_kontributor,
|
||||
u.email AS kontak_kontributor
|
||||
FROM kontribusi ko
|
||||
JOIN kebutuhan k ON ko.kebutuhan_id = k.id
|
||||
JOIN rumah_ibadah ri ON k.rumah_ibadah_id = ri.id
|
||||
JOIN users u ON ko.user_id = u.id
|
||||
$where
|
||||
ORDER BY ko.created_at DESC
|
||||
");
|
||||
$stmt->execute($params);
|
||||
$rows = $stmt->fetchAll();
|
||||
}
|
||||
|
||||
echo json_encode(['success' => true, 'data' => $rows]);
|
||||
break;
|
||||
|
||||
case 'POST':
|
||||
// Wajib login sebagai Kontributor untuk berdonasi
|
||||
$currentUser = requireAuth(['kontributor']);
|
||||
$body = json_decode(file_get_contents('php://input'), true) ?: $_POST;
|
||||
|
||||
$kebutuhanId = (int)($body['kebutuhan_id'] ?? 0);
|
||||
$jenisKontribusi = trim($body['jenis_kontribusi'] ?? $body['tipe_kontribusi'] ?? ''); // uang/barang/jasa
|
||||
$jumlahDonasi = (float)($body['jumlah'] ?? 0);
|
||||
$detail = trim($body['detail_barang_jasa'] ?? $body['catatan'] ?? '');
|
||||
|
||||
if (!$kebutuhanId || !$jenisKontribusi || $jumlahDonasi <= 0) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'message' => 'Input data donasi tidak lengkap/valid.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
// VALIDASI SISA KEBUTUHAN
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT k.jumlah_dibutuhkan, k.kategori,
|
||||
COALESCE(SUM(CASE WHEN ko.status = 'confirmed' THEN ko.jumlah ELSE 0 END), 0) AS jumlah_terkumpul
|
||||
FROM kebutuhan k
|
||||
LEFT JOIN kontribusi ko ON k.id = ko.kebutuhan_id
|
||||
WHERE k.id = ?
|
||||
GROUP BY k.id
|
||||
");
|
||||
$stmt->execute([$kebutuhanId]);
|
||||
$kebutuhan = $stmt->fetch();
|
||||
|
||||
if (!$kebutuhan) {
|
||||
http_response_code(404);
|
||||
echo json_encode(['success' => false, 'message' => 'Data kebutuhan bantuan tidak ditemukan.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Hitung sisa kebutuhan secara dinamis
|
||||
$sisaKebutuhan = (float)$kebutuhan['jumlah_dibutuhkan'] - (float)$kebutuhan['jumlah_terkumpul'];
|
||||
|
||||
if ($jumlahDonasi > $sisaKebutuhan) {
|
||||
http_response_code(400);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => sprintf(
|
||||
'Donasi ditolak! Jumlah kontribusi (%.2f) melebihi sisa kebutuhan bantuan saat ini (%.2f).',
|
||||
$jumlahDonasi,
|
||||
$sisaKebutuhan
|
||||
)
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Simpan kontribusi (default status = pending)
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO kontribusi (user_id, kebutuhan_id, jenis_kontribusi, jumlah, detail_barang_jasa, status)
|
||||
VALUES (?, ?, ?, ?, ?, 'pending')
|
||||
");
|
||||
$stmt->execute([$currentUser['id'], $kebutuhanId, $jenisKontribusi, $jumlahDonasi, $detail]);
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => 'Terima kasih atas kontribusi Anda! Status kontribusi saat ini menunggu verifikasi (pending).',
|
||||
'id' => $pdo->lastInsertId()
|
||||
]);
|
||||
break;
|
||||
|
||||
case 'PUT':
|
||||
// Konfirmasi kontribusi (pending -> confirmed)
|
||||
// Hanya Admin atau Koordinator terkait
|
||||
$currentUser = requireAuth(['admin', 'koordinator']);
|
||||
$body = json_decode(file_get_contents('php://input'), true) ?: $_POST;
|
||||
$kontribusiId = (int)($body['id'] ?? 0);
|
||||
$newStatus = trim($body['status'] ?? 'pending'); // pending/confirmed
|
||||
|
||||
if (!$kontribusiId || !in_array($newStatus, ['pending', 'confirmed'])) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'message' => 'Parameter input status tidak valid.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Ambil detail kontribusi
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT ko.jumlah, ko.status AS status_sebelumnya, ko.kebutuhan_id, k.rumah_ibadah_id
|
||||
FROM kontribusi ko
|
||||
JOIN kebutuhan k ON ko.kebutuhan_id = k.id
|
||||
WHERE ko.id = ?
|
||||
");
|
||||
$stmt->execute([$kontribusiId]);
|
||||
$kontribusi = $stmt->fetch();
|
||||
|
||||
if (!$kontribusi) {
|
||||
http_response_code(404);
|
||||
echo json_encode(['success' => false, 'message' => 'Data kontribusi tidak ditemukan.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Batasan wilayah untuk Koordinator
|
||||
if ($currentUser['role'] === 'koordinator' && $currentUser['rumah_ibadah_id'] != $kontribusi['rumah_ibadah_id']) {
|
||||
http_response_code(403);
|
||||
echo json_encode(['success' => false, 'message' => 'Koordinator hanya berhak mengonfirmasi donasi untuk rumah ibadahnya sendiri.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Jika status dirubah ke 'confirmed' dari 'pending'
|
||||
if ($newStatus === 'confirmed' && $kontribusi['status_sebelumnya'] !== 'confirmed') {
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT k.jumlah_dibutuhkan,
|
||||
COALESCE(SUM(CASE WHEN ko.status = 'confirmed' THEN ko.jumlah ELSE 0 END), 0) AS jumlah_terkumpul
|
||||
FROM kebutuhan k
|
||||
LEFT JOIN kontribusi ko ON k.id = ko.kebutuhan_id
|
||||
WHERE k.id = ?
|
||||
GROUP BY k.id
|
||||
");
|
||||
$stmt->execute([$kontribusi['kebutuhan_id']]);
|
||||
$kebutuhan = $stmt->fetch();
|
||||
|
||||
$sisaKebutuhan = (float)$kebutuhan['jumlah_dibutuhkan'] - (float)$kebutuhan['jumlah_terkumpul'];
|
||||
|
||||
if ($kontribusi['jumlah'] > $sisaKebutuhan) {
|
||||
http_response_code(400);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => 'Gagal konfirmasi! Total confirmed donasi melebihi kapasitas kebutuhan.'
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("UPDATE kontribusi SET status = ? WHERE id = ?");
|
||||
$stmt->execute([$newStatus, $kontribusiId]);
|
||||
|
||||
echo json_encode(['success' => true, 'message' => 'Status kontribusi berhasil diperbarui ke ' . $newStatus]);
|
||||
break;
|
||||
|
||||
default:
|
||||
http_response_code(405);
|
||||
echo json_encode(['error' => 'Method not allowed']);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
||||
}
|
||||
@@ -0,0 +1,305 @@
|
||||
<?php
|
||||
// api/laporan.php — Pelaporan Warga oleh Kontributor & Koordinator
|
||||
header('Content-Type: application/json');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
|
||||
header('Access-Control-Allow-Headers: Content-Type');
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { http_response_code(204); exit; }
|
||||
|
||||
require_once '../config/db.php';
|
||||
require_once 'middleware.php';
|
||||
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
if ($method === 'POST' && isset($_POST['_method'])) {
|
||||
$method = strtoupper($_POST['_method']);
|
||||
}
|
||||
$action = $_GET['action'] ?? '';
|
||||
|
||||
try {
|
||||
switch ($method) {
|
||||
|
||||
// ── GET: List laporan ──────────────────────────────────────────────────
|
||||
case 'GET':
|
||||
$currentUser = requireAuth(['admin', 'petugas', 'koordinator', 'kontributor']);
|
||||
$role = $currentUser['role'];
|
||||
$userId = (int)$currentUser['id'];
|
||||
|
||||
// Jika ada action=stats untuk admin dashboard
|
||||
if ($action === 'stats') {
|
||||
if (!in_array($role, ['admin', 'petugas'])) {
|
||||
http_response_code(403);
|
||||
echo json_encode(['success' => false, 'message' => 'Forbidden']);
|
||||
exit;
|
||||
}
|
||||
$stmtStats = $pdo->query("
|
||||
SELECT status, COUNT(*) as jumlah
|
||||
FROM laporan_warga
|
||||
GROUP BY status
|
||||
");
|
||||
$stats = [];
|
||||
while ($r = $stmtStats->fetch(PDO::FETCH_ASSOC)) {
|
||||
$stats[$r['status']] = (int)$r['jumlah'];
|
||||
}
|
||||
$stmtTotal = $pdo->query("SELECT COUNT(*) FROM laporan_warga");
|
||||
echo json_encode(['success' => true, 'stats' => $stats, 'total' => (int)$stmtTotal->fetchColumn()]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Filter berdasarkan role
|
||||
$where = [];
|
||||
$params = [];
|
||||
|
||||
// Jika ada action=get_petugas
|
||||
if ($action === 'get_petugas') {
|
||||
if (!in_array($role, ['admin', 'petugas'])) {
|
||||
http_response_code(403); echo json_encode(['success' => false, 'message' => 'Forbidden']); exit;
|
||||
}
|
||||
$stmt = $pdo->query("SELECT username FROM users WHERE role = 'petugas' ORDER BY username ASC");
|
||||
$petugas = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
||||
echo json_encode(['success' => true, 'petugas' => $petugas]);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($role === 'kontributor') {
|
||||
// Kontributor hanya lihat laporan milik sendiri
|
||||
$where[] = 'lw.pelapor_id = ?';
|
||||
$params[] = $userId;
|
||||
} elseif ($role === 'koordinator') {
|
||||
// Koordinator lihat laporan dari wilayahnya + laporan milik sendiri
|
||||
$where[] = '(lw.pelapor_id = ? OR lw.koordinator_ibadah_id = ?)';
|
||||
$params[] = $userId;
|
||||
$params[] = !empty($currentUser['rumah_ibadah_id']) ? (int)$currentUser['rumah_ibadah_id'] : 0;
|
||||
} elseif ($role === 'petugas') {
|
||||
// Petugas hanya lihat laporan yang ditugaskan ke mereka
|
||||
$where[] = 'lw.ditugaskan_ke = ?';
|
||||
$params[] = $currentUser['username'];
|
||||
}
|
||||
// Admin: lihat semua
|
||||
|
||||
// Filter status
|
||||
if (!empty($_GET['status'])) {
|
||||
$where[] = 'lw.status = ?';
|
||||
$params[] = $_GET['status'];
|
||||
}
|
||||
|
||||
// Filter prioritas
|
||||
if (!empty($_GET['prioritas'])) {
|
||||
$where[] = 'lw.prioritas = ?';
|
||||
$params[] = $_GET['prioritas'];
|
||||
}
|
||||
|
||||
$whereSql = $where ? 'WHERE ' . implode(' AND ', $where) : '';
|
||||
|
||||
$sql = "
|
||||
SELECT
|
||||
lw.*,
|
||||
rw.nama_kepala_keluarga,
|
||||
rw.kelurahan,
|
||||
rw.kecamatan,
|
||||
rw.kategori_kemiskinan,
|
||||
rw.status_bantuan,
|
||||
u.username AS pelapor_nama,
|
||||
u.role AS pelapor_role
|
||||
FROM laporan_warga lw
|
||||
JOIN rumah_warga rw ON rw.id = lw.rumah_warga_id
|
||||
JOIN users u ON u.id = lw.pelapor_id
|
||||
$whereSql
|
||||
ORDER BY lw.created_at DESC
|
||||
LIMIT 100
|
||||
";
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
$data = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// Summary counts for filter badges
|
||||
$stmtSummary = $pdo->query("SELECT status, COUNT(*) as n FROM laporan_warga GROUP BY status");
|
||||
$summary = [];
|
||||
while ($r = $stmtSummary->fetch(PDO::FETCH_ASSOC)) {
|
||||
$summary[$r['status']] = (int)$r['n'];
|
||||
}
|
||||
|
||||
$response = ['success' => true, 'data' => $data, 'summary' => $summary, 'total' => count($data)];
|
||||
|
||||
// Sertakan list petugas jika user admin
|
||||
if ($role === 'admin' || $role === 'petugas') {
|
||||
$stmtP = $pdo->query("SELECT username FROM users WHERE role = 'petugas' ORDER BY username ASC");
|
||||
$response['petugas'] = $stmtP->fetchAll(PDO::FETCH_COLUMN);
|
||||
}
|
||||
|
||||
echo json_encode($response);
|
||||
break;
|
||||
|
||||
// ── POST: Buat laporan baru ──────────────────────────────────────────
|
||||
case 'POST':
|
||||
$currentUser = requireAuth(['koordinator', 'kontributor']);
|
||||
|
||||
$input = json_decode(file_get_contents('php://input'), true) ?: $_POST;
|
||||
|
||||
$rumahWargaId = (int)($input['rumah_warga_id'] ?? 0);
|
||||
$jenisLaporan = trim($input['jenis_laporan'] ?? '');
|
||||
$deskripsi = trim($input['deskripsi'] ?? '');
|
||||
|
||||
// Penentuan prioritas otomatis berdasarkan jenis laporan
|
||||
$prioritasMap = [
|
||||
'warga_meninggal' => 'tinggi',
|
||||
'warga_pindah' => 'tinggi',
|
||||
'warga_mampu' => 'tinggi',
|
||||
'data_ganda' => 'sedang',
|
||||
'data_tidak_sesuai'=> 'sedang',
|
||||
'lainnya' => 'rendah'
|
||||
];
|
||||
$prioritas = $prioritasMap[$jenisLaporan] ?? 'sedang';
|
||||
|
||||
if (!$rumahWargaId || !$jenisLaporan || !$deskripsi) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'message' => 'Data tidak lengkap: rumah_warga_id, jenis_laporan, dan deskripsi wajib diisi.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Cek warga ada
|
||||
$stmtCek = $pdo->prepare("SELECT id, nama_kepala_keluarga FROM rumah_warga WHERE id = ?");
|
||||
$stmtCek->execute([$rumahWargaId]);
|
||||
$warga = $stmtCek->fetch();
|
||||
if (!$warga) {
|
||||
http_response_code(404);
|
||||
echo json_encode(['success' => false, 'message' => 'Data warga tidak ditemukan.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Cek apakah laporan duplikat dalam 7 hari terakhir
|
||||
$stmtDupCheck = $pdo->prepare("
|
||||
SELECT id FROM laporan_warga
|
||||
WHERE rumah_warga_id = ? AND pelapor_id = ?
|
||||
AND status NOT IN ('selesai', 'ditolak')
|
||||
AND created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)
|
||||
");
|
||||
$stmtDupCheck->execute([$rumahWargaId, $currentUser['id']]);
|
||||
if ($stmtDupCheck->fetch()) {
|
||||
http_response_code(409);
|
||||
echo json_encode(['success' => false, 'message' => 'Anda sudah membuat laporan untuk warga ini dalam 7 hari terakhir. Harap tunggu laporan sebelumnya diproses.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$koordinatorIbadahId = !empty($currentUser['rumah_ibadah_id']) ? (int)$currentUser['rumah_ibadah_id'] : null;
|
||||
|
||||
$stmtInsert = $pdo->prepare("
|
||||
INSERT INTO laporan_warga
|
||||
(rumah_warga_id, pelapor_id, koordinator_ibadah_id, jenis_laporan, deskripsi, prioritas, status)
|
||||
VALUES (?, ?, ?, ?, ?, ?, 'pending')
|
||||
");
|
||||
$stmtInsert->execute([
|
||||
$rumahWargaId,
|
||||
$currentUser['id'],
|
||||
$koordinatorIbadahId,
|
||||
$jenisLaporan,
|
||||
$deskripsi,
|
||||
$prioritas,
|
||||
]);
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => 'Laporan berhasil dikirim. Admin akan meninjau laporan Anda segera.',
|
||||
'id' => $pdo->lastInsertId()
|
||||
]);
|
||||
break;
|
||||
|
||||
case 'PUT':
|
||||
$currentUser = requireAuth(['admin', 'petugas']);
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
if (!$input) $input = $_POST;
|
||||
|
||||
$id = (int)($input['id'] ?? 0);
|
||||
$status = $input['status'] ?? '';
|
||||
$catatanAdmin = trim($input['catatan_admin'] ?? '');
|
||||
$ditugaskanKe = trim($input['ditugaskan_ke'] ?? '');
|
||||
|
||||
// Ambil dari old state
|
||||
$stmtDok = $pdo->prepare("SELECT dokumentasi FROM laporan_warga WHERE id = ?");
|
||||
$stmtDok->execute([$id]);
|
||||
$oldDok = $stmtDok->fetchColumn();
|
||||
|
||||
$dokumentasi = trim($input['dokumentasi'] ?? $oldDok ?? '');
|
||||
|
||||
// Handle file upload
|
||||
if (isset($_FILES['dokumentasi_file']) && $_FILES['dokumentasi_file']['error'] === UPLOAD_ERR_OK) {
|
||||
$uploadDir = '../uploads/dokumentasi/';
|
||||
if (!is_dir($uploadDir)) {
|
||||
mkdir($uploadDir, 0755, true);
|
||||
}
|
||||
$ext = strtolower(pathinfo($_FILES['dokumentasi_file']['name'], PATHINFO_EXTENSION));
|
||||
$allowedExts = ['jpg', 'jpeg', 'png', 'pdf'];
|
||||
if (in_array($ext, $allowedExts)) {
|
||||
$filename = 'dok_' . $id . '_' . time() . '.' . $ext;
|
||||
$dest = $uploadDir . $filename;
|
||||
if (move_uploaded_file($_FILES['dokumentasi_file']['tmp_name'], $dest)) {
|
||||
$dokumentasi = 'uploads/dokumentasi/' . $filename;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$allowedStatus = ['pending', 'ditinjau', 'diproses', 'selesai', 'ditolak'];
|
||||
if (!$id || !in_array($status, $allowedStatus)) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'message' => 'ID dan status valid wajib diisi.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Jika petugas, pastikan hanya bisa update dokumentasi, status, catatan. (ditugaskan_ke biarkan tetap)
|
||||
if ($currentUser['role'] === 'petugas') {
|
||||
$stmt = $pdo->prepare("
|
||||
UPDATE laporan_warga
|
||||
SET status = ?, catatan_admin = ?, dokumentasi = ?, updated_at = NOW()
|
||||
WHERE id = ? AND ditugaskan_ke = ?
|
||||
");
|
||||
$stmt->execute([$status, $catatanAdmin, $dokumentasi, $id, $currentUser['username']]);
|
||||
} else {
|
||||
// Admin bisa update semua
|
||||
$stmt = $pdo->prepare("
|
||||
UPDATE laporan_warga
|
||||
SET status = ?, catatan_admin = ?, ditugaskan_ke = ?, dokumentasi = ?, updated_at = NOW()
|
||||
WHERE id = ?
|
||||
");
|
||||
$stmt->execute([$status, $catatanAdmin, $ditugaskanKe ?: null, $dokumentasi, $id]);
|
||||
}
|
||||
|
||||
$statusLabel = [
|
||||
'pending' => 'Menunggu',
|
||||
'ditinjau' => 'Sedang Ditinjau',
|
||||
'diproses' => 'Sedang Diproses',
|
||||
'selesai' => 'Selesai',
|
||||
'ditolak' => 'Ditolak',
|
||||
];
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => 'Laporan berhasil diperbarui ke status: ' . ($statusLabel[$status] ?? $status)
|
||||
]);
|
||||
break;
|
||||
|
||||
// ── DELETE: Hapus laporan (admin only) ──────────────────────────────
|
||||
case 'DELETE':
|
||||
$currentUser = requireAuth(['admin']);
|
||||
$id = (int)($_GET['id'] ?? 0);
|
||||
if (!$id) {
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
$id = (int)($input['id'] ?? 0);
|
||||
}
|
||||
if (!$id) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'message' => 'ID laporan wajib diisi.']);
|
||||
exit;
|
||||
}
|
||||
$stmt = $pdo->prepare("DELETE FROM laporan_warga WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
echo json_encode(['success' => true, 'message' => 'Laporan berhasil dihapus.']);
|
||||
break;
|
||||
|
||||
default:
|
||||
http_response_code(405);
|
||||
echo json_encode(['error' => 'Method not allowed']);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['success' => false, 'message' => 'Kesalahan sistem: ' . $e->getMessage()]);
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
// api/middleware.php
|
||||
|
||||
// Memulai session jika belum dimulai
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
// Pastikan session cookie aman dan hanya terakses lewat HTTP (mencegah XSS hijack)
|
||||
ini_set('session.cookie_httponly', 1);
|
||||
session_start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Middleware untuk memvalidasi user yang login dan mencocokkan perannya (RBAC).
|
||||
* Jika user belum login, akan mengembalikan 401 Unauthorized.
|
||||
* Jika role user tidak ada dalam daftar role yang diizinkan, mengembalikan 403 Forbidden.
|
||||
*
|
||||
* @param array $allowedRoles Daftar role yang diizinkan (contoh: ['admin', 'petugas'])
|
||||
* @return array Data user yang saat ini sedang login dari Session
|
||||
*/
|
||||
function requireAuth(array $allowedRoles = []): array
|
||||
{
|
||||
// Cek apakah ada data user di Session
|
||||
if (!isset($_SESSION['user']) || !is_array($_SESSION['user'])) {
|
||||
http_response_code(401);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => 'Unauthorized: Silakan login terlebih dahulu untuk mengakses fitur ini.'
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$user = $_SESSION['user'];
|
||||
|
||||
// Cek kecocokan role (RBAC) jika list allowedRoles tidak kosong
|
||||
if (!empty($allowedRoles) && !in_array($user['role'], $allowedRoles)) {
|
||||
http_response_code(403);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => 'Forbidden: Peran Anda (' . $user['role'] . ') tidak memiliki hak akses ke fitur ini.'
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Middleware Opsional untuk mendukung Guest Mode.
|
||||
* Jika user login, akan mengembalikan data user.
|
||||
* Jika belum login, akan mengembalikan null (sebagai Guest) dan proses tetap berlanjut.
|
||||
*
|
||||
* @return array|null Data user jika login, atau null jika guest
|
||||
*/
|
||||
function optionalAuth(): ?array
|
||||
{
|
||||
if (isset($_SESSION['user']) && is_array($_SESSION['user'])) {
|
||||
return $_SESSION['user'];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
require_once '../config/db.php';
|
||||
|
||||
$sql = "
|
||||
CREATE TABLE IF NOT EXISTS `laporan_warga` (
|
||||
`id` INT(11) NOT NULL AUTO_INCREMENT,
|
||||
`rumah_warga_id` INT(11) NOT NULL,
|
||||
`pelapor_id` INT(11) NOT NULL,
|
||||
`koordinator_ibadah_id` INT(11) NULL DEFAULT NULL,
|
||||
`jenis_laporan` ENUM(
|
||||
'data_tidak_sesuai',
|
||||
'warga_mampu',
|
||||
'warga_pindah',
|
||||
'warga_meninggal',
|
||||
'data_ganda',
|
||||
'lainnya'
|
||||
) NOT NULL DEFAULT 'data_tidak_sesuai',
|
||||
`deskripsi` TEXT NOT NULL,
|
||||
`prioritas` ENUM('rendah', 'sedang', 'tinggi') NOT NULL DEFAULT 'sedang',
|
||||
`status` ENUM(
|
||||
'pending',
|
||||
'ditinjau',
|
||||
'diproses',
|
||||
'selesai',
|
||||
'ditolak'
|
||||
) NOT NULL DEFAULT 'pending',
|
||||
`catatan_admin` TEXT NULL DEFAULT NULL,
|
||||
`ditugaskan_ke` VARCHAR(150) NULL DEFAULT NULL,
|
||||
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP(),
|
||||
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP() ON UPDATE CURRENT_TIMESTAMP(),
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `fk_laporan_warga` (`rumah_warga_id`),
|
||||
KEY `fk_laporan_pelapor` (`pelapor_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
";
|
||||
|
||||
try {
|
||||
$pdo->exec($sql);
|
||||
echo "Tabel laporan_warga berhasil dibuat!";
|
||||
} catch (Exception $e) {
|
||||
echo "Error: " . $e->getMessage();
|
||||
}
|
||||
@@ -0,0 +1,401 @@
|
||||
<?php
|
||||
// api/redistribusi.php
|
||||
// Fitur 1: Redistribusi Bantuan antar Rumah Ibadah
|
||||
// Method: GET (analisis), POST (buat saran manual), PUT (update status), DELETE
|
||||
|
||||
header('Content-Type: application/json');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, 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: ambil konfigurasi dari DB
|
||||
───────────────────────────────────────────── */
|
||||
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 beban tiap rumah ibadah
|
||||
berdasarkan radius dan jumlah warga miskin
|
||||
───────────────────────────────────────────── */
|
||||
function hitungBebanIbadah(PDO $pdo, float $radius): array {
|
||||
// Ambil semua rumah ibadah
|
||||
$ibadahList = $pdo->query("SELECT * FROM rumah_ibadah ORDER BY id")->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// Ambil semua rumah warga dengan status relevan
|
||||
$wargaList = $pdo->query("
|
||||
SELECT id, latitude, longitude, kategori_kemiskinan,
|
||||
status_bantuan, rekomendasi_bantuan, jumlah_tanggungan,
|
||||
penghasilan_bulanan, status_kemandirian, skor_kemandirian,
|
||||
dikelola_oleh_ibadah_id
|
||||
FROM rumah_warga
|
||||
WHERE status_kemandirian NOT IN ('mandiri')
|
||||
AND kategori_kemiskinan IN ('sangat_miskin','miskin','rentan_miskin')
|
||||
")->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$result = [];
|
||||
|
||||
foreach ($ibadahList as $ibadah) {
|
||||
$lat1 = (float)$ibadah['latitude'];
|
||||
$lng1 = (float)$ibadah['longitude'];
|
||||
$rad = $ibadah['radius_primer'] ?? $radius;
|
||||
$kap = (int)($ibadah['kapasitas_kk'] ?? 20);
|
||||
|
||||
$wargaDalam = [];
|
||||
foreach ($wargaList as $warga) {
|
||||
$kelolaId = $warga['dikelola_oleh_ibadah_id'];
|
||||
if ($kelolaId !== null) {
|
||||
if ((int)$kelolaId === (int)$ibadah['id']) {
|
||||
// Explicitly assigned to this ibadah
|
||||
$d = haversine($lat1, $lng1, (float)$warga['latitude'], (float)$warga['longitude']);
|
||||
$wargaDalam[] = array_merge($warga, ['jarak_meter' => round($d)]);
|
||||
}
|
||||
} else {
|
||||
// Not assigned explicitly, check radius
|
||||
$d = haversine($lat1, $lng1, (float)$warga['latitude'], (float)$warga['longitude']);
|
||||
if ($d <= $rad) {
|
||||
$wargaDalam[] = array_merge($warga, ['jarak_meter' => round($d)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Hitung kategori
|
||||
$sangat = count(array_filter($wargaDalam, fn($w) => $w['kategori_kemiskinan'] === 'sangat_miskin'));
|
||||
$miskin = count(array_filter($wargaDalam, fn($w) => $w['kategori_kemiskinan'] === 'miskin'));
|
||||
$rentan = count(array_filter($wargaDalam, fn($w) => $w['kategori_kemiskinan'] === 'rentan_miskin'));
|
||||
$total = count($wargaDalam);
|
||||
|
||||
// Indeks beban: (sangat_miskin*3 + miskin*2 + rentan*1) / kapasitas
|
||||
// Skor 0-100, > 100 = overload
|
||||
$bobotBeban = ($sangat * 3 + $miskin * 2 + $rentan * 1);
|
||||
$maxBobot = $kap * 3; // kalau semua sangat miskin
|
||||
$pctBeban = $maxBobot > 0 ? round(min($bobotBeban / $maxBobot * 100, 200)) : 0;
|
||||
|
||||
// Rasio kemiskinan: berapa % warga dalam radius yang masuk kategori miskin
|
||||
$rasioMiskin = $total > 0 ? round(($sangat + $miskin) / $total * 100) : 0;
|
||||
|
||||
// Status beban
|
||||
$statusBeban = match(true) {
|
||||
$total === 0 => 'kosong',
|
||||
$total < $kap * 0.5 => 'ringan',
|
||||
$total <= $kap => 'normal',
|
||||
$total <= $kap * 1.3 => 'padat',
|
||||
default => 'overload',
|
||||
};
|
||||
|
||||
$result[$ibadah['id']] = [
|
||||
'ibadah' => $ibadah,
|
||||
'radius_dipakai' => $rad,
|
||||
'kapasitas' => $kap,
|
||||
'total_warga' => $total,
|
||||
'sangat_miskin' => $sangat,
|
||||
'miskin' => $miskin,
|
||||
'rentan_miskin' => $rentan,
|
||||
'rasio_miskin' => $rasioMiskin,
|
||||
'bobot_beban' => $bobotBeban,
|
||||
'pct_beban' => $pctBeban,
|
||||
'status_beban' => $statusBeban,
|
||||
'surplus_kapasitas' => max(0, $kap - $total),
|
||||
'defisit_kapasitas' => max(0, $total - $kap),
|
||||
'warga_list' => $wargaDalam,
|
||||
];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────
|
||||
CORE: Generate saran redistribusi otomatis
|
||||
───────────────────────────────────────────── */
|
||||
function generateSaranRedistribusi(array $bebanData): array {
|
||||
$saranList = [];
|
||||
|
||||
// Pisahkan ibadah surplus vs defisit
|
||||
$surplus = array_filter($bebanData, fn($d) => $d['status_beban'] === 'ringan' || $d['status_beban'] === 'kosong');
|
||||
$defisit = array_filter($bebanData, fn($d) => in_array($d['status_beban'], ['padat','overload']));
|
||||
|
||||
// Sort: defisit paling parah dulu, surplus paling longgar dulu
|
||||
uasort($surplus, fn($a,$b) => $a['pct_beban'] <=> $b['pct_beban']);
|
||||
uasort($defisit, fn($a,$b) => $b['pct_beban'] <=> $a['pct_beban']);
|
||||
|
||||
foreach ($defisit as $def) {
|
||||
foreach ($surplus as &$sur) {
|
||||
if ($sur['surplus_kapasitas'] <= 0) continue;
|
||||
|
||||
$ibadahDef = $def['ibadah'];
|
||||
$ibadahSur = $sur['ibadah'];
|
||||
|
||||
// Hitung jarak antar kedua ibadah
|
||||
$jarakKm = round(haversine(
|
||||
(float)$ibadahDef['latitude'], (float)$ibadahDef['longitude'],
|
||||
(float)$ibadahSur['latitude'], (float)$ibadahSur['longitude']
|
||||
) / 1000, 2);
|
||||
|
||||
// Hanya sarankan jika jarak tidak terlalu jauh (< 5km)
|
||||
if ($jarakKm > 5) continue;
|
||||
|
||||
$jumlahPindah = min($def['defisit_kapasitas'], $sur['surplus_kapasitas']);
|
||||
if ($jumlahPindah <= 0) continue;
|
||||
|
||||
// Estimasi dana: rata-rata penghasilan warga * faktor bantuan
|
||||
$estDana = $jumlahPindah * 200000; // estimasi Rp200rb/KK/bulan
|
||||
|
||||
// Tingkat prioritas
|
||||
$prioritas = match(true) {
|
||||
$def['rasio_miskin'] >= 80 => 'tinggi',
|
||||
$def['rasio_miskin'] >= 50 => 'sedang',
|
||||
default => 'rendah',
|
||||
};
|
||||
|
||||
$saranList[] = [
|
||||
'pengirim_id' => (int)$ibadahSur['id'],
|
||||
'pengirim_nama' => $ibadahSur['nama'],
|
||||
'penerima_id' => (int)$ibadahDef['id'],
|
||||
'penerima_nama' => $ibadahDef['nama'],
|
||||
'jumlah_kk' => $jumlahPindah,
|
||||
'estimasi_dana' => $estDana,
|
||||
'jarak_km' => $jarakKm,
|
||||
'prioritas' => $prioritas,
|
||||
'alasan' => sprintf(
|
||||
'%s memiliki surplus %d kapasitas (beban %d%%), sementara %s kelebihan %d KK (beban %d%%) dengan rasio kemiskinan %d%%.',
|
||||
$ibadahSur['nama'],
|
||||
$sur['surplus_kapasitas'],
|
||||
$sur['pct_beban'],
|
||||
$ibadahDef['nama'],
|
||||
$def['defisit_kapasitas'],
|
||||
$def['pct_beban'],
|
||||
$def['rasio_miskin']
|
||||
),
|
||||
];
|
||||
|
||||
// Kurangi surplus yang sudah dipakai
|
||||
$sur['surplus_kapasitas'] -= $jumlahPindah;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Sort hasil saran berdasarkan prioritas
|
||||
usort($saranList, function($a, $b) {
|
||||
$p = ['tinggi' => 0, 'sedang' => 1, 'rendah' => 2];
|
||||
return ($p[$a['prioritas']] ?? 9) <=> ($p[$b['prioritas']] ?? 9);
|
||||
});
|
||||
|
||||
return $saranList;
|
||||
}
|
||||
|
||||
function haversine(float $lat1, float $lng1, float $lat2, float $lng2): float {
|
||||
$R = 6371000;
|
||||
$dLat = deg2rad($lat2 - $lat1);
|
||||
$dLng = deg2rad($lng2 - $lng1);
|
||||
$a = sin($dLat/2)**2 + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * sin($dLng/2)**2;
|
||||
return $R * 2 * atan2(sqrt($a), sqrt(1-$a));
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────
|
||||
ROUTING
|
||||
───────────────────────────────────────────── */
|
||||
try {
|
||||
// Hanya Admin yang bisa mengakses fitur redistribusi bantuan / analisis spasial
|
||||
requireAuth(['admin']);
|
||||
|
||||
// Guard: PDO connection must be available
|
||||
if (!$pdo) {
|
||||
throw new RuntimeException('Koneksi database (PDO) tidak tersedia. Pastikan XAMPP MySQL aktif.');
|
||||
}
|
||||
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
|
||||
if ($method === 'GET') {
|
||||
$action = $_GET['action'] ?? 'analisis';
|
||||
|
||||
if ($action === 'analisis') {
|
||||
// Analisis beban semua rumah ibadah
|
||||
$radius = (float)($_GET['radius'] ?? getKonfig($pdo, 'kapasitas_default_ibadah') ?? 500);
|
||||
$beban = hitungBebanIbadah($pdo, $radius);
|
||||
|
||||
// Hapus warga_list dari response utama (terlalu besar)
|
||||
$ringkasan = array_map(function($d) {
|
||||
$r = $d;
|
||||
unset($r['warga_list']);
|
||||
return $r;
|
||||
}, $beban);
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'data' => array_values($ringkasan),
|
||||
'saran' => generateSaranRedistribusi($beban),
|
||||
'summary' => [
|
||||
'total_ibadah' => count($beban),
|
||||
'overload' => count(array_filter($beban, fn($d) => $d['status_beban'] === 'overload')),
|
||||
'padat' => count(array_filter($beban, fn($d) => $d['status_beban'] === 'padat')),
|
||||
'normal' => count(array_filter($beban, fn($d) => $d['status_beban'] === 'normal')),
|
||||
'ringan' => count(array_filter($beban, fn($d) => $d['status_beban'] === 'ringan')),
|
||||
'kosong' => count(array_filter($beban, fn($d) => $d['status_beban'] === 'kosong')),
|
||||
],
|
||||
]);
|
||||
|
||||
} elseif ($action === 'riwayat') {
|
||||
// Riwayat keputusan redistribusi
|
||||
$limit = (int)($_GET['limit'] ?? 20);
|
||||
$status = $_GET['status'] ?? null;
|
||||
$sql = "SELECT r.*,
|
||||
ip.nama as pengirim_nama, ip.jenis as pengirim_jenis,
|
||||
ir.nama as penerima_nama, ir.jenis as penerima_jenis
|
||||
FROM redistribusi_bantuan r
|
||||
JOIN rumah_ibadah ip ON r.ibadah_pengirim_id = ip.id
|
||||
JOIN rumah_ibadah ir ON r.ibadah_penerima_id = ir.id";
|
||||
if ($status) $sql .= " WHERE r.status = " . $pdo->quote($status);
|
||||
$sql .= " ORDER BY r.tanggal_saran DESC LIMIT $limit";
|
||||
$rows = $pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC);
|
||||
echo json_encode(['success' => true, 'data' => $rows]);
|
||||
|
||||
} elseif ($action === 'detail') {
|
||||
// Detail beban 1 ibadah
|
||||
$id = (int)($_GET['id'] ?? 0);
|
||||
$radius = (float)($_GET['radius'] ?? 500);
|
||||
$beban = hitungBebanIbadah($pdo, $radius);
|
||||
if (!isset($beban[$id])) {
|
||||
echo json_encode(['success' => false, 'message' => 'Rumah ibadah tidak ditemukan']);
|
||||
exit;
|
||||
}
|
||||
echo json_encode(['success' => true, 'data' => $beban[$id]]);
|
||||
}
|
||||
|
||||
} elseif ($method === 'POST') {
|
||||
// Simpan saran redistribusi (dari sistem atau manual admin)
|
||||
$body = json_decode(file_get_contents('php://input'), true);
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO redistribusi_bantuan
|
||||
(ibadah_pengirim_id, ibadah_penerima_id, jumlah_kk, estimasi_dana,
|
||||
status, catatan_sistem, catatan_admin, periode_bulan)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
");
|
||||
$stmt->execute([
|
||||
(int)$body['pengirim_id'],
|
||||
(int)$body['penerima_id'],
|
||||
(int)($body['jumlah_kk'] ?? 0),
|
||||
(float)($body['estimasi_dana'] ?? 0),
|
||||
$body['status'] ?? 'saran_sistem',
|
||||
$body['catatan_sistem'] ?? null,
|
||||
$body['catatan_admin'] ?? null,
|
||||
$body['periode_bulan'] ?? date('Y-m'),
|
||||
]);
|
||||
|
||||
$redistId = (int)$pdo->lastInsertId();
|
||||
|
||||
// Jika disetujui, alihkan warga dari penerima (defisit) ke pengirim (surplus)
|
||||
if (($body['status'] ?? '') === 'disetujui') {
|
||||
$penerimaId = (int)$body['penerima_id'];
|
||||
$pengirimId = (int)$body['pengirim_id'];
|
||||
$limit = (int)($body['jumlah_kk'] ?? 0);
|
||||
|
||||
if (isset($body['warga_ids']) && is_array($body['warga_ids']) && count($body['warga_ids']) > 0) {
|
||||
$ids = array_map('intval', $body['warga_ids']);
|
||||
$placeholders = str_repeat('?,', count($ids) - 1) . '?';
|
||||
$updateStmt = $pdo->prepare("UPDATE rumah_warga SET dikelola_oleh_ibadah_id = ? WHERE id IN ($placeholders)");
|
||||
$params = array_merge([$pengirimId], $ids);
|
||||
$updateStmt->execute($params);
|
||||
} elseif ($limit > 0) {
|
||||
// Ambil radius penerima (untuk mencari warga yang saat ini masuk radiusnya)
|
||||
$stmtPen = $pdo->prepare("SELECT latitude, longitude, radius_primer FROM rumah_ibadah WHERE id = ?");
|
||||
$stmtPen->execute([$penerimaId]);
|
||||
$penerima = $stmtPen->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
// Ambil koordinat pengirim (untuk mencari warga terdekat ke pengirim)
|
||||
$stmtPeng = $pdo->prepare("SELECT latitude, longitude FROM rumah_ibadah WHERE id = ?");
|
||||
$stmtPeng->execute([$pengirimId]);
|
||||
$pengirim = $stmtPeng->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($penerima && $pengirim) {
|
||||
$radPenerima = (float)($penerima['radius_primer'] ?? 500);
|
||||
|
||||
// Ambil warga yang dikelola oleh penerima atau belum dikelola siapapun
|
||||
$stmtWarga = $pdo->prepare("SELECT id, latitude, longitude, dikelola_oleh_ibadah_id FROM rumah_warga WHERE (dikelola_oleh_ibadah_id = ? OR dikelola_oleh_ibadah_id IS NULL) AND kategori_kemiskinan IN ('sangat_miskin', 'miskin', 'rentan_miskin') AND status_kemandirian != 'mandiri'");
|
||||
$stmtWarga->execute([$penerimaId]);
|
||||
$wargaList = $stmtWarga->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$eligible = [];
|
||||
foreach ($wargaList as $w) {
|
||||
// Jika sudah secara eksplisit dikelola oleh penerima, atau jika NULL dan masih dalam radius penerima
|
||||
$isManaged = ($w['dikelola_oleh_ibadah_id'] == $penerimaId);
|
||||
$distPenerima = haversine((float)$w['latitude'], (float)$w['longitude'], (float)$penerima['latitude'], (float)$penerima['longitude']);
|
||||
|
||||
if ($isManaged || $distPenerima <= $radPenerima) {
|
||||
$distPengirim = haversine((float)$w['latitude'], (float)$w['longitude'], (float)$pengirim['latitude'], (float)$pengirim['longitude']);
|
||||
$w['dist_pengirim'] = $distPengirim;
|
||||
$eligible[] = $w;
|
||||
}
|
||||
}
|
||||
|
||||
// Urutkan warga berdasarkan jarak terdekat ke rumah ibadah pengirim (surplus)
|
||||
usort($eligible, fn($a, $b) => $a['dist_pengirim'] <=> $b['dist_pengirim']);
|
||||
|
||||
// Ambil sejumlah $limit
|
||||
$toTransfer = array_slice($eligible, 0, $limit);
|
||||
|
||||
if (!empty($toTransfer)) {
|
||||
$ids = array_column($toTransfer, 'id');
|
||||
$placeholders = str_repeat('?,', count($ids) - 1) . '?';
|
||||
$updateStmt = $pdo->prepare("UPDATE rumah_warga SET dikelola_oleh_ibadah_id = ? WHERE id IN ($placeholders)");
|
||||
$params = array_merge([$pengirimId], $ids);
|
||||
$updateStmt->execute($params);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => 'Saran redistribusi berhasil disimpan',
|
||||
'id' => $redistId,
|
||||
]);
|
||||
|
||||
} elseif ($method === 'PUT') {
|
||||
// Update status keputusan admin
|
||||
$body = json_decode(file_get_contents('php://input'), true);
|
||||
$id = (int)($body['id'] ?? 0);
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
UPDATE redistribusi_bantuan
|
||||
SET status = ?,
|
||||
catatan_admin = ?,
|
||||
disetujui_oleh = ?,
|
||||
tanggal_keputusan = NOW()
|
||||
WHERE id = ?
|
||||
");
|
||||
$stmt->execute([
|
||||
$body['status'],
|
||||
$body['catatan_admin'] ?? null,
|
||||
$body['disetujui_oleh'] ?? null,
|
||||
$id,
|
||||
]);
|
||||
|
||||
echo json_encode(['success' => true, 'message' => 'Status redistribusi diperbarui']);
|
||||
|
||||
} elseif ($method === 'DELETE') {
|
||||
$id = (int)($_GET['id'] ?? 0);
|
||||
$stmt = $pdo->prepare("DELETE FROM redistribusi_bantuan WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
echo json_encode(['success' => true, 'message' => 'Data redistribusi dihapus']);
|
||||
}
|
||||
|
||||
} catch (Throwable $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
// api/rumah_ibadah.php
|
||||
header('Content-Type: application/json');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
|
||||
header('Access-Control-Allow-Headers: Content-Type');
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { http_response_code(204); exit; }
|
||||
|
||||
require_once '../config/db.php';
|
||||
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
|
||||
switch ($method) {
|
||||
// ── GET ──────────────────────────────────────────────────────────────────
|
||||
case 'GET':
|
||||
if (isset($_GET['id'])) {
|
||||
$id = (int)$_GET['id'];
|
||||
$result = $conn->query("SELECT * FROM rumah_ibadah WHERE id = $id");
|
||||
if ($row = $result->fetch_assoc()) {
|
||||
echo json_encode(['success' => true, 'data' => $row]);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => 'Data tidak ditemukan']);
|
||||
}
|
||||
} else {
|
||||
// Filter opsional
|
||||
$where = [];
|
||||
if (!empty($_GET['jenis'])) $where[] = "jenis = '" . $conn->real_escape_string($_GET['jenis']) . "'";
|
||||
if (!empty($_GET['kecamatan'])) $where[] = "kecamatan LIKE '%" . $conn->real_escape_string($_GET['kecamatan']) . "%'";
|
||||
if (!empty($_GET['search'])) $where[] = "(nama LIKE '%" . $conn->real_escape_string($_GET['search']) . "%' OR alamat LIKE '%" . $conn->real_escape_string($_GET['search']) . "%')";
|
||||
|
||||
$sql = "SELECT * FROM rumah_ibadah" . ($where ? " WHERE " . implode(' AND ', $where) : "") . " ORDER BY nama ASC";
|
||||
$result = $conn->query($sql);
|
||||
if ($result === false) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => 'Gagal mengambil data dari tabel "rumah_ibadah": ' . $conn->error . '. Pastikan Anda telah mengimpor berkas database "database/bansos_gis.sql" di phpMyAdmin Anda!'
|
||||
]);
|
||||
break;
|
||||
}
|
||||
$data = [];
|
||||
while ($row = $result->fetch_assoc()) $data[] = $row;
|
||||
echo json_encode(['success' => true, 'data' => $data, 'total' => count($data)]);
|
||||
}
|
||||
break;
|
||||
|
||||
// ── POST ─────────────────────────────────────────────────────────────────
|
||||
case 'POST':
|
||||
$input = json_decode(file_get_contents('php://input'), true) ?: $_POST;
|
||||
|
||||
$nama = $conn->real_escape_string(trim($input['nama'] ?? ''));
|
||||
$jenis = $conn->real_escape_string($input['jenis'] ?? 'masjid');
|
||||
$alamat = $conn->real_escape_string($input['alamat'] ?? '');
|
||||
$kelurahan = $conn->real_escape_string($input['kelurahan'] ?? '');
|
||||
$kecamatan = $conn->real_escape_string($input['kecamatan'] ?? '');
|
||||
$kota = $conn->real_escape_string($input['kota'] ?? '');
|
||||
$lat = (float)($input['latitude'] ?? 0);
|
||||
$lng = (float)($input['longitude'] ?? 0);
|
||||
$pj = $conn->real_escape_string($input['penanggung_jawab'] ?? '');
|
||||
$kontak = $conn->real_escape_string($input['kontak'] ?? '');
|
||||
$ket = $conn->real_escape_string($input['keterangan'] ?? '');
|
||||
|
||||
$kap = (int)($input['kapasitas_kk'] ?? 20);
|
||||
$rad = (int)($input['radius_primer'] ?? 500);
|
||||
$anggaran = (float)($input['anggaran_bulanan'] ?? 0);
|
||||
|
||||
if (!$nama || !$lat || !$lng) {
|
||||
echo json_encode(['success' => false, 'message' => 'Nama, latitude, dan longitude wajib diisi']);
|
||||
break;
|
||||
}
|
||||
|
||||
$sql = "INSERT INTO rumah_ibadah (nama,jenis,alamat,kelurahan,kecamatan,kota,latitude,longitude,penanggung_jawab,kontak,keterangan,kapasitas_kk,radius_primer,anggaran_bulanan)
|
||||
VALUES ('$nama','$jenis','$alamat','$kelurahan','$kecamatan','$kota',$lat,$lng,'$pj','$kontak','$ket',$kap,$rad,$anggaran)";
|
||||
|
||||
if ($conn->query($sql)) {
|
||||
echo json_encode(['success' => true, 'message' => 'Data berhasil disimpan', 'id' => $conn->insert_id]);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => 'Gagal menyimpan: ' . $conn->error]);
|
||||
}
|
||||
break;
|
||||
|
||||
// ── PUT ──────────────────────────────────────────────────────────────────
|
||||
case 'PUT':
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
$id = (int)($input['id'] ?? 0);
|
||||
$nama = $conn->real_escape_string(trim($input['nama'] ?? ''));
|
||||
$jenis = $conn->real_escape_string($input['jenis'] ?? 'masjid');
|
||||
$alamat = $conn->real_escape_string($input['alamat'] ?? '');
|
||||
$kelurahan = $conn->real_escape_string($input['kelurahan'] ?? '');
|
||||
$kecamatan = $conn->real_escape_string($input['kecamatan'] ?? '');
|
||||
$kota = $conn->real_escape_string($input['kota'] ?? '');
|
||||
$lat = (float)($input['latitude'] ?? 0);
|
||||
$lng = (float)($input['longitude'] ?? 0);
|
||||
$pj = $conn->real_escape_string($input['penanggung_jawab'] ?? '');
|
||||
$kontak = $conn->real_escape_string($input['kontak'] ?? '');
|
||||
$ket = $conn->real_escape_string($input['keterangan'] ?? '');
|
||||
|
||||
$kap = (int)($input['kapasitas_kk'] ?? 20);
|
||||
$rad = (int)($input['radius_primer'] ?? 500);
|
||||
$anggaran = (float)($input['anggaran_bulanan'] ?? 0);
|
||||
|
||||
if (!$id) { echo json_encode(['success' => false, 'message' => 'ID tidak valid']); break; }
|
||||
|
||||
$sql = "UPDATE rumah_ibadah SET
|
||||
nama='$nama', jenis='$jenis', alamat='$alamat',
|
||||
kelurahan='$kelurahan', kecamatan='$kecamatan', kota='$kota',
|
||||
latitude=$lat, longitude=$lng,
|
||||
penanggung_jawab='$pj', kontak='$kontak', keterangan='$ket',
|
||||
kapasitas_kk=$kap, radius_primer=$rad, anggaran_bulanan=$anggaran
|
||||
WHERE id=$id";
|
||||
|
||||
if ($conn->query($sql)) {
|
||||
echo json_encode(['success' => true, 'message' => 'Data berhasil diperbarui']);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => 'Gagal memperbarui: ' . $conn->error]);
|
||||
}
|
||||
break;
|
||||
|
||||
// ── DELETE ───────────────────────────────────────────────────────────────
|
||||
case 'DELETE':
|
||||
$id = (int)($_GET['id'] ?? 0);
|
||||
if (!$id) {
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
$id = (int)($input['id'] ?? 0);
|
||||
}
|
||||
if ($conn->query("DELETE FROM rumah_ibadah WHERE id=$id")) {
|
||||
echo json_encode(['success' => true, 'message' => 'Data berhasil dihapus']);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => 'Gagal menghapus: ' . $conn->error]);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
http_response_code(405);
|
||||
echo json_encode(['error' => 'Method not allowed']);
|
||||
}
|
||||
$conn->close();
|
||||
@@ -0,0 +1,511 @@
|
||||
<?php
|
||||
// api/rumah_warga.php
|
||||
header('Content-Type: application/json');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
|
||||
header('Access-Control-Allow-Headers: Content-Type');
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { http_response_code(204); exit; }
|
||||
|
||||
require_once '../config/db.php';
|
||||
require_once 'middleware.php';
|
||||
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
// FUNGSI: Hitung rekomendasi bantuan otomatis berdasarkan data anggota
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
function hitungRekomendasi(mysqli $conn, int $wargaId, float $penghasilan, string $statusPekerjaan): string
|
||||
{
|
||||
$today = new DateTime();
|
||||
|
||||
// Ambil semua anggota keluarga
|
||||
$res = $conn->query("SELECT tanggal_lahir, status_bekerja, dapat_bekerja, kondisi_kesehatan FROM anggota_keluarga WHERE rumah_warga_id = $wargaId");
|
||||
$anggota = [];
|
||||
while ($r = $res->fetch_assoc()) $anggota[] = $r;
|
||||
|
||||
$adaProduktif = false; // usia 18-55 th
|
||||
$adaProduktifBisaKerja = false; // produktif & bisa kerja & belum bekerja tetap
|
||||
$semuaProduktifTakBisa = true; // semua yg produktif tidak bisa kerja
|
||||
|
||||
foreach ($anggota as $a) {
|
||||
$usia = null;
|
||||
if ($a['tanggal_lahir']) {
|
||||
$lahir = new DateTime($a['tanggal_lahir']);
|
||||
$usia = (int)$today->diff($lahir)->y;
|
||||
}
|
||||
|
||||
if ($usia !== null && $usia >= 18 && $usia <= 55) {
|
||||
$adaProduktif = true;
|
||||
$bisaKerja = (int)$a['dapat_bekerja'] === 1
|
||||
&& in_array($a['kondisi_kesehatan'], ['sehat', 'sakit_ringan']);
|
||||
|
||||
if ($bisaKerja) $semuaProduktifTakBisa = false;
|
||||
|
||||
// Produktif, bisa kerja, belum bekerja tetap → kandidat pelatihan
|
||||
if ($bisaKerja && $a['status_bekerja'] !== 'bekerja') {
|
||||
$adaProduktifBisaKerja = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Kriteria bantuan rutin
|
||||
$perluRutin = !$adaProduktif
|
||||
|| ($adaProduktif && $semuaProduktifTakBisa)
|
||||
|| $penghasilan < 500000
|
||||
|| $statusPekerjaan === 'tidak_bekerja';
|
||||
|
||||
// Kriteria bantuan pelatihan
|
||||
$perluPelatihan = $adaProduktifBisaKerja;
|
||||
|
||||
if ($perluRutin && $perluPelatihan) return 'keduanya';
|
||||
if ($perluRutin) return 'bantuan_rutin';
|
||||
if ($perluPelatihan) return 'bantuan_pelatihan';
|
||||
return 'tidak_ada';
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
// FUNGSI: Hitung kategori kesejahteraan otomatis berdasarkan data warga
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
function hitungKategoriKesejahteraan(float $penghasilan, int $tanggungan, string $statusTempatTinggal, string $sumberAir): string
|
||||
{
|
||||
// Logika sederhana:
|
||||
// Jika penghasilan sangat rendah dan tanggungan banyak
|
||||
$pendapatanPerKapita = $tanggungan > 0 ? $penghasilan / $tanggungan : $penghasilan;
|
||||
|
||||
$skor = 0;
|
||||
|
||||
if ($pendapatanPerKapita < 300000) {
|
||||
$skor += 40;
|
||||
} elseif ($pendapatanPerKapita < 600000) {
|
||||
$skor += 20;
|
||||
}
|
||||
|
||||
if ($statusTempatTinggal === 'numpang' || $statusTempatTinggal === 'sewa') {
|
||||
$skor += 20;
|
||||
}
|
||||
|
||||
if ($sumberAir === 'sungai' || $sumberAir === 'hujan') {
|
||||
$skor += 10;
|
||||
}
|
||||
|
||||
if ($skor >= 50) return 'sangat_miskin';
|
||||
if ($skor >= 20) return 'miskin';
|
||||
return 'rentan_miskin';
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
// FUNGSI: Memeriksa akses koordinator berdasarkan kelurahan/kecamatan ATAU radius geografis <= 5km
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
function checkCoordinatorAccess(mysqli $conn, int $koorIbadahId, float $lat, float $lng, string $kelurahan, string $kecamatan): bool
|
||||
{
|
||||
$wilRes = $conn->query("SELECT latitude, longitude, kelurahan, kecamatan FROM rumah_ibadah WHERE id = $koorIbadahId");
|
||||
if ($wil = $wilRes->fetch_assoc()) {
|
||||
// Cek 1: Pencocokan nama kelurahan/kecamatan (sebagai fallback tangguh)
|
||||
$kel = strtolower($wil['kelurahan']);
|
||||
$kec = strtolower($wil['kecamatan']);
|
||||
$wKel = strtolower(trim($kelurahan));
|
||||
$wKec = strtolower(trim($kecamatan));
|
||||
if (($kel !== '' && strpos($wKel, $kel) !== false) || ($kec !== '' && strpos($wKec, $kec) !== false)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Cek 2: Jarak radius geografis <= 5000m (karena batas slider maksimal 5km)
|
||||
$lat1 = (float)$wil['latitude'];
|
||||
$lng1 = (float)$wil['longitude'];
|
||||
if ($lat1 !== 0.0 && $lng1 !== 0.0 && $lat !== 0.0 && $lng !== 0.0) {
|
||||
$earthRadius = 6371000;
|
||||
$dLat = deg2rad($lat - $lat1);
|
||||
$dLng = deg2rad($lng - $lng1);
|
||||
$a = sin($dLat/2) * sin($dLat/2) + cos(deg2rad($lat1)) * cos(deg2rad($lat)) * sin($dLng/2) * sin($dLng/2);
|
||||
$c = 2 * atan2(sqrt($a), sqrt(1-$a));
|
||||
$dist = $earthRadius * $c;
|
||||
if ($dist <= 5000.0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
switch ($method) {
|
||||
// ── GET ──────────────────────────────────────────────────────────────────
|
||||
case 'GET':
|
||||
// Izinkan guest mode (opsional) untuk melihat data rumah warga
|
||||
$currentUser = optionalAuth();
|
||||
$isGuest = $currentUser === null;
|
||||
|
||||
// Jika tidak login (guest), validasi akses
|
||||
if ($isGuest) {
|
||||
$currentUser = ['role' => 'guest'];
|
||||
} else {
|
||||
// Validasi role jika sudah login
|
||||
if (!in_array($currentUser['role'], ['admin', 'petugas', 'koordinator', 'kontributor'])) {
|
||||
http_response_code(403);
|
||||
echo json_encode(['success' => false, 'message' => 'Forbidden: Role Anda tidak memiliki akses.']);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($_GET['id'])) {
|
||||
$id = (int)$_GET['id'];
|
||||
$res = $conn->query("SELECT * FROM rumah_warga WHERE id=$id");
|
||||
if ($row = $res->fetch_assoc()) {
|
||||
// Batasi Koordinator agar hanya bisa melihat detail warga di wilayah tugasnya sendiri (radius / kelurahan / kecamatan)
|
||||
if ($currentUser['role'] === 'koordinator') {
|
||||
$koorIbadahId = (int)$currentUser['rumah_ibadah_id'];
|
||||
$lat = (float)($row['latitude'] ?? 0);
|
||||
$lng = (float)($row['longitude'] ?? 0);
|
||||
$kelurahan = $row['kelurahan'] ?? '';
|
||||
$kecamatan = $row['kecamatan'] ?? '';
|
||||
if (!checkCoordinatorAccess($conn, $koorIbadahId, $lat, $lng, $kelurahan, $kecamatan)) {
|
||||
http_response_code(403);
|
||||
echo json_encode(['success' => false, 'message' => 'Forbidden: Detail warga di luar wilayah tugas Anda.']);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
// Sertakan anggota keluarga jika bukan koordinator / kontributor / guest
|
||||
if ($currentUser['role'] === 'koordinator') {
|
||||
$row['anggota'] = [];
|
||||
unset($row['no_kk']);
|
||||
unset($row['nik']);
|
||||
unset($row['penghasilan_bulanan']);
|
||||
unset($row['pekerjaan']);
|
||||
unset($row['status_pekerjaan']);
|
||||
unset($row['luas_rumah']);
|
||||
unset($row['jenis_lantai']);
|
||||
unset($row['jenis_dinding']);
|
||||
unset($row['sumber_air']);
|
||||
unset($row['status_tempat_tinggal']);
|
||||
unset($row['keterangan']);
|
||||
unset($row['skor_kepercayaan']);
|
||||
unset($row['is_rekomendasi_manual']);
|
||||
unset($row['is_kategori_manual']);
|
||||
// KEEP: kategori_kemiskinan, jumlah_tanggungan (needed for display)
|
||||
} elseif ($currentUser['role'] === 'kontributor' || $currentUser['role'] === 'guest') {
|
||||
$row['anggota'] = [];
|
||||
unset($row['no_kk']);
|
||||
unset($row['nik']);
|
||||
unset($row['penghasilan_bulanan']);
|
||||
unset($row['pekerjaan']);
|
||||
unset($row['status_pekerjaan']);
|
||||
unset($row['luas_rumah']);
|
||||
unset($row['jenis_lantai']);
|
||||
unset($row['jenis_dinding']);
|
||||
unset($row['sumber_air']);
|
||||
unset($row['status_tempat_tinggal']);
|
||||
unset($row['keterangan']);
|
||||
unset($row['skor_kepercayaan']);
|
||||
unset($row['is_rekomendasi_manual']);
|
||||
unset($row['is_kategori_manual']);
|
||||
unset($row['created_at']);
|
||||
unset($row['updated_at']);
|
||||
// KEEP: kategori_kemiskinan, jumlah_tanggungan, nama_kepala_keluarga (needed for display)
|
||||
} else {
|
||||
$resA = $conn->query("SELECT * FROM anggota_keluarga WHERE rumah_warga_id=$id ORDER BY tanggal_lahir ASC");
|
||||
$anggota = [];
|
||||
while ($a = $resA->fetch_assoc()) $anggota[] = $a;
|
||||
$row['anggota'] = $anggota;
|
||||
}
|
||||
echo json_encode(['success' => true, 'data' => $row]);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => 'Data tidak ditemukan']);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Filter pencarian
|
||||
$where = [];
|
||||
if (!empty($_GET['kategori'])) $where[] = "kategori_kemiskinan = '" . $conn->real_escape_string($_GET['kategori']) . "'";
|
||||
if (!empty($_GET['status'])) $where[] = "status_bantuan = '" . $conn->real_escape_string($_GET['status']) . "'";
|
||||
if (!empty($_GET['rekomendasi'])) $where[] = "rekomendasi_bantuan = '" . $conn->real_escape_string($_GET['rekomendasi']) . "'";
|
||||
if (!empty($_GET['kecamatan'])) $where[] = "kecamatan LIKE '%" . $conn->real_escape_string($_GET['kecamatan']) . "%'";
|
||||
if (!empty($_GET['kelurahan'])) $where[] = "kelurahan LIKE '%" . $conn->real_escape_string($_GET['kelurahan']) . "%'";
|
||||
if (!empty($_GET['search'])) $where[] = "(nama_kepala_keluarga LIKE '%" . $conn->real_escape_string($_GET['search']) . "%' OR no_kk LIKE '%" . $conn->real_escape_string($_GET['search']) . "%' OR nik LIKE '%" . $conn->real_escape_string($_GET['search']) . "%')";
|
||||
|
||||
// Filter wilayah untuk koordinator tidak lagi dibatasi per kelurahan/kecamatan di SQL,
|
||||
// melainkan akan difilter secara dinamis berdasarkan radius geografis di frontend.
|
||||
|
||||
$sql = "SELECT * FROM rumah_warga" . ($where ? " WHERE " . implode(' AND ', $where) : "") . " ORDER BY nama_kepala_keluarga ASC";
|
||||
$result = $conn->query($sql);
|
||||
if ($result === false) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => 'Gagal mengambil data dari tabel "rumah_warga": ' . $conn->error
|
||||
]);
|
||||
break;
|
||||
}
|
||||
$data = [];
|
||||
while ($row = $result->fetch_assoc()) {
|
||||
if ($currentUser['role'] === 'koordinator') {
|
||||
unset($row['no_kk']);
|
||||
unset($row['nik']);
|
||||
unset($row['penghasilan_bulanan']);
|
||||
unset($row['pekerjaan']);
|
||||
unset($row['status_pekerjaan']);
|
||||
unset($row['luas_rumah']);
|
||||
unset($row['jenis_lantai']);
|
||||
unset($row['jenis_dinding']);
|
||||
unset($row['sumber_air']);
|
||||
unset($row['status_tempat_tinggal']);
|
||||
unset($row['keterangan']);
|
||||
unset($row['skor_kepercayaan']);
|
||||
unset($row['is_rekomendasi_manual']);
|
||||
// KEEP: kategori_kemiskinan, jumlah_tanggungan (needed for frontend display)
|
||||
} elseif ($currentUser['role'] === 'kontributor' || $currentUser['role'] === 'guest') {
|
||||
unset($row['no_kk']);
|
||||
unset($row['nik']);
|
||||
unset($row['penghasilan_bulanan']);
|
||||
unset($row['pekerjaan']);
|
||||
unset($row['status_pekerjaan']);
|
||||
unset($row['luas_rumah']);
|
||||
unset($row['jenis_lantai']);
|
||||
unset($row['jenis_dinding']);
|
||||
unset($row['sumber_air']);
|
||||
unset($row['status_tempat_tinggal']);
|
||||
unset($row['keterangan']);
|
||||
unset($row['skor_kepercayaan']);
|
||||
unset($row['is_rekomendasi_manual']);
|
||||
unset($row['created_at']);
|
||||
unset($row['updated_at']);
|
||||
// KEEP: kategori_kemiskinan, jumlah_tanggungan, nama_kepala_keluarga (needed for frontend display)
|
||||
}
|
||||
$data[] = $row;
|
||||
}
|
||||
echo json_encode(['success' => true, 'data' => $data, 'total' => count($data)]);
|
||||
break;
|
||||
|
||||
// ── POST ─────────────────────────────────────────────────────────────────
|
||||
case 'POST':
|
||||
// Membatasi akses: Hanya Admin dan Petugas Lapangan yang bisa menginput
|
||||
requireAuth(['admin', 'petugas']);
|
||||
|
||||
$input = json_decode(file_get_contents('php://input'), true) ?: $_POST;
|
||||
|
||||
$no_kk = $conn->real_escape_string(trim($input['no_kk'] ?? ''));
|
||||
$nama = $conn->real_escape_string(trim($input['nama_kepala_keluarga'] ?? ''));
|
||||
$nik = $conn->real_escape_string($input['nik'] ?? '');
|
||||
$alamat = $conn->real_escape_string($input['alamat'] ?? '');
|
||||
$kelurahan = $conn->real_escape_string($input['kelurahan'] ?? '');
|
||||
$kecamatan = $conn->real_escape_string($input['kecamatan'] ?? '');
|
||||
$kota = $conn->real_escape_string($input['kota'] ?? '');
|
||||
$lat = (float)($input['latitude'] ?? 0);
|
||||
$lng = (float)($input['longitude'] ?? 0);
|
||||
$tanggungan = (int)($input['jumlah_tanggungan'] ?? 1);
|
||||
$penghasilan= (float)($input['penghasilan_bulanan'] ?? 0);
|
||||
$pekerjaan = $conn->real_escape_string($input['pekerjaan'] ?? '');
|
||||
$stsPekerjaan = $conn->real_escape_string($input['status_pekerjaan'] ?? 'tidak_bekerja');
|
||||
$luas = !empty($input['luas_rumah']) ? (float)$input['luas_rumah'] : 'NULL';
|
||||
$lantai = $conn->real_escape_string($input['jenis_lantai'] ?? 'semen');
|
||||
$dinding = $conn->real_escape_string($input['jenis_dinding'] ?? 'tembok');
|
||||
$air = $conn->real_escape_string($input['sumber_air'] ?? 'sumur');
|
||||
$tinggal = $conn->real_escape_string($input['status_tempat_tinggal'] ?? 'milik_sendiri');
|
||||
$kategoriInput = $conn->real_escape_string($input['kategori_kemiskinan'] ?? 'miskin');
|
||||
$isKatManual = ($kategoriInput !== 'sistem') ? 1 : 0;
|
||||
$kategori = ($isKatManual === 1) ? $kategoriInput : hitungKategoriKesejahteraan($penghasilan, $tanggungan, $tinggal, $air);
|
||||
|
||||
$stsbansos = $conn->real_escape_string($input['status_bantuan'] ?? 'belum_terima');
|
||||
$ket = $conn->real_escape_string($input['keterangan'] ?? '');
|
||||
$dikelola = !empty($input['dikelola_oleh_ibadah_id']) ? (int)$input['dikelola_oleh_ibadah_id'] : 'NULL';
|
||||
|
||||
// Validasi
|
||||
if (!$nama || !$lat || !$lng) {
|
||||
echo json_encode(['success' => false, 'message' => 'Nama KK, latitude, dan longitude wajib diisi']); break;
|
||||
}
|
||||
if (!$no_kk || strlen(preg_replace('/\D/', '', $no_kk)) !== 16) {
|
||||
echo json_encode(['success' => false, 'message' => 'Nomor KK harus 16 digit angka']); break;
|
||||
}
|
||||
// Cek duplikat KK
|
||||
$cek = $conn->query("SELECT id FROM rumah_warga WHERE no_kk='$no_kk'");
|
||||
if ($cek->num_rows > 0) {
|
||||
echo json_encode(['success' => false, 'message' => 'Nomor KK sudah terdaftar']); break;
|
||||
}
|
||||
|
||||
$luasVal = is_numeric($luas) ? $luas : 'NULL';
|
||||
$rekInput = $conn->real_escape_string($input['rekomendasi_bantuan'] ?? 'sistem');
|
||||
$isManual = ($rekInput !== 'sistem') ? 1 : 0;
|
||||
|
||||
$sql = "INSERT INTO rumah_warga
|
||||
(no_kk,nama_kepala_keluarga,nik,alamat,kelurahan,kecamatan,kota,latitude,longitude,
|
||||
jumlah_tanggungan,penghasilan_bulanan,pekerjaan,status_pekerjaan,
|
||||
luas_rumah,jenis_lantai,jenis_dinding,sumber_air,status_tempat_tinggal,
|
||||
kategori_kemiskinan,is_kategori_manual,status_bantuan,rekomendasi_bantuan,is_rekomendasi_manual,keterangan,
|
||||
dikelola_oleh_ibadah_id, tanggal_verifikasi, skor_kepercayaan, status_verifikasi)
|
||||
VALUES
|
||||
('$no_kk','$nama','$nik','$alamat','$kelurahan','$kecamatan','$kota',$lat,$lng,
|
||||
$tanggungan,$penghasilan,'$pekerjaan','$stsPekerjaan',
|
||||
$luasVal,'$lantai','$dinding','$air','$tinggal',
|
||||
'$kategori',$isKatManual,'$stsbansos','bantuan_rutin',$isManual,'$ket',
|
||||
$dikelola, CURRENT_DATE(), 100, 'terverifikasi')";
|
||||
|
||||
if ($conn->query($sql)) {
|
||||
$newId = $conn->insert_id;
|
||||
|
||||
// Simpan anggota jika ada
|
||||
if (!empty($input['anggota']) && is_array($input['anggota'])) {
|
||||
simpanAnggota($conn, $newId, $input['anggota']);
|
||||
}
|
||||
|
||||
// Hitung rekomendasi otomatis
|
||||
if ($isManual === 1) {
|
||||
$rek = $rekInput;
|
||||
} else {
|
||||
$rek = hitungRekomendasi($conn, $newId, $penghasilan, $stsPekerjaan);
|
||||
}
|
||||
$conn->query("UPDATE rumah_warga SET rekomendasi_bantuan='$rek' WHERE id=$newId");
|
||||
|
||||
echo json_encode(['success' => true, 'message' => 'Data berhasil disimpan', 'id' => $newId, 'rekomendasi' => $rek]);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => 'Gagal menyimpan: ' . $conn->error]);
|
||||
}
|
||||
break;
|
||||
|
||||
// ── PUT ──────────────────────────────────────────────────────────────────
|
||||
case 'PUT':
|
||||
// Membatasi akses: Admin, Petugas Lapangan, dan Koordinator Rumah Ibadah
|
||||
$currentUser = requireAuth(['admin', 'petugas', 'koordinator']);
|
||||
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
$id = (int)($input['id'] ?? 0);
|
||||
if (!$id) { echo json_encode(['success' => false, 'message' => 'ID tidak valid']); break; }
|
||||
|
||||
if ($currentUser['role'] === 'koordinator') {
|
||||
$stsbansos = $conn->real_escape_string($input['status_bantuan'] ?? 'belum_terima');
|
||||
|
||||
// Cek apakah warga berada di wilayah tugas koordinator (radius / kelurahan / kecamatan)
|
||||
$koorIbadahId = (int)$currentUser['rumah_ibadah_id'];
|
||||
$wargaRes = $conn->query("SELECT latitude, longitude, kelurahan, kecamatan FROM rumah_warga WHERE id = $id");
|
||||
if ($wargaRow = $wargaRes->fetch_assoc()) {
|
||||
$lat = (float)($wargaRow['latitude'] ?? 0);
|
||||
$lng = (float)($wargaRow['longitude'] ?? 0);
|
||||
$kelurahan = $wargaRow['kelurahan'] ?? '';
|
||||
$kecamatan = $wargaRow['kecamatan'] ?? '';
|
||||
if (!checkCoordinatorAccess($conn, $koorIbadahId, $lat, $lng, $kelurahan, $kecamatan)) {
|
||||
http_response_code(403);
|
||||
echo json_encode(['success' => false, 'message' => 'Forbidden: Warga di luar wilayah tugas Anda.']);
|
||||
exit;
|
||||
}
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => 'Warga tidak ditemukan.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$sql = "UPDATE rumah_warga SET status_bantuan='$stsbansos' WHERE id=$id";
|
||||
if ($conn->query($sql)) {
|
||||
echo json_encode(['success' => true, 'message' => 'Status bantuan warga berhasil diperbarui']);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => 'Gagal memperbarui status bantuan: ' . $conn->error]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
$no_kk = $conn->real_escape_string(trim($input['no_kk'] ?? ''));
|
||||
$nama = $conn->real_escape_string(trim($input['nama_kepala_keluarga'] ?? ''));
|
||||
$nik = $conn->real_escape_string($input['nik'] ?? '');
|
||||
$alamat = $conn->real_escape_string($input['alamat'] ?? '');
|
||||
$kelurahan = $conn->real_escape_string($input['kelurahan'] ?? '');
|
||||
$kecamatan = $conn->real_escape_string($input['kecamatan'] ?? '');
|
||||
$kota = $conn->real_escape_string($input['kota'] ?? '');
|
||||
$lat = (float)($input['latitude'] ?? 0);
|
||||
$lng = (float)($input['longitude'] ?? 0);
|
||||
$tanggungan = (int)($input['jumlah_tanggungan'] ?? 1);
|
||||
$penghasilan= (float)($input['penghasilan_bulanan'] ?? 0);
|
||||
$pekerjaan = $conn->real_escape_string($input['pekerjaan'] ?? '');
|
||||
$stsPekerjaan = $conn->real_escape_string($input['status_pekerjaan'] ?? 'tidak_bekerja');
|
||||
$luasVal = !empty($input['luas_rumah']) ? (float)$input['luas_rumah'] : 'NULL';
|
||||
$lantai = $conn->real_escape_string($input['jenis_lantai'] ?? 'semen');
|
||||
$dinding = $conn->real_escape_string($input['jenis_dinding'] ?? 'tembok');
|
||||
$air = $conn->real_escape_string($input['sumber_air'] ?? 'sumur');
|
||||
$tinggal = $conn->real_escape_string($input['status_tempat_tinggal'] ?? 'milik_sendiri');
|
||||
$kategoriInput = $conn->real_escape_string($input['kategori_kemiskinan'] ?? 'miskin');
|
||||
$isKatManual = ($kategoriInput !== 'sistem') ? 1 : 0;
|
||||
$kategori = ($isKatManual === 1) ? $kategoriInput : hitungKategoriKesejahteraan($penghasilan, $tanggungan, $tinggal, $air);
|
||||
|
||||
$stsbansos = $conn->real_escape_string($input['status_bantuan'] ?? 'belum_terima');
|
||||
$rekInput = $conn->real_escape_string($input['rekomendasi_bantuan'] ?? 'sistem');
|
||||
$isManual = ($rekInput !== 'sistem') ? 1 : 0;
|
||||
$ket = $conn->real_escape_string($input['keterangan'] ?? '');
|
||||
$dikelola = isset($input['dikelola_oleh_ibadah_id']) && $input['dikelola_oleh_ibadah_id'] !== '' ? (int)$input['dikelola_oleh_ibadah_id'] : 'NULL';
|
||||
|
||||
// Cek duplikat KK (kecuali diri sendiri)
|
||||
$cek = $conn->query("SELECT id FROM rumah_warga WHERE no_kk='$no_kk' AND id != $id");
|
||||
if ($cek->num_rows > 0) {
|
||||
echo json_encode(['success' => false, 'message' => 'Nomor KK sudah digunakan data lain']); break;
|
||||
}
|
||||
|
||||
// Update anggota jika ada
|
||||
if (!empty($input['anggota']) && is_array($input['anggota'])) {
|
||||
$conn->query("DELETE FROM anggota_keluarga WHERE rumah_warga_id=$id");
|
||||
simpanAnggota($conn, $id, $input['anggota']);
|
||||
}
|
||||
|
||||
// Hitung rekomendasi otomatis
|
||||
if ($isManual === 1) {
|
||||
$rek = $rekInput;
|
||||
} else {
|
||||
$rek = hitungRekomendasi($conn, $id, $penghasilan, $stsPekerjaan);
|
||||
}
|
||||
|
||||
$sql = "UPDATE rumah_warga SET
|
||||
no_kk='$no_kk', nama_kepala_keluarga='$nama', nik='$nik',
|
||||
alamat='$alamat', kelurahan='$kelurahan', kecamatan='$kecamatan', kota='$kota',
|
||||
latitude=$lat, longitude=$lng,
|
||||
jumlah_tanggungan=$tanggungan, penghasilan_bulanan=$penghasilan,
|
||||
pekerjaan='$pekerjaan', status_pekerjaan='$stsPekerjaan',
|
||||
luas_rumah=$luasVal, jenis_lantai='$lantai', jenis_dinding='$dinding',
|
||||
sumber_air='$air', status_tempat_tinggal='$tinggal',
|
||||
kategori_kemiskinan='$kategori', is_kategori_manual=$isKatManual, status_bantuan='$stsbansos',
|
||||
rekomendasi_bantuan='$rek', is_rekomendasi_manual=$isManual, keterangan='$ket',
|
||||
dikelola_oleh_ibadah_id=$dikelola
|
||||
WHERE id=$id";
|
||||
|
||||
if ($conn->query($sql)) {
|
||||
echo json_encode(['success' => true, 'message' => 'Data berhasil diperbarui', 'rekomendasi' => $rek]);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => 'Gagal memperbarui: ' . $conn->error]);
|
||||
}
|
||||
break;
|
||||
|
||||
// ── DELETE ───────────────────────────────────────────────────────────────
|
||||
case 'DELETE':
|
||||
// Membatasi akses: HANYA Admin yang boleh menghapus data warga
|
||||
requireAuth(['admin']);
|
||||
|
||||
$id = (int)($_GET['id'] ?? 0);
|
||||
if (!$id) {
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
$id = (int)($input['id'] ?? 0);
|
||||
}
|
||||
if ($conn->query("DELETE FROM rumah_warga WHERE id=$id")) {
|
||||
echo json_encode(['success' => true, 'message' => 'Data berhasil dihapus']);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => 'Gagal menghapus: ' . $conn->error]);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
http_response_code(405);
|
||||
echo json_encode(['error' => 'Method not allowed']);
|
||||
}
|
||||
$conn->close();
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
function simpanAnggota(mysqli $conn, int $wargaId, array $list): void
|
||||
{
|
||||
foreach ($list as $a) {
|
||||
$nama = $conn->real_escape_string(trim($a['nama'] ?? ''));
|
||||
if (!$nama) continue;
|
||||
$nik = $conn->real_escape_string($a['nik'] ?? '');
|
||||
$tl = !empty($a['tanggal_lahir']) ? "'" . $conn->real_escape_string($a['tanggal_lahir']) . "'" : 'NULL';
|
||||
$jk = $conn->real_escape_string($a['jenis_kelamin'] ?? 'laki_laki');
|
||||
$pend = $conn->real_escape_string($a['pendidikan'] ?? 'sd');
|
||||
$pek = $conn->real_escape_string($a['pekerjaan'] ?? '');
|
||||
$stsBk = $conn->real_escape_string($a['status_bekerja'] ?? 'tidak_bekerja');
|
||||
$kes = $conn->real_escape_string($a['kondisi_kesehatan'] ?? 'sehat');
|
||||
$bisa = (int)($a['dapat_bekerja'] ?? 1);
|
||||
|
||||
$conn->query("INSERT INTO anggota_keluarga (rumah_warga_id,nama,nik,tanggal_lahir,jenis_kelamin,pendidikan,pekerjaan,status_bekerja,kondisi_kesehatan,dapat_bekerja)
|
||||
VALUES ($wargaId,'$nama','$nik',$tl,'$jk','$pend','$pek','$stsBk','$kes',$bisa)");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
// api/statistik.php
|
||||
header('Content-Type: application/json');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
|
||||
require_once '../config/db.php';
|
||||
|
||||
$stats = [];
|
||||
|
||||
// Total rumah ibadah per jenis
|
||||
$result = $conn->query("SELECT jenis, COUNT(*) as total FROM rumah_ibadah GROUP BY jenis");
|
||||
if ($result === false) {
|
||||
http_response_code(500);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => 'Gagal memuat statistik. Tabel "rumah_ibadah" tidak ditemukan. Pastikan Anda telah mengimpor berkas database "database/bansos_gis.sql" di phpMyAdmin Anda!'
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
$ibadah = [];
|
||||
while ($row = $result->fetch_assoc()) $ibadah[$row['jenis']] = (int)$row['total'];
|
||||
$stats['rumah_ibadah'] = $ibadah;
|
||||
$stats['total_ibadah'] = array_sum($ibadah);
|
||||
|
||||
// Total rumah warga per kategori
|
||||
$result = $conn->query("SELECT kategori_kemiskinan, COUNT(*) as total FROM rumah_warga GROUP BY kategori_kemiskinan");
|
||||
$warga = [];
|
||||
while ($row = $result->fetch_assoc()) $warga[$row['kategori_kemiskinan']] = (int)$row['total'];
|
||||
$stats['kategori_kemiskinan'] = $warga;
|
||||
$stats['total_warga'] = array_sum($warga);
|
||||
|
||||
// Status bantuan
|
||||
$result = $conn->query("SELECT status_bantuan, COUNT(*) as total FROM rumah_warga GROUP BY status_bantuan");
|
||||
$bansos = [];
|
||||
while ($row = $result->fetch_assoc()) $bansos[$row['status_bantuan']] = (int)$row['total'];
|
||||
$stats['status_bansos'] = $bansos;
|
||||
|
||||
// Rekomendasi bantuan
|
||||
$result = $conn->query("SELECT rekomendasi_bantuan, COUNT(*) as total FROM rumah_warga GROUP BY rekomendasi_bantuan");
|
||||
$rek = [];
|
||||
while ($row = $result->fetch_assoc()) $rek[$row['rekomendasi_bantuan']] = (int)$row['total'];
|
||||
$stats['rekomendasi_bantuan'] = $rek;
|
||||
|
||||
// Total jiwa
|
||||
$result = $conn->query("SELECT SUM(jumlah_tanggungan) as total FROM rumah_warga");
|
||||
$row = $result->fetch_assoc();
|
||||
$stats['total_jiwa'] = (int)$row['total'];
|
||||
|
||||
// Total anggota di DB
|
||||
$result = $conn->query("SELECT COUNT(*) as total FROM anggota_keluarga");
|
||||
$row = $result->fetch_assoc();
|
||||
$stats['total_anggota_db'] = (int)$row['total'];
|
||||
|
||||
// Rata-rata penghasilan
|
||||
$result = $conn->query("SELECT AVG(penghasilan_bulanan) as avg_ph FROM rumah_warga");
|
||||
$row = $result->fetch_assoc();
|
||||
$stats['rata_penghasilan'] = round((float)$row['avg_ph'], 0);
|
||||
|
||||
echo json_encode(['success' => true, 'data' => $stats]);
|
||||
$conn->close();
|
||||
@@ -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