migrate to gtea from bistbucket

This commit is contained in:
2026-03-15 17:08:23 +07:00
commit 129ca2260c
3716 changed files with 566316 additions and 0 deletions

View File

@@ -0,0 +1,115 @@
<?php
declare(strict_types=1);
namespace Modules\RestaurantDelivery\DTOs;
use Illuminate\Http\Request;
readonly class CreateDeliveryDTO
{
public function __construct(
public int $restaurantId,
public string $restaurantName,
public string $pickupAddress,
public float $pickupLatitude,
public float $pickupLongitude,
public string $customerName,
public string $dropAddress,
public float $dropLatitude,
public float $dropLongitude,
public string $dropContactPhone,
public ?int $zoneId = null,
public ?string $orderableType = null,
public ?int $orderableId = null,
public ?string $pickupContactName = null,
public ?string $pickupContactPhone = null,
public ?string $pickupInstructions = null,
public ?string $dropContactName = null,
public ?string $dropInstructions = null,
public ?string $dropFloor = null,
public ?string $dropApartment = null,
public bool $isScheduled = false,
public ?\DateTimeInterface $scheduledFor = null,
public bool $isPriority = false,
public ?float $orderValue = null,
public ?float $tipAmount = null,
public ?string $tipType = null,
public ?array $meta = null,
) {}
/**
* Create DTO from request.
*
* IMPORTANT: restaurant_id is NEVER passed from request.
* It is always obtained via getUserRestaurantId().
*/
public static function fromRequest(Request $request): self
{
return new self(
restaurantId: (int) getUserRestaurantId(),
restaurantName: $request->string('restaurant_name')->toString(),
pickupAddress: $request->string('pickup_address')->toString(),
pickupLatitude: (float) $request->input('pickup_latitude'),
pickupLongitude: (float) $request->input('pickup_longitude'),
customerName: $request->string('customer_name')->toString(),
dropAddress: $request->string('drop_address')->toString(),
dropLatitude: (float) $request->input('drop_latitude'),
dropLongitude: (float) $request->input('drop_longitude'),
dropContactPhone: $request->string('drop_contact_phone')->toString(),
zoneId: $request->filled('zone_id') ? $request->integer('zone_id') : null,
orderableType: $request->filled('orderable_type') ? $request->string('orderable_type')->toString() : null,
orderableId: $request->filled('orderable_id') ? $request->integer('orderable_id') : null,
pickupContactName: $request->filled('pickup_contact_name') ? $request->string('pickup_contact_name')->toString() : null,
pickupContactPhone: $request->filled('pickup_contact_phone') ? $request->string('pickup_contact_phone')->toString() : null,
pickupInstructions: $request->filled('pickup_instructions') ? $request->string('pickup_instructions')->toString() : null,
dropContactName: $request->filled('drop_contact_name') ? $request->string('drop_contact_name')->toString() : null,
dropInstructions: $request->filled('drop_instructions') ? $request->string('drop_instructions')->toString() : null,
dropFloor: $request->filled('drop_floor') ? $request->string('drop_floor')->toString() : null,
dropApartment: $request->filled('drop_apartment') ? $request->string('drop_apartment')->toString() : null,
isScheduled: $request->boolean('is_scheduled'),
scheduledFor: $request->filled('scheduled_for') ? new \DateTime($request->input('scheduled_for')) : null,
isPriority: $request->boolean('is_priority'),
orderValue: $request->filled('order_value') ? (float) $request->input('order_value') : null,
tipAmount: $request->filled('tip_amount') ? (float) $request->input('tip_amount') : null,
tipType: $request->filled('tip_type') ? $request->string('tip_type')->toString() : null,
meta: $request->input('meta'),
);
}
/**
* Convert to array for model creation.
*/
public function toArray(): array
{
return array_filter([
'restaurant_id' => $this->restaurantId,
'restaurant_name' => $this->restaurantName,
'pickup_address' => $this->pickupAddress,
'pickup_latitude' => $this->pickupLatitude,
'pickup_longitude' => $this->pickupLongitude,
'customer_name' => $this->customerName,
'drop_address' => $this->dropAddress,
'drop_latitude' => $this->dropLatitude,
'drop_longitude' => $this->dropLongitude,
'drop_contact_phone' => $this->dropContactPhone,
'zone_id' => $this->zoneId,
'orderable_type' => $this->orderableType,
'orderable_id' => $this->orderableId,
'pickup_contact_name' => $this->pickupContactName,
'pickup_contact_phone' => $this->pickupContactPhone,
'pickup_instructions' => $this->pickupInstructions,
'drop_contact_name' => $this->dropContactName,
'drop_instructions' => $this->dropInstructions,
'drop_floor' => $this->dropFloor,
'drop_apartment' => $this->dropApartment,
'is_scheduled' => $this->isScheduled,
'scheduled_for' => $this->scheduledFor?->format('Y-m-d H:i:s'),
'is_priority' => $this->isPriority,
'order_value' => $this->orderValue,
'tip_amount' => $this->tipAmount,
'tip_type' => $this->tipType,
'meta' => $this->meta,
], fn ($value) => $value !== null);
}
}

View File

@@ -0,0 +1,123 @@
<?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";
}
}

View File

@@ -0,0 +1,88 @@
<?php
declare(strict_types=1);
namespace Modules\RestaurantDelivery\DTOs;
readonly class EarningsDTO
{
public function __construct(
public float $baseAmount,
public float $distanceAmount,
public float $totalBonus,
public float $totalPenalty,
public float $tipAmount,
public float $grossAmount,
public float $commissionRate,
public float $commissionAmount,
public float $netAmount,
public string $currency,
public array $breakdown = [],
public array $bonuses = [],
public array $penalties = [],
) {}
/**
* Create DTO from calculation result.
*/
public static function fromCalculation(array $data): self
{
return new self(
baseAmount: (float) ($data['base_amount'] ?? 0),
distanceAmount: (float) ($data['distance_amount'] ?? 0),
totalBonus: (float) ($data['total_bonus'] ?? 0),
totalPenalty: (float) ($data['total_penalty'] ?? 0),
tipAmount: (float) ($data['tip_amount'] ?? 0),
grossAmount: (float) ($data['gross_amount'] ?? 0),
commissionRate: (float) ($data['commission_rate'] ?? 0),
commissionAmount: (float) ($data['commission_amount'] ?? 0),
netAmount: (float) ($data['net_amount'] ?? 0),
currency: $data['currency'] ?? config('restaurant-delivery.pricing.currency', 'BDT'),
breakdown: $data['breakdown'] ?? [],
bonuses: $data['bonuses'] ?? [],
penalties: $data['penalties'] ?? [],
);
}
/**
* Convert to array.
*/
public function toArray(): array
{
return [
'base_amount' => $this->baseAmount,
'distance_amount' => $this->distanceAmount,
'total_bonus' => $this->totalBonus,
'total_penalty' => $this->totalPenalty,
'tip_amount' => $this->tipAmount,
'gross_amount' => $this->grossAmount,
'commission_rate' => $this->commissionRate,
'commission_amount' => $this->commissionAmount,
'net_amount' => $this->netAmount,
'currency' => $this->currency,
'breakdown' => $this->breakdown,
'bonuses' => $this->bonuses,
'penalties' => $this->penalties,
];
}
/**
* Get formatted amounts for display.
*/
public function toFormattedArray(): array
{
$symbol = config('restaurant-delivery.pricing.currency_symbol', '৳');
return [
'base_amount' => $symbol.number_format($this->baseAmount, 2),
'distance_amount' => $symbol.number_format($this->distanceAmount, 2),
'total_bonus' => $symbol.number_format($this->totalBonus, 2),
'total_penalty' => $symbol.number_format($this->totalPenalty, 2),
'tip_amount' => $symbol.number_format($this->tipAmount, 2),
'gross_amount' => $symbol.number_format($this->grossAmount, 2),
'commission_amount' => $symbol.number_format($this->commissionAmount, 2),
'net_amount' => $symbol.number_format($this->netAmount, 2),
'commission_rate' => $this->commissionRate.'%',
];
}
}

View File

@@ -0,0 +1,74 @@
<?php
declare(strict_types=1);
namespace Modules\RestaurantDelivery\DTOs;
use Illuminate\Http\Request;
readonly class RatingDTO
{
public function __construct(
public int $overallRating,
public ?int $speedRating = null,
public ?int $communicationRating = null,
public ?int $foodConditionRating = null,
public ?int $professionalismRating = null,
public ?string $review = null,
public array $tags = [],
public bool $isAnonymous = false,
public bool $isRestaurantRating = false,
public ?int $customerId = null,
) {}
/**
* Create DTO from request.
*/
public static function fromRequest(Request $request): self
{
return new self(
overallRating: $request->integer('overall_rating'),
speedRating: $request->filled('speed_rating') ? $request->integer('speed_rating') : null,
communicationRating: $request->filled('communication_rating') ? $request->integer('communication_rating') : null,
foodConditionRating: $request->filled('food_condition_rating') ? $request->integer('food_condition_rating') : null,
professionalismRating: $request->filled('professionalism_rating') ? $request->integer('professionalism_rating') : null,
review: $request->filled('review') ? $request->string('review')->toString() : null,
tags: $request->input('tags', []),
isAnonymous: $request->boolean('is_anonymous'),
isRestaurantRating: $request->boolean('is_restaurant_rating'),
customerId: $request->filled('customer_id') ? $request->integer('customer_id') : null,
);
}
/**
* Get category ratings as array.
*/
public function categoryRatings(): array
{
return array_filter([
'speed' => $this->speedRating,
'communication' => $this->communicationRating,
'food_condition' => $this->foodConditionRating,
'professionalism' => $this->professionalismRating,
]);
}
/**
* Convert to array.
*/
public function toArray(): array
{
return [
'overall_rating' => $this->overallRating,
'speed_rating' => $this->speedRating,
'communication_rating' => $this->communicationRating,
'food_condition_rating' => $this->foodConditionRating,
'professionalism_rating' => $this->professionalismRating,
'review' => $this->review,
'tags' => $this->tags,
'is_anonymous' => $this->isAnonymous,
'is_restaurant_rating' => $this->isRestaurantRating,
'customer_id' => $this->customerId,
];
}
}

View File

@@ -0,0 +1,108 @@
<?php
declare(strict_types=1);
namespace Modules\RestaurantDelivery\DTOs;
use Illuminate\Http\Request;
readonly class RiderLocationDTO
{
public function __construct(
public float $latitude,
public float $longitude,
public ?float $speed = null,
public ?float $bearing = null,
public ?float $accuracy = null,
public ?float $altitude = null,
public ?\DateTimeInterface $timestamp = null,
) {}
/**
* Create DTO from request.
*/
public static function fromRequest(Request $request): self
{
return new self(
latitude: (float) $request->input('latitude'),
longitude: (float) $request->input('longitude'),
speed: $request->filled('speed') ? (float) $request->input('speed') : null,
bearing: $request->filled('bearing') ? (float) $request->input('bearing') : null,
accuracy: $request->filled('accuracy') ? (float) $request->input('accuracy') : null,
altitude: $request->filled('altitude') ? (float) $request->input('altitude') : null,
timestamp: $request->filled('timestamp') ? new \DateTime($request->input('timestamp')) : null,
);
}
/**
* Create DTO from array.
*/
public static function fromArray(array $data): self
{
return new self(
latitude: (float) ($data['latitude'] ?? $data['lat'] ?? 0),
longitude: (float) ($data['longitude'] ?? $data['lng'] ?? 0),
speed: isset($data['speed']) ? (float) $data['speed'] : null,
bearing: isset($data['bearing']) ? (float) $data['bearing'] : null,
accuracy: isset($data['accuracy']) ? (float) $data['accuracy'] : null,
altitude: isset($data['altitude']) ? (float) $data['altitude'] : null,
timestamp: isset($data['timestamp']) ? new \DateTime($data['timestamp']) : null,
);
}
/**
* Convert to array.
*/
public function toArray(): array
{
return array_filter([
'latitude' => $this->latitude,
'longitude' => $this->longitude,
'speed' => $this->speed,
'bearing' => $this->bearing,
'accuracy' => $this->accuracy,
'altitude' => $this->altitude,
'timestamp' => $this->timestamp?->format('Y-m-d H:i:s'),
], fn ($value) => $value !== null);
}
/**
* Convert to Firebase format.
*/
public function toFirebaseFormat(): array
{
return [
'lat' => $this->latitude,
'lng' => $this->longitude,
'speed' => $this->speed,
'bearing' => $this->bearing,
'accuracy' => $this->accuracy,
'timestamp' => ($this->timestamp ?? now())->getTimestamp() * 1000, // JavaScript timestamp
];
}
/**
* Check if location is valid.
*/
public function isValid(): bool
{
return $this->latitude >= -90
&& $this->latitude <= 90
&& $this->longitude >= -180
&& $this->longitude <= 180;
}
/**
* Check if accuracy is acceptable.
*/
public function hasAcceptableAccuracy(?float $threshold = null): bool
{
if ($this->accuracy === null) {
return true;
}
$threshold = $threshold ?? config('restaurant-delivery.firebase.location.accuracy_threshold', 50);
return $this->accuracy <= $threshold;
}
}