validated(); $user = $request->user(); $existing = Household::where('head_user_id', $user->id)->first(); if ($existing) { return $this->fail( 'You already have a household. Update it instead.', ['head_user_id' => ['already_has_household']], 409, ); } $point = new Point((float) $data['lat'], (float) $data['lng'], 4326); $barangayId = $data['barangay_id'] ?? null; if (! $barangayId) { $resolved = $geo->findBarangayByCoordinates((float) $data['lat'], (float) $data['lng']); $barangayId = $resolved?->id; } $nearestDop = $dopFinder->nearest((float) $data['lat'], (float) $data['lng']); $household = DB::transaction(function () use ($data, $user, $point, $barangayId, $nearestDop) { $h = Household::create([ 'head_user_id' => $user->id, 'barangay_id' => $barangayId, 'address_line' => $data['address_line'], 'coordinates' => $point, 'household_size' => $data['household_size'], 'assigned_drop_off_point_id' => $nearestDop?->id, 'verification_status' => Household::VERIFICATION_PENDING, ]); HouseholdMember::create([ 'household_id' => $h->id, 'user_id' => $user->id, 'relationship' => HouseholdMember::RELATIONSHIP_HEAD, 'full_name' => $user->full_name, ]); return $h; }); return $this->created( new HouseholdResource( $household->fresh() ->load(['head', 'barangay', 'members.user', 'assignedDropOffPoint']) ->loadCount('members'), ), 'Household created. Upload proof of residency to begin verification.', ); } public function show(Request $request, Household $household): JsonResponse { $this->authorizeView($request->user(), $household); $household->load(['head', 'barangay.cityMunicipality', 'members.user', 'assignedDropOffPoint']) ->loadCount('members'); return $this->ok(new HouseholdResource($household)); } public function reassignDropOff(Request $request, Household $household, DropOffPointFinder $finder): JsonResponse { $this->authorizeOwn($request->user(), $household); if (! $household->coordinates) { return $this->fail('Household has no coordinates on file', null, 422); } $nearest = $finder->nearest( $household->coordinates->latitude, $household->coordinates->longitude, ); $household->forceFill(['assigned_drop_off_point_id' => $nearest?->id])->save(); return $this->ok( new HouseholdResource( $household->fresh()->load(['assignedDropOffPoint', 'barangay'])->loadCount('members'), ), $nearest ? 'Reassigned to nearest drop-off point' : 'No nearby drop-off point found', ); } public function update(UpdateHouseholdRequest $request, Household $household): JsonResponse { $this->authorizeOwn($request->user(), $household); if ($household->verification_status === Household::VERIFICATION_APPROVED) { return $this->fail( 'Approved households cannot be edited. Contact an admin.', null, 422, ); } $data = $request->validated(); if (isset($data['lat'], $data['lng'])) { $data['coordinates'] = new Point((float) $data['lat'], (float) $data['lng'], 4326); } unset($data['lat'], $data['lng']); $household->update($data); // Editing a rejected household resets it to pending. if ($household->verification_status === Household::VERIFICATION_REJECTED) { $household->forceFill([ 'verification_status' => Household::VERIFICATION_PENDING, 'rejection_reason' => null, ])->save(); } return $this->ok( new HouseholdResource( $household->fresh()->load(['head', 'barangay', 'members.user'])->loadCount('members'), ), 'Household updated', ); } public function uploadProof(UploadProofRequest $request, Household $household): JsonResponse { $this->authorizeOwn($request->user(), $household); $path = $request->file('proof')->store('households/proofs', 'local'); $household->forceFill(['proof_of_residency_path' => $path]); if ($household->verification_status === Household::VERIFICATION_REJECTED) { $household->forceFill([ 'verification_status' => Household::VERIFICATION_PENDING, 'rejection_reason' => null, ]); } $household->save(); return $this->ok( new HouseholdResource($household->fresh()->load(['head', 'barangay'])->loadCount('members')), 'Proof of residency uploaded', ); } public function addMember(StoreHouseholdMemberRequest $request, Household $household): JsonResponse { $this->authorizeOwn($request->user(), $household); $data = $request->validated(); $member = HouseholdMember::create([ 'household_id' => $household->id, 'user_id' => $data['user_id'] ?? null, 'relationship' => $data['relationship'], 'full_name' => $data['full_name'] ?? null, 'date_of_birth' => $data['date_of_birth'] ?? null, ]); return $this->created( new HouseholdResource( $household->fresh()->load(['head', 'barangay', 'members.user'])->loadCount('members'), ), 'Member added', ); } public function removeMember(Request $request, Household $household, HouseholdMember $member): JsonResponse { $this->authorizeOwn($request->user(), $household); if ($member->household_id !== $household->id) { return $this->notFound('Member not found in this household'); } if ($member->relationship === HouseholdMember::RELATIONSHIP_HEAD) { return $this->fail('Cannot remove the head of household', null, 422); } $member->delete(); return $this->ok(null, 'Member removed'); } private function authorizeOwn(User $user, Household $household): void { if ($household->head_user_id !== $user->id) { abort(403, 'You do not own this household'); } } private function authorizeView(User $user, Household $household): void { if ($household->head_user_id === $user->id) { return; } if ($user->role === User::ROLE_ADMIN) { return; } if ($household->members()->where('user_id', $user->id)->exists()) { return; } abort(403, 'You do not have access to this household'); } }