> /dev/null 2>&1 * * Oder für stündliche Ausführung: * 0 * * * * cd /path/to/project && php artisan payments:reminders >> /dev/null 2>&1 * * Logs werden automatisch in storage/logs/laravel.log geschrieben */ class PaymentsReminders extends Command { /** * The name and signature of the console command. * * @var string */ protected $signature = 'payments:reminders'; protected $description = 'Run Payments Reminders'; private $timeStart; private $dev = false; private $paymentReminderService; private $stats = [ 'total_processed' => 0, 'reminders_sent' => 0, 'errors' => 0, 'skipped' => 0 ]; public function __construct(PaymentReminderService $paymentReminderService) { parent::__construct(); $this->paymentReminderService = $paymentReminderService; } /** * Execute the console command. * * @return int */ public function handle() { \Log::info('Starting PaymentsReminders Command', ['timestamp' => now()]); $this->info('RUN Command Payments Reminders: '.date('d.m.Y H:i')); $this->timeStart = microtime(true); try { $this->functionReminder(); $executionTime = round(microtime(true) - $this->timeStart, 2); $this->info("\n=== PAYMENT REMINDERS ABGESCHLOSSEN ==="); $this->info("Ausführungszeit: {$executionTime} Sekunden"); $this->info("Statistiken:"); $this->info(" - Gesamt verarbeitet: {$this->stats['total_processed']}"); $this->info(" - Erinnerungen gesendet: {$this->stats['reminders_sent']}"); $this->info(" - Fehler: {$this->stats['errors']}"); $this->info(" - Übersprungen: {$this->stats['skipped']}"); \Log::info('PaymentsReminders Command completed successfully', [ 'execution_time' => $executionTime, 'stats' => $this->stats ]); return 0; } catch (\Exception $e) { \Log::error('PaymentsReminders Command failed', [ 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString() ]); $this->error('Command failed: ' . $e->getMessage()); return 1; } } /** * Hauptfunktion für die Verarbeitung der Zahlungserinnerungen */ private function functionReminder() { $this->info('=== STARTE PAYMENT REMINDERS ==='); // Hole alle aktiven PaymentReminder und gruppiere sie nach clearingtype $payment_reminders = PaymentReminder::where('active', true)->get(); $this->info("Gefundene aktive PaymentReminder: " . $payment_reminders->count()); if ($payment_reminders->isEmpty()) { $this->warn("Keine aktiven PaymentReminder gefunden!"); return; } // Finde für jeden clearingtype das kleinste Intervall (in Tagen) $intervals = $this->paymentReminderService->getActiveIntervals(); $this->info("Gefundene clearingtypes mit kleinsten Intervallen:"); foreach($intervals as $clearingtype => $interval){ $this->line(" - {$clearingtype}: {$interval} Tage"); } // Verarbeite jeden clearingtype mit seinem kleinsten Intervall foreach($intervals as $clearingtype => $interval){ $this->info("\n--- Verarbeite clearingtype: {$clearingtype} mit Intervall: {$interval} Tage ---"); $date = Carbon::now()->subDays($interval); $this->line("Suche Zahlungen vor: " . $date->format('d.m.Y H:i:s')); // Hole nur die neueste ShoppingPayment pro shopping_order_id $shopping_payments = $this->paymentReminderService->getOpenPaymentsForClearingType($clearingtype, $interval); $this->info("Gefundene offene Zahlungen für {$clearingtype}: " . $shopping_payments->count()); if ($shopping_payments->isEmpty()) { $this->line(" Keine Zahlungen für {$clearingtype} gefunden."); continue; } // Verarbeite jede Zahlung foreach($shopping_payments as $shopping_payment){ $this->processPayment($shopping_payment, $clearingtype); } } } /** * Verarbeitet eine einzelne Zahlung und sendet ggf. eine Erinnerung */ private function processPayment($shopping_payment, $clearingtype) { $this->stats['total_processed']++; try { $this->line(" Verarbeite Order ID: {$shopping_payment->shopping_order_id}, Created: {$shopping_payment->created_at->format('d.m.Y H:i:s')}, Amount: {$shopping_payment->amount}, Reminder: {$shopping_payment->reminder}"); // Prüfe ob eine Erinnerung gesendet werden soll if ($this->shouldSendReminder($shopping_payment, $clearingtype)) { $this->sendReminderForPayment($shopping_payment); } else { $this->line(" ⏭️ Übersprungen - Keine Erinnerung fällig"); $this->stats['skipped']++; } } catch (\Exception $e) { $this->error(" ❌ Fehler bei Order ID {$shopping_payment->shopping_order_id}: " . $e->getMessage()); $this->stats['errors']++; \Log::error('Error processing payment reminder', [ 'order_id' => $shopping_payment->shopping_order_id, 'payment_id' => $shopping_payment->id, 'error' => $e->getMessage() ]); } } /** * Prüft ob eine Erinnerung für diese Zahlung gesendet werden soll * * Logik: * - Erste Erinnerung: Nach X Tagen ab Bestelldatum * - Weitere Erinnerungen: Nach Y Tagen ab letzter Erinnerung */ private function shouldSendReminder($shopping_payment, $clearingtype) { // Hole alle aktiven Erinnerungen für diesen Clearingtype $payment_reminders = PaymentReminder::where('active', true) ->where('clearingtype', $clearingtype) ->orderBy('interval', 'asc') ->get(); if ($payment_reminders->isEmpty()) { return false; } // Wenn alle Erinnerungen bereits gesendet wurden if ($shopping_payment->reminder >= $payment_reminders->count()) { return false; } // Hole die nächste Erinnerung $next_reminder = $payment_reminders[$shopping_payment->reminder]; // Wenn noch keine Erinnerung gesendet wurde, prüfe das erste Intervall if ($shopping_payment->reminder == 0) { $daysSinceOrder = $shopping_payment->created_at->diffInDays(now()); return $daysSinceOrder >= $next_reminder->interval; } // Wenn bereits Erinnerungen gesendet wurden, prüfe das nächste Intervall if ($shopping_payment->reminder_date) { $current_reminder = $payment_reminders[$shopping_payment->reminder - 1]; $interval_difference = $next_reminder->interval - $current_reminder->interval; $next_reminder_date = Carbon::parse($shopping_payment->reminder_date)->addDays($interval_difference); return now()->gte($next_reminder_date); } return false; } /** * Sendet eine Erinnerung für eine spezifische Zahlung */ private function sendReminderForPayment($shopping_payment) { try { $this->line(" 📧 Sende Erinnerung..."); $result = $this->paymentReminderService->sendReminder($shopping_payment); if ($result) { $this->line(" ✅ Erinnerung erfolgreich gesendet"); $this->stats['reminders_sent']++; // Log für Cron-Job \Log::info('Payment reminder sent via cron', [ 'order_id' => $shopping_payment->shopping_order_id, 'payment_id' => $shopping_payment->id, 'reminder_count' => $shopping_payment->reminder + 1, 'email' => $shopping_payment->shopping_order->shopping_user->billing_email ?? 'N/A' ]); } else { $this->line(" ⚠️ Keine Erinnerung gesendet (keine weitere Erinnerung verfügbar)"); $this->stats['skipped']++; } } catch (\Exception $e) { $this->error(" ❌ Fehler beim Senden der Erinnerung: " . $e->getMessage()); $this->stats['errors']++; \Log::error('Error sending payment reminder', [ 'order_id' => $shopping_payment->shopping_order_id, 'payment_id' => $shopping_payment->id, 'error' => $e->getMessage() ]); } } }