Files
Verde-Web/app/Http/Controllers/Api/V1/Me/UpcomingPickupsController.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

73 lines
2.6 KiB
PHP

<?php
namespace App\Http\Controllers\Api\V1\Me;
use App\Http\Controllers\Api\V1\ApiController;
use App\Models\Household;
use App\Models\Trip;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class UpcomingPickupsController extends ApiController
{
/**
* Trips scheduled at or after today whose route includes this
* resident's assigned drop-off point. In-progress trips are
* always included so the resident sees a "truck on the way"
* card even mid-day.
*/
public function index(Request $request): JsonResponse
{
$household = Household::with('assignedDropOffPoint')
->where('head_user_id', $request->user()->id)
->first();
if (! $household || ! $household->assigned_drop_off_point_id) {
return $this->ok([
'household_assigned' => false,
'pickups' => [],
]);
}
$dopId = $household->assigned_drop_off_point_id;
$trips = Trip::query()
->with(['route:id,name,code', 'team:id,name,driver_id', 'team.driver:id,first_name,last_name', 'truck:id,plate_number'])
->whereHas('route.stops', fn ($q) => $q->where('drop_off_point_id', $dopId))
->where(function ($q) {
$q->where('scheduled_date', '>=', now()->toDateString())
->orWhereIn('status', [Trip::STATUS_IN_PROGRESS, Trip::STATUS_AT_DUMPSITE]);
})
->whereNotIn('status', [Trip::STATUS_CANCELLED, Trip::STATUS_COMPLETED])
->orderBy('scheduled_date')
->orderBy('scheduled_start_time')
->limit(10)
->get();
$pickups = $trips->map(function (Trip $trip) use ($dopId) {
$myStop = $trip->route?->stops()->where('drop_off_point_id', $dopId)->first();
return [
'trip_id' => $trip->uuid,
'trip_number' => $trip->trip_number,
'status' => $trip->status,
'scheduled_date' => $trip->scheduled_date?->toDateString(),
'scheduled_start_time' => $trip->scheduled_start_time,
'route' => $trip->route?->name,
'truck_plate' => $trip->truck?->plate_number,
'driver_name' => $trip->team?->driver?->full_name,
'my_stop_sequence' => $myStop?->sequence,
];
});
return $this->ok([
'household_assigned' => true,
'drop_off_point' => [
'id' => $household->assignedDropOffPoint?->uuid,
'name' => $household->assignedDropOffPoint?->name,
],
'pickups' => $pickups,
]);
}
}