*/ use HasFactory; protected $fillable = [ 'store_status', 'notice_headline', 'notice_subtext', 'override_open_today', 'override_close_today', 'next_appointment_date', 'next_appointment_time', 'hours_monday_open', 'hours_monday_close', 'hours_tuesday_open', 'hours_tuesday_close', 'hours_wednesday_open', 'hours_wednesday_close', 'hours_thursday_open', 'hours_thursday_close', 'hours_friday_open', 'hours_friday_close', 'hours_saturday_open', 'hours_saturday_close', 'hours_sunday_open', 'hours_sunday_close', 'contact_phone', 'contact_email', ]; protected function casts(): array { return [ 'next_appointment_date' => 'date', ]; } private const DAYS = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday']; private const GERMAN_DAY_LABELS = [ 'monday' => 'Montag', 'tuesday' => 'Dienstag', 'wednesday' => 'Mittwoch', 'thursday' => 'Donnerstag', 'friday' => 'Freitag', 'saturday' => 'Samstag', 'sunday' => 'Sonntag', ]; /** * Get or create the singleton settings row. */ public static function current(): self { return static::firstOrCreate(['id' => 1]); } /** * Get opening hours as display strings for the frontend (e.g. "10:00 – 18:00" or "Geschlossen"). * * @return array */ public function getHoursArray(): array { $result = []; foreach (self::DAYS as $day) { $open = $this->{"hours_{$day}_open"}; $close = $this->{"hours_{$day}_close"}; $result[$day] = ($open && $close) ? "{$open} – {$close}" : 'Geschlossen'; } return $result; } /** * Compute the effective store status based on opening hours and current Berlin time. * * Returns the display status ('open', 'closed', 'notice'), the next opening time when * closed, and today's closing time when open. * * @return array{status: string, today_close: string|null, next_open: array{label: string, time: string}|null} */ public function computeStatus(): array { if ($this->store_status === 'notice' || $this->store_status === 'warning') { return ['status' => $this->store_status, 'today_close' => null, 'next_open' => null]; } if ($this->store_status === 'closed') { $now = Carbon::now('Europe/Berlin'); return ['status' => 'closed', 'today_close' => null, 'next_open' => $this->findNextOpenTime($now, true)]; } // Auto mode: compute from opening hours $now = Carbon::now('Europe/Berlin'); $dayKey = strtolower($now->englishDayOfWeek); $openTime = $this->override_open_today ?: $this->{"hours_{$dayKey}_open"}; $closeTime = $this->override_close_today ?: $this->{"hours_{$dayKey}_close"}; $currentHHMM = $now->format('H:i'); if ($openTime && $closeTime && $currentHHMM >= $openTime && $currentHHMM < $closeTime) { return ['status' => 'open', 'today_close' => $closeTime, 'next_open' => null]; } return ['status' => 'closed', 'today_close' => null, 'next_open' => $this->findNextOpenTime($now, false)]; } /** * Find the next upcoming opening time from the given Berlin datetime. * * @return array{label: string, time: string}|null */ private function findNextOpenTime(Carbon $now, bool $skipToday): ?array { // Check if today still has an upcoming opening (we might be before opening time) if (! $skipToday) { $dayKey = strtolower($now->englishDayOfWeek); $todayOpen = $this->override_open_today ?: $this->{"hours_{$dayKey}_open"}; if ($todayOpen && $now->format('H:i') < $todayOpen) { return ['label' => 'Heute', 'time' => $todayOpen]; } } // Look ahead up to 7 days for ($i = 1; $i <= 7; $i++) { $checkDate = $now->copy()->addDays($i); $dayKey = strtolower($checkDate->englishDayOfWeek); $openTime = $this->{"hours_{$dayKey}_open"}; if ($openTime) { $label = $i === 1 ? 'Morgen' : self::GERMAN_DAY_LABELS[$dayKey]; return ['label' => $label, 'time' => $openTime]; } } return null; } /** * Clear override times (called by midnight scheduler). */ public function clearOverrides(): void { $this->update([ 'override_open_today' => null, 'override_close_today' => null, ]); } }