Abo Einmalprodukte: Phasen 5-7, konfigurierbare Sperrfristen, Lokalisierung & Dashboard
Rechnung/Lieferschein: - PDF-Gruppierung Abo- vs. Einmal-Artikel (is_abo_addon), rueckwaertskompatibel - Vorschau-Command sales:preview-invoice (umgeht Test-System-Gate, ohne Rechnungsnummer) Reminder-Mail (vorbereitet, Cron inaktiv): - MailAboExecutionReminder + Template, abo:send-execution-reminder, Spalte onetime_reminder_sent_for - Berater -> Sales Center, Kunde -> Portal; Vorschau abo:preview-reminder - Datum-Fix: formatDateDB() statt formatDate() (ICU-Format) Sperrfristen konfigurierbar (Admin-Settings): - abo-lock-days-change (Liefertag + Abo-Produkte), abo-lock-days-pause-cancel - Meldungen mit :lock-Platzhalter; Pausieren/Kuendigen vom Change-Lock entkoppelt - Abo-Produkt-Aenderungen im Lock-Fenster server- + clientseitig eingefroren - Selbstbedienung: Add-only/Lock nach $view (kein Admin-Bypass), Stale-Cache-Fix beim Mengen-Update Dashboard: - Einmalprodukte-Card + Team-Abo-KPI-Card, News-Card responsiv (col-xl-6) Lokalisierung: - Sprachdateien en/es/fr vollstaendig mit de synchronisiert (inkl. marketingplan/portal) - LocalizationSyncTest sichert Key-Gleichheit ab Tests: AboOneTimeInvoiceGrouping, AboExecutionReminder, AboLockDaysSetting, AboOnetimeDashboardAndMail, PreviewInvoiceCommand, TeamAboOverview + erweiterte View-Tests Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
9f996a7f1b
commit
65b867f8ee
96 changed files with 4154 additions and 1189 deletions
110
app/Console/Commands/AboSendExecutionReminder.php
Normal file
110
app/Console/Commands/AboSendExecutionReminder.php
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
<?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;
|
||||
}
|
||||
|
||||
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';
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue