docs(14): add D-13..D-15 (per-tenant email visibility, RSS feed-list, poll granularity)

This commit is contained in:
2026-07-23 11:58:10 +02:00
parent 7f757772f1
commit 9bd93ce8ff
@@ -36,6 +36,11 @@ Phase 14 rundet die Ausschreibungs-Abdeckung im Unterschwellenbereich ab und mac
- **D-08:** RSS-Feeds sind öffentlich und für alle gleich → globale Plattform-Konfiguration (bestehendes Singleton-Muster), nicht pro Mandant. - **D-08:** RSS-Feeds sind öffentlich und für alle gleich → globale Plattform-Konfiguration (bestehendes Singleton-Muster), nicht pro Mandant.
- **D-09:** Die bestehende Admin-Seite (`apps/web/.../tender-radar/settings/`) wird **erweitert**, nicht neu gebaut: ein Abschnitt „E-Mail-Alerts" (pro Mandant, durch Mandanten-Admin konfigurierbar) und einer „RSS-Feeds" (global). Rollen-Trennung über bestehendes `@Roles(ADMIN, SUPER_ADMIN)`. - **D-09:** Die bestehende Admin-Seite (`apps/web/.../tender-radar/settings/`) wird **erweitert**, nicht neu gebaut: ein Abschnitt „E-Mail-Alerts" (pro Mandant, durch Mandanten-Admin konfigurierbar) und einer „RSS-Feeds" (global). Rollen-Trennung über bestehendes `@Roles(ADMIN, SUPER_ADMIN)`.
### Datensichtbarkeit & Mandantenfähigkeit (aus Research aufgedeckt)
- **D-13:** Per E-Mail eingelesene Ausschreibungen sind **nur für den Mandanten sichtbar**, dessen privates Postfach sie geliefert hat — NICHT plattformweit. Grund: Alert-Mails spiegeln private Abonnements eines einzelnen Mandanten; global sichtbar zu machen würde dessen Interessen offenlegen und den gemeinsamen Pool verwässern. Das löst den Konflikt zwischen D-03 (Tender ist global, Phase 13) und D-06 (Mailbox per Mandant): Das `Tender`-Modell bekommt für **private Quellen** (E-Mail) eine mandantenbezogene Sichtbarkeit; öffentliche Quellen (DÖE, NetServer, cosinex, RSS) bleiben global. Umsetzung lokal begrenzt — z.B. ein nullable `ownerTenantId` auf Tender (null = global sichtbar, gesetzt = nur dieser Mandant) plus Filter in der Ergebnis-Query. Planner entscheidet exakte Mechanik.
- **D-14:** RSS-Quellen sind eine **vom Plattform-Admin pflegbare Feed-Liste** (CRUD), keine fest verdrahteten URLs. Grund: subreport-elvis hat keine globale Feed-Adresse, sondern eine pro Kommune (z.B. Stadt Neuss); service.bund.de hat einen globalen Feed. Da Feed-URLs Admin-Laufzeit-Eingabe sind, greift der code-level `SourceRegistry`-Denylist-Gate hier nicht — es braucht eine **Hostnamen-Prüfung gegen die Denylist beim Speichern** (SSRF-Absicherung analog T-10-06).
- **D-15:** RSS/E-Mail-Quellen dürfen **feiner als täglich** gepollt werden. Der bestehende Tages-Cursor (`lastIngestedDay`) von `TenderIngestionService` würde diese Quellen sonst auf einen Abruf pro Kalendertag drosseln, unabhängig vom konfigurierten Poll-Intervall. Für diese Quellen wird nicht auf `lastIngestedDay` gegated; das admin-konfigurierte `pollIntervalMin` gilt.
### i18n (UI-06) ### i18n (UI-06)
- **D-10:** „Zweisprachig" = alle Modul-Texte liegen in Deutsch UND Englisch vor. Ein neuer `tenderRadar`-Namensraum wird in `apps/web/src/messages/de.json` und `en.json` angelegt; alle hartcodierten deutschen Strings der tender-radar-Komponenten werden in `useTranslations`-Keys überführt (ResultsList, FilterPanel, SavedSearchBar, TenderDetail, CoverageBanner, Settings-Seite, SourceConfigForm). - **D-10:** „Zweisprachig" = alle Modul-Texte liegen in Deutsch UND Englisch vor. Ein neuer `tenderRadar`-Namensraum wird in `apps/web/src/messages/de.json` und `en.json` angelegt; alle hartcodierten deutschen Strings der tender-radar-Komponenten werden in `useTranslations`-Keys überführt (ResultsList, FilterPanel, SavedSearchBar, TenderDetail, CoverageBanner, Settings-Seite, SourceConfigForm).
- **D-11:** KEIN sichtbarer Sprachumschalter. Die Sprachwahl folgt dem bestehenden next-intl-Mechanismus. Ein app-weiter Language-Switcher ist ausdrücklich NICHT Teil dieser Phase (siehe Deferred Ideas). - **D-11:** KEIN sichtbarer Sprachumschalter. Die Sprachwahl folgt dem bestehenden next-intl-Mechanismus. Ein app-weiter Language-Switcher ist ausdrücklich NICHT Teil dieser Phase (siehe Deferred Ideas).