Files

231 lines
6.8 KiB
PHP
Raw Permalink Normal View History

2026-03-15 17:08:23 +07:00
<?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,
};
}
}