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
|
|
@ -98,6 +98,7 @@ class UserAbo extends Model
|
|||
'start_date' => 'datetime',
|
||||
'last_date' => 'datetime',
|
||||
'next_date' => 'datetime',
|
||||
'onetime_reminder_sent_for' => 'date',
|
||||
'cancel_date' => 'datetime',
|
||||
'user_deleted_at' => 'datetime',
|
||||
'carddata' => 'array',
|
||||
|
|
@ -121,6 +122,7 @@ class UserAbo extends Model
|
|||
'start_date',
|
||||
'last_date',
|
||||
'next_date',
|
||||
'onetime_reminder_sent_for',
|
||||
'cancel_date',
|
||||
'user_deleted_at',
|
||||
];
|
||||
|
|
@ -151,6 +153,34 @@ class UserAbo extends Model
|
|||
8 => 'success',
|
||||
];
|
||||
|
||||
/**
|
||||
* Aktive Abos (laufend): active=true und kein terminaler/inaktiver Status
|
||||
* (4 = storniert, 5 = beendet, 6 = inaktiv). Spiegelt die Definition aus
|
||||
* {@see \App\Services\Backoffice\BackofficeDashboardService}.
|
||||
*/
|
||||
public function scopeActiveAbo(\Illuminate\Database\Eloquent\Builder $query): \Illuminate\Database\Eloquent\Builder
|
||||
{
|
||||
return $query
|
||||
->where('active', true)
|
||||
->whereNotIn('status', [4, 5, 6]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Schränkt die Abfrage auf Abos ein, die im angegebenen Monat/Jahr hinzugekommen
|
||||
* sind (Eingangsmonat = start_date). Bei month=0 (oder außerhalb 1–12) bleibt die
|
||||
* Abfrage unverändert.
|
||||
*/
|
||||
public function scopeAddedInMonth(\Illuminate\Database\Eloquent\Builder $query, int $year, int $month): \Illuminate\Database\Eloquent\Builder
|
||||
{
|
||||
if ($month < 1 || $month > 12) {
|
||||
return $query;
|
||||
}
|
||||
|
||||
return $query
|
||||
->whereYear('start_date', $year)
|
||||
->whereMonth('start_date', $month);
|
||||
}
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'user_id');
|
||||
|
|
@ -171,6 +201,55 @@ class UserAbo extends Model
|
|||
return $this->hasMany(UserAboOrder::class);
|
||||
}
|
||||
|
||||
public function initialUserAboOrder(): ?UserAboOrder
|
||||
{
|
||||
return $this->user_abo_orders()
|
||||
->orderBy('id')
|
||||
->first();
|
||||
}
|
||||
|
||||
public function initialShoppingOrder(): ?ShoppingOrder
|
||||
{
|
||||
return $this->initialUserAboOrder()?->shopping_order;
|
||||
}
|
||||
|
||||
public function initialShoppingPayment(): ?ShoppingPayment
|
||||
{
|
||||
return $this->initialShoppingOrder()?->shopping_payments()
|
||||
->orderBy('id')
|
||||
->first();
|
||||
}
|
||||
|
||||
public function isPendingInitialPayment(): bool
|
||||
{
|
||||
return (int) $this->status === 1;
|
||||
}
|
||||
|
||||
public function hasInitialPaymentStatus(): bool
|
||||
{
|
||||
$payment = $this->initialShoppingPayment();
|
||||
$order = $this->initialShoppingOrder();
|
||||
|
||||
return filled($payment?->txaction)
|
||||
|| filled($payment?->status)
|
||||
|| filled($order?->txaction);
|
||||
}
|
||||
|
||||
public function hasFailedInitialPayment(): bool
|
||||
{
|
||||
$payment = $this->initialShoppingPayment();
|
||||
$order = $this->initialShoppingOrder();
|
||||
|
||||
return in_array($payment?->txaction, ['failed'], true)
|
||||
|| in_array($payment?->status, ['error', 'cancel'], true)
|
||||
|| in_array($order?->txaction, ['failed'], true);
|
||||
}
|
||||
|
||||
public function canDeletePendingInitialPayment(): bool
|
||||
{
|
||||
return $this->isPendingInitialPayment() && $this->hasFailedInitialPayment();
|
||||
}
|
||||
|
||||
public function user_abo_items()
|
||||
{
|
||||
return $this->hasMany(UserAboItem::class);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue