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
|
|
@ -8,9 +8,33 @@ use Carbon\Carbon;
|
|||
|
||||
class AboRepository extends BaseRepository
|
||||
{
|
||||
private const LOCK_DAYS_CHANGE = 10;
|
||||
private const DEFAULT_LOCK_DAYS_CHANGE = 10;
|
||||
|
||||
private const LOCK_DAYS_PAUSE_CANCEL = 3;
|
||||
private const DEFAULT_LOCK_DAYS_PAUSE_CANCEL = 3;
|
||||
|
||||
/**
|
||||
* Sperrfrist (Kalendertage) vor der Ausführung, ab der das Bearbeiten des Abos
|
||||
* (Intervall/Produkte) blockiert wird. Konfigurierbar über das Setting
|
||||
* `abo-lock-days-change`; Fallback auf den Default, wenn leer/0.
|
||||
*/
|
||||
public static function getLockDaysChange(): int
|
||||
{
|
||||
$days = (int) \App\Models\Setting::getContentBySlug('abo-lock-days-change');
|
||||
|
||||
return $days > 0 ? $days : self::DEFAULT_LOCK_DAYS_CHANGE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sperrfrist (Kalendertage) vor der Ausführung, ab der das Pausieren/Kündigen
|
||||
* des Abos blockiert wird. Konfigurierbar über das Setting
|
||||
* `abo-lock-days-pause-cancel`; Fallback auf den Default, wenn leer/0.
|
||||
*/
|
||||
public static function getLockDaysPauseCancel(): int
|
||||
{
|
||||
$days = (int) \App\Models\Setting::getContentBySlug('abo-lock-days-pause-cancel');
|
||||
|
||||
return $days > 0 ? $days : self::DEFAULT_LOCK_DAYS_PAUSE_CANCEL;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
|
|
@ -59,8 +83,8 @@ class AboRepository extends BaseRepository
|
|||
// Sperre: 3 Tage vor Ausführung kann nicht mehr pausiert/gekündigt werden
|
||||
if (! $isAdminUpdate && $this->model->next_date) {
|
||||
$daysUntil = (int) now()->diffInDays(\Carbon\Carbon::parse($this->model->next_date), false);
|
||||
if ($daysUntil >= 0 && $daysUntil < self::LOCK_DAYS_PAUSE_CANCEL) {
|
||||
\Session()->flash('alert-error', __('abo.error_cancel_locked', ['days' => $daysUntil]));
|
||||
if ($daysUntil >= 0 && $daysUntil < self::getLockDaysPauseCancel()) {
|
||||
\Session()->flash('alert-error', __('abo.error_cancel_locked', ['days' => $daysUntil, 'lock' => self::getLockDaysPauseCancel()]));
|
||||
|
||||
return;
|
||||
}
|
||||
|
|
@ -79,8 +103,8 @@ class AboRepository extends BaseRepository
|
|||
// Sperre: 3 Tage vor Ausführung kann nicht mehr pausiert werden
|
||||
if (! $isAdminUpdate && $this->model->active && ! $active && $this->model->next_date) {
|
||||
$daysUntil = (int) now()->diffInDays(\Carbon\Carbon::parse($this->model->next_date), false);
|
||||
if ($daysUntil >= 0 && $daysUntil < self::LOCK_DAYS_PAUSE_CANCEL) {
|
||||
\Session()->flash('alert-error', __('abo.error_pause_locked', ['days' => $daysUntil]));
|
||||
if ($daysUntil >= 0 && $daysUntil < self::getLockDaysPauseCancel()) {
|
||||
\Session()->flash('alert-error', __('abo.error_pause_locked', ['days' => $daysUntil, 'lock' => self::getLockDaysPauseCancel()]));
|
||||
|
||||
return;
|
||||
}
|
||||
|
|
@ -149,26 +173,33 @@ class AboRepository extends BaseRepository
|
|||
return true;
|
||||
}
|
||||
|
||||
// Sperre: 10 Tage vor nächster Ausführung keine Änderungen mehr (Pakete werden vorgepackt)
|
||||
if ($this->model->next_date) {
|
||||
$daysUntilExecution = (int) now()->diffInDays(\Carbon\Carbon::parse($this->model->next_date), false);
|
||||
if ($daysUntilExecution >= 0 && $daysUntilExecution < self::LOCK_DAYS_CHANGE) {
|
||||
\Session()->flash('alert-error', __('abo.error_change_locked', ['days' => $daysUntilExecution]));
|
||||
// Die Change-Sperre (Liefertag/Intervall) greift NUR, wenn der Liefertag tatsächlich
|
||||
// geändert wird. Reines Pausieren/Kündigen (Intervall unverändert) unterliegt
|
||||
// ausschließlich der eigenen Sperrfrist in updateStatus() + der Mindestlaufzeit.
|
||||
$intervalChanged = (int) $data['abo_interval'] !== (int) $this->model->abo_interval;
|
||||
|
||||
if ($intervalChanged) {
|
||||
// Sperre: X Tage vor nächster Ausführung kein Liefertag-Wechsel mehr (Pakete werden vorgepackt)
|
||||
if ($this->model->next_date) {
|
||||
$daysUntilExecution = (int) now()->diffInDays(\Carbon\Carbon::parse($this->model->next_date), false);
|
||||
if ($daysUntilExecution >= 0 && $daysUntilExecution < self::getLockDaysChange()) {
|
||||
\Session()->flash('alert-error', __('abo.error_change_locked', ['days' => $daysUntilExecution, 'lock' => self::getLockDaysChange()]));
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Prüfung: Das neue berechnete Ausführungsdatum muss mindestens getLockDaysChange() Tage entfernt sein.
|
||||
// Falls next_date bereits in einem zukünftigen Monat liegt, wird das neue Datum in diesem Monat berechnet.
|
||||
$newNextDate = $this->calculateNewNextDate($data['abo_interval']);
|
||||
$daysUntilNewDate = (int) now()->diffInDays($newNextDate, false);
|
||||
if ($daysUntilNewDate < self::getLockDaysChange()) {
|
||||
\Session()->flash('alert-error', __('abo.error_abo_interval_too_soon', ['days' => $daysUntilNewDate, 'lock' => self::getLockDaysChange()]));
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Prüfung: Das neue berechnete Ausführungsdatum muss mindestens LOCK_DAYS_CHANGE Tage entfernt sein.
|
||||
// Falls next_date bereits in einem zukünftigen Monat liegt, wird das neue Datum in diesem Monat berechnet.
|
||||
$newNextDate = $this->calculateNewNextDate($data['abo_interval']);
|
||||
$daysUntilNewDate = (int) now()->diffInDays($newNextDate, false);
|
||||
if ($daysUntilNewDate < self::LOCK_DAYS_CHANGE) {
|
||||
\Session()->flash('alert-error', __('abo.error_abo_interval_too_soon', ['days' => $daysUntilNewDate]));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue