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() { // $this->model = $model; } public function setModel(UserAbo $model) { $this->model = $model; } public function update($data) { if (isset($data['action'])) { if ($data['action'] === 'abo_update_settings') { if ($this->validate($data)) { $this->updateStatus($data); $this->model->abo_interval = (int) $data['abo_interval']; $nextDate = $this->isAdminUpdate($data) ? $this->calculateAdminNextDate($data) : $this->calculateNewNextDate((int) $data['abo_interval']); $this->model->next_date = $nextDate; $this->model->save(); $daysUntilNext = AboHelper::calendarDaysUntil(now(), $nextDate); \Session()->flash('alert-warning', __('abo.warning_next_date_info', ['days' => $daysUntilNext, 'date' => $nextDate->format('d.m.Y')])); return true; } return false; } } return false; } public function create($data) {} private function updateStatus($data) { $isAdminUpdate = $this->isAdminUpdate($data); // Handle cancellation if (isset($data['abo_cancel']) && $data['abo_cancel'] == 'true') { // 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::getLockDaysPauseCancel()) { \Session()->flash('alert-error', __('abo.error_cancel_locked', ['days' => $daysUntil, 'lock' => self::getLockDaysPauseCancel()])); return; } } // Status 4 = abo_cancel (storniert/gekündigt) $this->model->status = 4; $this->model->active = false; $this->model->cancel_date = now(); $this->model->save(); return; } $active = (isset($data['abo_is_active']) && $data['abo_is_active']) ? true : false; // 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::getLockDaysPauseCancel()) { \Session()->flash('alert-error', __('abo.error_pause_locked', ['days' => $daysUntil, 'lock' => self::getLockDaysPauseCancel()])); return; } } // if status is active and active is false, set status to inactive if ($this->model->active && ! $active) { if ($this->model->status == 2) { // okay $this->model->status = 6; // inactive $this->model->active = false; $this->model->save(); } } if (! $this->model->active && $active) { if ($this->model->status == 6) { // inactive $this->model->status = 2; // okay $this->model->active = true; $this->model->save(); } } $this->model->active = $active; } private function validate($data) { if ($data['view'] !== 'admin' && $data['view'] !== 'portal') { if ($this->model->is_for === 'me' && $this->model->user_id !== \Auth::user()->id) { \Session()->flash('alert-error', 'Unauthorized action. User ID does not match.'); return false; } if ($this->model->is_for === 'ot' && $this->model->member_id !== \Auth::user()->id) { \Session()->flash('alert-error', 'Unauthorized action. User ID does not match.'); return false; } if ($data['view'] === 'me' && $this->model->is_for !== 'me') { \Session()->flash('alert-error', 'Unauthorized action. Is not for me'); return false; } if ($data['view'] === 'ot' && $this->model->is_for !== 'ot') { \Session()->flash('alert-error', 'Unauthorized action. Is not your customer'); return false; } } if (! isset($data['abo_interval']) || ! in_array((int) $data['abo_interval'], \App\Models\UserAbo::$aboDeliveryDays, true)) { \Session()->flash('alert-error', __('abo.error_abo_interval')); return false; } if ($this->isAdminUpdate($data)) { if (! isset($data['abo_next_month']) || ! in_array($data['abo_next_month'], $this->getAdminExecutionMonths(), true)) { \Session()->flash('alert-error', __('abo.error_next_date')); return false; } if ($this->calculateAdminNextDate($data)->startOfDay()->lt(now()->startOfDay())) { \Session()->flash('alert-error', __('abo.error_next_date')); return false; } return true; } // 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; } } return true; } /** * Berechnet das neue Ausführungsdatum unter Berücksichtigung des aktuellen next_date. * Falls next_date bereits in einem zukünftigen Monat liegt, wird der Monatsanfang * dieses Monats als Referenz verwendet, sodass der neue Tag im selben Monat landet. */ private function calculateNewNextDate(int $aboInterval): \Carbon\Carbon { $referenceDate = now(); if ($this->model->next_date) { $currentNextDate = \Carbon\Carbon::parse($this->model->next_date); if ($currentNextDate->format('Y-m') > now()->format('Y-m')) { $referenceDate = $currentNextDate->startOfMonth(); } } return AboHelper::setNextDate($referenceDate, $aboInterval); } private function calculateAdminNextDate(array $data): Carbon { return Carbon::createFromFormat('Y-m-d', $data['abo_next_month'].'-01') ->startOfMonth() ->addDays(((int) $data['abo_interval']) - 1); } /** * @return array */ private function getAdminExecutionMonths(): array { return collect(range(0, 3)) ->map(fn (int $offset): string => now()->copy()->startOfMonth()->addMonths($offset)->format('Y-m')) ->all(); } private function isAdminUpdate(array $data): bool { return ($data['view'] ?? null) === 'admin'; } }