231 lines
6.8 KiB
PHP
231 lines
6.8 KiB
PHP
<?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\BelongsTo;
|
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
|
use Modules\RestaurantDelivery\Traits\HasUuid;
|
|
|
|
class ZonePricingRule extends Model
|
|
{
|
|
use HasFactory, HasUuid, SoftDeletes;
|
|
|
|
public const TABLE_NAME = 'restaurant_zone_pricing_rules';
|
|
|
|
protected $table = self::TABLE_NAME;
|
|
|
|
protected $fillable = [
|
|
'uuid',
|
|
'restaurant_id',
|
|
'zone_id',
|
|
'name',
|
|
'priority',
|
|
'is_active',
|
|
'base_fare',
|
|
'minimum_fare',
|
|
'per_km_charge',
|
|
'free_distance',
|
|
'max_distance',
|
|
'surge_enabled',
|
|
'surge_multiplier',
|
|
'conditions',
|
|
'valid_from',
|
|
'valid_until',
|
|
'valid_days',
|
|
];
|
|
|
|
protected $casts = [
|
|
'is_active' => 'boolean',
|
|
'surge_enabled' => 'boolean',
|
|
'base_fare' => 'decimal:2',
|
|
'minimum_fare' => 'decimal:2',
|
|
'per_km_charge' => 'decimal:2',
|
|
'free_distance' => 'decimal:2',
|
|
'max_distance' => 'decimal:2',
|
|
'surge_multiplier' => 'decimal:2',
|
|
'conditions' => 'array',
|
|
'valid_from' => 'datetime:H:i',
|
|
'valid_until' => 'datetime:H:i',
|
|
];
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Relationships
|
|
|--------------------------------------------------------------------------
|
|
*/
|
|
|
|
public function zone(): BelongsTo
|
|
{
|
|
return $this->belongsTo(DeliveryZone::class, 'zone_id');
|
|
}
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Scopes
|
|
|--------------------------------------------------------------------------
|
|
*/
|
|
|
|
public function scopeActive($query)
|
|
{
|
|
return $query->where('is_active', true);
|
|
}
|
|
|
|
public function scopeByPriority($query)
|
|
{
|
|
return $query->orderBy('priority');
|
|
}
|
|
|
|
public function scopeValidNow($query)
|
|
{
|
|
$now = now();
|
|
$currentTime = $now->format('H:i:s');
|
|
$currentDay = pow(2, $now->dayOfWeek); // Bitmask for current day
|
|
|
|
return $query->where(function ($q) use ($currentTime) {
|
|
$q->whereNull('valid_from')
|
|
->orWhere(function ($q2) use ($currentTime) {
|
|
$q2->where('valid_from', '<=', $currentTime)
|
|
->where('valid_until', '>=', $currentTime);
|
|
});
|
|
})->where(function ($q) use ($currentDay) {
|
|
$q->whereNull('valid_days')
|
|
->orWhereRaw('(valid_days & ?) > 0', [$currentDay]);
|
|
});
|
|
}
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Methods
|
|
|--------------------------------------------------------------------------
|
|
*/
|
|
|
|
public function isValidNow(): bool
|
|
{
|
|
$now = now();
|
|
|
|
// Check time validity
|
|
if ($this->valid_from && $this->valid_until) {
|
|
$currentTime = $now->format('H:i:s');
|
|
if ($currentTime < $this->valid_from || $currentTime > $this->valid_until) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Check day validity (bitmask)
|
|
if ($this->valid_days !== null) {
|
|
$currentDayBit = pow(2, $now->dayOfWeek);
|
|
if (($this->valid_days & $currentDayBit) === 0) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public function calculateCharge(float $distance): float
|
|
{
|
|
// Check max distance
|
|
if ($this->max_distance && $distance > $this->max_distance) {
|
|
return -1; // Indicates delivery not possible
|
|
}
|
|
|
|
// Calculate distance charge
|
|
$chargeableDistance = max(0, $distance - $this->free_distance);
|
|
$distanceCharge = $chargeableDistance * $this->per_km_charge;
|
|
|
|
// Calculate total
|
|
$total = $this->base_fare + $distanceCharge;
|
|
|
|
// Apply surge if enabled
|
|
if ($this->surge_enabled && $this->surge_multiplier > 1) {
|
|
$total *= $this->surge_multiplier;
|
|
}
|
|
|
|
// Apply minimum fare
|
|
$total = max($total, $this->minimum_fare);
|
|
|
|
return round($total, 2);
|
|
}
|
|
|
|
public function getChargeBreakdown(float $distance): array
|
|
{
|
|
$chargeableDistance = max(0, $distance - $this->free_distance);
|
|
$distanceCharge = $chargeableDistance * $this->per_km_charge;
|
|
$baseTotal = $this->base_fare + $distanceCharge;
|
|
|
|
$surgeAmount = 0;
|
|
if ($this->surge_enabled && $this->surge_multiplier > 1) {
|
|
$surgeAmount = $baseTotal * ($this->surge_multiplier - 1);
|
|
}
|
|
|
|
$total = $baseTotal + $surgeAmount;
|
|
$minimumApplied = false;
|
|
|
|
if ($total < $this->minimum_fare) {
|
|
$total = $this->minimum_fare;
|
|
$minimumApplied = true;
|
|
}
|
|
|
|
return [
|
|
'base_fare' => $this->base_fare,
|
|
'distance' => $distance,
|
|
'free_distance' => $this->free_distance,
|
|
'chargeable_distance' => $chargeableDistance,
|
|
'per_km_charge' => $this->per_km_charge,
|
|
'distance_charge' => $distanceCharge,
|
|
'surge_enabled' => $this->surge_enabled,
|
|
'surge_multiplier' => $this->surge_multiplier,
|
|
'surge_amount' => $surgeAmount,
|
|
'minimum_fare' => $this->minimum_fare,
|
|
'minimum_applied' => $minimumApplied,
|
|
'total' => round($total, 2),
|
|
];
|
|
}
|
|
|
|
public function meetsConditions(array $deliveryData): bool
|
|
{
|
|
if (empty($this->conditions)) {
|
|
return true;
|
|
}
|
|
|
|
foreach ($this->conditions as $condition) {
|
|
if (! $this->evaluateCondition($condition, $deliveryData)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
protected function evaluateCondition(array $condition, array $data): bool
|
|
{
|
|
$field = $condition['field'] ?? null;
|
|
$operator = $condition['operator'] ?? null;
|
|
$value = $condition['value'] ?? null;
|
|
|
|
if (! $field || ! $operator) {
|
|
return true;
|
|
}
|
|
|
|
$fieldValue = $data[$field] ?? null;
|
|
|
|
return match ($operator) {
|
|
'equals' => $fieldValue == $value,
|
|
'not_equals' => $fieldValue != $value,
|
|
'greater_than' => $fieldValue > $value,
|
|
'less_than' => $fieldValue < $value,
|
|
'greater_or_equal' => $fieldValue >= $value,
|
|
'less_or_equal' => $fieldValue <= $value,
|
|
'in' => in_array($fieldValue, (array) $value),
|
|
'not_in' => ! in_array($fieldValue, (array) $value),
|
|
'is_true' => (bool) $fieldValue === true,
|
|
'is_false' => (bool) $fieldValue === false,
|
|
default => true,
|
|
};
|
|
}
|
|
}
|