Öffnungszeiten: Livegang-Notiz

Reihenfolge nach Spec 13.6 mit allen LIVEGANG-Hinweisen, Probelauf, Rechtsrunde
und Checkliste der offenen Code-Punkte.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Opus 5.5 committed 2026-10-05 08:32:59 +00:00
1 parent 90e8607e26
commit c10e50d0b3
1 file changed
+111
@@ -0,0 +1,111 @@
# Öffnungszeiten live – Livegang-Notiz
Stand 05.10.2026, für Kenso. Grundlage: Spec Abschnitt 13.6, das Protokoll der Umsetzung (`.superpowers/sdd/2026-10-04-oeffnungszeiten-live/progress.md`, alle LIVEGANG-Hinweise und Rechtsrunde-Punkte) und die Gesamtprüfung (Task 37, Bericht `task-37-report.md` im selben Ordner). Nichts davon ist gepusht. Keine Rechtsberatung.
## Stand der drei Branches
| Repo | Worktree / Branch | Stand | Basis und Abstand |
|---|---|---|---|
| Portal (+ Runner) | `dev-zeiten`, `feature/oeffnungszeiten` | Inhalt von `90e8607` plus diese Notiz | enthält TP2 bis `a629b25`; TP2 steht inzwischen auf `af63b69` (34 Commits weiter) |
| WOMS | `ticket.webklar.com/dev-zeiten`, `feature/oeffnungszeiten` | `3d67aa8` | auf WOMS-TP2 `72eedeb`; TP2 steht inzwischen auf `4c3f67e` (5 Commits weiter) |
| Relaunch | `webklar.com/dev-zeiten-station`, `feature/zeiten-station` | `8f5d522` | auf `main` `4139f31` (kein Abstand, Stand 05.10.) |
Letzte Prüfung 05.10.2026, alles grün: Portal 4544/4545 (der eine Fehler war der bekannte wacklige `previewDevRouter`-Test, einzeln 12/12), Portal unter Node 20 (wie in Produktion) 4543/4543 ohne `analyticsContract`, Runner 177/177, UI-Prüfungen 31/31 + 8/8 + alle OK, WOMS 377/377 + `vite build`, Relaunch 349/349 + Build (Haupt-JS 128 084 Byte gz, Grenze 133 120).
## A. Reihenfolge am Livegang-Tag
1. **TP2 geht zuerst live** (Portal und WOMS) nach seiner eigenen Reihenfolge (Kanzlei-Paket Abschnitt 7.6, mit `termine-setup.mjs` und `feature-setup.mjs`). Öffnungszeiten live baut auf TP2 auf und braucht dessen Schema (`aboEndeMailAt`, `disabledReason`, `featureConsents`).
2. **Portal neu aufsetzen.** In `dev-zeiten`: das jetzt live gegangene TP2 einmal hineinführen (`git merge main`; ist TP2 noch nicht in `main`: `git merge feature/terminplaner-tp2`). Es gibt einen Merge statt Rebase, damit die Commit-Nummern des Protokolls gültig bleiben. Bei der ersten Zusammenführung (`1d5a565`) überlappten 14 Dateien, das gleiche ist wieder zu erwarten (`server/config.js`, `siteFeatures.js`, `routes/funktionen.js`, `public/funktionen.js` …). Danach alles erneut laufen lassen:
- `nice npm test` (nur `previewDevRouter` darf wackeln, dann einzeln wiederholen), `cd runner && nice npm test`
- Node 20: `docker run --rm --cpus 3 -v /home/webklar/apps/kundenbereich.webklar.com/dev-zeiten:/app:ro -w /app node:20 sh -c 'node --test --test-concurrency=3 $(ls test/*.test.js | grep -v analyticsContract)'`
- UI, je einzeln mit `nice` und vorher `free -m`: `test/ui/zeiten-embed.ui.mjs`, `test/ui/zeiten-portal.ui.mjs`, `test/ui/funktionen.ui.mjs`
3. **WOMS neu aufsetzen.** In `ticket.webklar.com/dev-zeiten`: die WOMS-Hauptlinie (`test`, mit TP2) hineinführen, dann `nice npm test` und `nice npx vite build`. `node_modules` ist dort im Git verfolgt und wurde von `npm ci` verändert: **nie stagen**.
4. **Relaunch prüfen.** `git log --oneline 4139f31..main` im Relaunch-Repo; gibt es neue Commits, `feature/zeiten-station` darauf aufsetzen, dann `nice npm test`, `nice npm run build` und `gzip -9 -c dist/assets/index-*.js | wc -c` (höchstens 133 120).
5. **Schema anlegen, vor dem Portal-Deploy.** `scripts/zeiten-setup.mjs` legt `oeffnungszeiten` und `zeitenErinnerungen` an und ergänzt `portalFeatures` um `unlockedBy`, `aboEndeAt`, `einbau`, `behaltenSessionId`, `k24At`. Wichtig: `activate()` schreibt jetzt `aboEndeAt` und `behaltenSessionId` bei **jeder** Funktion; ohne die Felder scheitert die Team-Aktivierung von News und Terminplaner mit „Unknown attribute“. Das Skript braucht den neuen Code, also aus dem Worktree in einem Wegwerf-Container (kein `docker compose run`: der Container bekäme die Traefik-Labels des Portals). Erst `--check`, dann ohne:
```
docker inspect webklar-kundenbereich-server -f '{{range .Config.Env}}{{println .}}{{end}}' > /root/zeiten-env.tmp && chmod 600 /root/zeiten-env.tmp
docker run --rm --env-file /root/zeiten-env.tmp --network webklar-network -v /home/webklar/apps/kundenbereich.webklar.com/dev-zeiten:/app:ro -w /app node:20-alpine node scripts/zeiten-setup.mjs --check
docker run --rm --env-file /root/zeiten-env.tmp --network webklar-network -v /home/webklar/apps/kundenbereich.webklar.com/dev-zeiten:/app:ro -w /app node:20-alpine node scripts/zeiten-setup.mjs
rm /root/zeiten-env.tmp
```
(Der Aufruf ist so noch nie gegen die Produktion gelaufen; er folgt dem Muster der früheren Schema-Skripte. Das Skript ist idempotent, `--check` schreibt nichts.)
6. **Stripe prüfen.** `node scripts/stripe-products.mjs --pruefen` (gleicher Wegwerf-Container). Erwartet wird ein Preis `webklar_zeiten_einrichtung_3000` (30 €, einmalig, `tax_behavior` inclusive, Standard-Produktbild). Fehlt er: `--anlegen`; danach `--pruefen` nochmal.
7. **Einstellungen.** In `/home/webklar/apps/.env`: `ZEITEN_ENABLED=pilot`, `ZEITEN_PILOT_EMAILS=kenso.gri@gmail.com`, `ZEITEN_KEIN_NEUBAU=emmas-schatzkiste`. In `/home/webklar/apps/docker-compose.yml`, Dienst `kundenbereich-server`, drei Zeilen neben `TERMINE_ENABLED`: `- ZEITEN_ENABLED=${ZEITEN_ENABLED:-off}`, `- ZEITEN_PILOT_EMAILS=${ZEITEN_PILOT_EMAILS:-}`, `- ZEITEN_KEIN_NEUBAU=${ZEITEN_KEIN_NEUBAU:-emmas-schatzkiste}`. Neue Umgebungswerte gelten erst nach `docker compose up -d kundenbereich-server`, ein bloßes `docker restart` liest sie nicht (nach dem Deploy in Schritt 9 ausführen).
8. **Runner: kein Image-Bau nötig.** Der Runner ist `node:20-alpine` mit eingehängtem `build/runner` (das Protokoll sagt „Image neu bauen“, das stimmt für die echte Konfiguration nicht). `deploy.sh` startet ihn neu, wenn sich `runner/` ändert und die Warteschlange leer ist. `ZEITEN_PUBLIC_BASE` nicht setzen: die Vorgabe `https://project.webklar.com/embed/` stimmt. Im Deploy-Log nach „Preview-Runner: runner/ geaendert, starte neu…“ suchen; steht dort „Neustart uebersprungen“, später von Hand `docker restart webklar-preview-runner` (ein alter Runner verwirft das neue Feld `zeiten` stillschweigend, der Einbau meldete dann „angestoßen“, setzt aber nichts ein).
9. **Pushen, in dieser Reihenfolge** (du pushst, ich nicht):
1. Portal samt Runner (`main` des Portal-Repos).
2. WOMS erst **nach** dem Portal, sonst antwortet das Portal auf die neuen Zeiten-Aktionen mit 404. Der WOMS-Webhook liefert jeden gepushten Branch aus: `feature/oeffnungszeiten` also nie allein pushen, solange das Portal nicht live ist. Danach `node scripts/setup-legal.mjs` im WOMS (legt die Rechtliches-Karte `leistungsbeschreibung-zeiten` an, Schlüssel ≤ 36 Zeichen).
3. Relaunch (`main`). Die Station „Öffnungszeiten live“ steht dort auf `bald: true` („Bald verfügbar“) und bleibt so bis Schritt C.
10. **Pilot-Probelauf** (Abschnitt B), danach Rechtsrunde abschließen (Abschnitt D), dann Abschnitt C.
## B. Probelauf mit `ZEITEN_ENABLED=pilot` auf `test-website`
Nur dein Konto sieht die Kachel. Beachte: Das Portal prüft nur „Leistungsbeschreibung vorhanden“, nicht „freigegeben“ (`unterlagenBereit` ist für Zeiten immer wahr). Der Pilot ist deshalb der einzige Schutz vor dem Entwurfstext, bis `on` kommt.
1. Kachel im Funktionen-Tab sichtbar. Je nach Konto: „Einschalten“ (Wartungs-Abo aktiv) oder „30 € einmalig“. Den Bezahlweg nur mit echter 30-€-Zahlung testen (danach erstatten) oder weglassen; die Tests decken ihn ab.
2. Einschalten: Ticket „Öffnungszeiten live einbauen“ in WOMS, Mail K24, `einbau` = `angestossen` (Runner-Log) oder `manuell`.
3. Editor: Wochenplan mit Fenstern und Mitternacht, zweiter Plan, Bundesland und Feiertage, Urlaub (Hinweis-Vorschlag), Hinweis-Leiste, Kontakt-Leiste mit Telefon und Adresse, „Für Google kopieren“, Uhrzeit-Regler.
4. Preview der `test-website` nach dem Neubau: im Quelltext `<script data-wk-zeiten … data-site="…">`; Hinweis-Leiste oben (schiebt den Header?), Kontakt-Leiste auf dem Handy, JSON-LD im Quelltext (kein Duplikat), Platzhalter mit Status, Tabelle und Badge; Ersatz-HTML aus WOMS („Ersatz-HTML kopieren“) in die Platzhalter. Dazu der Datenschutz-Absatz aus `docs/funktionen/website-datenschutz-absatz.md` (Entwurf) auf der Test-Seite.
5. WOMS: Panel (Zeiten-Einstellungen `navSelektor`, Leisten aus), „Aktivieren ohne Zahlung“ und „Abschalten“ am Testprojekt; Ticket schließen ⇒ nach spätestens 5 Minuten `active` und Mail K16; Abschalten ⇒ Ticket „Ersatz-Tabelle aktualisieren“ (nur wenn der Wochenplan vom Ersatz-HTML abweicht).
6. Mails K24 bis K27 ansehen (K25 über einen Feiertag in 1–7 Tagen, K26/K27 nur mit einem Abo-Ende; sonst über die Tests bewertet).
7. Aufräumen: Funktion am Testprojekt abschalten, Testdaten und Merker löschen lassen, Ticket schließen.
## C. Schalter `on` (letzter Schritt)
Erst wenn Abschnitt B ohne offene Fehler und Abschnitt D vollständig erledigt ist:
1. `ZEITEN_ENABLED=on` in `.env`, `docker compose up -d kundenbereich-server`.
2. Relaunch: in `src/components/home/Kundenportal.tsx` den Eintrag `oeffnungszeiten` auf `bald: false` (die Marke „Bald verfügbar“ verschwindet), Tests und Build, du pushst. Das Handy-Bild `phone-12-zeiten` enthält den Einleitungstext des Editors: nach der Textrunde neu aufnehmen, wenn er sich geändert hat.
3. Logs beobachten (`[zeiten]`, `[zeiten-jobs]`): Abo-Abgleich täglich, Feiertags-Erinnerung stündlich zwischen 08:00 und 20:00.
4. Zurück auf `off`: Kachel verschwindet, die öffentliche Schnittstelle antwortet `aktiv: false`, die Platzhalter zeigen ihr Ersatz-HTML; der Abo-Abgleich läuft weiter.
## D. Rechtsrunde und Texte (Voraussetzung für `on`)
Reihenfolge laut Spec Abschnitt 9: gemeinsame TP2-Bausteine, Leistungsbeschreibung, Datenschutz, Oberflächen- und Mailtexte, Fragen an die Kanzlei, Uploads, `on`.
1. **Gemeinsame TP2-Bausteine** (Block F: Widerrufsbelehrung, AVV) zuerst.
2. **Leistungsbeschreibung** `public/vertragsunterlagen/zeiten-leistungsbeschreibung.html`: Entwurf („ENTWURF – vor Livegang Rechtsrunde“ im Kopf). Wortlaut-Punkte der Gesamtprüfung: Nr. 4 Abs. 2 „sobald das Wartungs-Abo aktiv ist“; Nr. 1 Abs. 1 (wann die Vereinbarung bei Team-Aktivierung entsteht, siehe ZQ9); Nr. 3 Abs. 2 „oder stehen sollen“ streichen; Nr. 3 Abs. 3 „bestätigt den Einbau als abgeschlossen“ und „Tag, der nicht geschlossen ist“; Nr. 8 Abs. 7 zweiter Satz unklar; „Behalten nach Abschaltung“. Danach Fassung festlegen (Register `leistungsbeschreibung.version` und `consent.version` hochzählen, Tests nachziehen).
3. **Datenschutz Portal § 6d** (`docs/rechtliches/zeiten-datenschutz-6d-entwurf.md`) in `public/datenschutz.html` einbauen, mit dem Anker `id="zeiten"`, plus der Satz in § 6 (Öffnungszeiten im Kundenauftrag). **Er muss live sein, bevor der Link der Leistungsbeschreibung funktioniert.** Danach `scripts/build-legal-pdfs.sh datenschutz` (nicht `all`), Prüfsumme nachziehen, `test/legalTexts.test.js` und `test/zeitenRecht.test.js` anpassen. Wortlaut-Punkte: „Ihre Eingaben“ gegen „der Kunde“, „keine Werbung“ gegen ZQ6.
4. **Widerrufsbelehrung für das 30-€-Produkt freigeben.** Die Bestellseite muss genau `featureHaken('zeiten').belehrung()` vor dem Knopf zeigen (Ruling R32), sonst belegt der Nachweis einen Text, den der Kunde nie sah.
5. **Wertersatz-Satz** (§ 357a Abs. 2 BGB) im Beginn-Haken der bezahlten Bestellung, wie bei News und Terminplaner (ZQ3); Fassung hochzählen. → Code, siehe Abschnitt E, N1.
6. **Widerruf-Seite:** `public/widerrufen.html` und `server/routes/abo.js` (`FEATURE_OF_ORDER`) kennen „Öffnungszeiten live“ nicht; ein zahlender Verbraucher, der widerruft, würde sonst der News-Pinnwand zugeordnet. → Code, siehe N2.
7. **Fragen an die Kanzlei:** `docs/rechtliches/zeiten-kanzlei-fragen.md` (ZQ1 bis ZQ10) ins Kanzlei-Paket des Terminplaners als Q27 ff. übernehmen (AVV Funktionen per Haken, Behalten-Haken, Wertersatz, Haftung ohne Abo, Ende mit dem Abo, Mails, Einstellung bei Unternehmern, Website-Absatz, Vertragsrahmen ohne Abo, Preisangaben in der Hinweis-Leiste).
8. **K02/K03:** Entwurf für den Block `funktionen` in `docs/rechtliches/zeiten-k02-k03-entwurf.md`. Nach der Freigabe die Vorlagen ändern und die Pins nachziehen (`test/mailTemplates.test.js`, `test/publicForms.test.js` `FUNKTIONEN_SATZ`).
9. **Website-Absatz** `docs/funktionen/website-datenschutz-absatz.md` (Varianten A/B, Baustein Route und WhatsApp nur wenn die Leiste die Knöpfe zeigt): freigeben; der Einbau auf einer Kundenseite erst, wenn er in deren Datenschutzerklärung steht.
10. **K15 für bezahlte Öffnungszeiten** trägt keine Vertragsunterlagen und keine Belehrung als Anhang (dauerhafter Datenträger): entscheiden, ob nötig.
11. **Mailtexte K24 bis K27** und die Texte im Editor in deiner Textrunde: K25 sagt „deine Zeiten gelten normal“, obwohl die Seite an gesetzlichen Feiertagen „geschlossen“ zeigt, wenn du nichts änderst; K24-Variante „wieder eingeschaltet“ (ohne „per Ticket“ / „von Hand“); T19-Text spricht von „Einrichtung“; `disabledMessage` schreibt „du“ klein; eigene Texte des Editors (Preishinweis, Vorschau); Betreffzeile K26 nennt „Wartungs-Abo“ (so gewollt).
12. **Uploads:** die Fassungen (Leistungsbeschreibung, Datenschutzerklärung) in WOMS „Rechtliches“ hochladen (Karte `leistungsbeschreibung-zeiten`).
## E. Offene Code-Punkte (Checkliste für die letzte Fixrunde)
Mit ☐ markiert, was noch im Code offen ist; die Fixrunde des Controllers kann einzelne schließen (dann hier abhaken).
**Vor `on` nötig**
- [ ] N1 Wertersatz-Satz im Beginn-Haken `features.zeiten.consent` (`server/config.js`) und Fassung hochzählen; Pins in `test/featureConsentTexte.test.js`, `test/zeitenRegister.test.js`, `test/zeitenRecht.test.js`.
- [ ] N2 „Öffnungszeiten live“ in `ORDERS`, `FEATURE_OF_ORDER` und im Bestell-Label von `server/routes/abo.js`, im Radioknopf von `public/widerrufen.html`, in `public/legal-form.js` und den Pins (`widerrufen_html_radio`).
- [ ] N3 Rechtstexte aus den Entwürfen übernehmen (Abschnitt D, Nr. 2 und 3) und den Entwurfskommentar der Leistungsbeschreibung entfernen.
- [ ] N4 Entscheidung zu Anhängen in K15 für Zeiten (D, Nr. 10); K02/K03-Block (D, Nr. 8).
**Härtung (vor `on` empfohlen, für den Pilot nicht nötig)**
- [ ] N5 Website-Skript: im JSON-LD `<` als `<` schreiben (`public/embed/zeiten.js`, plus Test); Kopf-Elemente behalten bei Scroll ≥ Leistenhöhe ihr Inline-`top` (bei klassengesteuertem Verstecken beim Scrollen wiederherstellen).
- [ ] N6 Abo-Abgleich: wenn `disable()` gelingt, aber das Leeren von `aboEndeMailAt` scheitert, laufen K27 und die Ersatz-Tabelle nie (K27 als offen werten, solange `aboEndeMailAt` < `disabledAt`, oder beides in einem Schreibzugriff); `nachAbschaltung` „fehler“ wiederholen oder melden; `activatedAt` bei der Löschung leeren.
- [ ] N7 Team: Sperre `zeiten-ersatz:<docId>` gegen zwei Ersatz-Tickets (paralleles Abschalten und Abgleich); `PATCH …/zeiten/team` nur für `setup`/`active`/`disabled` (409 sonst); `createFeatureTicket` mit `fallbackDetails` für das lange Ersatz-Ticket.
- [ ] N8 WOMS „Ticket nachlegen“ bei Abo- oder Team-Zeiten ohne `ticketId`: Portal über `holeZeitenNach` routen (heute 409 bis zum nächsten 5-Minuten-Lauf); Texte „keine Zahlung vermerkt“ und der 409 der Abnahme („noch keine Öffnungszeiten eingetragen“).
- [ ] N9 Löschen: in `loescheFunktion` erst die Merker, dann das Dokument; fehlgeschlagene Löschung in `retention.js` beim nächsten Lauf wiederholen.
- [ ] N10 `heileZeiten` in `try/finally`, wenn `activateClosedTickets` wirft.
**Qualität (kann nach `on`)**
- [ ] N11 Zeitlogik: `wechselListe` um Schwellen (Ende minus 24 h und minus 7 Tage) ergänzen (Texte wie „bis Sonntag Mitternacht“ bleiben sonst den ganzen Sonntag stehen); Mitternachts-Einträge nicht zusammenfassen („heute“ hinkt sonst bis zu 10 Minuten nach).
- [ ] N12 Oberfläche: Portal-Knopf auf dem Desktop 40 px hoch (Ziel 44 px); Relaunch-Stationsliste kann bei 721–780 px Fensterhöhe überlaufen (Schwelle prüfen).
## F. Bewusste Abweichungen und Hinweise
- Schema der neuen `portalFeatures`-Felder liegt in `scripts/zeiten-setup.mjs` (nicht in `feature-setup.mjs`), weil dessen Tests genau drei Attribute festschreiben.
- Das Angebot liefert `GET /api/funktionen` als `zeiten` auf oberster Ebene plus `zeitenStatus` je Projekt (die Spec nennt `zeitenAngebot` je Projekt); die Kachel entscheidet nach `inklusive` und `grund` (Ruling R20).
- Der Editor einer abgeschalteten Funktion (`disabled`) ist nur lesbar (Spec Z13 sagt „nie gesperrt“); die Website zeigt dann ohnehin nichts mehr.
- Der Block `funktionen` in K02/K03 ist bewusst nur ein Entwurf (Rechtstext mit Pins).
- Schalter aus (`off`): `GET /api/public/zeiten/v1/<siteKey>` antwortet `aktiv: false`; die Jobs für Abo-Abgleich laufen unabhängig vom Schalter (ein beendetes Abo muss abschalten und löschen können).
- Wie im Terminplaner-Livegang: den Code zu einer ruhigen Zeit deployen.