Update Status kelengkapan data

This commit is contained in:
2026-11-04 17:31:22 +07:00
parent 89c5a1371a
commit 8ef4ae1f15
18 changed files with 245 additions and 53 deletions
@@ -21,6 +21,10 @@ class PetaniStatusController extends Controller
$validated = $request->validate([ $validated = $request->validate([
'status' => ['required', Rule::in([StatusDataPetani::LENGKAP->value, StatusDataPetani::DRAFT->value])], 'status' => ['required', Rule::in([StatusDataPetani::LENGKAP->value, StatusDataPetani::DRAFT->value])],
// Enumerator wajib menyetujui pernyataan kelengkapan saat menandai lengkap.
'pernyataan' => ['exclude_unless:status,' . StatusDataPetani::LENGKAP->value, 'accepted'],
], [
'pernyataan.accepted' => 'Centang pernyataan kelengkapan data terlebih dahulu.',
]); ]);
$tujuan = StatusDataPetani::from($validated['status']); $tujuan = StatusDataPetani::from($validated['status']);
@@ -0,0 +1,85 @@
<?php
namespace App\Models\Concerns;
use App\Enums\StatusDataPetani;
use App\Models\Pertanian\Petani;
use Closure;
/**
* Data petani yang sudah ditandai "Lengkap" lalu diubah/ditambah/dihapus
* otomatis kembali ke "Draf", supaya enumerator menandai lengkap lagi setelah selesai.
*
* Dipasang di semua model yang menyimpan data profil petani (web maupun API).
* Model yang tidak punya kolom petani_id meng-override petaniIdUntukStatus().
*/
trait KembalikanStatusPetaniKeDraf
{
// Kolom otomatis/audit yang tidak dihitung sebagai perubahan data.
private static array $kolomBukanPerubahanData = [
'created_at', 'updated_at', 'deleted_at', 'created_by', 'updated_by', 'deleted_by', 'status',
];
public static function bootKembalikanStatusPetaniKeDraf(): void
{
static::saved(function ($model) {
$kolomBerubah = array_diff(array_keys($model->getChanges()), self::$kolomBukanPerubahanData);
if ($model->wasRecentlyCreated || $kolomBerubah) {
$model->kembalikanStatusPetaniKeDraf();
}
});
static::deleted(fn ($model) => $model->kembalikanStatusPetaniKeDraf());
}
protected function petaniIdUntukStatus(): ?int
{
return $this->petani_id;
}
public function kembalikanStatusPetaniKeDraf(): void
{
$petaniId = $this->petaniIdUntukStatus();
if (! $petaniId) {
return;
}
// Satu simpan bisa memicu ratusan baris (mis. sinkron jenis hama ke semua tanaman).
// Cukup sekali per petani per request: setelah dicek, status pasti bukan "Lengkap" lagi.
self::ingatPerRequest('status_petani.dicek', $petaniId, function () use ($petaniId) {
// Query langsung (tanpa event model) supaya tidak memicu hook lain / berulang.
$diubah = Petani::withoutGlobalScopes()
->whereKey($petaniId)
->where('status', StatusDataPetani::LENGKAP->value)
->update(['status' => StatusDataPetani::DRAFT->value]);
if ($diubah && request()->hasSession()) {
request()->session()->flash('status_petani_info', 'Data petani diubah, status kembali ke Draf. Tandai lengkap lagi setelah selesai.');
}
return true;
});
}
/**
* Simpan hasil $ambil() selama satu request web, per $kunci + $id.
* Di console/queue tidak disimpan (proses berjalan lama, data bisa berubah antar-job).
*/
protected static function ingatPerRequest(string $kunci, int|string|null $id, Closure $ambil): mixed
{
if ($id === null || app()->runningInConsole()) {
return $ambil();
}
$bag = request()->attributes;
$cache = $bag->get($kunci, []);
if (! array_key_exists($id, $cache)) {
$cache[$id] = $ambil();
$bag->set($kunci, $cache);
}
return $cache[$id];
}
}
+3 -2
View File
@@ -2,6 +2,7 @@
namespace App\Models\Pertanian; namespace App\Models\Pertanian;
use App\Models\Concerns\KembalikanStatusPetaniKeDraf;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Str; use Illuminate\Support\Str;
@@ -15,7 +16,7 @@ use App\Enums\AksesPasar\SkorKesadaranPremium;
class AksesPasar extends Model class AksesPasar extends Model
{ {
use SoftDeletes; use SoftDeletes, KembalikanStatusPetaniKeDraf;
protected $table = 'akses_pasar'; protected $table = 'akses_pasar';
@@ -77,7 +78,7 @@ class AksesPasar extends Model
$skor->update([ $skor->update([
'skor_d6_k1_6_pengalaman_menjual_ke_saluran_berbeda' => $aksesPasar->skor_pengalaman, 'skor_d6_k1_6_pengalaman_menjual_ke_saluran_berbeda' => $aksesPasar->skor_pengalaman,
'skor_d6_k4_1_kesadaran_akses_pasar_premium' => $aksesPasar->skor_kesadaran_premium, 'skor_d6_k4_1_kesadaran_akses_pasar_premium' => $aksesPasar->skor_kesadaran_premium,
'skor_d6_k5_1_hambatan_mutu' => $aksesPasar->skor_kepercayaan_responden // skor_d6_k5_1_hambatan_mutu diisi dari SurveyPanen (tingkat hasil panen ditolak).
]); ]);
}); });
} }
+3
View File
@@ -2,12 +2,15 @@
namespace App\Models\Pertanian; namespace App\Models\Pertanian;
use App\Models\Concerns\KembalikanStatusPetaniKeDraf;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use App\Models\User; use App\Models\User;
use App\Models\Pertanian\Petani; use App\Models\Pertanian\Petani;
class HargaJualPanen extends Model class HargaJualPanen extends Model
{ {
use KembalikanStatusPetaniKeDraf;
protected $table = 'harga_jual_panen'; protected $table = 'harga_jual_panen';
protected $fillable = [ protected $fillable = [
+2 -1
View File
@@ -2,6 +2,7 @@
namespace App\Models\Pertanian; namespace App\Models\Pertanian;
use App\Models\Concerns\KembalikanStatusPetaniKeDraf;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
use App\Models\Wilayah\DesaKelurahan; use App\Models\Wilayah\DesaKelurahan;
@@ -22,7 +23,7 @@ use App\Models\Pertanian\Petani;
class Lahan extends Model class Lahan extends Model
{ {
use SoftDeletes; use SoftDeletes, KembalikanStatusPetaniKeDraf;
protected $table = 'map_lahan'; protected $table = 'map_lahan';
@@ -2,11 +2,14 @@
namespace App\Models\Pertanian; namespace App\Models\Pertanian;
use App\Models\Concerns\KembalikanStatusPetaniKeDraf;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
class PendapatanKomoditas extends Model class PendapatanKomoditas extends Model
{ {
use KembalikanStatusPetaniKeDraf;
protected $table = 'pendapatan_komoditas'; protected $table = 'pendapatan_komoditas';
protected $fillable = [ protected $fillable = [
@@ -29,4 +32,11 @@ class PendapatanKomoditas extends Model
{ {
return $this->belongsTo(SosialEkonomi::class, 'sosial_ekonomi_id'); return $this->belongsTo(SosialEkonomi::class, 'sosial_ekonomi_id');
} }
// Untuk trait KembalikanStatusPetaniKeDraf: petani diambil dari data sosial ekonomi.
protected function petaniIdUntukStatus(): ?int
{
return self::ingatPerRequest('status_petani.sosial_ekonomi', $this->sosial_ekonomi_id,
fn () => SosialEkonomi::withoutGlobalScopes()->whereKey($this->sosial_ekonomi_id)->value('petani_id'));
}
} }
@@ -2,11 +2,14 @@
namespace App\Models\Pertanian; namespace App\Models\Pertanian;
use App\Models\Concerns\KembalikanStatusPetaniKeDraf;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
class PendapatanLuarPertanian extends Model class PendapatanLuarPertanian extends Model
{ {
use KembalikanStatusPetaniKeDraf;
protected $table = 'pendapatan_luar_pertanian'; protected $table = 'pendapatan_luar_pertanian';
protected $fillable = [ protected $fillable = [
@@ -27,4 +30,11 @@ class PendapatanLuarPertanian extends Model
{ {
return $this->belongsTo(SosialEkonomi::class, 'sosial_ekonomi_id'); return $this->belongsTo(SosialEkonomi::class, 'sosial_ekonomi_id');
} }
// Untuk trait KembalikanStatusPetaniKeDraf: petani diambil dari data sosial ekonomi.
protected function petaniIdUntukStatus(): ?int
{
return self::ingatPerRequest('status_petani.sosial_ekonomi', $this->sosial_ekonomi_id,
fn () => SosialEkonomi::withoutGlobalScopes()->whereKey($this->sosial_ekonomi_id)->value('petani_id'));
}
} }
+8 -1
View File
@@ -2,6 +2,7 @@
namespace App\Models\Pertanian; namespace App\Models\Pertanian;
use App\Models\Concerns\KembalikanStatusPetaniKeDraf;
use App\Enums\Pendidikan; use App\Enums\Pendidikan;
use App\Enums\Pernikahan; use App\Enums\Pernikahan;
use App\Enums\Role; use App\Enums\Role;
@@ -30,7 +31,7 @@ use Illuminate\Support\Str;
class Petani extends Model class Petani extends Model
{ {
use SoftDeletes; use SoftDeletes, KembalikanStatusPetaniKeDraf;
protected $table = 'petani'; protected $table = 'petani';
@@ -420,4 +421,10 @@ class Petani extends Model
{ {
return $this->belongsTo(User::class, 'created_by'); return $this->belongsTo(User::class, 'created_by');
} }
// Untuk trait KembalikanStatusPetaniKeDraf: petani ini sendiri.
protected function petaniIdUntukStatus(): ?int
{
return $this->id;
}
} }
+38 -22
View File
@@ -2,6 +2,7 @@
namespace App\Models\Pertanian; namespace App\Models\Pertanian;
use App\Models\Concerns\KembalikanStatusPetaniKeDraf;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
@@ -18,7 +19,7 @@ use App\Models\Pertanian\Lahan;
class PraktikBudidaya extends Model class PraktikBudidaya extends Model
{ {
use SoftDeletes; use SoftDeletes, KembalikanStatusPetaniKeDraf;
protected $table = 'praktik_budidaya'; protected $table = 'praktik_budidaya';
@@ -101,27 +102,11 @@ class PraktikBudidaya extends Model
$jenisHama = $praktikBudidaya->jenis_hama ?? []; $jenisHama = $praktikBudidaya->jenis_hama ?? [];
$jenisPenyakit = $praktikBudidaya->jenis_penyakit ?? []; $jenisPenyakit = $praktikBudidaya->jenis_penyakit ?? [];
if (Schema::hasColumn((new Lahan)->getTable(), 'jenis_hama') // Salin jenis hama/penyakit ke semua lahan & tanaman petani: satu query per tabel
&& Schema::hasColumn((new Lahan)->getTable(), 'jenis_penyakit')) { // (sebelumnya satu query per baris; petani bisa punya ratusan titik tanaman).
Lahan::where('petani_id', $praktikBudidaya->petani_id)->get()->each(function (Lahan $lahan) use ($jenisHama, $jenisPenyakit, $praktikBudidaya) { // Hanya baris yang isinya berbeda yang diubah, supaya updated_at baris lain tidak ikut berubah.
$lahan->update([ self::sinkronHamaPenyakit(new Lahan, $praktikBudidaya, $jenisHama, $jenisPenyakit);
'jenis_hama' => $jenisHama, self::sinkronHamaPenyakit(new Tanaman, $praktikBudidaya, $jenisHama, $jenisPenyakit);
'jenis_penyakit' => $jenisPenyakit,
'updated_by' => $praktikBudidaya->updated_by,
]);
});
}
if (Schema::hasColumn((new Tanaman)->getTable(), 'jenis_hama')
&& Schema::hasColumn((new Tanaman)->getTable(), 'jenis_penyakit')) {
Tanaman::where('petani_id', $praktikBudidaya->petani_id)->get()->each(function (Tanaman $tanaman) use ($jenisHama, $jenisPenyakit, $praktikBudidaya) {
$tanaman->update([
'jenis_hama' => $jenisHama,
'jenis_penyakit' => $jenisPenyakit,
'updated_by' => $praktikBudidaya->updated_by,
]);
});
}
if ($kebiasaan == 0) { if ($kebiasaan == 0) {
$skor_konservasi_tanah = 1; $skor_konservasi_tanah = 1;
@@ -146,6 +131,37 @@ class PraktikBudidaya extends Model
}); });
} }
/** @var array<string, bool> hasil cek kolom per tabel (cukup sekali per proses) */
private static array $punyaKolomHama = [];
private static function sinkronHamaPenyakit(Model $model, PraktikBudidaya $praktikBudidaya, array $jenisHama, array $jenisPenyakit): void
{
$tabel = $model->getTable();
$ada = self::$punyaKolomHama[$tabel] ??= Schema::hasColumns($tabel, ['jenis_hama', 'jenis_penyakit']);
if (! $ada) {
return;
}
// Format JSON sama dengan cast 'array' di model Lahan/Tanaman.
$hama = json_encode($jenisHama);
$penyakit = json_encode($jenisPenyakit);
// Query builder: tidak memicu event per baris. Status petani sudah dikembalikan ke Draf
// oleh event PraktikBudidaya sendiri (trait KembalikanStatusPetaniKeDraf).
$model->newQuery()
->where('petani_id', $praktikBudidaya->petani_id)
// Kolom jsonb: dibandingkan sebagai jsonb (bukan teks) karena PostgreSQL menormalkan formatnya.
->where(fn ($q) => $q
->whereRaw('jenis_hama::jsonb IS DISTINCT FROM ?::jsonb', [$hama])
->orWhereRaw('jenis_penyakit::jsonb IS DISTINCT FROM ?::jsonb', [$penyakit]))
->update([
'jenis_hama' => $hama,
'jenis_penyakit' => $penyakit,
'updated_by' => $praktikBudidaya->updated_by,
'updated_at' => now(),
]);
}
public function petani(): BelongsTo public function petani(): BelongsTo
{ {
return $this->belongsTo(Petani::class, 'petani_id'); return $this->belongsTo(Petani::class, 'petani_id');
+2 -1
View File
@@ -2,6 +2,7 @@
namespace App\Models\Pertanian; namespace App\Models\Pertanian;
use App\Models\Concerns\KembalikanStatusPetaniKeDraf;
use App\Enums\SosialEkonomi\AlasanKreditDitolak; use App\Enums\SosialEkonomi\AlasanKreditDitolak;
use App\Enums\SosialEkonomi\BisaBeliPupukBersubsidi; use App\Enums\SosialEkonomi\BisaBeliPupukBersubsidi;
use App\Enums\SosialEkonomi\JenisAsetProduktif; use App\Enums\SosialEkonomi\JenisAsetProduktif;
@@ -51,7 +52,7 @@ use Illuminate\Support\Str;
class SosialEkonomi extends Model class SosialEkonomi extends Model
{ {
use SoftDeletes; use SoftDeletes, KembalikanStatusPetaniKeDraf;
protected $table = 'sosial_ekonomi'; protected $table = 'sosial_ekonomi';
+5 -1
View File
@@ -2,6 +2,7 @@
namespace App\Models\Pertanian; namespace App\Models\Pertanian;
use App\Models\Concerns\KembalikanStatusPetaniKeDraf;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
@@ -11,7 +12,7 @@ use App\Models\Pertanian\Petani;
class SurveyPanen extends Model class SurveyPanen extends Model
{ {
use SoftDeletes; use SoftDeletes, KembalikanStatusPetaniKeDraf;
protected $table = 'survey_panen'; protected $table = 'survey_panen';
@@ -64,6 +65,9 @@ class SurveyPanen extends Model
$skor->update([ $skor->update([
'skor_d2_k3_1_penentuan_pematangan' => $survey->dasar_penentuan_panen, 'skor_d2_k3_1_penentuan_pematangan' => $survey->dasar_penentuan_panen,
'skor_d2_k3_5_sortasi_awal_pemisahan_kualitas' => $survey->praktik_sortasi, 'skor_d2_k3_5_sortasi_awal_pemisahan_kualitas' => $survey->praktik_sortasi,
// Hambatan mutu = "Tingkat hasil panen ditolak pembeli" (TingkatPanenReject, 1–5).
// Kolomnya bernama hasil_panen_reject, tetapi isinya tingkat reject.
'skor_d6_k5_1_hambatan_mutu' => $survey->hasil_panen_reject,
]); ]);
}); });
} }
+2 -1
View File
@@ -2,6 +2,7 @@
namespace App\Models\Pertanian; namespace App\Models\Pertanian;
use App\Models\Concerns\KembalikanStatusPetaniKeDraf;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
@@ -11,7 +12,7 @@ use App\Models\Pertanian\Petani;
class SurveyPelatihan extends Model class SurveyPelatihan extends Model
{ {
use HasFactory, SoftDeletes; use HasFactory, SoftDeletes, KembalikanStatusPetaniKeDraf;
protected $table = 'survey_pelatihan'; protected $table = 'survey_pelatihan';
+2 -1
View File
@@ -2,6 +2,7 @@
namespace App\Models\Pertanian; namespace App\Models\Pertanian;
use App\Models\Concerns\KembalikanStatusPetaniKeDraf;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
@@ -9,7 +10,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
class SurveyProduktivitas extends Model class SurveyProduktivitas extends Model
{ {
use SoftDeletes; use SoftDeletes, KembalikanStatusPetaniKeDraf;
protected $table = 'survey_produktivitas'; protected $table = 'survey_produktivitas';
+2 -1
View File
@@ -2,6 +2,7 @@
namespace App\Models\Pertanian; namespace App\Models\Pertanian;
use App\Models\Concerns\KembalikanStatusPetaniKeDraf;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
@@ -9,7 +10,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
class SurveySertifikasi extends Model class SurveySertifikasi extends Model
{ {
use HasFactory, SoftDeletes; use HasFactory, SoftDeletes, KembalikanStatusPetaniKeDraf;
protected $table = 'survey_sertifikasi'; protected $table = 'survey_sertifikasi';
+9 -1
View File
@@ -2,6 +2,7 @@
namespace App\Models\Pertanian; namespace App\Models\Pertanian;
use App\Models\Concerns\KembalikanStatusPetaniKeDraf;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
use App\Models\Profile; use App\Models\Profile;
@@ -17,7 +18,7 @@ use App\Enums\KeparahanSerangan;
class Tanaman extends Model class Tanaman extends Model
{ {
use SoftDeletes; use SoftDeletes, KembalikanStatusPetaniKeDraf;
protected $table = 'map_tanaman'; protected $table = 'map_tanaman';
@@ -80,4 +81,11 @@ class Tanaman extends Model
{ {
return $this->belongsTo(Lahan::class, 'lahan_id'); return $this->belongsTo(Lahan::class, 'lahan_id');
} }
// Untuk trait KembalikanStatusPetaniKeDraf: petani diambil dari lahannya.
protected function petaniIdUntukStatus(): ?int
{
return $this->petani_id ?? self::ingatPerRequest('status_petani.lahan', $this->lahan_id,
fn () => Lahan::withoutGlobalScopes()->whereKey($this->lahan_id)->value('petani_id'));
}
} }
@@ -36,7 +36,7 @@ class KelengkapanPetani
private const KONSERVASI_TIDAK_ADA = 'Tidak ada yang seperti itu di kebun saya'; private const KONSERVASI_TIDAK_ADA = 'Tidak ada yang seperti itu di kebun saya';
/* /*
* Seluruh isian Profil Petani (total 209 untuk 1 lahan), per tabel. * Seluruh isian Profil Petani (total 207 untuk 1 lahan), per tabel.
* Aturan hitung: satu pertanyaan = satu isian; kolom repeater dihitung sekali per kolom; * Aturan hitung: satu pertanyaan = satu isian; kolom repeater dihitung sekali per kolom;
* teks "Lainnya" tidak dihitung; provinsi/kabupaten/kecamatan ikut terisi bila desa terisi. * teks "Lainnya" tidak dihitung; provinsi/kabupaten/kecamatan ikut terisi bila desa terisi.
*/ */
@@ -46,9 +46,9 @@ class KelengkapanPetani
'komoditas_id', 'desa_kelurahan_id', 'komoditas_id', 'desa_kelurahan_id',
]; ];
private const ISIAN_LAHAN = [ // 18 + 3 wilayah = 21, per lahan private const ISIAN_LAHAN = [ // 16 + 3 wilayah = 19, per lahan
'desa_kelurahan_id', 'path', 'luas_lahan', 'komoditas_id', 'jenis_komoditas_id', 'varietas_id', 'desa_kelurahan_id', 'path', 'luas_lahan', 'komoditas_id', 'jenis_komoditas_id', 'varietas_id',
'jumlah_tanaman', 'kondisi_tanaman', 'serangan_hama', 'penyakit_tanaman', 'keparahan_serangan', 'jumlah_tanaman', 'kondisi_tanaman', 'keparahan_serangan',
'jenis_hama', 'jenis_penyakit', 'umur_tanaman', 'umur_lahan', 'komoditas_lain', 'tanaman_penaung', 'foto', 'jenis_hama', 'jenis_penyakit', 'umur_tanaman', 'umur_lahan', 'komoditas_lain', 'tanaman_penaung', 'foto',
]; ];
@@ -153,7 +153,7 @@ class KelengkapanPetani
} }
/** /**
* Hitungan seluruh isian: terisi, total (209 untuk 1 lahan; +21 per lahan tambahan), * Hitungan seluruh isian: terisi, total (207 untuk 1 lahan; +19 per lahan tambahan),
* tidak_berlaku (pertanyaan lanjutan yang tidak perlu dijawab), berlaku, persen. * tidak_berlaku (pertanyaan lanjutan yang tidak perlu dijawab), berlaku, persen.
*/ */
public function hitungIsian(): array public function hitungIsian(): array
@@ -349,7 +349,9 @@ class KelengkapanPetani
'lahan_id' => $l->id, 'lahan_id' => $l->id,
'url' => route('petani.lahan-tanaman.edit', [$this->petani->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); // Jenis hama/penyakit lahan terisi otomatis dari Praktik Budidaya; kolom lama serangan_hama/penyakit_tanaman ikut dicek untuk data lama.
$adaSerangan = self::terisi($l->jenis_hama) || self::terisi($l->jenis_penyakit)
|| self::terisi($l->serangan_hama) || self::terisi($l->penyakit_tanaman);
return array_filter([ return array_filter([
$this->item('Luas lahan', self::PERLU, 'luas_lahan', $l->luas_lahan, $tambahan), $this->item('Luas lahan', self::PERLU, 'luas_lahan', $l->luas_lahan, $tambahan),
@@ -358,8 +360,6 @@ class KelengkapanPetani
$this->item('Umur tanaman', self::PERLU, 'umur_tanaman', $l->umur_tanaman, $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('Jumlah tanaman', self::PERLU, 'jumlah_tanaman', $l->jumlah_tanaman, $tambahan),
$this->item('Kondisi tanaman', self::PERLU, 'kondisi_tanaman', $l->kondisi_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 $adaSerangan
? $this->item('Keparahan serangan', self::PERLU, 'keparahan_serangan', $l->keparahan_serangan, $tambahan) ? $this->item('Keparahan serangan', self::PERLU, 'keparahan_serangan', $l->keparahan_serangan, $tambahan)
: null, : null,
@@ -396,6 +396,7 @@ class KelengkapanPetani
$this->item('Dasar penentuan panen', self::WAJIB, 'dasar_penentuan_panen', $panen?->dasar_penentuan_panen), $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('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), $this->item('Praktik sortasi', self::WAJIB, 'praktik_sortasi', $panen?->praktik_sortasi),
$this->item('Tingkat hasil panen ditolak pembeli', self::WAJIB, 'tingkat_reject', $panen?->hasil_panen_reject),
]); ]);
} }
@@ -181,7 +181,7 @@
if (!sorot(btn.dataset.input)) { if (!sorot(btn.dataset.input)) {
const pesan = 'Kolom ini belum tampil di halaman ini. Simpan bagian yang sudah ada terlebih dahulu, atau periksa jawaban pertanyaan sebelumnya.'; const pesan = 'Kolom ini belum tampil di halaman ini. Simpan bagian yang sudah ada terlebih dahulu, atau periksa jawaban pertanyaan sebelumnya.';
window.Swal ? Swal.fire({ icon: 'info', text: pesan }) : alert(pesan); window.Swal ? Swal.fire({ icon: 'info', text: pesan, buttonsStyling: false, customClass: { confirmButton: 'btn btn-primary' } }) : alert(pesan);
} }
}); });
@@ -1,5 +1,5 @@
{{-- {{--
Kelengkapan data (terisi X dari 209) + status Draf/Lengkap/Terverifikasi di kartu Profil Petani. Kelengkapan data (terisi X dari total isian) + status Draf/Lengkap/Terverifikasi di kartu Profil Petani.
Tombol "Tandai Lengkap" hanya untuk enumerator penginput, dan nonaktif selama isian wajib masih kosong. Tombol "Tandai Lengkap" hanya untuk enumerator penginput, dan nonaktif selama isian wajib masih kosong.
--}} --}}
@props(['petani']) @props(['petani'])
@@ -42,11 +42,15 @@
@if ($bisaTandai && $status === StatusDataPetani::DRAFT) @if ($bisaTandai && $status === StatusDataPetani::DRAFT)
<form method="POST" action="{{ route('petani.status.update', $petani->id) }}" class="form-status-petani mt-2" <form method="POST" action="{{ route('petani.status.update', $petani->id) }}" class="form-status-petani mt-2"
data-konfirmasi="Tandai data petani ini sudah lengkap? Koordinator akan memverifikasinya."> data-judul="Pernyataan Kelengkapan Data"
data-konfirmasi="Setelah ditandai lengkap, data akan diteruskan ke koordinator untuk diverifikasi."
data-pernyataan="Saya menyatakan bahwa data petani {{ $petani->nama }} telah saya periksa, sudah lengkap, dan sesuai dengan hasil survei bersama pihak terkait di lapangan. Saya bertanggung jawab atas kebenaran data ini."
data-tombol="Ya, Tandai Lengkap">
@csrf @csrf
@method('PUT') @method('PUT')
<input type="hidden" name="status" value="{{ StatusDataPetani::LENGKAP->value }}"> <input type="hidden" name="status" value="{{ StatusDataPetani::LENGKAP->value }}">
<button type="submit" class="btn btn-sm btn-success w-100" @disabled($wajibKosong > 0) <input type="hidden" name="pernyataan" value="0">
<button type="submit" class="btn btn-sm btn-primary w-100" @disabled($wajibKosong > 0)
@if ($wajibKosong) title="Lengkapi {{ $wajibKosong }} isian wajib terlebih dahulu" @endif> @if ($wajibKosong) title="Lengkapi {{ $wajibKosong }} isian wajib terlebih dahulu" @endif>
<i class="icon-base ti tabler-checks icon-sm me-1"></i>Tandai Sudah Lengkap <i class="icon-base ti tabler-checks icon-sm me-1"></i>Tandai Sudah Lengkap
</button> </button>
@@ -61,6 +65,7 @@
<i class="icon-base ti tabler-arrow-back-up icon-sm me-1"></i>Batalkan Tanda Lengkap <i class="icon-base ti tabler-arrow-back-up icon-sm me-1"></i>Batalkan Tanda Lengkap
</button> </button>
</form> </form>
<p class="mb-0 mt-1 text-muted fst-italic" style="font-size: 0.7rem;">Mengubah data petani akan mengembalikan status ke Draf.</p>
@elseif ($status === StatusDataPetani::DRAFT) @elseif ($status === StatusDataPetani::DRAFT)
<p class="mb-0 mt-1 text-muted fst-italic" style="font-size: 0.7rem;">Hanya enumerator penginput yang dapat menandai lengkap.</p> <p class="mb-0 mt-1 text-muted fst-italic" style="font-size: 0.7rem;">Hanya enumerator penginput yang dapat menandai lengkap.</p>
@elseif ($status === StatusDataPetani::LENGKAP) @elseif ($status === StatusDataPetani::LENGKAP)
@@ -70,31 +75,64 @@
@once @once
@push('scripts') @push('scripts')
<script> <script>
// Tombol popup mengikuti warna tombol aplikasi (bukan ungu bawaan SweetAlert).
const gayaTombolSwal = () => ({
buttonsStyling: false,
customClass: { confirmButton: 'btn btn-primary me-2', cancelButton: 'btn btn-label-secondary' },
});
document.addEventListener('submit', function (e) { document.addEventListener('submit', function (e) {
const form = e.target.closest('.form-status-petani'); const form = e.target.closest('.form-status-petani');
if (!form || form.dataset.terkonfirmasi) return; if (!form || form.dataset.terkonfirmasi) return;
e.preventDefault(); e.preventDefault();
const kirim = () => { form.dataset.terkonfirmasi = '1'; form.submit(); }; const pernyataan = form.dataset.pernyataan;
const kirim = () => {
if (pernyataan) form.querySelector('input[name="pernyataan"]').value = '1';
form.dataset.terkonfirmasi = '1';
form.submit();
};
if (!window.Swal) { if (!window.Swal) {
if (confirm(form.dataset.konfirmasi)) kirim(); if (confirm((pernyataan ? pernyataan + '\n\n' : '') + form.dataset.konfirmasi)) kirim();
return; return;
} }
Swal.fire({
icon: 'question', const opsi = {
icon: pernyataan ? 'warning' : 'question',
title: form.dataset.judul || undefined,
text: form.dataset.konfirmasi, text: form.dataset.konfirmasi,
showCancelButton: true, showCancelButton: true,
confirmButtonText: 'Ya', confirmButtonText: form.dataset.tombol || 'Ya',
cancelButtonText: 'Batal', cancelButtonText: 'Batal',
}).then((r) => { if (r.isConfirmed) kirim(); }); ...gayaTombolSwal(),
};
// Tandai Lengkap: wajib mencentang pernyataan sebelum bisa lanjut.
if (pernyataan) {
opsi.input = 'checkbox';
opsi.inputValue = 0;
opsi.inputPlaceholder = pernyataan;
opsi.inputValidator = (dicentang) => !dicentang && 'Centang pernyataan terlebih dahulu.';
opsi.customClass.input = 'text-start small';
}
Swal.fire(opsi).then((r) => { if (r.isConfirmed) kirim(); });
}); });
@if (session('status_petani_success') || session('status_petani_error')) @if (session('status_petani_info'))
// Data diubah setelah ditandai lengkap → status kembali ke Draf (KembalikanStatusPetaniKeDraf).
window.addEventListener('load', function () { window.addEventListener('load', function () {
const pesan = @json(session('status_petani_success') ?? session('status_petani_error')); const pesan = @json(session('status_petani_info'));
window.Swal ? Swal.fire({ icon: 'info', title: 'Status kembali ke Draf', text: pesan, ...gayaTombolSwal() }) : alert(pesan);
});
@endif
@if (session('status_petani_success') || session('status_petani_error') || $errors->has('pernyataan'))
window.addEventListener('load', function () {
const pesan = @json(session('status_petani_success') ?? session('status_petani_error') ?? $errors->first('pernyataan'));
const ikon = @json(session('status_petani_success') ? 'success' : 'error'); const ikon = @json(session('status_petani_success') ? 'success' : 'error');
window.Swal ? Swal.fire({ icon: ikon, text: pesan }) : alert(pesan); window.Swal ? Swal.fire({ icon: ikon, text: pesan, ...gayaTombolSwal() }) : alert(pesan);
}); });
@endif @endif
</script> </script>