132 lines
3.9 KiB
PHP
132 lines
3.9 KiB
PHP
<?php
|
||
|
||
namespace App\Models;
|
||
|
||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||
use Illuminate\Database\Eloquent\Model;
|
||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||
use Carbon\Carbon;
|
||
|
||
class ParkingLot extends Model
|
||
{
|
||
use HasFactory;
|
||
|
||
protected $fillable = [
|
||
'name',
|
||
'address',
|
||
'total_capacity',
|
||
'price_per_hour',
|
||
'pricing_rules',
|
||
'latitude',
|
||
'longitude',
|
||
'working_hours',
|
||
'is_active',
|
||
];
|
||
|
||
protected $casts = [
|
||
'latitude' => 'decimal:8',
|
||
'longitude' => 'decimal:8',
|
||
'price_per_hour' => 'decimal:2',
|
||
'total_capacity' => 'integer',
|
||
'is_active' => 'boolean',
|
||
'pricing_rules' => 'array',
|
||
];
|
||
|
||
public function scopeActive($query)
|
||
{
|
||
return $query->where('is_active', true);
|
||
}
|
||
|
||
public function scopeWithStatus($query)
|
||
{
|
||
return $query->withCount([
|
||
'carRegistries as active_registries_count' => function ($q) {
|
||
$q->active();
|
||
},
|
||
'bookings as active_bookings_count' => function ($q) {
|
||
$q->where('status', 'active');
|
||
}
|
||
]);
|
||
}
|
||
|
||
public function bookings(): HasMany
|
||
{
|
||
return $this->hasMany(Booking::class);
|
||
}
|
||
|
||
public function carRegistries(): HasMany
|
||
{
|
||
return $this->hasMany(CarRegistry::class);
|
||
}
|
||
|
||
public function scopeSearch($query, $search)
|
||
{
|
||
return $query->where('name', 'like', "%{$search}%")
|
||
->orWhere('address', 'like', "%{$search}%");
|
||
}
|
||
|
||
public function getAvailableSpacesAttribute()
|
||
{
|
||
$activeBookings = $this->bookings()->where('status', 'active')->count();
|
||
$activeRegistries = $this->carRegistries()->active()->count();
|
||
$occupied = $activeBookings + $activeRegistries;
|
||
return max(0, $this->total_capacity - $occupied);
|
||
}
|
||
|
||
public function getOccupiedSpacesAttribute()
|
||
{
|
||
$activeBookings = $this->bookings()->where('status', 'active')->count();
|
||
$activeRegistries = $this->carRegistries()->active()->count();
|
||
return $activeBookings + $activeRegistries;
|
||
}
|
||
|
||
/**
|
||
* Calculate fee and per-day breakdown between two timestamps.
|
||
* Uses pricing_rules (ISO weekday keys 1–7) if set, otherwise price_per_hour.
|
||
*
|
||
* Returns ['total' => float, 'details' => [['day','date','hours','rate','subtotal'], ...]]
|
||
*/
|
||
public function calculateFee(Carbon $start, Carbon $end): array
|
||
{
|
||
$rules = $this->pricing_rules ?? [];
|
||
$details = [];
|
||
$total = 0.0;
|
||
$cursor = $start->copy()->seconds(0);
|
||
|
||
while ($cursor < $end) {
|
||
$dayEnd = $cursor->copy()->endOfDay()->addSecond();
|
||
$segEnd = ($dayEnd < $end) ? $dayEnd : $end;
|
||
|
||
$dow = (int) $cursor->format('N'); // 1=Mon … 7=Sun
|
||
$rate = isset($rules[$dow]) ? (float) $rules[$dow] : (float) $this->price_per_hour;
|
||
$hours = round($cursor->diffInMinutes($segEnd) / 60, 4);
|
||
$subtotal = $hours * $rate;
|
||
|
||
$details[] = [
|
||
'day' => $this->arabicDayName($dow),
|
||
'date' => $cursor->format('Y/m/d'),
|
||
'hours' => round($hours, 2),
|
||
'rate' => $rate,
|
||
'subtotal' => round($subtotal, 2),
|
||
];
|
||
|
||
$total += $subtotal;
|
||
$cursor = $segEnd;
|
||
}
|
||
|
||
return ['total' => (float) number_format(ceil($total), 2, '.', ''), 'details' => $details];
|
||
}
|
||
|
||
private function arabicDayName(int $iso): string
|
||
{
|
||
return ['الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت', 'الأحد'][$iso - 1] ?? '';
|
||
}
|
||
|
||
public function getUsagePercentageAttribute()
|
||
{
|
||
$occupied = $this->occupied_spaces;
|
||
return $this->total_capacity > 0 ? round(($occupied / $this->total_capacity) * 100, 2) : 0;
|
||
}
|
||
}
|
||
?>
|