diff --git a/app/Http/Controllers/Api/TanamanController.php b/app/Http/Controllers/Api/TanamanController.php index 29c1a84..5a9736e 100644 --- a/app/Http/Controllers/Api/TanamanController.php +++ b/app/Http/Controllers/Api/TanamanController.php @@ -118,11 +118,13 @@ class TanamanController extends Controller 'latitude' => ['nullable', 'numeric', 'between:-90,90', 'required_with:longitude'], 'longitude' => ['nullable', 'numeric', 'between:-180,180', 'required_with:latitude'], 'kondisi_tanaman' => ['nullable', 'string', new Enum(KondisiTanaman::class)], + 'uid' => ['required', 'string'], ]); $tanaman = Tanaman::create([ ...$validated, 'profile_id' => $profile->id, + 'uid' => $validated['uid'] ?? $request->input('uid'), ]); return (new TanamanResource($tanaman)) diff --git a/app/Http/Controllers/Web/Konfigurasi/UserController.php b/app/Http/Controllers/Web/Konfigurasi/UserController.php index 52f14c8..7d79fc1 100644 --- a/app/Http/Controllers/Web/Konfigurasi/UserController.php +++ b/app/Http/Controllers/Web/Konfigurasi/UserController.php @@ -10,6 +10,7 @@ use App\Enums\Role; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Validator; +use Illuminate\Support\Str; class UserController extends Controller { @@ -70,6 +71,7 @@ class UserController extends Controller public function store(Request $request) { $validator = Validator::make($request->all(), [ + 'username' => ['required', 'string', 'max:255', 'unique:users,name'], 'name' => ['required', 'string', 'max:255'], 'email' => ['required', 'string', 'email', 'max:255', 'unique:users'], 'password' => ['required', 'string', 'min:8', 'confirmed'], @@ -90,14 +92,17 @@ class UserController extends Controller $user = User::create([ 'name' => $request->name, + 'username' => $request->username, 'email' => $request->email, 'password' => Hash::make($request->password), 'role' => $request->role, + 'uid' => Str::random(10), 'created_by' => auth()->id(), ]); Profile::create([ 'user_id' => $user->id, + 'user_uid' => $user->uid, 'role' => $request->role, 'nama' => $request->name, 'email' => $request->email, diff --git a/app/Http/Controllers/Web/Master/VarietasController.php b/app/Http/Controllers/Web/Master/VarietasController.php index 420380c..f195ea4 100644 --- a/app/Http/Controllers/Web/Master/VarietasController.php +++ b/app/Http/Controllers/Web/Master/VarietasController.php @@ -44,6 +44,16 @@ class VarietasController extends Controller )); } + public function getByJenisKomoditas($jenisKomoditasId) + { + $varietas = Varietas::where('jenis_komoditas_id', $jenisKomoditasId)->get(); + + return response()->json([ + 'status' => 'success', + 'data' => $varietas, + ]); + } + public function store(Request $request) { $komoditasTable = (new Komoditas)->getTable(); diff --git a/app/Http/Controllers/Web/Produktivitas/LahanController.php b/app/Http/Controllers/Web/Produktivitas/LahanController.php index f9fddbf..09fdf9d 100644 --- a/app/Http/Controllers/Web/Produktivitas/LahanController.php +++ b/app/Http/Controllers/Web/Produktivitas/LahanController.php @@ -10,6 +10,7 @@ use App\Models\Pertanian\Adat; use App\Models\Wilayah\Provinsi; use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Validator; +use Illuminate\Support\Str; class LahanController extends Controller { @@ -76,6 +77,25 @@ class LahanController extends Controller return view('produktivitas.lahan.index', compact('lahan', 'provinsi', 'profiles', 'adat', 'kabupaten', 'kecamatan', 'desa')); } + public function getByProfile(Request $request, $profileId) + { + $desaKelurahanId = $request->get('desa_kelurahan_id'); + + $lahan = Lahan::with('profile') + ->where('profile_id', $profileId) + ->when($desaKelurahanId, function ($query) use ($desaKelurahanId) { + $query->where('desa_kelurahan_id', $desaKelurahanId); + }) + ->orderBy('created_at', 'desc') + ->get(['id', 'nama', 'path', 'profile_id', 'desa_kelurahan_id']); + + return response()->json([ + 'status' => 'success', + 'data' => $lahan, + ]); + } + + public function create() { $provinsi = Provinsi::orderBy('nama', 'asc')->get(); @@ -112,8 +132,12 @@ class LahanController extends Controller return response()->json(['status' => 'error', 'message' => 'Format koordinat (path) bukan JSON array yang valid.'], 422); } } + + $userUID = Profile::where('id', $request->profile_id)->value('user_uid'); + $userUID = substr($userUID, 2); Lahan::create([ + 'uid' => 'LH' . $userUID . '-' . strtoupper(Str::random(10)), 'nama' => $request->nama, 'status_kepemilikan' => $request->status_kepemilikan, 'profile_id' => $request->profile_id, diff --git a/app/Http/Controllers/Web/Produktivitas/MapTanamanController.php b/app/Http/Controllers/Web/Produktivitas/MapTanamanController.php new file mode 100644 index 0000000..ef0e5c1 --- /dev/null +++ b/app/Http/Controllers/Web/Produktivitas/MapTanamanController.php @@ -0,0 +1,421 @@ +get('per_page', 10); + $search = $request->get('search'); + + $tanaman = Tanaman::query() + ->select([ + DB::raw('MIN(id) as id'), + 'profile_id', + 'lahan_id', + 'komoditas_id', + 'jenis_komoditas_id', + 'varietas_id', + DB::raw('COUNT(*) as total_tanaman'), + DB::raw("SUM(CASE WHEN kondisi_tanaman = 'sehat' THEN 1 ELSE 0 END) as total_sehat"), + DB::raw("SUM(CASE WHEN kondisi_tanaman = 'sakit' THEN 1 ELSE 0 END) as total_sakit"), + DB::raw("SUM(CASE WHEN kondisi_tanaman = 'mati' THEN 1 ELSE 0 END) as total_mati"), + DB::raw('MAX(created_at) as max_created_at'), + ]) + ->with([ + 'profile', + 'lahan.desaKelurahan.kecamatan.kabupatenkota.provinsi', + 'komoditas', + 'jenisKomoditas', + 'varietas', + ]) + ->when($search, function ($query) use ($search) { + $query->where(function ($q) use ($search) { + $q->whereHas('profile', fn ($q) => $q->where('nama', 'ILIKE', "%{$search}%")) + ->orWhereHas('lahan', fn ($q) => $q->where('nama', 'ILIKE', "%{$search}%")) + ->orWhereHas('komoditas', fn ($q) => $q->where('nama', 'ILIKE', "%{$search}%")) + ->orWhereHas('jenisKomoditas', fn ($q) => $q->where('nama', 'ILIKE', "%{$search}%")) + ->orWhereHas('varietas', fn ($q) => $q->where('nama', 'ILIKE', "%{$search}%")) + ->orWhereHas('lahan.desaKelurahan', fn ($q) => $q->where('nama', 'ILIKE', "%{$search}%")) + ->orWhereHas('lahan.desaKelurahan.kecamatan', fn ($q) => $q->where('nama', 'ILIKE', "%{$search}%")) + ->orWhereHas('lahan.desaKelurahan.kecamatan.kabupatenkota', fn ($q) => $q->where('nama', 'ILIKE', "%{$search}%")) + ->orWhereHas('lahan.desaKelurahan.kecamatan.kabupatenkota.provinsi', fn ($q) => $q->where('nama', 'ILIKE', "%{$search}%")); + }); + }) + ->groupBy([ + 'profile_id', + 'lahan_id', + 'komoditas_id', + 'jenis_komoditas_id', + 'varietas_id', + ]) + ->orderByDesc('max_created_at') + ->paginate($perPage); + + return view('produktivitas.mapTanaman.index', compact('tanaman')); + } + + private function getTanamanGroupByRepresentativeId($id) + { + $representative = Tanaman::findOrFail($id); + + $points = Tanaman::with([ + 'profile', + 'lahan.desaKelurahan.kecamatan.kabupatenkota.provinsi', + 'komoditas', + 'jenisKomoditas', + 'varietas', + ]) + ->where('profile_id', $representative->profile_id) + ->where('lahan_id', $representative->lahan_id) + ->where('komoditas_id', $representative->komoditas_id) + ->where('jenis_komoditas_id', $representative->jenis_komoditas_id) + ->where('varietas_id', $representative->varietas_id) + ->orderBy('id') + ->get(); + + return [ + 'representative' => $representative, + 'points' => $points, + ]; + } + + public function create() + { + $provinsi = Provinsi::select('id', 'nama', 'path') + ->orderBy('nama') + ->get(); + + $komoditas = Komoditas::orderBy('nama')->get(); + $profiles = Profile::orderBy('nama')->get(); + + return view('produktivitas.mapTanaman.add', compact( + 'provinsi', + 'komoditas', + 'profiles' + )); + } + + public function store(Request $request) + { + $desaKelurahanTable = (new DesaKelurahan)->getTable(); + $profileTable = (new Profile)->getTable(); + $lahanTable = (new Lahan)->getTable(); + $komoditasTable = (new Komoditas)->getTable(); + $jenisKomoditasTable = (new JenisKomoditas)->getTable(); + $varietasTable = (new Varietas)->getTable(); + + $validated = $request->validate([ + 'desa_kelurahan_id' => [ + 'required', + 'integer', + Rule::exists($desaKelurahanTable, 'id'), + ], + + 'profile_id' => [ + 'required', + 'integer', + Rule::exists($profileTable, 'id'), + ], + + 'lahan_id' => [ + 'required', + 'integer', + Rule::exists($lahanTable, 'id') + ->where('profile_id', $request->profile_id) + ->where('desa_kelurahan_id', $request->desa_kelurahan_id), + ], + + 'komoditas_id' => [ + 'required', + 'integer', + Rule::exists($komoditasTable, 'id'), + ], + + 'jenis_komoditas_id' => [ + 'required', + 'integer', + Rule::exists($jenisKomoditasTable, 'id') + ->where('komoditas_id', $request->komoditas_id), + ], + + 'varietas_id' => [ + 'required', + 'integer', + Rule::exists($varietasTable, 'id') + ->where('jenis_komoditas_id', $request->jenis_komoditas_id), + ], + + 'points' => [ + 'required', + 'array', + 'min:1', + ], + + 'points.*.latitude' => [ + 'required', + 'numeric', + ], + + 'points.*.longitude' => [ + 'required', + 'numeric', + ], + + 'points.*.kondisi_tanaman' => [ + 'required', + Rule::in(['sehat', 'sakit', 'mati']), + ], + + 'metode_pengumpulan' => [ + 'nullable', + 'string', + 'max:50', + ], + + 'deskripsi' => [ + 'nullable', + 'string', + ], + ], [ + 'points.required' => 'Minimal tambahkan satu titik tanaman pada peta.', + 'points.min' => 'Minimal tambahkan satu titik tanaman pada peta.', + 'points.*.latitude.required' => 'Latitude titik tanaman wajib ada.', + 'points.*.longitude.required' => 'Longitude titik tanaman wajib ada.', + 'points.*.kondisi_tanaman.required' => 'Kondisi tanaman wajib dipilih.', + ]); + + try { + DB::beginTransaction(); + + $lahan = Lahan::findOrFail($validated['lahan_id']); + $komoditas = Komoditas::findOrFail($validated['komoditas_id']); + $profile = Profile::findOrFail($validated['profile_id']); + + if (!$lahan->uid) { + throw new \Exception('UID lahan tidak ditemukan.'); + } + + $profileName = str_replace(' ', '', $profile->nama); + $lahanUidPart = substr($lahan->uid, 2); + + $existingCount = Tanaman::where('lahan_id', $validated['lahan_id']) + ->where('komoditas_id', $validated['komoditas_id']) + ->where('jenis_komoditas_id', $validated['jenis_komoditas_id']) + ->where('varietas_id', $validated['varietas_id']) + ->count(); + + $createdTanaman = []; + + foreach ($validated['points'] as $index => $point) { + $number = $existingCount + $index + 1; + + $createdTanaman[] = Tanaman::create([ + 'uid' => 'TM' . $lahanUidPart . '-' . strtoupper(Str::random(10)), + 'nama' => $komoditas->nama . ' - ' . $profileName . ' - ' . $number, + 'desa_kelurahan_id' => $validated['desa_kelurahan_id'], + + 'profile_id' => $validated['profile_id'], + 'lahan_id' => $validated['lahan_id'], + + 'komoditas_id' => $validated['komoditas_id'], + 'jenis_komoditas_id' => $validated['jenis_komoditas_id'], + 'varietas_id' => $validated['varietas_id'], + + 'latitude' => $point['latitude'], + 'longitude' => $point['longitude'], + 'kondisi_tanaman' => $point['kondisi_tanaman'], + 'metode_pengumpulan' => $validated['metode_pengumpulan'] ?? 'manual', + 'deskripsi' => $validated['deskripsi'] ?? null, + 'created_by' => auth()->id(), + 'updated_by' => auth()->id(), + ]); + } + + DB::commit(); + + return response()->json([ + 'status' => 'success', + 'message' => count($createdTanaman) . ' data tanaman berhasil disimpan.', + 'redirect' => route('map-tanaman.index'), + 'data' => $createdTanaman, + ]); + } catch (\Throwable $e) { + DB::rollBack(); + + Log::error('Gagal menyimpan tanaman bulk', [ + 'error' => $e->getMessage(), + 'request' => $request->all(), + ]); + + return response()->json([ + 'status' => 'error', + 'message' => 'Gagal menyimpan data: ' . $e->getMessage(), + ], 500); + } + } + + public function show(string $id) + { + $group = $this->getTanamanGroupByRepresentativeId($id); + + $tanaman = $group['representative']->load([ + 'profile', + 'lahan.desaKelurahan.kecamatan.kabupatenkota.provinsi', + 'komoditas', + 'jenisKomoditas', + 'varietas', + ]); + + $points = $group['points']; + + return view('produktivitas.mapTanaman.view', compact( + 'tanaman', + 'points' + )); + } + public function edit(string $id) + { + $group = $this->getTanamanGroupByRepresentativeId($id); + + $tanaman = $group['representative']->load([ + 'profile', + 'lahan.desaKelurahan.kecamatan.kabupatenkota.provinsi', + 'komoditas', + 'jenisKomoditas', + 'varietas', + ]); + + $points = $group['points']; + + return view('produktivitas.mapTanaman.edit', compact( + 'tanaman', + 'points' + )); + } + + public function update(Request $request, string $id) + { + $group = $this->getTanamanGroupByRepresentativeId($id); + + $representative = $group['representative']; + + $validated = $request->validate([ + 'points' => ['required', 'array', 'min:1'], + 'points.*.id' => ['nullable', 'integer', 'exists:map_tanaman,id'], + 'points.*.latitude' => ['required', 'numeric'], + 'points.*.longitude' => ['required', 'numeric'], + 'points.*.kondisi_tanaman' => [ + 'required', + Rule::in(['sehat', 'sakit', 'mati']), + ], + 'points.*._delete' => ['nullable'], + ]); + + try { + DB::beginTransaction(); + + foreach ($validated['points'] as $point) { + if (!empty($point['_delete']) && !empty($point['id'])) { + $tanaman = Tanaman::where('id', $point['id']) + ->where('profile_id', $representative->profile_id) + ->where('lahan_id', $representative->lahan_id) + ->where('komoditas_id', $representative->komoditas_id) + ->where('jenis_komoditas_id', $representative->jenis_komoditas_id) + ->where('varietas_id', $representative->varietas_id) + ->first(); + + if ($tanaman) { + $tanaman->deleted_by = auth()->id(); + $tanaman->save(); + $tanaman->delete(); + } + + continue; + } + + if (!empty($point['id'])) { + $tanaman = Tanaman::where('id', $point['id']) + ->where('profile_id', $representative->profile_id) + ->where('lahan_id', $representative->lahan_id) + ->where('komoditas_id', $representative->komoditas_id) + ->where('jenis_komoditas_id', $representative->jenis_komoditas_id) + ->where('varietas_id', $representative->varietas_id) + ->firstOrFail(); + + $tanaman->update([ + 'latitude' => $point['latitude'], + 'longitude' => $point['longitude'], + 'kondisi_tanaman' => $point['kondisi_tanaman'], + 'updated_by' => auth()->id(), + ]); + } + } + + DB::commit(); + + return response()->json([ + 'status' => 'success', + 'message' => 'Data titik tanaman berhasil diperbarui.', + 'redirect' => route('map-tanaman.index'), + ]); + } catch (\Throwable $e) { + DB::rollBack(); + + return response()->json([ + 'status' => 'error', + 'message' => 'Gagal memperbarui data: ' . $e->getMessage(), + ], 500); + } + } + + public function destroy(string $id) + { + try { + DB::beginTransaction(); + + $group = $this->getTanamanGroupByRepresentativeId($id); + $points = $group['points']; + + foreach ($points as $point) { + $point->update([ + 'deleted_by' => auth()->id(), + ]); + + $point->delete(); + } + + DB::commit(); + + return response()->json([ + 'status' => 'success', + 'message' => $points->count() . ' titik tanaman berhasil dihapus.', + ]); + } catch (\Throwable $e) { + DB::rollBack(); + + return response()->json([ + 'status' => 'error', + 'message' => 'Gagal menghapus data: ' . $e->getMessage(), + ], 500); + } + } +} diff --git a/app/Http/Controllers/Web/Produktivitas/ProfileController.php b/app/Http/Controllers/Web/Produktivitas/ProfileController.php index 7424625..70c33d5 100644 --- a/app/Http/Controllers/Web/Produktivitas/ProfileController.php +++ b/app/Http/Controllers/Web/Produktivitas/ProfileController.php @@ -30,6 +30,25 @@ class ProfileController extends Controller return view('master.profile.index', compact('profiles', 'provinsi')); } + public function getByDesaKelurahan($desaId) + { + try { + $profiles = Profile::where('desa_kelurahan_id', $desaId) + ->orderBy('nama', 'asc') + ->get(['id', 'nama']); + + return response()->json([ + 'status' => 'success', + 'data' => $profiles, + ]); + } catch (\Throwable $e) { + return response()->json([ + 'status' => 'error', + 'message' => $e->getMessage(), + ], 500); + } + } + public function create() { $provinsi = Provinsi::orderBy('nama', 'asc')->get(); diff --git a/app/Http/Resources/Pertanian/LahanResource.php b/app/Http/Resources/Pertanian/LahanResource.php index 16eb72a..c8b7e07 100644 --- a/app/Http/Resources/Pertanian/LahanResource.php +++ b/app/Http/Resources/Pertanian/LahanResource.php @@ -25,6 +25,7 @@ class LahanResource extends JsonResource 'luas_lahan' => $this->luas_lahan, 'keluarga_pengelola_id' => $this->keluarga_pengelola_id, 'adat_pemilik_id' => $this->adat_pemilik_id, + 'uid' => $this->uid, ]; } } diff --git a/app/Http/Resources/Pertanian/TanamanResource.php b/app/Http/Resources/Pertanian/TanamanResource.php index d1234fd..f2128ec 100644 --- a/app/Http/Resources/Pertanian/TanamanResource.php +++ b/app/Http/Resources/Pertanian/TanamanResource.php @@ -24,6 +24,7 @@ class TanamanResource extends JsonResource 'latitude' => $this->latitude, 'longitude' => $this->longitude, 'kondisi_tanaman' => $this->kondisi_tanaman, + 'uid' => $this->uid, ]; } } diff --git a/app/Models/Master/JenisKomoditas.php b/app/Models/Master/JenisKomoditas.php index cce5c46..8632301 100644 --- a/app/Models/Master/JenisKomoditas.php +++ b/app/Models/Master/JenisKomoditas.php @@ -25,4 +25,9 @@ class JenisKomoditas extends Model { return $this->belongsTo(Komoditas::class, 'komoditas_id'); } + + public function varietas() + { + return $this->hasMany(Varietas::class, 'jenis_komoditas_id'); + } } diff --git a/app/Models/Pertanian/Komoditas.php b/app/Models/Pertanian/Komoditas.php index a0b5858..d40315b 100644 --- a/app/Models/Pertanian/Komoditas.php +++ b/app/Models/Pertanian/Komoditas.php @@ -5,6 +5,8 @@ namespace App\Models\Pertanian; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\Builder; +use App\Models\Master\JenisKomoditas; +use App\Models\Master\Varietas; class Komoditas extends Model { @@ -30,4 +32,14 @@ class Komoditas extends Model $builder->whereIn('id', app('SystemProgram')->komoditas()->pluck('id')); }); } + + public function jenisKomoditas() + { + return $this->hasMany(JenisKomoditas::class, 'komoditas_id'); + } + + public function varietas() + { + return $this->hasMany(Varietas::class, 'komoditas_id'); + } } diff --git a/app/Models/Pertanian/Tanaman.php b/app/Models/Pertanian/Tanaman.php index 8106923..5b89bb2 100644 --- a/app/Models/Pertanian/Tanaman.php +++ b/app/Models/Pertanian/Tanaman.php @@ -4,9 +4,11 @@ namespace App\Models\Pertanian; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; -use App\Models\Pertanian\Komoditas; -use App\Models\Pertanian\Lahan; use App\Models\Profile; +use App\Models\Pertanian\Lahan; +use App\Models\Pertanian\Komoditas; +use App\Models\Master\JenisKomoditas; +use App\Models\Master\Varietas; class Tanaman extends Model { @@ -15,13 +17,18 @@ class Tanaman extends Model protected $table = 'map_tanaman'; protected $fillable = [ + 'uid', 'nama', + 'jenis_komoditas_id', + 'varietas_id', 'komoditas_id', 'profile_id', 'lahan_id', 'latitude', 'longitude', 'kondisi_tanaman', + 'metode_pengumpulan', + 'deskripsi', 'created_by', 'updated_by', 'deleted_by', @@ -32,6 +39,16 @@ class Tanaman extends Model return $this->belongsTo(Komoditas::class, 'komoditas_id'); } + public function jenisKomoditas() + { + return $this->belongsTo(JenisKomoditas::class, 'jenis_komoditas_id'); + } + + public function varietas() + { + return $this->belongsTo(Varietas::class, 'varietas_id'); + } + public function profile() { return $this->belongsTo(Profile::class, 'profile_id'); diff --git a/resources/views/konfigurasi/user/add.blade.php b/resources/views/konfigurasi/user/add.blade.php index d11d1cc..640a69c 100644 --- a/resources/views/konfigurasi/user/add.blade.php +++ b/resources/views/konfigurasi/user/add.blade.php @@ -10,6 +10,10 @@ +