124 lines
4.4 KiB
PHP
124 lines
4.4 KiB
PHP
|
|
<?php
|
||
|
|
|
||
|
|
declare(strict_types=1);
|
||
|
|
|
||
|
|
namespace Modules\RestaurantDelivery\DTOs;
|
||
|
|
|
||
|
|
use Modules\RestaurantDelivery\Enums\DeliveryStatus;
|
||
|
|
use Modules\RestaurantDelivery\Models\Delivery;
|
||
|
|
|
||
|
|
readonly class DeliveryTrackingDTO
|
||
|
|
{
|
||
|
|
public function __construct(
|
||
|
|
public int $deliveryId,
|
||
|
|
public string $trackingCode,
|
||
|
|
public DeliveryStatus $status,
|
||
|
|
public ?RiderLocationDTO $riderLocation = null,
|
||
|
|
public ?float $pickupLatitude = null,
|
||
|
|
public ?float $pickupLongitude = null,
|
||
|
|
public ?float $dropLatitude = null,
|
||
|
|
public ?float $dropLongitude = null,
|
||
|
|
public ?string $pickupAddress = null,
|
||
|
|
public ?string $dropAddress = null,
|
||
|
|
public ?int $eta = null,
|
||
|
|
public ?float $remainingDistance = null,
|
||
|
|
public ?string $routePolyline = null,
|
||
|
|
public ?array $riderInfo = null,
|
||
|
|
public ?array $timeline = null,
|
||
|
|
) {}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Create DTO from Delivery model and Firebase data.
|
||
|
|
*/
|
||
|
|
public static function fromDeliveryAndFirebase(Delivery $delivery, ?array $firebaseData = null): self
|
||
|
|
{
|
||
|
|
$riderLocation = null;
|
||
|
|
if ($firebaseData && isset($firebaseData['rider_location'])) {
|
||
|
|
$riderLocation = RiderLocationDTO::fromArray($firebaseData['rider_location']);
|
||
|
|
}
|
||
|
|
|
||
|
|
return new self(
|
||
|
|
deliveryId: $delivery->id,
|
||
|
|
trackingCode: $delivery->tracking_code,
|
||
|
|
status: $delivery->status,
|
||
|
|
riderLocation: $riderLocation,
|
||
|
|
pickupLatitude: (float) $delivery->pickup_latitude,
|
||
|
|
pickupLongitude: (float) $delivery->pickup_longitude,
|
||
|
|
dropLatitude: (float) $delivery->drop_latitude,
|
||
|
|
dropLongitude: (float) $delivery->drop_longitude,
|
||
|
|
pickupAddress: $delivery->pickup_address,
|
||
|
|
dropAddress: $delivery->drop_address,
|
||
|
|
eta: $firebaseData['eta'] ?? null,
|
||
|
|
remainingDistance: $firebaseData['remaining_distance'] ?? null,
|
||
|
|
routePolyline: $firebaseData['route']['polyline'] ?? $delivery->route_polyline,
|
||
|
|
riderInfo: $delivery->rider ? [
|
||
|
|
'id' => $delivery->rider->id,
|
||
|
|
'name' => $delivery->rider->full_name,
|
||
|
|
'phone' => $delivery->rider->phone,
|
||
|
|
'photo' => $delivery->rider->photo_url,
|
||
|
|
'rating' => $delivery->rider->rating,
|
||
|
|
'vehicle_type' => $delivery->rider->vehicle_type,
|
||
|
|
] : null,
|
||
|
|
timeline: $delivery->statusHistory?->map(fn ($h) => [
|
||
|
|
'status' => $h->to_status,
|
||
|
|
'timestamp' => $h->changed_at->toIso8601String(),
|
||
|
|
'label' => DeliveryStatus::from($h->to_status)->label(),
|
||
|
|
])->toArray(),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Convert to array.
|
||
|
|
*/
|
||
|
|
public function toArray(): array
|
||
|
|
{
|
||
|
|
return [
|
||
|
|
'delivery_id' => $this->deliveryId,
|
||
|
|
'tracking_code' => $this->trackingCode,
|
||
|
|
'status' => $this->status->value,
|
||
|
|
'status_label' => $this->status->label(),
|
||
|
|
'status_description' => $this->status->description(),
|
||
|
|
'status_color' => $this->status->color(),
|
||
|
|
'rider_location' => $this->riderLocation?->toArray(),
|
||
|
|
'pickup' => [
|
||
|
|
'latitude' => $this->pickupLatitude,
|
||
|
|
'longitude' => $this->pickupLongitude,
|
||
|
|
'address' => $this->pickupAddress,
|
||
|
|
],
|
||
|
|
'drop' => [
|
||
|
|
'latitude' => $this->dropLatitude,
|
||
|
|
'longitude' => $this->dropLongitude,
|
||
|
|
'address' => $this->dropAddress,
|
||
|
|
],
|
||
|
|
'eta' => $this->eta,
|
||
|
|
'eta_formatted' => $this->eta ? $this->formatEta($this->eta) : null,
|
||
|
|
'remaining_distance' => $this->remainingDistance,
|
||
|
|
'remaining_distance_formatted' => $this->remainingDistance
|
||
|
|
? round($this->remainingDistance, 1).' km'
|
||
|
|
: null,
|
||
|
|
'route_polyline' => $this->routePolyline,
|
||
|
|
'rider' => $this->riderInfo,
|
||
|
|
'timeline' => $this->timeline,
|
||
|
|
];
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Format ETA in human-readable format.
|
||
|
|
*/
|
||
|
|
protected function formatEta(int $minutes): string
|
||
|
|
{
|
||
|
|
if ($minutes < 60) {
|
||
|
|
return "{$minutes} min";
|
||
|
|
}
|
||
|
|
|
||
|
|
$hours = floor($minutes / 60);
|
||
|
|
$mins = $minutes % 60;
|
||
|
|
|
||
|
|
if ($mins === 0) {
|
||
|
|
return "{$hours} hr";
|
||
|
|
}
|
||
|
|
|
||
|
|
return "{$hours} hr {$mins} min";
|
||
|
|
}
|
||
|
|
}
|