Files
Verde-Web/app/Http/Controllers/Api/V1/Household/HouseholdController.php
2026-07-02 13:55:34 +08:00

257 lines
9.1 KiB
PHP

<?php
namespace App\Http\Controllers\Api\V1\Household;
use App\Http\Controllers\Api\V1\ApiController;
use App\Http\Requests\Household\StoreHouseholdMemberRequest;
use App\Http\Requests\Household\StoreHouseholdRequest;
use App\Http\Requests\Household\UpdateHouseholdRequest;
use App\Http\Requests\Household\UploadProofRequest;
use App\Http\Resources\HouseholdResource;
use App\Models\Household;
use App\Models\HouseholdMember;
use App\Models\User;
use App\Services\DropOff\DropOffPointFinder;
use App\Services\Geo\GeoLocationService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use MatanYadaev\EloquentSpatial\Objects\Point;
class HouseholdController extends ApiController
{
public function store(
StoreHouseholdRequest $request,
GeoLocationService $geo,
DropOffPointFinder $dopFinder,
): JsonResponse {
$data = $request->validated();
$user = $request->user();
$existingHead = Household::where('head_user_id', $user->id)->exists();
$existingMember = HouseholdMember::where('user_id', $user->id)->exists();
if ($existingHead || $existingMember) {
return $this->fail(
'You are already part of a household.',
['head_user_id' => ['You are already in a 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();
$userToAdd = User::where('email', $data['email'])->first();
// Check if this user is already a member of any household or the head of one
$isMember = HouseholdMember::where('user_id', $userToAdd->id)->exists();
$isHead = Household::where('head_user_id', $userToAdd->id)->exists();
if ($isMember || $isHead) {
return $this->fail('This user is already part of a household.', ['email' => ['Already in a household.']], 422);
}
$existingSoftDeleted = HouseholdMember::withTrashed()
->where('household_id', $household->id)
->where('user_id', $userToAdd->id)
->first();
if ($existingSoftDeleted) {
$existingSoftDeleted->restore();
$existingSoftDeleted->update([
'relationship' => HouseholdMember::RELATIONSHIP_OTHER,
'full_name' => $userToAdd->full_name,
'date_of_birth' => $data['date_of_birth'] ?? null,
]);
$member = $existingSoftDeleted;
} else {
$member = HouseholdMember::create([
'household_id' => $household->id,
'user_id' => $userToAdd->id,
'relationship' => HouseholdMember::RELATIONSHIP_OTHER,
'full_name' => $userToAdd->full_name,
'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');
}
}