Abo (A1+A2): Liefertag frei waehlbar 1-31, Aenderung nur nach vorn, Sperre erst am Ausfuehrungstag

A1: Jeder Monatstag ist als Liefertag erlaubt (UserAbo::allowedDeliveryDays),
Tage 29-31 liefern in kuerzeren Monaten am letzten Tag. Eine Stelle
AboHelper::dateForDayInMonth klammert; setNextDate, Erstausfuehrung,
Admin-Termin und Pause rechnen darueber, der Folgemonat immer aus dem
Liefertag (31.01 -> 28.02 -> 31.03). Liefertag-Aenderung gilt fuer den
laufenden Zyklus und nur nach vorn: ist der Tag im Referenzmonat vorbei,
wird abgelehnt, der Monat verschiebt sich nie (Rueckfrage R1).
Admin-Filter "naechste Ausfuehrung" als Datumsbereich. Prognose-Kacheln
bekommen die Kachel Monatsende, Team-Abos rechnen bis Monatsende.

A2: Sperrfristen Default 0, Setting 0 ist ausdruecklich 0 (fiel bisher auf
den Default zurueck). Bedingung Kalendertage <= Sperre, zentral in
AboHelper::isWithinLock: am Ausfuehrungstag ab 0:00 Uhr fixiert, Vortag bis
23:59 Uhr frei, fuer Liefertag, Produkte, Pause und Kuendigung (R2). Texte
mit _zero-Varianten ueber AboHelper::lockMessage.

Sprachdateien de/en/es/fr, neuer AboDeliveryDayTest, bestehende Tests
angepasst, Plan-Status mit Deploy-Schritt (Settings auf 0).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Kevin 2026-09-16 11:28:55 +00:00
parent d6efe56cd4
commit cf0311afc1
30 changed files with 638 additions and 185 deletions

View file

@ -1,6 +1,6 @@
# Entwicklungsplan – Protokoll Zoom-Meeting 08.09.2026
**Stand:** 16.09.2026 · **Status:** C1/C2 live seit 14.09., A4 und A3 umgesetzt (committet, Deploy offen); als Nächstes A1+A2
**Stand:** 16.09.2026 · **Status:** C1/C2 live seit 14.09., A4, A3 und A1+A2 umgesetzt (committet, Deploy offen); Abo-Paket damit bis auf A5 komplett
**Quellen:** `2026 09 Protokoll September-komprimiert.pdf` (Protokoll), `2026-09-14.md` (Ticket-Entwurf Abos #1–#3)
**Verwandte Pläne:** `dev/2026-07-22/entwicklungsplan-2026-09-07.md` (K11–K14 behandeln dieselben Themen aus dem Kundenbrief; die dortigen Rückfragen F4–F7 sind weiterhin offen), `dev/2026-09-10-business-plan/` (Ledger-Plan, löst A07/K11 langfristig)
@ -10,8 +10,8 @@
| # | Thema | Quelle | Aufwand | Risiko | Blockiert durch |
|---|---|---|---|---|---|
| **A1** | Abo-Ausführungstag frei wählbar (1–31, Monatsende-Regel) | Ticket #1, Protokoll | 3–4 PT | mittel | – |
| **A2** | Änderungssperre erst ab 0:00 Uhr am Ausführungstag | Ticket #1 | 1 PT | niedrig | zusammen mit A1 |
| **A1** | Abo-Ausführungstag frei wählbar (1–31, Monatsende-Regel) – **umgesetzt 16.09., Deploy offen** (Lager/Versand: Freigabe Kevin 16.09.) | Ticket #1, Protokoll | 3–4 PT | mittel | – |
| **A2** | Änderungssperre erst ab 0:00 Uhr am Ausführungstag – **umgesetzt 16.09., Deploy offen (Settings auf 0!)** | Ticket #1 | 1 PT | niedrig | zusammen mit A1 |
| **A3** | Abo-Status und Grund sichtbar (Zahlung / Kunde / Admin) – **umgesetzt 16.09., Deploy offen (Migration + Backfill)** | Ticket #2, Protokoll | 2–3 PT | niedrig | – |
| **A4** | Pausieren/Kündigen beschriften, Kündigungs-Hinweis (6 Ausführungen) – **umgesetzt 16.09., Deploy offen** | Ticket #3, Protokoll | 1 PT | niedrig | – |
| **A5** | Kundenabo → Partnerabo automatisch umwandeln | Protokoll | 4–6 PT | hoch | Rückfragen R5 |
@ -37,7 +37,7 @@
## 1. Abo-Paket
### A1 – Ausführungstag frei wählbar (1–31)
### A1 – Ausführungstag frei wählbar (1–31) — umgesetzt 16.09., Deploy offen
**Ziel (Ticket #1):** Jeder Tag 1–31 wählbar; bei 29/30/31 in kürzeren Monaten der letzte Monatstag. Genau eine Ausführung pro Monat. Änderung nur „nach vorne“ (10. → 24. ja, 10. → 5. nein, wenn der 5. schon vorbei ist). Wird am Ausführungstag 0:00 Uhr fixiert (→ A2).
@ -72,7 +72,7 @@
- **Rückfrage R1:** Wenn der gewünschte Tag im laufenden Monat schon vorbei ist – Fehlermeldung, oder Änderung automatisch für den Folgemonat übernehmen (dann würde diese Monatslieferung noch am alten Tag laufen)?
- Bestehende Abos ändern sich nicht; keine Datenmigration.
### A2 – Änderungssperre erst am Ausführungstag ab 0:00 Uhr
### A2 – Änderungssperre erst am Ausführungstag ab 0:00 Uhr — umgesetzt 16.09., Deploy offen
**Ziel (Ticket #1):** Tag und Produkte bis 23:59 Uhr des Vortags änderbar, dann fixiert. Die Sperre bleibt im Code (Setting), Default effektiv 0. Einmalprodukte behalten ihr eigenes Fenster (4 Tage vorher).
@ -95,6 +95,24 @@
**Risiko:** niedrig. Zeitliche Lücke 0:00–04:00 Uhr ist durch die neue Bedingung geschlossen.
**Umsetzung A1+A2 16.09.2026 (ein Commit auf `payment/phase-r-payone-refactoring`):**
- **Liefertage:** `UserAbo::$aboDeliveryDays` ersetzt durch `UserAbo::allowedDeliveryDays()` (1–31) und `isAllowedDeliveryDay()`; alle sieben Verwendungen umgestellt. Select mit 31 Einträgen, Tage 29–31 mit Zusatz „(in kürzeren Monaten der letzte Tag)“. Kein Schema-Change, bestehende Werte 5/10/20/25 unverändert.
- **Datum:** eine Stelle `AboHelper::dateForDayInMonth($monat, $tag)` (Klammerung auf Monatsende). `setNextDate()`, Erst-Ausführung (`getFirstAboDate`), Admin-Termin (`calculateAdminNextDate`) und Pause rechnen darüber. Der Folgemonat kommt immer aus `abo_interval`: 31. Januar → 28. Februar → 31. März → 30. April (Test). Nebenbei behoben: Tag 1 mit Referenz Monatsanfang sprang bisher einen Monat weiter.
- **Änderungsregel (R1):** `calculateNewNextDate()` = gewählter Tag im Referenzmonat (Monat des nächsten Termins, wenn der schon im Folgemonat liegt, sonst laufender Monat), nie ein anderer Monat. Liegt der Tag nicht nach heute → Fehler `error_abo_interval_passed` („Der 5. ist in diesem Monat schon vorbei. Die nächste Lieferung bleibt am …“). Die 10-Tage-Prüfung entfällt; bei gepflegter Sperrfrist > 0 gilt weiterhin `error_abo_interval_too_soon`.
- **Sperre (A2, R2):** Defaults `abo-lock-days-change` und `abo-lock-days-pause-cancel` = 0; Setting leer → Default, Setting `0` → 0 (vorher fiel 0 auf 10/3 zurück). Bedingung jetzt `Kalendertage bis next_date <= lock`, zentral in `AboHelper::isWithinLock()` — mit 0 ist genau der Ausführungstag ab 0:00 Uhr gesperrt, der Vortag bis 23:59 Uhr frei. Gilt für Liefertag, Produkte (AJAX-Guards), Pause und Kündigung; Admin unverändert frei. Texte über `AboHelper::lockMessage()`: bei 0 die `_zero`-Varianten („…wird heute ausgeführt. Nach der Lieferung kannst du…“), sonst die Varianten mit `:lock` Tagen.
- **Admin-Filter:** „nächste Ausführung“ als Datumsbereich (von/bis auf `next_date`) statt Tages-Dropdown.
- **Prognosen:** Kacheln 5./10./20./25. bleiben, dazu die Kachel **Monatsende** (`is_month_end`, Label „Prognose zum Monatsende (30.09.2026)“) in Dashboard und Kundenabo-Liste; Nachzügler-Buchungen hängen jetzt an dieser Kachel. Team-Abos: „bis Monatsende erwartet“ statt „bis zum 25.“ (`TeamAboOverviewService`, Fenster bis Monatsende).
- Sprachdateien de/en/es/fr (abo.php, home.php). Tests: neu `AboDeliveryDayTest` (7 Tests: Tage 1–31, Klammerung, 31er-Kette, Erstausführung, „nur nach vorn“, Sperre am Ausführungstag, Admin-Klammerung); angepasst `AboLockDaysSettingTest`, `AboRepositoryDateChangeTest`, `AboPauseTest`, `AboPauseCancelSectionTest`, `PaylineForecastServiceTest`. 335 Tests der Abo-/Prognose-Suite grün (bis auf den vorbestehenden Fehlschlag in `AboPendingInitialPaymentTest`).
- **Deploy:** Dateien hochladen, `php artisan view:clear`, dann die beiden Settings auf 0 setzen — sonst bleiben die auf Produktion gepflegten Werte (10 bzw. 3) wirksam:
```bash
php artisan tinker --execute="App\\Models\\Setting::setContentBySlug('abo-lock-days-change', 0, 'int'); App\\Models\\Setting::setContentBySlug('abo-lock-days-pause-cancel', 0, 'int');"
```
Keine Migration, keine Datenänderung. Danach prüfen: Abo-Einstellungen zeigen 31 Tage; ein Abo mit Termin morgen lässt sich noch ändern; heute fälliges Abo ist gesperrt.
- **Nicht enthalten:** die Mindestfrist 10 Tage bis zur Erst-Ausführung bleibt (`MIN_DAYS_UNTIL_FIRST_ABO_EXECUTION`); das Cron-Fenster 04:00 Uhr bleibt; Einmalprodukte behalten ihr eigenes 4-Tage-Fenster.
### A3 – Status und Grund eines Abos sichtbar machen — umgesetzt 16.09., Deploy offen
**Ziel (Ticket #2, Protokoll):** Für Admin, Berater und Kunde sofort erkennbar: aktiv und ordnungsgemäß ausgeführt / pausiert bis wann und durch wen / Zahlung fehlgeschlagen (und dass sie fehlgeschlagen ist) / gekündigt durch wen.