feat(backend): complete Module 5 (drop-off points + auto-assign)
drop_off_points (with SPATIAL INDEX on coordinates) +
drop_off_capacity_logs. Public nearby query via ST_Distance_Sphere
returns DOPs sorted by distance with distance_meters in payload.
Admin CRUD plus capacity-log endpoint. Household creation auto-assigns
to the nearest active DOP within 25km; resident can re-run the lookup
via POST /households/{uuid}/reassign-drop-off.
97 feature tests passing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\V1\Admin;
|
||||
|
||||
use App\Http\Controllers\Api\V1\ApiController;
|
||||
use App\Http\Requests\DropOff\StoreCapacityLogRequest;
|
||||
use App\Http\Requests\DropOff\StoreDropOffPointRequest;
|
||||
use App\Http\Requests\DropOff\UpdateDropOffPointRequest;
|
||||
use App\Http\Resources\DropOffCapacityLogResource;
|
||||
use App\Http\Resources\DropOffPointResource;
|
||||
use App\Models\DropOffCapacityLog;
|
||||
use App\Models\DropOffPoint;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use MatanYadaev\EloquentSpatial\Objects\Point;
|
||||
|
||||
class AdminDropOffPointController extends ApiController
|
||||
{
|
||||
public function index(Request $request): JsonResponse
|
||||
{
|
||||
$request->validate([
|
||||
'status' => ['nullable', 'in:active,maintenance,closed'],
|
||||
'barangay_id' => ['nullable', 'integer'],
|
||||
'q' => ['nullable', 'string', 'max:100'],
|
||||
'per_page' => ['nullable', 'integer', 'min:1', 'max:100'],
|
||||
]);
|
||||
|
||||
$perPage = (int) $request->input('per_page', 25);
|
||||
|
||||
$points = DropOffPoint::query()
|
||||
->with('barangay')
|
||||
->when($request->filled('status'), fn ($q) => $q->where('status', $request->string('status')))
|
||||
->when($request->filled('barangay_id'), fn ($q) => $q->where('barangay_id', $request->integer('barangay_id')))
|
||||
->when($request->filled('q'), function ($q) use ($request) {
|
||||
$term = '%'.$request->string('q').'%';
|
||||
$q->where(fn ($qq) => $qq->where('name', 'like', $term)
|
||||
->orWhere('code', 'like', $term)
|
||||
->orWhere('address_line', 'like', $term));
|
||||
})
|
||||
->orderBy('name')
|
||||
->paginate($perPage);
|
||||
|
||||
return $this->ok(
|
||||
DropOffPointResource::collection($points),
|
||||
null,
|
||||
[
|
||||
'page' => $points->currentPage(),
|
||||
'per_page' => $points->perPage(),
|
||||
'total' => $points->total(),
|
||||
'last_page' => $points->lastPage(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
public function store(StoreDropOffPointRequest $request): JsonResponse
|
||||
{
|
||||
$data = $request->validated();
|
||||
$point = $this->makePoint($data);
|
||||
|
||||
$dop = DropOffPoint::create([
|
||||
'name' => $data['name'],
|
||||
'code' => $data['code'],
|
||||
'barangay_id' => $data['barangay_id'] ?? null,
|
||||
'coordinates' => $point,
|
||||
'address_line' => $data['address_line'],
|
||||
'capacity_kg' => $data['capacity_kg'] ?? null,
|
||||
'operating_hours' => $data['operating_hours'] ?? null,
|
||||
'accepted_waste_types' => $data['accepted_waste_types'] ?? null,
|
||||
'status' => $data['status'] ?? DropOffPoint::STATUS_ACTIVE,
|
||||
'photo_path' => $data['photo_path'] ?? null,
|
||||
'contact_person' => $data['contact_person'] ?? null,
|
||||
'contact_phone' => $data['contact_phone'] ?? null,
|
||||
]);
|
||||
|
||||
return $this->created(new DropOffPointResource($dop->load('barangay')), 'Drop-off point created');
|
||||
}
|
||||
|
||||
public function show(DropOffPoint $dropOffPoint): JsonResponse
|
||||
{
|
||||
$dropOffPoint->load('barangay.cityMunicipality');
|
||||
$dropOffPoint->loadMissing(['capacityLogs' => fn ($q) => $q->latest('recorded_at')->limit(20)]);
|
||||
|
||||
return $this->ok([
|
||||
'drop_off_point' => new DropOffPointResource($dropOffPoint),
|
||||
'recent_capacity_logs' => DropOffCapacityLogResource::collection($dropOffPoint->capacityLogs),
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(UpdateDropOffPointRequest $request, DropOffPoint $dropOffPoint): JsonResponse
|
||||
{
|
||||
$data = $request->validated();
|
||||
if (isset($data['lat'], $data['lng'])) {
|
||||
$data['coordinates'] = $this->makePoint($data);
|
||||
}
|
||||
unset($data['lat'], $data['lng']);
|
||||
|
||||
$dropOffPoint->update($data);
|
||||
|
||||
return $this->ok(
|
||||
new DropOffPointResource($dropOffPoint->fresh()->load('barangay')),
|
||||
'Drop-off point updated',
|
||||
);
|
||||
}
|
||||
|
||||
public function destroy(DropOffPoint $dropOffPoint): JsonResponse
|
||||
{
|
||||
$dropOffPoint->delete();
|
||||
|
||||
return $this->ok(null, 'Drop-off point deleted');
|
||||
}
|
||||
|
||||
public function logCapacity(StoreCapacityLogRequest $request, DropOffPoint $dropOffPoint): JsonResponse
|
||||
{
|
||||
$data = $request->validated();
|
||||
|
||||
$log = DropOffCapacityLog::create([
|
||||
'drop_off_point_id' => $dropOffPoint->id,
|
||||
'fill_percent' => $data['fill_percent'],
|
||||
'recorded_at' => $data['recorded_at'] ?? now(),
|
||||
'recorded_by_user_id' => $request->user()->id,
|
||||
'notes' => $data['notes'] ?? null,
|
||||
]);
|
||||
|
||||
return $this->created(
|
||||
new DropOffCapacityLogResource($log->load('recordedBy')),
|
||||
'Capacity reading recorded',
|
||||
);
|
||||
}
|
||||
|
||||
private function makePoint(array $data): Point
|
||||
{
|
||||
return new Point((float) $data['lat'], (float) $data['lng'], 4326);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user