validate([ 'q' => ['nullable', 'string', 'max:100'], 'per_page' => ['nullable', 'integer', 'min:1', 'max:100'], ]); $tenant = $request->user()->tenant; $cityId = $tenant->city_municipality_id; $perPage = (int) $request->input('per_page', 25); $barangays = Barangay::query() ->with(['cityMunicipality.province']) ->withSum('households', 'household_size') ->where('city_municipality_id', $cityId) ->when($request->filled('q'), function ($q) use ($request) { $term = '%'.$request->string('q').'%'; $q->where(function ($qq) use ($term) { $qq->where('name', 'like', $term) ->orWhere('code', 'like', $term) ->orWhere('psgc_code', 'like', $term); }); }) ->orderBy('name') ->paginate($perPage); return $this->ok(BarangayResource::collection($barangays)); } public function store(StoreBarangayRequest $request): JsonResponse { $data = $request->validated(); $tenant = $request->user()->tenant; $cityId = $tenant->city_municipality_id; $boundary = $this->buildPolygon($data['boundary']); $centroid = $this->calculateCentroid($boundary); // Spatial validation check removed as per user request // Code auto-generation if empty $city = CityMunicipality::findOrFail($cityId); if (empty($data['psgc_code'])) { $prefix = substr($city->psgc_code, 0, 9); $maxLocal = Barangay::where('psgc_code', 'like', $prefix.'%') ->whereRaw('LENGTH(psgc_code) = 12') ->orderBy('psgc_code', 'desc') ->first(); $suffix = $maxLocal ? ((int) substr($maxLocal->psgc_code, -3) + 1) : 1; $data['psgc_code'] = $prefix.str_pad($suffix, 3, '0', STR_PAD_LEFT); } if (empty($data['code'])) { $prefix = substr($city->code, 0, 12); $maxLocal = Barangay::where('code', 'like', $prefix.'%') ->orderBy('code', 'desc') ->first(); $suffix = $maxLocal ? ((int) substr(strrchr($maxLocal->code, '-'), 1) + 1) : 1; $data['code'] = $prefix.'-'.str_pad($suffix, 3, '0', STR_PAD_LEFT); } $barangay = Barangay::create([ 'name' => $data['name'], 'city_municipality_id' => $cityId, 'psgc_code' => $data['psgc_code'], 'code' => $data['code'], 'urban_rural' => $data['urban_rural'], 'population' => $data['population'], 'boundary' => $boundary, 'centroid' => $centroid, ]); return $this->created(new BarangayResource($barangay), 'Barangay created successfully'); } public function show(Request $request, Barangay $barangay): JsonResponse { $tenant = $request->user()->tenant; if ($barangay->city_municipality_id !== $tenant->city_municipality_id) { abort(403, 'Unauthorized to view this barangay.'); } $barangay->load(['cityMunicipality.province']); return $this->ok(new BarangayResource($barangay)); } public function update(UpdateBarangayRequest $request, Barangay $barangay): JsonResponse { $tenant = $request->user()->tenant; if ($barangay->city_municipality_id !== $tenant->city_municipality_id) { abort(403, 'Unauthorized to modify this barangay.'); } $data = $request->validated(); $cityId = $tenant->city_municipality_id; $boundary = $this->buildPolygon($data['boundary']); $centroid = $this->calculateCentroid($boundary); // Spatial validation check removed as per user request // Code auto-generation if empty $city = CityMunicipality::findOrFail($cityId); if (empty($data['psgc_code'])) { $prefix = substr($city->psgc_code, 0, 9); $maxLocal = Barangay::where('psgc_code', 'like', $prefix.'%') ->whereRaw('LENGTH(psgc_code) = 12') ->orderBy('psgc_code', 'desc') ->first(); $suffix = $maxLocal ? ((int) substr($maxLocal->psgc_code, -3) + 1) : 1; $data['psgc_code'] = $prefix.str_pad($suffix, 3, '0', STR_PAD_LEFT); } if (empty($data['code'])) { $prefix = substr($city->code, 0, 12); $maxLocal = Barangay::where('code', 'like', $prefix.'%') ->orderBy('code', 'desc') ->first(); $suffix = $maxLocal ? ((int) substr(strrchr($maxLocal->code, '-'), 1) + 1) : 1; $data['code'] = $prefix.'-'.str_pad($suffix, 3, '0', STR_PAD_LEFT); } $barangay->update([ 'name' => $data['name'], 'psgc_code' => $data['psgc_code'], 'code' => $data['code'], 'urban_rural' => $data['urban_rural'], 'population' => $data['population'], 'boundary' => $boundary, 'centroid' => $centroid, ]); return $this->ok(new BarangayResource($barangay), 'Barangay updated successfully'); } public function destroy(Request $request, Barangay $barangay): JsonResponse { $tenant = $request->user()->tenant; if ($barangay->city_municipality_id !== $tenant->city_municipality_id) { abort(403, 'Unauthorized to delete this barangay.'); } $barangay->delete(); return $this->ok(null, 'Barangay deleted successfully'); } private function buildPolygon(array $points): Polygon { $ring = array_map( fn ($p) => new Point((float) $p['lat'], (float) $p['lng'], 4326), $points, ); $first = $ring[0]; $last = $ring[count($ring) - 1]; if ($first->latitude !== $last->latitude || $first->longitude !== $last->longitude) { $ring[] = new Point($first->latitude, $first->longitude, 4326); } return new Polygon([new LineString($ring)], 4326); } private function calculateCentroid(Polygon $polygon): Point { $res = DB::selectOne( 'SELECT ST_AsText(ST_SRID(ST_Centroid(ST_GeomFromText(?, 0)), 4326)) as wkt', [$polygon->toWkt()] ); if ($res && preg_match('/POINT\(([^ ]+) ([^ ]+)\)/', $res->wkt, $matches)) { $lat = (float) $matches[1]; $lng = (float) $matches[2]; return new Point($lat, $lng, 4326); } $rings = $polygon->getGeometries(); $ring = $rings->first(); if ($ring) { $coords = $ring->getGeometries(); $lats = $coords->map(fn ($p) => $p->latitude)->all(); $lngs = $coords->map(fn ($p) => $p->longitude)->all(); return new Point(array_sum($lats) / count($lats), array_sum($lngs) / count($lngs), 4326); } return new Point(0, 0, 4326); } }