'date', 'actual_start_time' => 'datetime', 'actual_end_time' => 'datetime', ]; } // --- Relationships --- public function creator(): BelongsTo { return $this->belongsTo(User::class, 'created_by'); } public function starter(): BelongsTo { return $this->belongsTo(User::class, 'started_by'); } public function staff(): BelongsToMany { return $this->belongsToMany(User::class, 'shift_staff') ->withPivot('assigned_by') ->withTimestamps(); } public function shiftStaff(): HasMany { return $this->hasMany(ShiftStaff::class); } public function attendances(): HasMany { return $this->hasMany(ShiftAttendance::class); } // --- State Checks --- public function isPlanned(): bool { return $this->status === 'planned'; } public function isInProgress(): bool { return $this->status === 'in_progress'; } public function isCompleted(): bool { return $this->status === 'completed'; } // --- Computed Attributes --- public function getPlannedDurationHoursAttribute(): float { $start = \Carbon\Carbon::parse($this->planned_start_time); $end = \Carbon\Carbon::parse($this->planned_end_time); if ($end->lt($start)) { $end->addDay(); } return round($start->diffInMinutes($end) / 60, 2); } public function getActualDurationHoursAttribute(): ?float { if (!$this->actual_start_time || !$this->actual_end_time) { return null; } return round($this->actual_start_time->diffInMinutes($this->actual_end_time) / 60, 2); } // --- Scopes --- public function scopePlanned($query) { return $query->where('status', 'planned'); } public function scopeInProgress($query) { return $query->where('status', 'in_progress'); } public function scopeCompleted($query) { return $query->where('status', 'completed'); } public function scopeForDate($query, $date) { return $query->where('shift_date', $date); } public function scopeForDateRange($query, $from, $to) { return $query->whereBetween('shift_date', [$from, $to]); } public function scopeForStaff($query, $userId) { return $query->whereHas('staff', fn ($q) => $q->where('users.id', $userId)); } }