option('dry-run'); $reminded = 0; $due = Instance::query() ->with('customer') ->where('status', 'cancellation_scheduled') ->whereNotNull('service_ends_at') // In der Zukunft und höchstens sieben Tage entfernt — siehe // Kopfkommentar. Eine bereits abgelaufene Instanz gehört // EndDueServices, nicht mehr dieser Erinnerung. ->where('service_ends_at', '>', now()) ->where('service_ends_at', '<=', now()->addDays(7)) ->whereNull('export_reminded_at') ->cursor(); foreach ($due as $instance) { $customer = $instance->customer; if ($customer === null || blank($customer->email)) { // $customer === null ist über die Anwendung praktisch // unerreichbar: instances.customer_id ist ein // Pflicht-Fremdschlüssel mit Kaskade, und customers.email ist // NOT NULL. Trotzdem abgefangen statt vorausgesetzt — eine // Instanz ohne ansprechbaren Kunden soll den ganzen Lauf // nicht mitreißen, welcher Zustand sie auch dorthin gebracht // hätte. Laut, aber ohne export_reminded_at zu setzen: eine // Mail, die nie eine Adresse hatte, ist keine, die verschickt // wurde. Log::warning('Instanz nähert sich dem Laufzeitende, aber kein erreichbarer Kunde dahinter.', [ 'instance' => $instance->uuid, ]); continue; } $this->line(($dryRun ? '[dry-run] ' : '') ."{$instance->subdomain}: Laufzeit endet am " .$instance->service_ends_at->local()->isoFormat('LL')); if ($dryRun) { $reminded++; continue; } try { $mail = new ServiceEndingSoonMail($instance); Mail::mailer($mail->mailer)->to($customer->email)->queue($mail); // In DERSELBEN Absicherung wie der Versand, nicht danach: // hinge ausgerechnet dieser Schreibzugriff (Datenbank kurz // weg), bliebe die Marke leer, obwohl die Mail schon // eingereiht ist — derselbe Kunde bekäme morgen eine zweite. // Und ein ungefangener Fehler hier würde den ganzen Lauf // abbrechen, statt nur diese eine Instanz zu überspringen — // die restlichen Instanzen des Tages kämen erst morgen an // die Reihe, ein Siebtel der sieben Tage Vorlauf verloren. $instance->update(['export_reminded_at' => now()]); } catch (Throwable $e) { Log::warning('Die Erinnerung vor dem Laufzeitende ließ sich nicht einreihen oder nicht vermerken.', [ 'instance' => $instance->uuid, 'exception' => $e->getMessage(), ]); continue; } $reminded++; } $this->info($dryRun ? "Probelauf: {$reminded} Erinnerung(en) würden verschickt. Nichts wurde geändert." : "{$reminded} Erinnerung(en) verschickt."); return self::SUCCESS; } }