From 94f6be04ffe99fe333f48ca3ff01399932e78e50 Mon Sep 17 00:00:00 2001
From: ValueOn AG
Date: Sat, 9 May 2026 17:00:39 +0200
Subject: [PATCH] upd
---
.../1-plan/2026-05-enterprise-subscription.md | 102 ++++++++++++++++++
c-work/_CHANGELOG.md | 7 ++
2 files changed, 109 insertions(+)
create mode 100644 c-work/1-plan/2026-05-enterprise-subscription.md
diff --git a/c-work/1-plan/2026-05-enterprise-subscription.md b/c-work/1-plan/2026-05-enterprise-subscription.md
new file mode 100644
index 0000000..35386ef
--- /dev/null
+++ b/c-work/1-plan/2026-05-enterprise-subscription.md
@@ -0,0 +1,102 @@
+
+
+
+
+# Enterprise Subscription
+
+## Beschreibung und Kontext
+
+Enterprise-Kunden wollen mit Pauschalen arbeiten statt mit nutzungsbasierter Stripe-Abrechnung.
+Der Sysadmin (Platform Admin) erstellt ein individuelles Abo mit fixen Limiten (Users, Features, Storage, AI-Budget) und einem Pauschalpreis. Die Abrechnung erfolgt per E-Mail-Rechnung, nicht über Stripe. Alle Limiten sind hart — Überschreitung wird blockiert, nicht nachverrechnet.
+
+Business-Treiber: Grosse Kunden verlangen Planbarkeit und interne Verrechnung statt Kreditkarten-Abrechnung.
+
+## Fokus und kritische Details
+
+- `assertCapacity` muss bei Enterprise die Custom-Felder statt den Plan-Katalog lesen
+- `creditSubscriptionBudget`, `adjustAiBudgetForUserChange`, `reconcileMandateStorageBilling` brauchen Enterprise-Guards
+- AI-Budget Top-Up via Stripe bleibt für Enterprise-Mandanten weiterhin möglich (separater Flow)
+- `syncQuantityToStripe` ist bereits sicher (Guard auf `stripeSubscriptionId=None`)
+- `SubscriptionCapacityException.userAction` darf bei Enterprise nicht `UPGRADE_SUBSCRIPTION` sein
+
+## Ziel und Nicht-Ziele
+
+- Ziel: Sysadmin kann Enterprise-Abo erstellen, erneuern, anpassen; fixe Limiten mit Hard-Block; Rechnung per E-Mail an Mandate Admins
+- Ziel: Auto-Renewal via Cron-Job (eventManager) für Enterprise-Abos mit autoRenew=True
+- Ziel: Mandate Admin sieht Enterprise-Abo readonly im Dashboard
+- Explizit NICHT: PDF-Invoice-System, Frontend Enterprise-Formular (Phase 2)
+- Explizit NICHT: Enterprise als Self-Service für Mandate Admin
+
+## Betroffene Module
+
+- Gateway: datamodelSubscription, interfaceDbSubscription, interfaceDbBilling, interfaceDbApp, mainServiceSubscription, routeSubscription, routeBilling, enterpriseRenewalScheduler (neu), app.py
+- Frontend: Subscription-Status-Anzeige (readonly für Enterprise), Sysadmin-Panel Enterprise-Badge
+- DB-Migration: nein (neue Felder auf MandateSubscription, auto-schema)
+- Andere Komponenten: E-Mail-Versand (notifyMandateAdmins, bestehende Logik)
+
+## Entscheidungen
+
+| Datum | Entscheidung | Begründung |
+|-------|-------------|------------|
+| 2026-05-09 | Custom-Werte auf MandateSubscription statt eigene Plan-Tabelle | Einfacher, kein neues DB-Schema nötig, Plan-Katalog bleibt statisch |
+| 2026-05-09 | Rechnung als strukturierte E-Mail, kein PDF | Vertrag wird extern geregelt, E-Mail reicht als Bestätigung |
+| 2026-05-09 | Renew = altes Abo EXPIRED + neues ACTIVE (Duplikat) | Saubere Audit-History, Budget-Gutschrift wie bei Erstaktivierung |
+| 2026-05-09 | Auto-Renewal via eventManager.registerCron (täglich 06:00 UTC) | Bestehende APScheduler-Infrastruktur nutzen, analog Audit-Log-Cleanup |
+| 2026-05-09 | Rechnungs-E-Mail nur an Mandate Admins | Gleiche Logik wie bestehende Pläne, kein Sysadmin als Empfänger |
+| 2026-05-09 | Storage-Overage bei Enterprise: Hard-Block statt Pool-Abzug | Konsistent mit dem Pauschalen-Konzept |
+
+## Umsetzungs-Checkliste
+
+- [ ] Datenmodell: ENTERPRISE in BUILTIN_PLANS + enterprise*-Felder auf MandateSubscription
+- [ ] Helper: getEffectiveLimits(sub, plan) für einheitliche Limiten-Auflösung
+- [ ] Guard: assertCapacity → Enterprise-Felder lesen
+- [ ] Guard: creditSubscriptionBudget → fixer Betrag bei Enterprise
+- [ ] Guard: adjustAiBudgetForUserChange → bei Enterprise überspringen
+- [ ] Guard: reconcileMandateStorageBilling → bei Enterprise überspringen
+- [ ] Guard: getDataVolumeWarning → Enterprise-Felder
+- [ ] Guard: SubscriptionCapacityException → userAction=CONTACT_ADMIN bei Enterprise
+- [ ] Service: createEnterprise, renewEnterprise, updateEnterprise
+- [ ] E-Mail: _buildEnterpriseInvoiceHtml via notifyMandateAdmins (nur Mandate Admins)
+- [ ] Auto-Renewal: Cron-Job via eventManager, täglich, Enterprise-Abos mit autoRenew=True erneuern
+- [ ] Routes: POST enterprise/create, POST enterprise/renew, PUT enterprise/update
+- [ ] Admin-View: _buildEnrichedSubscriptions + _computeUsage für Enterprise
+- [ ] RBAC / Permissions: alle 3 Endpoints isPlatformAdmin-only
+- [ ] Neutralisierung betroffen? Nein
+- [ ] Billing-Impact? Ja — neue Abrechnungsform
+
+## Akzeptanzkriterien
+
+| # | Kriterium (Given-When-Then) | Prio |
+|---|---------------------------|------|
+| 1 | Given Sysadmin, When POST enterprise/create mit validen Parametern, Then Enterprise-Abo ACTIVE mit Custom-Limiten + AI-Budget gutgeschrieben + Rechnung per E-Mail an Mandate Admins + Sysadmin | must |
+| 2 | Given Enterprise-Abo aktiv, When Mandate Admin User 21 hinzufügen will (Limit=20), Then Hard-Block mit Meldung "Enterprise-Limit erreicht, kontaktieren Sie den Administrator" | must |
+| 3 | Given Enterprise-Abo aktiv, When User hinzugefügt wird (unter Limit), Then KEIN pro-rata AI-Budget-Adjustment | must |
+| 4 | Given Enterprise-Abo aktiv, When Storage-Nutzung steigt über Limit, Then Hard-Block, KEIN Pool-Abzug | must |
+| 5 | Given Enterprise-Abo aktiv, When Mandate Admin AI-Budget Top-Up via Stripe macht, Then funktioniert wie bei normalem Abo | must |
+| 6 | Given Enterprise-Abo aktiv, When Sysadmin PUT enterprise/update mit neuen Limiten, Then Limiten sofort wirksam | must |
+| 7 | Given Enterprise-Abo aktiv, When Sysadmin POST enterprise/renew, Then altes Abo EXPIRED + neues ACTIVE + Budget + Rechnung | must |
+| 8 | Given Enterprise-Abo aktiv, When Mandate Admin GET /status, Then sieht Enterprise-Abo mit Limiten und Nutzung (readonly) | must |
+| 9 | Given Enterprise-Abo aktiv, When Mandate Admin versucht Plan zu wechseln, Then nicht möglich (kein Self-Service) | must |
+
+## Testplan
+
+| ID | AC | Art | Automatisiert | Repo-Pfad | Status |
+|----|----|-----|--------------|-----------|--------|
+| T1 | 1 | api | manuell | — | pending |
+| T2 | 2 | api | manuell | — | pending |
+| T3 | 3 | api | manuell | — | pending |
+| T4 | 4 | api | manuell | — | pending |
+| T5 | 5 | api | manuell | — | pending |
+| T6 | 6 | api | manuell | — | pending |
+| T7 | 7 | api | manuell | — | pending |
+| T8 | 8,9 | api | manuell | — | pending |
+
+## Links
+
+- Cursor Plan: enterprise_subscription_aca22e32.plan.md
+
+## Abschluss
+
+- [ ] b-reference/ aktualisiert (welche Seite?)
+- [ ] TOPICS.md aktualisiert (falls neues Thema)
+- [ ] Dieses Dokument → z-archive/ verschoben
diff --git a/c-work/_CHANGELOG.md b/c-work/_CHANGELOG.md
index 32dc606..c1e4183 100644
--- a/c-work/_CHANGELOG.md
+++ b/c-work/_CHANGELOG.md
@@ -12,8 +12,15 @@ type: `feat` `fix` `refactor` `docs` `test` `chore` `build` · scope: `gateway
Skip: reine Refactors, Formatting, Lint, Dep-Bumps, Test-only, Wiki-Tippfehler.
+## 2026-05-09
+
+- 2026-05-09 | feat | gateway | Enterprise Subscription: sysadmin-managed flat-price subscriptions with custom limits (users, features, storage, AI budget), invoice email, hard-block on overage, auto-renewal cron job. New endpoints: POST enterprise/create, enterprise/renew, PUT enterprise/update. (c-work: 1-plan/2026-05-enterprise-subscription.md)
+- 2026-05-09 | refactor | frontend-nyla, gateway | CommCoach: remove Dossier view (route, FeatureView registry, RBAC UI object, Dashboard link, pageRegistry icon, barrel export). Session details (summary, score, duration, export) moved into Modules expand view.
+
## 2026-05-08
+- 2026-05-08 | fix | frontend-nyla | CommCoach: add missing `Route path="dossier"` under feature-instance routes (was catch-all not-found).
+- 2026-05-08 | fix | gateway | JWT cookies: resolve SameSite/Secure per request via _cookiePolicy() (not import-time); optional APP_COOKIE_SECURE override; env-gateway int/prod: APP_COOKIE_SECURE=true + CORS nyla*.poweron-center.net.
- 2026-05-08 | fix | gateway | JWT cookies: SameSite=None+Secure when APP_API_URL is HTTPS (cross-origin SPA+API); SameSite=Lax on HTTP localhost. Fixes credentialed API calls when UI and gateway differ by site. CSRF middleware unchanged.
- 2026-05-08 | chore | frontend-nyla | config/.env.{dev,int,prod}: keep only VITE_API_BASE_URL and VITE_APP_NAME; removed unused flags and duplicated Entra/secret keys (backend owns secrets). env.d.ts aligned.
- 2026-05-08 | refactor | frontend-nyla | Remove MSAL from UI: deleted authConfig.ts + AuthProvider.tsx, rewrote ProtectedRoute (sessionStorage-only), removed useMsalRegister, simplified logout, uninstalled @azure/msal-browser + @azure/msal-react. All auth logic lives in gateway.