WilayahProvinsi::class, 'kabupaten' => WilayahKabupaten::class, 'kecamatan' => WilayahKecamatan::class, 'desa' => WilayahDesa::class, ]; private const PARENTS = [ 'kabupaten' => 'provinsi_id', 'kecamatan' => 'kabupaten_id', 'desa' => 'kecamatan_id', ]; private const RELATIONS = [ 'provinsi' => [], 'kabupaten' => ['provinsi'], 'kecamatan' => ['kabupaten.provinsi'], 'desa' => ['kecamatan.kabupaten.provinsi'], ]; public function list(string $tingkat, array $filters = []) { $model = $this->model($tingkat); return $model::query() ->with(self::RELATIONS[$tingkat]) ->when($filters['search'] ?? null, function ($query, $search) { $value = mb_strtolower($search); $query->where(function ($scope) use ($value) { $scope->whereRaw('LOWER(kode) LIKE ?', ["%{$value}%"]) ->orWhereRaw('LOWER(nama) LIKE ?', ["%{$value}%"]); }); }) ->when( isset(self::PARENTS[$tingkat]) && ! empty($filters[self::PARENTS[$tingkat]]), fn ($query) => $query->where( self::PARENTS[$tingkat], $filters[self::PARENTS[$tingkat]] ) ) ->orderBy( $filters['sort_by'] ?? 'nama', $filters['sort_direction'] ?? 'asc' ) ->paginate($filters['per_page'] ?? 10); } public function options(string $tingkat, array $filters = [], ?int $parentId = null) { $model = $this->model($tingkat); $parentColumn = self::PARENTS[$tingkat] ?? null; return $model::query() ->select(['id', 'kode', 'nama']) ->when( $parentColumn && $parentId, fn ($query) => $query->where($parentColumn, $parentId) ) ->when($filters['search'] ?? null, function ($query, $search) { $value = mb_strtolower($search); $query->where(function ($scope) use ($value) { $scope->whereRaw('LOWER(kode) LIKE ?', ["%{$value}%"]) ->orWhereRaw('LOWER(nama) LIKE ?', ["%{$value}%"]); }); }) ->orderBy('nama') ->get(); } public function create(string $tingkat, array $data): Model { $model = $this->model($tingkat); return $model::create($this->payload($tingkat, $data)) ->load(self::RELATIONS[$tingkat]); } public function find(string $tingkat, int $id): Model { $model = $this->model($tingkat); return $model::with(self::RELATIONS[$tingkat])->findOrFail($id); } public function update(string $tingkat, int $id, array $data): Model { $wilayah = $this->find($tingkat, $id); $wilayah->update($this->payload($tingkat, $data)); return $wilayah->fresh(self::RELATIONS[$tingkat]); } public function delete(string $tingkat, int $id): void { $wilayah = $this->find($tingkat, $id); $childRelation = match ($tingkat) { 'provinsi' => 'kabupaten', 'kabupaten' => 'kecamatan', 'kecamatan' => 'desa', default => null, }; if ($childRelation && $wilayah->{$childRelation}()->exists()) { throw ValidationException::withMessages([ 'wilayah' => "Wilayah {$tingkat} tidak dapat dihapus karena masih memiliki data di bawahnya.", ]); } $wilayah->delete(); } public function alamatLengkap(array $filters): array { if (! empty($filters['desa_id'])) { $desa = WilayahDesa::with('kecamatan.kabupaten.provinsi')->findOrFail($filters['desa_id']); return $this->addressPayload( $desa->kecamatan?->kabupaten?->provinsi, $desa->kecamatan?->kabupaten, $desa->kecamatan, $desa ); } if (! empty($filters['kecamatan_id'])) { $kecamatan = WilayahKecamatan::with('kabupaten.provinsi')->findOrFail($filters['kecamatan_id']); return $this->addressPayload( $kecamatan->kabupaten?->provinsi, $kecamatan->kabupaten, $kecamatan ); } if (! empty($filters['kabupaten_id'])) { $kabupaten = WilayahKabupaten::with('provinsi')->findOrFail($filters['kabupaten_id']); return $this->addressPayload($kabupaten->provinsi, $kabupaten); } $provinsi = WilayahProvinsi::findOrFail($filters['provinsi_id']); return $this->addressPayload($provinsi); } private function model(string $tingkat): string { abort_unless(isset(self::MODELS[$tingkat]), 404, 'Tingkat wilayah tidak ditemukan.'); return self::MODELS[$tingkat]; } private function payload(string $tingkat, array $data): array { $fields = ['kode', 'nama']; if (isset(self::PARENTS[$tingkat])) { $fields[] = self::PARENTS[$tingkat]; } return collect($data)->only($fields)->all(); } private function addressPayload( ?WilayahProvinsi $provinsi, ?WilayahKabupaten $kabupaten = null, ?WilayahKecamatan $kecamatan = null, ?WilayahDesa $desa = null ): array { $node = fn ($item) => $item ? [ 'id' => $item->id, 'kode' => $item->kode, 'nama' => $item->nama, ] : null; $parts = array_filter([ $desa ? "Desa/Kelurahan {$desa->nama}" : null, $kecamatan ? "Kecamatan {$kecamatan->nama}" : null, $kabupaten?->nama, $provinsi ? $provinsi->nama : null, ]); return [ 'desa' => $node($desa), 'kecamatan' => $node($kecamatan), 'kabupaten' => $node($kabupaten), 'provinsi' => $node($provinsi), 'alamat_wilayah' => implode(', ', $parts), ]; } }