delete folder backup
This commit is contained in:
@@ -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()]);
|
||||
}
|
||||
Reference in New Issue
Block a user