diff --git a/.planning/STATE.md b/.planning/STATE.md index 53a6ac3..ed89b24 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -122,6 +122,7 @@ Progress: [██████████] 100% ### Roadmap Evolution +- Phase 17 added (2026-08-12): Eigene Ausschreibungs-Quellen je Nutzer. TenderEmailConfig (heute `tenantId @unique`) und TenderRssFeedSource (heute `url @unique`, plattformweit) wandern auf `userId`; die Rollenpruefung faellt fuer diese beiden Abschnitte weg, das Abrufintervall der oeffentlichen Quelle bleibt Admin-Sache. Ausschreibungsdaten bleiben plattform-global (D-03 aus Phase 10 unangetastet) — geaendert wird nur, wer Quellen einspeist, nicht wer Treffer sieht. Ausloeser: Backlog `2026-08-11-tender-radar-einstellungen-mischen-rollen.md`; die urspruengliche Zustimmung zur gemeinsamen Konfiguration beruhte auf einer missverstaendlichen Erklaerung. - Phase 15 added (2026-08-04): Modul-Berechtigungen — Gruppen & User-Grants. Zweistufiger Modulzugriff (Mandanten-Aktivierung + Grants pro Gruppe/User), Gruppen mit optionaler AD-Bindung, default geschlossen, ADMIN/SUPER_ADMIN umgehen Grants, nur Zugriff an/aus. Startet Milestone v1.2 Plattform-Berechtigungen. ### Decisions diff --git a/.planning/phases/17-eigene-ausschreibungs-quellen-je-nutzer/.gitkeep b/.planning/phases/17-eigene-ausschreibungs-quellen-je-nutzer/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/.planning/phases/17-eigene-ausschreibungs-quellen-je-nutzer/.gitkeep @@ -0,0 +1 @@ + diff --git a/.planning/phases/17-eigene-ausschreibungs-quellen-je-nutzer/17-CONTEXT.md b/.planning/phases/17-eigene-ausschreibungs-quellen-je-nutzer/17-CONTEXT.md new file mode 100644 index 0000000..35c859a --- /dev/null +++ b/.planning/phases/17-eigene-ausschreibungs-quellen-je-nutzer/17-CONTEXT.md @@ -0,0 +1,138 @@ +# Phase 17 — Kontext: Eigene Ausschreibungs-Quellen je Nutzer + +**Quelle dieses Dokuments:** Gespraech mit dem User am 2026-08-12. Kein +`/gsd-discuss-phase`-Lauf — die Entscheidungen fielen im Gespraech beim +Durchsprechen des Backlog-Punkts +`2026-08-11-tender-radar-einstellungen-mischen-rollen.md`. + +## Warum diese Phase existiert + +Die Einstellungsseite `/modules/tender-radar/settings` traegt vier Abschnitte, +die drei verschiedenen Zustaendigkeiten gehoeren. Der Backlog-Punkt sah darin +urspruenglich nur ein Anzeigeproblem ("Admin-Bloecke hinter eine Rollenpruefung +legen"). Beim Durchsprechen kam heraus, dass zwei der drei Admin-Abschnitte +inhaltlich falsch zugeschnitten sind: + +> "Wenn Person A ihre Ausschreibungen auf kontoa@mail.de und Person B von +> kontob@mail.de holt, macht eine gemeinsame Einstellung keinen Sinn. Ebenso die +> RSS-Feeds. Wieso kann eine Person nicht mehrere Portale abfragen als eine +> andere?" + +Der User hat ausdruecklich festgehalten, dass seine urspruengliche Zustimmung zur +gemeinsamen Konfiguration auf einer missverstaendlichen Erklaerung meinerseits +beruhte. Das ist keine Meinungsaenderung, sondern eine Korrektur. + +**Heutiger Zustand, konkret:** `TenderEmailConfig.tenantId` ist `@unique` — es +gibt genau ein Alert-Postfach pro Mandant. Ein zweiter Nutzer mit eigenem +Portal-Konto kann seine Alerts gar nicht anbinden. `TenderRssFeedSource.url` ist +`@unique` ohne Besitzer — die Feed-Liste gilt fuer alle, und wer einen Feed +loescht, nimmt ihn allen weg. + +## Entscheidungen + +### D-01 — Alert-Postfach gehoert dem Nutzer + +`TenderEmailConfig` wechselt von `tenantId @unique` auf `userId @unique`. Jeder +Nutzer bindet sein eigenes Postfach an. `tenantId` bleibt als denormalisiertes +Feld erhalten (SMTP-Aufloesung, gleiche Rolle wie in `TenderMatch`). + +### D-02 — RSS-Feeds bekommen einen Besitzer, plattformweite Feeds bleiben moeglich + +`TenderRssFeedSource` bekommt ein **nullable** `userId`: + +- `userId = null` → plattformweiter Feed, von der Administration gepflegt, gilt + fuer alle. Der in Phase 14 gesetzte `service.bund.de`-Feed bleibt damit + unveraendert fuer jeden aktiv. +- `userId` gesetzt → persoenlicher Feed, nur dieser Nutzer bekommt ihn. + +`url @unique` faellt weg und wird `@@unique([userId, url])`, sonst koennten nicht +zwei Nutzer denselben Feed verfolgen. + +*Begruendung:* Die Alternative — alle Feeds persoenlich, niemand startet mit +etwas — haette den seit Phase 14 aktiven Standard-Feed fuer alle bestehenden +Nutzer stillgelegt. Diese Entscheidung ist im Gespraech nicht ausdruecklich +abgefragt worden; sie ist der am wenigsten zerstoererische Weg und sollte beim +Planen gegengelesen werden. + +### D-03 — Abrufintervall bleibt Administration + +`TenderSourcePollConfig` bleibt unveraendert plattformweit. Es gibt genau eine +oeffentliche Quelle (oeffentlichevergabe.de), die fuer alle dieselben Daten +liefert. Pro Nutzer abzurufen hiesse, dieselben Daten mehrfach zu holen. Der +User hat dem im Gespraech ausdruecklich zugestimmt. + +### D-04 — Digest-Intervall ist bereits nutzereigen, muss nur sichtbar werden + +`TenderNotificationPref` haengt schon an `userId` (NOTIFY-01/D-03 aus Phase 12). +Die Einstellung steht heute nur an der falschen Stelle: als letzter Abschnitt +unter drei Admin-Bloecken. Kein Datenmodell-Aenderung noetig, nur Platzierung. + +### D-05 — Ausschreibungsdaten bleiben plattform-global (HARTE GRENZE) + +`Tender` bleibt ohne `tenantId`, ohne RLS, ohne `forTenant()` — Entscheidung +D-03 aus Phase 10 bleibt unangetastet. Geaendert wird ausschliesslich, **wer +Quellen einspeist**, nicht **wer Treffer sieht**. + +Der User wurde darueber aufgeklaert und hat zugestimmt: + +> "Reicht dir 'jeder speist seine eigenen Quellen ein, gesehen wird alles +> gemeinsam'?" → "Ja, plane" + +**Praktische Folge, die in der UI ehrlich benannt werden muss:** Was ueber das +Postfach von Nutzer A hereinkommt, steht danach auch in der Trefferliste von +Nutzer B. Eine Sichtbarkeitstrennung waere ein deutlich groesserer Umbau (Tender +mandanten-/nutzerscoped, RLS, Dedup ueber Sichtbarkeitsgrenzen) und ist hier +bewusst ausgeklammert. + +## Offene Punkte fuer die Planung + +1. **Migration der bestehenden Zeilen.** Das eine vorhandene + `TenderEmailConfig`-Row (falls auf dem Testserver konfiguriert) braucht einen + Besitzer. Vorschlag: dem aeltesten ADMIN des Mandanten zuordnen, nicht + loeschen. Zu pruefen, ob ueberhaupt eine Zeile existiert. +2. **Bestehende RSS-Feeds** werden zu `userId = null` (plattformweit) — das ist + der Ist-Zustand und aendert fuer niemanden etwas. +3. **`email-alert.adapter.ts:143`** liest heute bewusst mandantenuebergreifend + (`findMany({ where: { isActive: true }})`) und darf **nicht** in `forTenant()` + gewickelt werden — der Kommentar dort ist ausdruecklich. Der Wechsel auf + `userId` aendert daran nichts: der Scheduler loest weiterhin alle aktiven + Postfaecher in einem Durchlauf auf. Die `catch`-pro-Postfach-Disziplin muss + erhalten bleiben. +4. **Wohin mit den nutzereigenen Abschnitten?** Es gibt bereits + `/settings/general/account`. Drei Varianten standen im Backlog-Punkt; die + Frage betrifft auch DKV-Fleet (gleiche Bauform) und jedes kuenftige Modul. + Beim Planen zu entscheiden — eine eigene nutzerseitige Modulseite scheint am + tragfaehigsten, weil modulfremde Einstellungen sich sonst in einer + allgemeinen Seite sammeln. +5. **Rollenpruefung der verbleibenden Admin-Abschnitte.** Das urspruengliche + Anliegen des Backlog-Punkts: die Seite selbst prueft keine Rolle, ein normaler + Nutzer sieht die Bedienelemente und laeuft beim Speichern in eine + Fehlermeldung. Nach dem Umbau bleibt nur noch das Abrufintervall Admin-Sache — + das gehoert hinter eine Rollenpruefung. + +## Betroffene Stellen (Bestandsaufnahme 2026-08-12) + +| Datei | Rolle | +|---|---| +| `apps/api/prisma/schema.prisma:278` | `TenderEmailConfig` (`tenantId @unique`) | +| `apps/api/prisma/schema.prisma:550` | `TenderRssFeedSource` (`url @unique`) | +| `apps/api/prisma/schema.prisma:512` | `TenderNotificationPref` (bereits `userId @unique`) | +| `apps/api/prisma/schema.prisma:524` | `TenderSourcePollConfig` (bleibt) | +| `apps/api/src/tenders/tender-email-config.service.ts` | 4 Zugriffe auf `tenantId` | +| `apps/api/src/tenders/tender-rss-feed.service.ts` | 3 Zugriffe, kein Besitzer-Scoping | +| `apps/api/src/tenders/adapters/email-alert.adapter.ts:143` | Fan-out ueber alle aktiven Postfaecher | +| `apps/api/src/tenders/adapters/rss.adapter.ts:79` | Fan-out ueber alle aktiven Feeds | +| `apps/api/src/tenders/tenders.controller.ts:241-301` | 5 Endpunkte, heute `@Roles(ADMIN, SUPER_ADMIN)` | +| `apps/api/src/tenders/tenders.module.ts:288` | Seed des `service.bund.de`-Feeds | +| `apps/web/src/app/(portal)/modules/tender-radar/settings/page.tsx` | die vier Abschnitte | + +**Guenstig:** Beide Adapter holen heute schon *alle* aktiven Konfigurationen in +einem Durchlauf. Mehrere Postfaecher und Feeds sind keine neue Mechanik, nur +mehr Zeilen in der Tabelle. Der Scheduler muss dafuer nicht angefasst werden. + +## Route-Order-Falle (Bestandswissen) + +`tenders.controller.ts` traegt mehrere Kommentare dazu: statische Routen +(`rss-feeds`, `email-config`, `source-config`) MUESSEN vor `@Get(':id')` +deklariert werden, sonst schattet die Parameter-Route sie mit 404 ab. Unit-Tests +fangen das nicht. Beim Umbau der Endpunkte erhalten bleiben.