Files
Verde-Web/app/Services/Store/PartnerStoreFinder.php
admin 306f8fb4e8 feat(backend): customer-site API gaps + CORS
- GET /partner-stores/nearby — public, residents browse active stores
  ranked by distance via ST_Distance_Sphere; PublicPartnerStoreResource
  excludes commission rate / owner / permit (only what a buyer needs).
- GET /partner-stores/{uuid} — public details, 404 if not active.
- GET /me/collections — paginated resident QR scan history with
  optional from/to date filters.
- GET /me/upcoming-pickups — finds scheduled/in-progress trips whose
  route includes the resident's assigned drop-off point. Returns
  household_assigned: false when no household yet.
- GET /me/notifications — paginated database notifications inbox
  with unread_only filter + unread_count in meta.
- POST /me/notifications/{id}/read, POST .../mark-all-read,
  GET .../unread-count.
- config/cors.php — allow CUSTOMER_APP_URL and any EXTRA_CORS_ORIGINS
  to call the API with credentials. Same-origin admin web is
  unaffected.

202 feature tests passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 17:55:10 +08:00

59 lines
1.8 KiB
PHP

<?php
namespace App\Services\Store;
use App\Models\PartnerStore;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
class PartnerStoreFinder
{
/**
* Active partner stores within radius (km) of the given coords,
* ordered by distance ascending. Each result has a distance_meters
* attribute attached.
*
* @return Collection<int, PartnerStore>
*/
public function nearby(float $latitude, float $longitude, float $radiusKm = 5.0, int $limit = 25): Collection
{
$radiusMeters = $radiusKm * 1000;
$rows = DB::table('partner_stores')
->whereNull('deleted_at')
->where('status', PartnerStore::STATUS_ACTIVE)
->whereNotNull('coordinates')
->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');
$stores = PartnerStore::with(['barangay', 'inventory'])
->whereIn('id', $rows->pluck('id'))
->get()
->keyBy('id');
return $rows->map(function ($r) use ($stores, $distancesById) {
$s = $stores->get($r->id);
if ($s) {
$s->distance_meters = (float) $distancesById->get($r->id);
}
return $s;
})->filter()->values();
}
}