257 lines
9.1 KiB
PHP
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');
|
|
}
|
|
}
|