Log-Analyse 26.08. (P1-4): Adressen wie "ruediger@..." mit Umlaut im lokalen Teil lehnt der Mailer ab; Checkout-Bestaetigung und Abo-Erinnerung scheiterten. Registrierung, Checkout, Kunden- und Profilformulare pruefen jetzt mit email:filter. Die Abo-Erinnerung ueberspringt bestehende Adressen dieser Art mit einem Log-Eintrag statt einer Transport-Exception. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
122 lines
3.6 KiB
PHP
122 lines
3.6 KiB
PHP
<?php
|
|
|
|
namespace App\Console\Commands;
|
|
|
|
use App\Mail\MailAboExecutionReminder;
|
|
use App\Models\UserAbo;
|
|
use App\Services\AboHelper;
|
|
use Carbon\Carbon;
|
|
use Illuminate\Console\Command;
|
|
use Illuminate\Support\Facades\Log;
|
|
use Illuminate\Support\Facades\Mail;
|
|
|
|
class AboSendExecutionReminder extends Command
|
|
{
|
|
/**
|
|
* Reminder vor der Abo-Ausführung: weist auf das Zeitfenster für einmalige
|
|
* Zusatzprodukte hin. Berater erhalten den Link ins Sales Center, Endkunden
|
|
* ins Kundenportal.
|
|
*/
|
|
protected $signature = 'abo:send-execution-reminder {--dry-run : Nur zählen, nichts versenden}';
|
|
|
|
protected $description = 'Versendet Reminder-Mails für anstehende Abo-Lieferungen (Einmalprodukte-Fenster)';
|
|
|
|
public function handle(): int
|
|
{
|
|
$windowDays = AboHelper::getOneTimeWindowDays();
|
|
$targetDate = Carbon::today()->addDays($windowDays);
|
|
$dryRun = (bool) $this->option('dry-run');
|
|
|
|
$abos = UserAbo::query()
|
|
->with(['user', 'shopping_user'])
|
|
->where('active', true)
|
|
->where('status', 2)
|
|
->whereIn('is_for', ['me', 'ot'])
|
|
->whereDate('next_date', $targetDate->toDateString())
|
|
->where(function ($query) use ($targetDate): void {
|
|
$query->whereNull('onetime_reminder_sent_for')
|
|
->orWhereDate('onetime_reminder_sent_for', '!=', $targetDate->toDateString());
|
|
})
|
|
->get();
|
|
|
|
$sent = 0;
|
|
$skipped = 0;
|
|
|
|
foreach ($abos as $abo) {
|
|
$email = $this->recipientEmail($abo);
|
|
|
|
if (! $email) {
|
|
$skipped++;
|
|
|
|
continue;
|
|
}
|
|
|
|
// Symfony Mailer lehnt Nicht-ASCII im local-part ab. Vorab pruefen, damit
|
|
// im Log die Adresse statt einer Transport-Exception steht.
|
|
if (! filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
|
$skipped++;
|
|
Log::channel('abo_order')->error('AboSendExecutionReminder: Empfaengeradresse nicht versandfaehig', [
|
|
'abo_id' => $abo->id,
|
|
'email' => $email,
|
|
]);
|
|
|
|
continue;
|
|
}
|
|
|
|
if ($dryRun) {
|
|
$sent++;
|
|
|
|
continue;
|
|
}
|
|
|
|
try {
|
|
Mail::to($email)
|
|
->locale($this->recipientLocale($abo))
|
|
->send(new MailAboExecutionReminder($abo));
|
|
|
|
$abo->onetime_reminder_sent_for = $abo->next_date;
|
|
$abo->save();
|
|
$sent++;
|
|
} catch (\Throwable $e) {
|
|
$skipped++;
|
|
Log::channel('abo_order')->error('AboSendExecutionReminder: Versand fehlgeschlagen', [
|
|
'abo_id' => $abo->id,
|
|
'error' => $e->getMessage(),
|
|
]);
|
|
}
|
|
}
|
|
|
|
$this->info(sprintf(
|
|
'Reminder %s: %d versendet, %d übersprungen (Zieldatum %s, Fenster %d Tage).',
|
|
$dryRun ? '(dry-run)' : '',
|
|
$sent,
|
|
$skipped,
|
|
$targetDate->toDateString(),
|
|
$windowDays
|
|
));
|
|
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
private function recipientEmail(UserAbo $abo): ?string
|
|
{
|
|
if ($abo->email) {
|
|
return $abo->email;
|
|
}
|
|
|
|
if ($abo->is_for === 'me') {
|
|
return $abo->user?->email;
|
|
}
|
|
|
|
return $abo->shopping_user?->billing_email;
|
|
}
|
|
|
|
private function recipientLocale(UserAbo $abo): string
|
|
{
|
|
$locale = $abo->is_for === 'me'
|
|
? $abo->user?->getLocale()
|
|
: $abo->shopping_user?->getLocale();
|
|
|
|
return $locale ?: 'de';
|
|
}
|
|
}
|