Update Alert Profil Petani

This commit is contained in:
2026-11-04 16:20:33 +07:00
parent 41da3a71ee
commit 89c5a1371a
10 changed files with 943 additions and 1 deletions
+36
View File
@@ -0,0 +1,36 @@
<?php
namespace App\Enums;
// Status kelengkapan data profil petani.
// Draf → Lengkap (ditandai enumerator penginput) → Terverifikasi (oleh koordinator, menu menyusul).
enum StatusDataPetani: string
{
case DRAFT = 'draft';
case LENGKAP = 'lengkap';
case TERVERIFIKASI = 'terverifikasi';
public function label(): string
{
return match ($this) {
self::DRAFT => 'Draf',
self::LENGKAP => 'Lengkap',
self::TERVERIFIKASI => 'Terverifikasi',
};
}
// Warna badge Vuexy (bg-label-{warna})
public function warna(): string
{
return match ($this) {
self::DRAFT => 'secondary',
self::LENGKAP => 'info',
self::TERVERIFIKASI => 'success',
};
}
public static function values(): array
{
return array_column(self::cases(), 'value');
}
}
@@ -0,0 +1,54 @@
<?php
namespace App\Http\Controllers\Web\Produktivitas;
use App\Enums\StatusDataPetani;
use App\Http\Controllers\Controller;
use App\Models\Pertanian\Petani;
use App\Services\Produktivitas\KelengkapanPetani;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
/**
* Enumerator penginput menandai data petani Lengkap, atau membatalkannya kembali ke Draf.
* Perubahan ke Terverifikasi dilakukan koordinator (menu menyusul), bukan di sini.
*/
class PetaniStatusController extends Controller
{
public function update(Request $request, Petani $petani)
{
abort_unless($petani->bisaDitandaiOleh($request->user()), 403);
$validated = $request->validate([
'status' => ['required', Rule::in([StatusDataPetani::LENGKAP->value, StatusDataPetani::DRAFT->value])],
]);
$tujuan = StatusDataPetani::from($validated['status']);
if ($petani->status === StatusDataPetani::TERVERIFIKASI) {
return back()->with('status_petani_error', 'Data petani sudah terverifikasi. Hubungi koordinator untuk mengubahnya.');
}
if ($tujuan === StatusDataPetani::LENGKAP) {
if ($petani->status !== StatusDataPetani::DRAFT) {
return back()->with('status_petani_error', 'Data petani sudah ditandai lengkap.');
}
$wajibKosong = KelengkapanPetani::untuk($petani)->jumlahWajibKosong();
if ($wajibKosong > 0) {
return back()->with('status_petani_error', "Masih ada {$wajibKosong} isian wajib yang kosong.");
}
}
if ($tujuan === StatusDataPetani::DRAFT && $petani->status !== StatusDataPetani::LENGKAP) {
return back()->with('status_petani_error', 'Data petani masih berstatus draf.');
}
$petani->status = $tujuan;
$petani->updated_by = $request->user()->id;
$petani->save();
return back()->with('status_petani_success', $tujuan === StatusDataPetani::LENGKAP
? 'Data petani berhasil ditandai lengkap.'
: 'Tanda lengkap berhasil dibatalkan. Status kembali ke draf.');
}
}
+16
View File
@@ -4,6 +4,8 @@ namespace App\Models\Pertanian;
use App\Enums\Pendidikan;
use App\Enums\Pernikahan;
use App\Enums\Role;
use App\Enums\StatusDataPetani;
use App\Models\User;
use App\Models\Wilayah\DesaKelurahan;
use App\Models\Pertanian\Lahan;
@@ -70,6 +72,12 @@ class Petani extends Model
'pendidikan_terakhir' => Pendidikan::class,
'status_perkawinan' => Pernikahan::class,
'status' => StatusDataPetani::class,
];
// Sengaja tidak masuk $fillable: status hanya diubah lewat PetaniStatusController.
protected $attributes = [
'status' => 'draft',
];
public function getUmurAttribute(): ?int
@@ -113,6 +121,14 @@ class Petani extends Model
return $this->belongsTo(Komoditas::class, 'komoditas_id');
}
// Hanya enumerator yang menginput petani ini yang boleh menandai Lengkap / membatalkannya.
public function bisaDitandaiOleh(?User $user): bool
{
return $user
&& $user->role === Role::ENUMERATOR
&& (int) $this->created_by === (int) $user->id;
}
// Pertanyaan penyerbukan manual hanya berlaku untuk petani vanili.
public function isKomoditasVanili(): bool
{
@@ -0,0 +1,466 @@
<?php
namespace App\Services\Produktivitas;
use App\Enums\AksesPasar\PraktikFermentasi;
use App\Enums\PraktikBudidaya\MetodePenangananHamaPenyakit;
use App\Enums\PraktikBudidaya\PemberianPupuk;
use App\Enums\SosialEkonomi\TerdaftarKelompokTani;
use App\Models\Pertanian\Lahan;
use App\Models\Pertanian\Petani;
use Illuminate\Support\Collection;
/**
* Daftar isian Profil Petani yang WAJIB (dipakai skor Kesiapan Adopsi)
* dan PERLU (dipakai dashboard Karakteristik, Produksi, Pasar).
*
* Satu-satunya tempat daftar ini ditulis. Dipakai oleh ringkasan kelengkapan
* di atas setiap tab petani, dan nanti oleh fitur verifikasi.
*
* Isian yang tidak berlaku (mis. jenis hama kalau "tidak ada hama",
* data kelompok kalau bukan anggota) tidak dimasukkan.
*/
class KelengkapanPetani
{
public const WAJIB = 'wajib';
public const PERLU = 'perlu';
public const TAB_LABEL = [
'profil' => 'Profil',
'lahan' => 'Lahan & Tanaman',
'praktik-budidaya' => 'Praktik Budidaya',
'sosial-ekonomi' => 'Sosial Ekonomi',
'akses-pasar' => 'Akses Pasar',
];
private const KONSERVASI_TIDAK_ADA = 'Tidak ada yang seperti itu di kebun saya';
/*
* Seluruh isian Profil Petani (total 209 untuk 1 lahan), per tabel.
* Aturan hitung: satu pertanyaan = satu isian; kolom repeater dihitung sekali per kolom;
* teks "Lainnya" tidak dihitung; provinsi/kabupaten/kecamatan ikut terisi bila desa terisi.
*/
private const ISIAN_PROFIL = [ // 15 + 3 wilayah = 18
'nama', 'nomor_telepon', 'alamat', 'kk', 'ktp', 'path_kk', 'path_ktp', 'foto',
'jenis_kelamin', 'tempat_lahir', 'tanggal_lahir', 'pendidikan_terakhir', 'status_perkawinan',
'komoditas_id', 'desa_kelurahan_id',
];
private const ISIAN_LAHAN = [ // 18 + 3 wilayah = 21, per lahan
'desa_kelurahan_id', 'path', 'luas_lahan', 'komoditas_id', 'jenis_komoditas_id', 'varietas_id',
'jumlah_tanaman', 'kondisi_tanaman', 'serangan_hama', 'penyakit_tanaman', 'keparahan_serangan',
'jenis_hama', 'jenis_penyakit', 'umur_tanaman', 'umur_lahan', 'komoditas_lain', 'tanaman_penaung', 'foto',
];
private const ISIAN_PRAKTIK_BUDIDAYA = [ // 39
'tenaga_kerja', 'frekuensi_pemangkasan_utama', 'teknik_pemangkasan', 'frekuensi_pemangkasan_penaung',
'penyerbukan_manual', 'deskripsi_penyerbukan_manual', 'tingkat_keberhasilan_penyerbukan',
'pemberian_pupuk', 'jenis_pupuk', 'frekuensi_pemberian_pupuk', 'estimasi_dosis_pupuk_organik',
'estimasi_dosis_pupuk_anorganik', 'waktu_pemberian_pupuk', 'cara_pemberian_pupuk',
'metode_penanganan_opt', 'jenis_hama', 'jenis_penyakit', 'intensitas_serangan', 'waktu_kemunculan_terparah',
'metode_paling_sering_dilakukan', 'volume_obat_obatan_padat_permusim', 'volume_obat_obatan_cair_permusim',
'frekuensi_sanitasi_lahan', 'penanganan_sisa_buah_ranting', 'penanganan_gulma',
'kondisi_pengelolaan_air', 'sumber_air', 'sistem_irigasi', 'kebutuhan_air_permusim',
'kebiasaan_konservasi_tanah', 'pemahaman_manfaat_konservasi_tanah',
'upah_tenaga_kerja', 'biaya_bibit', 'biaya_pupuk', 'biaya_obat_obatan', 'biaya_sewa_alat_produksi',
'biaya_penyediaan_air', 'biaya_produksi_lainnya', 'total_estimasi_biaya_produksi',
];
private const ISIAN_PANEN = [ // 21
'dasar_penentuan_panen', 'frekuensi_panen_pertahun', 'bulan_panen_utama', 'hasil_komoditas_utama_permusim',
'hasil_komoditas_lainnya_permusim', 'disimpan_untuk_sendiri', 'hasil_panen_disimpan', 'tujuan_disimpan',
'praktik_sortasi', 'bentuk_sortasi', 'hasil_panen_reject', 'jumlah_panen_reject', 'tindakan_panen_reject',
'upah_tenaga_panen', 'biaya_pengangkutan_panen', 'biaya_wadah_panen', 'biaya_sewa_alat_panen',
'biaya_panen_lainnya', 'total_estimasi_biaya_panen', 'estimasi_kehilangan_akibat_opt',
'estimasi_kehilangan_akibat_hama_penyakit',
];
private const ISIAN_PRODUKTIVITAS = ['komoditas_id', 'kuantitas', 'satuan']; // 3 (repeater)
private const ISIAN_PELATIHAN = ['jenis_pelatihan', 'penyelenggara', 'tahun_pelaksanaan']; // 3 (repeater)
private const ISIAN_SERTIFIKASI = ['jenis_sertifikasi', 'lembaga_penerbit', 'tahun_pelaksanaan', 'is_aktif']; // 4 (repeater)
private const ISIAN_SOSIAL_EKONOMI = [ // 70 + 4 bantuan NGO + 4 pendapatan komoditas + 2 pendapatan luar = 80
'jumlah_tanggungan', 'anak_masih_sekolah', 'yang_ikut_bekerja', 'skor_periode_paceklik', 'bulan_paceklik',
'sumber_penghasilan_paceklik', 'total_estimasi_biaya_kebun_per_musim', 'skor_kemampuan_menabung',
'estimasi_tabungan_per_bulan', 'skor_kecukupan_penghasilan', 'alasan_kecukupan_penghasilan',
'status_kepemilikan_lahan', 'skor_kejelasan_status_lahan', 'ada_sertifikat_dokumen', 'ada_sengketa',
'keterangan_sengketa', 'pihak_membantu_konflik', 'pihak_membantu_konflik_nama', 'tahun_bantuan_konflik',
'status_konflik', 'kemauan_mencoba_praktik_baru', 'alasan_mencoba_praktik_baru',
'hambatan_mengubah_cara_bertani', 'pemahaman_mutu_harga', 'contoh_pemahaman_mutu_harga',
'pengetahuan_harga_hasil_olah', 'pernah_jual_harga_lebih_tinggi', 'penyebab_harga_lebih_tinggi',
'usaha_mutu_tambahan_harga', 'alasan_usaha_mutu_tambahan_harga', 'panutan_petani_sukses',
'nama_lokasi_panutan', 'kemauan_ikut_program', 'harapan_program', 'orientasi_rencana_jangka_panjang',
'rencana_jangka_panjang', 'terdaftar_kelompok_tani', 'nama_kelompok_tani', 'lama_bergabung_kelompok_tahun',
'kedekatan_sesama_anggota', 'keaktifan_pertemuan_kelompok', 'topik_biasa_dibahas', 'peran_dalam_kelompok',
'manfaat_kelompok_tani', 'status_kartu_tani', 'bisa_beli_pupuk_bersubsidi', 'manfaat_lain_kartu_tani',
'status_rekening_tabungan', 'nama_bank', 'pengalaman_kredit_kebun', 'alasan_kredit_ditolak',
'kondisi_aset_produktif', 'jenis_aset_produktif', 'keterangan_kondisi_aset',
'keterlibatan_program_keuangan_formal', 'status_program_keuangan_formal', 'pernah_bantuan_ngo_perusahaan',
'keterikatan_hutang_panen', 'pinjaman_tokoh_adat_panen', 'keterangan_pinjaman_tokoh_adat',
'total_hutang_rupiah', 'total_hutang_setara_kg_panen', 'total_hutang_tidak_bersedia', 'praktik_ijon',
'tekanan_jual_segera', 'penyebab_tekanan_jual_segera', 'kebebasan_memilih_pembeli',
'alasan_tidak_bebas_memilih_pembeli', 'minat_pembeli_harga_stabil', 'alasan_minat_pembeli_harga_stabil',
];
private const ISIAN_BANTUAN_NGO = ['nama_lembaga', 'bentuk_bantuan', 'frekuensi', 'tahun_terakhir'];
private const ISIAN_PENDAPATAN_KOMODITAS = ['kategori', 'komoditas', 'pendapatan', 'periode'];
private const ISIAN_PENDAPATAN_LUAR = ['jenis_pekerjaan', 'estimasi_per_bulan'];
private const ISIAN_AKSES_PASAR = [ // 16 + 4 harga jual = 20
'tujuan_penjualan', 'sifat_hubungan_bisnis', 'bentuk_penjualan', 'frekuensi_penjualan', 'skor_pengalaman',
'saluran_terdahulu', 'alasan_berhenti', 'tahu_harga_sebelumnya', 'sumber_info_harga', 'praktik_fermentasi',
'proses_fermentasi', 'model_fermentasi_memungkinkan', 'skor_kesadaran_premium', 'sumber_info_premium',
'tingkat_kepercayaan_pembeli_baru', 'alasan_kepercayaan_pembeli_baru',
];
private const ISIAN_HARGA_JUAL = ['bentuk_produk', 'harga_panen_terakhir', 'harga_tahun_lalu', 'harga_dua_tahun_lalu'];
/**
* Satu instance per objek Petani, supaya header dan ringkasan tidak memuat/menghitung ulang.
* WeakMap: otomatis dibuang saat objek Petani tidak dipakai lagi (tidak menumpuk antar-request).
*/
private static ?\WeakMap $cache = null;
private ?Collection $semuaKosong = null;
private ?array $hitungIsian = null;
public function __construct(private Petani $petani)
{
$petani->loadMissing([
'komoditas',
'lahans',
'praktikBudidaya',
'surveyPanen',
'surveyProduktivitas',
'surveyPelatihan',
'surveySertifikasi',
'sosialEkonomi.pendapatanKomoditas',
'sosialEkonomi.pendapatanLuarPertanian',
'aksesPasar',
'hargaJualPanen',
]);
}
public static function untuk(Petani $petani): self
{
self::$cache ??= new \WeakMap();
return self::$cache[$petani] ??= new self($petani);
}
/** Jumlah isian WAJIB (skor) yang masih kosong di semua tab. */
public function jumlahWajibKosong(): int
{
return $this->semuaKosong()->flatten(1)->where('prioritas', self::WAJIB)->count();
}
/**
* Hitungan seluruh isian: terisi, total (209 untuk 1 lahan; +21 per lahan tambahan),
* tidak_berlaku (pertanyaan lanjutan yang tidak perlu dijawab), berlaku, persen.
*/
public function hitungIsian(): array
{
return $this->hitungIsian ??= $this->hitungSemuaIsian();
}
private function hitungSemuaIsian(): array
{
$hasil = ['terisi' => 0, 'total' => 0, 'tidak_berlaku' => 0];
$p = $this->petani;
// Profil (+ provinsi/kabupaten/kecamatan yang mengikuti desa)
$this->hitungKolom($hasil, $p, self::ISIAN_PROFIL);
$this->hitungNilai($hasil, 3, self::terisi($p->desa_kelurahan_id));
// Lahan & Tanaman, per lahan. Belum ada lahan = dihitung 1 lahan kosong.
$daftarLahan = $p->lahans->isEmpty() ? collect([null]) : $p->lahans;
foreach ($daftarLahan as $lahan) {
$this->hitungKolom($hasil, $lahan, self::ISIAN_LAHAN);
$this->hitungNilai($hasil, 3, self::terisi($lahan?->desa_kelurahan_id));
}
// Praktik Budidaya, Panen, Produktivitas
$pb = $p->praktikBudidaya;
$tidakBerlakuPb = [];
if (! $p->isKomoditasVanili()) {
array_push($tidakBerlakuPb, 'penyerbukan_manual', 'deskripsi_penyerbukan_manual', 'tingkat_keberhasilan_penyerbukan');
}
if ((int) $pb?->pemberian_pupuk === PemberianPupuk::TIDAK_PERNAH->value) {
array_push($tidakBerlakuPb, 'jenis_pupuk', 'frekuensi_pemberian_pupuk', 'estimasi_dosis_pupuk_organik',
'estimasi_dosis_pupuk_anorganik', 'waktu_pemberian_pupuk', 'cara_pemberian_pupuk');
}
if ((int) $pb?->metode_penanganan_opt === MetodePenangananHamaPenyakit::TIDAK_ADA->value) {
array_push($tidakBerlakuPb, 'jenis_hama', 'jenis_penyakit');
}
$this->hitungKolom($hasil, $pb, self::ISIAN_PRAKTIK_BUDIDAYA, $tidakBerlakuPb);
$this->hitungKolom($hasil, $p->surveyPanen, self::ISIAN_PANEN);
$this->hitungRepeater($hasil, $p->surveyProduktivitas, self::ISIAN_PRODUKTIVITAS);
// Pendidikan & Pelatihan
$this->hitungRepeater($hasil, $p->surveyPelatihan, self::ISIAN_PELATIHAN);
$this->hitungRepeater($hasil, $p->surveySertifikasi, self::ISIAN_SERTIFIKASI);
// Sosial Ekonomi (data kelompok tidak berlaku kalau bukan anggota)
$se = $p->sosialEkonomi;
$tidakBerlakuSe = self::nilaiEnum($se?->terdaftar_kelompok_tani) === TerdaftarKelompokTani::TIDAK->value
? ['nama_kelompok_tani', 'lama_bergabung_kelompok_tahun', 'kedekatan_sesama_anggota',
'keaktifan_pertemuan_kelompok', 'topik_biasa_dibahas', 'peran_dalam_kelompok', 'manfaat_kelompok_tani']
: [];
$this->hitungKolom($hasil, $se, self::ISIAN_SOSIAL_EKONOMI, $tidakBerlakuSe);
$this->hitungRepeater($hasil, collect($se?->detail_bantuan_ngo_perusahaan ?? []), self::ISIAN_BANTUAN_NGO);
$this->hitungRepeater($hasil, $se?->pendapatanKomoditas ?? collect(), self::ISIAN_PENDAPATAN_KOMODITAS);
$this->hitungRepeater($hasil, $se?->pendapatanLuarPertanian ?? collect(), self::ISIAN_PENDAPATAN_LUAR);
// Akses Pasar (detail fermentasi tidak berlaku kalau belum pernah)
$ap = $p->aksesPasar;
$tidakBerlakuAp = self::nilaiEnum($ap?->praktik_fermentasi) === PraktikFermentasi::BELUM_PERNAH->value
? ['proses_fermentasi', 'model_fermentasi_memungkinkan']
: [];
$this->hitungKolom($hasil, $ap, self::ISIAN_AKSES_PASAR, $tidakBerlakuAp);
$this->hitungRepeater($hasil, $p->hargaJualPanen, self::ISIAN_HARGA_JUAL);
$hasil['berlaku'] = $hasil['total'] - $hasil['tidak_berlaku'];
$hasil['persen'] = $hasil['berlaku'] > 0 ? (int) floor($hasil['terisi'] / $hasil['berlaku'] * 100) : 0;
return $hasil;
}
private static function nilaiEnum(mixed $nilai): mixed
{
return $nilai instanceof \BackedEnum ? $nilai->value : $nilai;
}
private function hitungKolom(array &$hasil, ?object $model, array $kolom, array $tidakBerlaku = []): void
{
foreach ($kolom as $k) {
$hasil['total']++;
if (in_array($k, $tidakBerlaku, true)) {
$hasil['tidak_berlaku']++;
} elseif ($model && self::terisi($model->{$k})) {
$hasil['terisi']++;
}
}
}
private function hitungNilai(array &$hasil, int $jumlah, bool $terisi): void
{
$hasil['total'] += $jumlah;
$hasil['terisi'] += $terisi ? $jumlah : 0;
}
/** Kolom repeater dianggap terisi kalau minimal satu baris mengisinya. */
private function hitungRepeater(array &$hasil, Collection $baris, array $kolom): void
{
foreach ($kolom as $k) {
$hasil['total']++;
$hasil['terisi'] += $baris->contains(fn ($row) => self::terisi(data_get($row, $k))) ? 1 : 0;
}
}
/**
* Isian yang masih kosong di satu tab.
* Setiap item: label, prioritas (wajib/perlu), input (atribut name di form),
* grup (judul pengelompokan, mis. nama lahan), lahan_id, url (opsional, halaman lain).
*/
public function kosong(string $tab): Collection
{
return $this->semuaKosong()->get($tab, collect());
}
private function hitungKosong(string $tab): Collection
{
$isian = match ($tab) {
'profil' => $this->profil(),
'lahan' => $this->lahan(),
'praktik-budidaya' => $this->praktikBudidaya(),
'sosial-ekonomi' => $this->sosialEkonomi(),
'akses-pasar' => $this->aksesPasar(),
default => [],
};
return collect($isian)
->reject(fn (array $item) => $item['terisi'])
->map(fn (array $item) => collect($item)->except('terisi')->all())
->values();
}
/** Isian kosong semua tab, dikelompokkan per tab (untuk tab Kesiapan Adopsi). */
public function semuaKosong(): Collection
{
return $this->semuaKosong ??= collect(array_keys(self::TAB_LABEL))
->mapWithKeys(fn (string $tab) => [$tab => $this->hitungKosong($tab)]);
}
private function item(string $label, string $prioritas, string $input, mixed $nilai, array $tambahan = []): array
{
return $this->itemCek($label, $prioritas, $input, self::terisi($nilai), $tambahan);
}
/** Untuk isian yang status terisinya dihitung sendiri (mis. repeater). */
private function itemCek(string $label, string $prioritas, string $input, bool $terisi, array $tambahan = []): array
{
return [
'label' => $label,
'prioritas' => $prioritas,
'input' => $input,
'terisi' => $terisi,
'grup' => null,
'lahan_id' => null,
'url' => null,
...$tambahan,
];
}
private static function terisi(mixed $nilai): bool
{
if ($nilai instanceof Collection) {
return $nilai->isNotEmpty();
}
return ! (is_null($nilai) || $nilai === '' || $nilai === []);
}
private function profil(): array
{
$p = $this->petani;
return [
$this->item('Jenis kelamin', self::PERLU, 'jenis_kelamin', $p->jenis_kelamin),
$this->item('Tanggal lahir', self::PERLU, 'tanggal_lahir', $p->tanggal_lahir),
$this->item('Pendidikan terakhir', self::PERLU, 'pendidikan_terakhir', $p->pendidikan_terakhir),
$this->item('Komoditas utama', self::PERLU, 'komoditas_id', $p->komoditas_id),
$this->item('Desa/kelurahan', self::PERLU, 'desa_kelurahan_id', $p->desa_kelurahan_id),
];
}
/** Diisi per lahan; setiap item membawa link ke halaman edit lahannya. */
private function lahan(): array
{
$daftarLahan = $this->petani->lahans->sortBy('id');
if ($daftarLahan->isEmpty()) {
return [$this->itemCek('Belum ada lahan', self::PERLU, '', false, [
'url' => route('petani.lahan-tanaman.create', $this->petani->id),
])];
}
return $daftarLahan->values()->flatMap(function (Lahan $l, int $i) {
$tambahan = [
'grup' => $l->nama ?: 'Lahan ' . ($i + 1),
'lahan_id' => $l->id,
'url' => route('petani.lahan-tanaman.edit', [$this->petani->id, $l->id]),
];
$adaSerangan = self::terisi($l->serangan_hama) || self::terisi($l->penyakit_tanaman);
return array_filter([
$this->item('Luas lahan', self::PERLU, 'luas_lahan', $l->luas_lahan, $tambahan),
$this->item('Peta/polygon lahan', self::PERLU, 'path', $l->path, $tambahan),
$this->item('Desa lahan', self::PERLU, 'desa_kelurahan_id', $l->desa_kelurahan_id, $tambahan),
$this->item('Umur tanaman', self::PERLU, 'umur_tanaman', $l->umur_tanaman, $tambahan),
$this->item('Jumlah tanaman', self::PERLU, 'jumlah_tanaman', $l->jumlah_tanaman, $tambahan),
$this->item('Kondisi tanaman', self::PERLU, 'kondisi_tanaman', $l->kondisi_tanaman, $tambahan),
$this->item('Serangan hama', self::PERLU, 'serangan_hama', $l->serangan_hama, $tambahan),
$this->item('Penyakit tanaman', self::PERLU, 'penyakit_tanaman', $l->penyakit_tanaman, $tambahan),
$adaSerangan
? $this->item('Keparahan serangan', self::PERLU, 'keparahan_serangan', $l->keparahan_serangan, $tambahan)
: null,
]);
})->all();
}
private function praktikBudidaya(): array
{
$pb = $this->petani->praktikBudidaya;
$panen = $this->petani->surveyPanen;
$adaHama = $pb && self::terisi($pb->metode_penanganan_opt)
&& (int) $pb->metode_penanganan_opt !== MetodePenangananHamaPenyakit::TIDAK_ADA->value;
$konservasi = (array) ($pb?->kebiasaan_konservasi_tanah ?? []);
$adaKonservasi = collect($konservasi)->contains(fn ($v) => $v !== self::KONSERVASI_TIDAK_ADA);
return array_filter([
$this->item('Frekuensi pemangkasan', self::WAJIB, 'frekuensi_pemangkasan_komoditas_utama', $pb?->frekuensi_pemangkasan_utama),
$this->item('Pemberian pupuk', self::WAJIB, 'pemberian_pupuk', $pb?->pemberian_pupuk),
$this->item('Metode penanganan hama & penyakit', self::WAJIB, 'metode_penanganan_hama_penyakit', $pb?->metode_penanganan_opt),
$adaHama ? $this->item('Jenis hama', self::PERLU, 'jenis_hama', $pb->jenis_hama) : null,
$adaHama ? $this->item('Jenis penyakit', self::PERLU, 'jenis_penyakit', $pb->jenis_penyakit) : null,
$this->item('Frekuensi sanitasi lahan', self::WAJIB, 'frekuensi_sanitasi_lahan', $pb?->frekuensi_sanitasi_lahan),
$this->item('Penanganan gulma', self::WAJIB, 'penanganan_gulma', $pb?->penanganan_gulma),
$this->item('Kondisi pengelolaan air & irigasi', self::WAJIB, 'kondisi_pengelolaan_air_irigasi', $pb?->kondisi_pengelolaan_air),
$this->item('Sumber air', self::PERLU, 'sumber_air', $pb?->sumber_air),
$this->item('Kebiasaan konservasi tanah', self::WAJIB, 'kebiasaan_konservasi_tanah', $konservasi),
$adaKonservasi
? $this->item('Pemahaman manfaat konservasi', self::WAJIB, 'pemahaman_manfaat_konservasi', $pb->pemahaman_manfaat_konservasi_tanah)
: null,
$this->item('Total estimasi biaya produksi', self::WAJIB, 'total_estimasi_biaya_produksi', $pb?->total_estimasi_biaya_produksi),
$this->item('Dasar penentuan panen', self::WAJIB, 'dasar_penentuan_panen', $panen?->dasar_penentuan_panen),
$this->item('Hasil komoditas utama per musim', self::PERLU, 'hasil_komoditas_utama_permusim', $panen?->hasil_komoditas_utama_permusim),
$this->item('Praktik sortasi', self::WAJIB, 'praktik_sortasi', $panen?->praktik_sortasi),
]);
}
private function sosialEkonomi(): array
{
$se = $this->petani->sosialEkonomi;
$anggotaKelompok = $se?->terdaftar_kelompok_tani === TerdaftarKelompokTani::YA
|| $se?->terdaftar_kelompok_tani === TerdaftarKelompokTani::YA->value;
$adaPendapatan = (bool) $se?->pendapatanKomoditas->contains(fn ($row) => (int) $row->pendapatan > 0);
return array_filter([
// Utama
$this->item('Jumlah tanggungan', self::PERLU, 'jumlah_tanggungan', $se?->jumlah_tanggungan),
$this->itemCek('Pendapatan per komoditas', self::WAJIB, 'pendapatan_komoditas', $adaPendapatan),
$this->item('Periode paceklik', self::WAJIB, 'skor_periode_paceklik', $se?->skor_periode_paceklik),
$this->item('Kemampuan menabung', self::WAJIB, 'skor_kemampuan_menabung', $se?->skor_kemampuan_menabung),
$this->item('Kecukupan penghasilan', self::WAJIB, 'skor_kecukupan_penghasilan', $se?->skor_kecukupan_penghasilan),
$this->item('Kejelasan status lahan', self::WAJIB, 'skor_kejelasan_status_lahan', $se?->skor_kejelasan_status_lahan),
// Sosial budaya
$this->item('Kemauan mencoba praktik baru', self::WAJIB, 'kemauan_mencoba_praktik_baru', $se?->kemauan_mencoba_praktik_baru),
$this->item('Pemahaman mutu & harga', self::WAJIB, 'pemahaman_mutu_harga', $se?->pemahaman_mutu_harga),
$this->item('Panutan petani sukses', self::WAJIB, 'panutan_petani_sukses', $se?->panutan_petani_sukses),
$this->item('Kemauan ikut program', self::WAJIB, 'kemauan_ikut_program', $se?->kemauan_ikut_program),
$this->item('Orientasi rencana jangka panjang', self::WAJIB, 'orientasi_rencana_jangka_panjang', $se?->orientasi_rencana_jangka_panjang),
// Keanggotaan kelompok: pertanyaan lanjutan hanya untuk anggota
$this->item('Terdaftar di kelompok tani', self::WAJIB, 'terdaftar_kelompok_tani', $se?->terdaftar_kelompok_tani),
$anggotaKelompok ? $this->item('Keaktifan pertemuan kelompok', self::WAJIB, 'keaktifan_pertemuan_kelompok', $se->keaktifan_pertemuan_kelompok) : null,
$anggotaKelompok ? $this->item('Peran dalam kelompok', self::WAJIB, 'peran_dalam_kelompok', $se->peran_dalam_kelompok) : null,
$anggotaKelompok ? $this->item('Manfaat kelompok tani', self::WAJIB, 'manfaat_kelompok_tani', $se->manfaat_kelompok_tani) : null,
// Akses keuangan
$this->item('Status rekening tabungan', self::WAJIB, 'status_rekening_tabungan', $se?->status_rekening_tabungan),
$this->item('Pengalaman kredit kebun', self::WAJIB, 'pengalaman_kredit_kebun', $se?->pengalaman_kredit_kebun),
$this->item('Kondisi aset produktif', self::WAJIB, 'kondisi_aset_produktif', $se?->kondisi_aset_produktif),
$this->item('Keterlibatan program keuangan formal', self::WAJIB, 'keterlibatan_program_keuangan_formal', $se?->keterlibatan_program_keuangan_formal),
// Keterikatan hutang
$this->item('Keterikatan hutang panen', self::WAJIB, 'keterikatan_hutang_panen', $se?->keterikatan_hutang_panen),
$this->item('Praktik ijon', self::WAJIB, 'praktik_ijon', $se?->praktik_ijon),
$this->item('Kebebasan memilih pembeli', self::WAJIB, 'kebebasan_memilih_pembeli', $se?->kebebasan_memilih_pembeli),
]);
}
private function aksesPasar(): array
{
$ap = $this->petani->aksesPasar;
$harga = $this->petani->hargaJualPanen;
return [
$this->item('Tujuan penjualan', self::PERLU, 'tujuan_penjualan', $ap?->tujuan_penjualan),
$this->item('Sifat hubungan dengan pembeli', self::PERLU, 'sifat_hubungan_bisnis', $ap?->sifat_hubungan_bisnis),
$this->item('Frekuensi penjualan', self::PERLU, 'frekuensi_penjualan', $ap?->frekuensi_penjualan),
$this->item('Pengalaman menjual ke saluran lain', self::WAJIB, 'skor_pengalaman', $ap?->skor_pengalaman),
$this->item('Praktik fermentasi', self::PERLU, 'praktik_fermentasi', $ap?->praktik_fermentasi),
$this->item('Kesadaran pasar premium', self::WAJIB, 'skor_kesadaran_premium', $ap?->skor_kesadaran_premium),
$this->item('Kepercayaan pada pembeli baru', self::WAJIB, 'tingkat_kepercayaan_pembeli_baru', $ap?->tingkat_kepercayaan_pembeli_baru),
$this->itemCek('Harga jual panen terakhir', self::PERLU, 'harga_jual',
$harga->contains(fn ($h) => self::terisi($h->bentuk_produk) && (float) $h->harga_panen_terakhir > 0)),
$this->itemCek('Harga jual tahun lalu', self::PERLU, 'harga_jual',
$harga->contains(fn ($h) => (float) $h->harga_tahun_lalu > 0)),
$this->itemCek('Harga jual dua tahun lalu', self::PERLU, 'harga_jual',
$harga->contains(fn ($h) => (float) $h->harga_dua_tahun_lalu > 0)),
];
}
}