Files
Verde-Web/app/Services/DropOff/DropOffPointFinder.php
admin 1150a518e8 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>
2026-04-30 00:11:22 +08:00

63 lines
1.9 KiB
PHP

<?php
namespace App\Services\DropOff;
use App\Models\DropOffPoint;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
class DropOffPointFinder
{
/**
* Return active DOPs within $radiusKm of the given coordinates,
* ordered by distance ascending. Each result has a `distance_meters`
* attribute attached.
*
* @return Collection<int, DropOffPoint>
*/
public function nearby(float $latitude, float $longitude, float $radiusKm = 5.0, int $limit = 25): Collection
{
$radiusMeters = $radiusKm * 1000;
$rows = DB::table('drop_off_points')
->whereNull('deleted_at')
->where('status', DropOffPoint::STATUS_ACTIVE)
->select('id')
->selectRaw(
'ST_Distance_Sphere(coordinates, ST_SRID(POINT(?, ?), 4326)) AS distance_meters',
[$longitude, $latitude],
)
->whereRaw(
'ST_Distance_Sphere(coordinates, ST_SRID(POINT(?, ?), 4326)) <= ?',
[$longitude, $latitude, $radiusMeters],
)
->orderBy('distance_meters')
->limit($limit)
->get();
if ($rows->isEmpty()) {
return collect();
}
$distancesById = $rows->pluck('distance_meters', 'id');
$points = DropOffPoint::with('barangay')
->whereIn('id', $rows->pluck('id'))
->get()
->keyBy('id');
return $rows->map(function ($r) use ($points, $distancesById) {
$p = $points->get($r->id);
if ($p) {
$p->distance_meters = (float) $distancesById->get($r->id);
}
return $p;
})->filter()->values();
}
public function nearest(float $latitude, float $longitude, float $maxKm = 25.0): ?DropOffPoint
{
return $this->nearby($latitude, $longitude, $maxKm, 1)->first();
}
}