migrate to gtea from bistbucket
This commit is contained in:
@@ -0,0 +1,250 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\RestaurantDelivery\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Modules\RestaurantDelivery\Traits\HasRestaurant;
|
||||
use Modules\RestaurantDelivery\Traits\HasUuid;
|
||||
|
||||
class DeliveryZone extends Model
|
||||
{
|
||||
use HasFactory, HasRestaurant, HasUuid, SoftDeletes;
|
||||
|
||||
public const TABLE_NAME = 'restaurant_delivery_zones';
|
||||
|
||||
protected $table = self::TABLE_NAME;
|
||||
|
||||
protected $fillable = [
|
||||
'id',
|
||||
'uuid',
|
||||
'restaurant_id',
|
||||
'name',
|
||||
'slug',
|
||||
'description',
|
||||
'color',
|
||||
'coordinates',
|
||||
'min_lat',
|
||||
'max_lat',
|
||||
'min_lng',
|
||||
'max_lng',
|
||||
'priority',
|
||||
'is_active',
|
||||
'is_default',
|
||||
'max_delivery_distance',
|
||||
'operating_hours',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'coordinates' => 'array',
|
||||
'operating_hours' => 'array',
|
||||
'is_active' => 'boolean',
|
||||
'is_default' => 'boolean',
|
||||
'min_lat' => 'decimal:7',
|
||||
'max_lat' => 'decimal:7',
|
||||
'min_lng' => 'decimal:7',
|
||||
'max_lng' => 'decimal:7',
|
||||
'max_delivery_distance' => 'decimal:2',
|
||||
];
|
||||
|
||||
protected static function boot()
|
||||
{
|
||||
parent::boot();
|
||||
|
||||
static::saving(function ($zone) {
|
||||
if (! empty($zone->coordinates)) {
|
||||
$zone->calculateBoundingBox();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Relationships
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
public function pricingRules(): HasMany
|
||||
{
|
||||
return $this->hasMany(ZonePricingRule::class, 'zone_id')
|
||||
->orderBy('priority');
|
||||
}
|
||||
|
||||
public function activePricingRule(): HasMany
|
||||
{
|
||||
return $this->hasMany(ZonePricingRule::class, 'zone_id')
|
||||
->where('is_active', true)
|
||||
->orderBy('priority')
|
||||
->limit(1);
|
||||
}
|
||||
|
||||
public function deliveries(): HasMany
|
||||
{
|
||||
return $this->hasMany(Delivery::class, 'zone_id');
|
||||
}
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Scopes
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
public function scopeActive($query)
|
||||
{
|
||||
return $query->where('is_active', true);
|
||||
}
|
||||
|
||||
public function scopeDefault($query)
|
||||
{
|
||||
return $query->where('is_default', true);
|
||||
}
|
||||
|
||||
public function scopeByPriority($query)
|
||||
{
|
||||
return $query->orderByDesc('priority');
|
||||
}
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Methods
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
public function calculateBoundingBox(): void
|
||||
{
|
||||
if (empty($this->coordinates)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$lats = array_column($this->coordinates, 0);
|
||||
$lngs = array_column($this->coordinates, 1);
|
||||
|
||||
$this->min_lat = min($lats);
|
||||
$this->max_lat = max($lats);
|
||||
$this->min_lng = min($lngs);
|
||||
$this->max_lng = max($lngs);
|
||||
}
|
||||
|
||||
public function containsPoint(float $latitude, float $longitude): bool
|
||||
{
|
||||
// Quick bounding box check first
|
||||
if (
|
||||
$latitude < $this->min_lat ||
|
||||
$latitude > $this->max_lat ||
|
||||
$longitude < $this->min_lng ||
|
||||
$longitude > $this->max_lng
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ray casting algorithm for polygon containment
|
||||
return $this->pointInPolygon($latitude, $longitude, $this->coordinates);
|
||||
}
|
||||
|
||||
protected function pointInPolygon(float $latitude, float $longitude, array $polygon): bool
|
||||
{
|
||||
$n = count($polygon);
|
||||
$inside = false;
|
||||
|
||||
$x = $longitude;
|
||||
$y = $latitude;
|
||||
|
||||
$p1x = $polygon[0][1];
|
||||
$p1y = $polygon[0][0];
|
||||
|
||||
for ($i = 1; $i <= $n; $i++) {
|
||||
$p2x = $polygon[$i % $n][1];
|
||||
$p2y = $polygon[$i % $n][0];
|
||||
|
||||
if ($y > min($p1y, $p2y)) {
|
||||
if ($y <= max($p1y, $p2y)) {
|
||||
if ($x <= max($p1x, $p2x)) {
|
||||
if ($p1y != $p2y) {
|
||||
$xinters = ($y - $p1y) * ($p2x - $p1x) / ($p2y - $p1y) + $p1x;
|
||||
}
|
||||
if ($p1x == $p2x || $x <= $xinters) {
|
||||
$inside = ! $inside;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$p1x = $p2x;
|
||||
$p1y = $p2y;
|
||||
}
|
||||
|
||||
return $inside;
|
||||
}
|
||||
|
||||
public function isOperatingNow(): bool
|
||||
{
|
||||
if (empty($this->operating_hours)) {
|
||||
return true; // No restrictions
|
||||
}
|
||||
|
||||
$now = now();
|
||||
$dayName = strtolower($now->format('l'));
|
||||
|
||||
if (! isset($this->operating_hours[$dayName])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$hours = $this->operating_hours[$dayName];
|
||||
|
||||
if (isset($hours['closed']) && $hours['closed']) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$openTime = \Carbon\Carbon::createFromTimeString($hours['open']);
|
||||
$closeTime = \Carbon\Carbon::createFromTimeString($hours['close']);
|
||||
|
||||
// Handle overnight hours
|
||||
if ($closeTime->lt($openTime)) {
|
||||
return $now->gte($openTime) || $now->lte($closeTime);
|
||||
}
|
||||
|
||||
return $now->between($openTime, $closeTime);
|
||||
}
|
||||
|
||||
public function getActivePricingRule(): ?ZonePricingRule
|
||||
{
|
||||
return $this->pricingRules()
|
||||
->where('is_active', true)
|
||||
->orderBy('priority')
|
||||
->first();
|
||||
}
|
||||
|
||||
public function canDeliverTo(float $latitude, float $longitude): bool
|
||||
{
|
||||
if (! $this->is_active) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $this->containsPoint($latitude, $longitude)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $this->isOperatingNow()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function findForPoint(float $latitude, float $longitude): ?self
|
||||
{
|
||||
return static::active()
|
||||
->byPriority()
|
||||
->get()
|
||||
->first(fn ($zone) => $zone->containsPoint($latitude, $longitude));
|
||||
}
|
||||
|
||||
public static function getDefault(): ?self
|
||||
{
|
||||
return static::active()->default()->first();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user