runBirthdayReminders(); } private function runBirthdayReminders(): void { $users = User::whereHas('automations', fn ($q) => $q ->where('type', 'birthday_reminder') ->where('active', true) )->with(['automations' => fn ($q) => $q->where('type', 'birthday_reminder')])->get(); foreach ($users as $user) { $auto = $user->automations->first(); $settings = []; if (!empty($auto->settings)) { $settings = is_array($auto->settings) ? $auto->settings : json_decode($auto->settings, true); } elseif (!empty($auto->config)) { $settings = is_array($auto->config) ? $auto->config : json_decode($auto->config, true); } $daysBefore = (int) ($settings['days_before'] ?? 0); $tz = $user->timezone ?? 'Europe/Vienna'; $targetMonthDay = now($tz)->addDays($daysBefore)->format('m-d'); $contacts = \App\Models\Contact::where('user_id', $user->id) ->whereNotNull('birthday') ->get() ->filter(fn ($c) => Carbon::parse($c->birthday)->format('m-d') === $targetMonthDay); foreach ($contacts as $contact) { $hash = md5($user->id . $contact->id . now($tz)->format('Y-m-d') . 'birthday'); $alreadySent = DB::table('sent_reminders') ->where('reminder_hash', $hash) ->where('type', 'birthday') ->whereDate('sent_at', now($tz)->toDateString()) ->exists(); if ($alreadySent) continue; $pushText = [ 'title' => $daysBefore === 0 ? "Heute: {$contact->name} hat Geburtstag!" : "Geburtstag: {$contact->name}", 'body' => $daysBefore === 0 ? 'Vergiss nicht zu gratulieren!' : "In {$daysBefore} Tag(en)", ]; try { PushService::send( $user, $pushText['title'], $pushText['body'], ['type' => 'birthday_reminder', 'contact_id' => $contact->id] ); DB::table('sent_reminders')->insert([ 'id' => (string) Str::uuid(), 'event_id' => null, 'reminder_hash' => $hash, 'type' => 'birthday', 'sent_at' => now(), ]); \Log::info('Birthday push sent', [ 'user' => $user->name, 'contact' => $contact->name, ]); } catch (\Throwable $e) { \Log::error('Birthday push failed', [ 'user' => $user->name, 'contact' => $contact->name, 'error' => $e->getMessage(), ]); } } } } }