prepare($SELECT_SQL . " WHERE pm.id = ?"); $stmt->execute([$id]); $row = $stmt->fetch(); if (!$row) jsonResponse(['success' => false, 'error' => 'Data tidak ditemukan'], 404); // Ambil semua foto $fotoStmt = $db->prepare("SELECT * FROM foto_rumah WHERE penduduk_id = ? ORDER BY is_primary DESC, created_at ASC"); $fotoStmt->execute([$id]); $row['fotos'] = $fotoStmt->fetchAll(); // Ambil semua bantuan $bantuanStmt = $db->prepare(" SELECT bp.*, jb.kode, jb.nama AS nama_bantuan, jb.warna_badge FROM bantuan_penduduk bp JOIN jenis_bantuan jb ON jb.id = bp.bantuan_id WHERE bp.penduduk_id = ? ORDER BY jb.kode "); $bantuanStmt->execute([$id]); $row['bantuans'] = $bantuanStmt->fetchAll(); jsonResponse(['success' => true, 'data' => $row]); } // GET list dengan filter $sql = $SELECT_SQL . " WHERE 1=1"; $params = []; if ($kelurahan) { $sql .= " AND w.kelurahan = ?"; $params[] = $kelurahan; } if ($kecamatan) { $sql .= " AND w.kecamatan = ?"; $params[] = $kecamatan; } if ($kategori) { $sql .= " AND pm.kategori_kemiskinan = ?"; $params[] = $kategori; } if ($verifikasi) { $sql .= " AND pm.status_verifikasi = ?"; $params[] = $verifikasi; } if ($surveyor) { $sql .= " AND pm.surveyor_id = ?"; $params[] = $surveyor; } if ($search) { $sql .= " AND (pm.nama_kk LIKE ? OR pm.nik_kk LIKE ? OR pm.alamat LIKE ?)"; $params[] = "%$search%"; $params[] = "%$search%"; $params[] = "%$search%"; } if ($bantuan === 'tidak') { $sql .= " AND (SELECT COUNT(*) FROM bantuan_penduduk bp WHERE bp.penduduk_id = pm.id AND bp.status='aktif') = 0"; } elseif ($bantuan === 'ada') { $sql .= " AND (SELECT COUNT(*) FROM bantuan_penduduk bp WHERE bp.penduduk_id = pm.id AND bp.status='aktif') > 0"; } $sql .= " ORDER BY pm.nama_kk ASC"; $stmt = $db->prepare($sql); $stmt->execute($params); $rows = $stmt->fetchAll(); // Statistik ringkas $stats = [ 'total' => count($rows), 'total_jiwa' => array_sum(array_column($rows, 'jumlah_anggota')), 'penerima_bantuan' => count(array_filter($rows, fn($r) => $r['jumlah_bantuan'] > 0)), 'belum_verifikasi' => count(array_filter($rows, fn($r) => $r['status_verifikasi'] === 'belum_diverifikasi')), ]; jsonResponse(['success' => true, 'data' => $rows, 'stats' => $stats]); break; // ════════════════════════════════════════════════════════ // POST – Tambah penduduk baru // ════════════════════════════════════════════════════════ case 'POST': $in = getInput(); // Validasi wajib $required = ['nik_kk', 'nama_kk', 'lat', 'lng']; foreach ($required as $f) { if (empty($in[$f])) jsonResponse(['success' => false, 'error' => "Field '$f' wajib diisi"], 400); } // Cek NIK duplikat $cek = $db->prepare("SELECT id FROM penduduk_miskin WHERE nik_kk = ?"); $cek->execute([trim($in['nik_kk'])]); if ($cek->fetch()) jsonResponse(['success' => false, 'error' => 'NIK sudah terdaftar di sistem'], 409); // Hitung kategori otomatis $penghasilan = isset($in['penghasilan_bulanan']) && $in['penghasilan_bulanan'] !== '' ? (float)$in['penghasilan_bulanan'] : null; $kategori = hitungKategori($penghasilan); $wilayahId = isset($in['wilayah_id']) && $in['wilayah_id'] !== '' ? (int)$in['wilayah_id'] : null; $surveyorId = isset($in['surveyor_id']) && $in['surveyor_id'] !== '' ? (int)$in['surveyor_id'] : null; $statusPekerjaan = $in['status_pekerjaan'] ?? 'tidak_bekerja'; $allowedPekerjaan = ['tidak_bekerja','buruh_harian','petani','nelayan','pedagang','pegawai_swasta','wiraswasta','lainnya']; if (!in_array($statusPekerjaan, $allowedPekerjaan)) { $statusPekerjaan = 'lainnya'; } $db->prepare(" INSERT INTO penduduk_miskin ( nik_kk, nama_kk, no_hp, alamat, wilayah_id, rt, rw, lat, lng, jumlah_anggota, jumlah_anak_sekolah, penghasilan_bulanan, sumber_penghasilan, status_pekerjaan, kategori_kemiskinan, kondisi_rumah, kepemilikan_rumah, punya_bpjs, jenis_bpjs, status_verifikasi, surveyor_id ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) ")->execute([ trim($in['nik_kk']), trim($in['nama_kk']), $in['no_hp'] ?? null, $in['alamat'] ?? null, $wilayahId, $in['rt'] ?? null, $in['rw'] ?? null, (float)$in['lat'], (float)$in['lng'], max(1, (int)($in['jumlah_anggota'] ?? 1)), max(0, (int)($in['jumlah_anak_sekolah'] ?? 0)), $penghasilan, $in['sumber_penghasilan'] ?? null, $statusPekerjaan, $kategori, $in['kondisi_rumah'] ?? 'tidak_layak', $in['kepemilikan_rumah'] ?? 'milik_sendiri', (int)($in['punya_bpjs'] ?? 0), $in['jenis_bpjs'] ?? 'tidak_punya', 'belum_diverifikasi', $surveyorId, ]); $newId = (int)$db->lastInsertId(); assignRumahIbadah($newId); // Catat riwayat & notifikasi – tidak block jika gagal (misal user_id tidak ada) try { catatRiwayat($newId, (int)($surveyorId ?? 1), 'submit', 'Data baru dari survei'); } catch(\Exception $e) { /* abaikan error audit trail */ } try { $db->prepare(" INSERT INTO notifikasi (jenis, judul, pesan, penduduk_id) VALUES ('belum_verifikasi', 'Data Baru Belum Diverifikasi', ?, ?) ")->execute(["Data KK {$in['nama_kk']} (NIK: {$in['nik_kk']}) menunggu verifikasi admin.", $newId]); } catch(\Exception $e) { /* abaikan error notifikasi */ } $stmt = $db->prepare($SELECT_SQL . " WHERE pm.id = ?"); $stmt->execute([$newId]); jsonResponse(['success' => true, 'data' => $stmt->fetch(), 'message' => 'Data penduduk berhasil ditambahkan']); break; // ════════════════════════════════════════════════════════ // PUT – Update data penduduk // ════════════════════════════════════════════════════════ case 'PUT': $in = getInput(); $id = (int)($in['id'] ?? $_GET['id'] ?? 0); if (!$id) jsonResponse(['success' => false, 'error' => 'ID diperlukan'], 400); // Ambil data lama untuk audit $old = $db->prepare("SELECT * FROM penduduk_miskin WHERE id = ?"); $old->execute([$id]); $dataLama = $old->fetch(); if (!$dataLama) jsonResponse(['success' => false, 'error' => 'Data tidak ditemukan'], 404); // Hitung kategori ulang jika penghasilan berubah $penghasilan = array_key_exists('penghasilan_bulanan', $in) ? (($in['penghasilan_bulanan'] !== '') ? (float)$in['penghasilan_bulanan'] : null) : (float)$dataLama['penghasilan_bulanan']; $kategori = hitungKategori($penghasilan); $allowed = [ 'nama_kk','no_hp','alamat','wilayah_id','rt','rw','lat','lng', 'jumlah_anggota','jumlah_anak_sekolah','penghasilan_bulanan', 'sumber_penghasilan','status_pekerjaan','kondisi_rumah', 'kepemilikan_rumah','punya_bpjs','jenis_bpjs', 'status_verifikasi','catatan_verifikasi' ]; $fields = []; $values = []; $posChanged = false; foreach ($allowed as $f) { if (!array_key_exists($f, $in)) continue; $fields[] = "$f = ?"; if ($f === 'lat' || $f === 'lng') { $values[] = (float)$in[$f]; $posChanged = true; } elseif ($f === 'wilayah_id') { $values[] = (isset($in[$f]) && $in[$f] !== '') ? (int)$in[$f] : null; } elseif ($f === 'status_pekerjaan') { $statusPekerjaan = $in[$f] ?? 'tidak_bekerja'; $allowedPekerjaan = ['tidak_bekerja','buruh_harian','petani','nelayan','pedagang','pegawai_swasta','wiraswasta','lainnya']; $values[] = in_array($statusPekerjaan, $allowedPekerjaan) ? $statusPekerjaan : 'lainnya'; } elseif (in_array($f, ['jumlah_anggota','jumlah_anak_sekolah','punya_bpjs'])) $values[] = (int)$in[$f]; elseif ($f === 'penghasilan_bulanan') $values[] = $penghasilan; else $values[] = trim((string)$in[$f]); } // Update kategori otomatis $fields[] = "kategori_kemiskinan = ?"; $values[] = $kategori; // Jika diverifikasi, catat verifikator if (isset($in['status_verifikasi']) && $in['status_verifikasi'] === 'terverifikasi') { $fields[] = "verified_by = ?"; $values[] = $in['verified_by'] ?? 1; $fields[] = "verified_at = NOW()"; } if (empty($fields)) jsonResponse(['success' => false, 'error' => 'Tidak ada data untuk diupdate'], 400); $values[] = $id; $db->prepare("UPDATE penduduk_miskin SET " . implode(', ', $fields) . " WHERE id = ?") ->execute($values); if ($posChanged) assignRumahIbadah($id); $aksi = 'update'; if (isset($in['status_verifikasi'])) { if ($in['status_verifikasi'] === 'terverifikasi') { $aksi = 'verifikasi'; } elseif ($in['status_verifikasi'] === 'ditolak') { $aksi = 'tolak'; } } try { catatRiwayat($id, (int)($in['updated_by'] ?? 1), $aksi, $in['catatan_verifikasi'] ?? null, $dataLama); } catch(\Exception $e) { /* abaikan error audit trail */ } $stmt = $db->prepare($SELECT_SQL . " WHERE pm.id = ?"); $stmt->execute([$id]); jsonResponse(['success' => true, 'data' => $stmt->fetch(), 'message' => 'Data berhasil diperbarui']); break; // ════════════════════════════════════════════════════════ // DELETE – Hapus penduduk // ════════════════════════════════════════════════════════ case 'DELETE': $id = (int)($_GET['id'] ?? getInput()['id'] ?? 0); if (!$id) jsonResponse(['success' => false, 'error' => 'ID diperlukan'], 400); // Hapus foto fisik terlebih dahulu $fotos = $db->prepare("SELECT file_path FROM foto_rumah WHERE penduduk_id = ?"); $fotos->execute([$id]); foreach ($fotos->fetchAll() as $foto) { $path = __DIR__ . '/../' . $foto['file_path']; if (file_exists($path)) @unlink($path); } catatRiwayat($id, 1, 'hapus', 'Data dihapus oleh admin'); $db->prepare("DELETE FROM penduduk_miskin WHERE id = ?")->execute([$id]); jsonResponse(['success' => true, 'message' => 'Data penduduk berhasil dihapus']); break; default: jsonResponse(['success' => false, 'error' => 'Method tidak diizinkan'], 405); } } catch (\Throwable $e) { jsonResponse(['success' => false, 'error' => 'Database error: ' . $e->getMessage()], 500); }