docs: Favoriten-Sortierung und Symbol-Ersatzweg im CHANGELOG und Anwenderhandbuch; Nachtraege zur Mandantenbindung

- CHANGELOG.md: je ein Stichpunkt unter Neu (Sortierpfeile) und Behoben
  (Symbol trotz Zertifikatsfehler/interner Adresse)
- docs/anleitung-anwender.md: Tabellenzeile „Favoriten" nennt die Pfeile
  und den Browser-Ersatzweg fuer das Symbol
- docs/mandantentrennung-zugriffsklassifikation.md: Nachtrag zu
  favoriteLink — reorder() laeuft ueber withTenantTransaction() ohne
  Benutzerdimension in der Sitzung, Stand bleibt gebunden
- prisma-tenant.extension.ts: Kopfkommentar-Nachtrag, favorites.service.ts
  (reorder) ist der erste Nutzer-CRUD-Aufrufer von withTenantTransaction()
  — nur Kommentartext, Funktionscode unveraendert (prisma-tenant.extension.spec.ts
  und rls-access-inventory.spec.ts weiterhin gruen)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-17 14:41:28 +02:00
parent b18ac25ccc
commit b023d6f726
4 changed files with 17 additions and 3 deletions
+8
View File
@@ -4,11 +4,19 @@ Diese Liste beschreibt in einfachen Worten, was sich von Version zu Version an T
## Unveröffentlicht ## Unveröffentlicht
### Neu
- Favoriten-Widget: Reihenfolge der Links im Bearbeitungsmodus mit den Pfeilen „Nach oben“/„Nach unten“ festlegen
### Geändert ### Geändert
- Tessera-Bildmarke: das ganze T übernimmt die persönliche Akzentfarbe (die vier Kacheln in einem dunkleren Ton derselben Farbe) - Tessera-Bildmarke: das ganze T übernimmt die persönliche Akzentfarbe (die vier Kacheln in einem dunkleren Ton derselben Farbe)
- Desktop-App: Beta-Pakete werden nur noch neu gebaut, wenn sich an der Desktop-App etwas geändert hat; sonst bleiben die zuletzt gebauten Pakete gültig, und die App meldet keinen neuen Beta-Stand - Desktop-App: Beta-Pakete werden nur noch neu gebaut, wenn sich an der Desktop-App etwas geändert hat; sonst bleiben die zuletzt gebauten Pakete gültig, und die App meldet keinen neuen Beta-Stand
### Behoben
- Favoriten-Widget: kein Symbol bei Seiten mit Zertifikatsfehler oder internen Adressen – das Symbol wird jetzt trotz Zertifikatsfehler geholt, bei internen Adressen versucht es der Browser direkt
## 1.2.0 – 2026-09-17 ## 1.2.0 – 2026-09-17
### Neu ### Neu
@@ -145,7 +145,13 @@ import { PrismaClient } from '@prisma/client';
* Mandanten), nicht zu einer Luecke. `withTenantTransaction()` bekommt * Mandanten), nicht zu einer Luecke. `withTenantTransaction()` bekommt
* KEINEN dritten Parameter: kein Nutzer-CRUD-Aufrufer nutzt diese Funktion * KEINEN dritten Parameter: kein Nutzer-CRUD-Aufrufer nutzt diese Funktion
* (nur `groups`, ein Verwaltungsweg) — ein unbenutzter Parameter waere * (nur `groups`, ein Verwaltungsweg) — ein unbenutzter Parameter waere
* Spekulation ohne heutigen Aufrufer. * Spekulation ohne heutigen Aufrufer. Nachtrag (260917-jdd):
* `favorites.service.ts` (`reorder`) ist seither der erste Nutzer-CRUD-
* Aufrufer — er kommt OHNE Benutzerdimension in der Sitzung aus und
* traegt `userId` UND `widgetId` in jeder Bedingung innerhalb der
* Transaktion selbst (zweites Netz). Ein dritter Parameter kommt erst,
* wenn ein Aufrufer die Benutzerdimension INNERHALB der Transaktion
* braucht.
* *
* SYSTEMKONTEXT (Etappe 3c, 260914-eym): * SYSTEMKONTEXT (Etappe 3c, 260914-eym):
* *
+1 -1
View File
@@ -77,7 +77,7 @@ Ihre Änderungen werden über **„Änderungen speichern"** übernommen. Verlass
| Kalender | Monatsübersicht mit der Anzahl der Termine je Tag (die Termine eines Tages erscheinen, wenn Sie mit der Maus darüberfahren) und darunter die nächsten Termine aus Ihren verbundenen Kalenderquellen. Ob die Monatsansicht erscheint, wie viele Termine und welcher Zeitraum gezeigt werden, stellen Sie unter Einstellungen > Dashboard > Widgets ein | | Kalender | Monatsübersicht mit der Anzahl der Termine je Tag (die Termine eines Tages erscheinen, wenn Sie mit der Maus darüberfahren) und darunter die nächsten Termine aus Ihren verbundenen Kalenderquellen. Ob die Monatsansicht erscheint, wie viele Termine und welcher Zeitraum gezeigt werden, stellen Sie unter Einstellungen > Dashboard > Widgets ein |
| Notizen | Freitext-Notizen mit Markdown-Formatierung; Listen zum Abhaken (`- [ ]`) lassen sich in der Ansicht direkt per Klick abhaken | | Notizen | Freitext-Notizen mit Markdown-Formatierung; Listen zum Abhaken (`- [ ]`) lassen sich in der Ansicht direkt per Klick abhaken |
| Taschenrechner | Grundrechenarten, auch per Tastatur bedienbar | | Taschenrechner | Grundrechenarten, auch per Tastatur bedienbar |
| Favoriten | Schnellzugriff auf mehrere selbst gepflegte Links, als Liste oder Kachelansicht, optional mit eigener Überschrift | | Favoriten | Schnellzugriff auf mehrere selbst gepflegte Links, als Liste oder Kachelansicht, optional mit eigener Überschrift. Im Bearbeitungsmodus des Dashboards bringen Sie die Links mit den Pfeilen „Nach oben"/„Nach unten" in die gewünschte Reihenfolge. Das Symbol einer Seite holt Tessera automatisch; bei internen Adressen versucht es zusätzlich Ihr Browser direkt |
| Stoppuhr | Zeitmessung mit Rundenzeiten | | Stoppuhr | Zeitmessung mit Rundenzeiten |
Für Uhr, Suchleiste, Kalender, Notizen und Favoriten gibt es zusätzliche Einstellungen (z. B. Zeitzone und Schriftgröße der Uhr, eigene Suchanbieter, Kalenderquellen, Überschrift der Notiz- und Favoriten-Kachel) — diese finden Sie unter **Einstellungen > Dashboard**, siehe [Persönliche Einstellungen](#persönliche-einstellungen). Für Uhr, Suchleiste, Kalender, Notizen und Favoriten gibt es zusätzliche Einstellungen (z. B. Zeitzone und Schriftgröße der Uhr, eigene Suchanbieter, Kalenderquellen, Überschrift der Notiz- und Favoriten-Kachel) — diese finden Sie unter **Einstellungen > Dashboard**, siehe [Persönliche Einstellungen](#persönliche-einstellungen).
@@ -670,7 +670,7 @@ werden.
| apps/api/src/dkv/dkv.service.ts | dkvInvoiceHistory | muss-mandantengebunden | gebunden | DKV-Rechnungshistorie je Mandant, `tenantId`-Spalte vorhanden. Seit 260909-mir (Aufgabe 3) laufen beide Historien-Schreibzugriffe der Verarbeitungsstrecke, beide parallelen Lesezugriffe von `getHistory` und der neue Riegel vor dem Ausfuhrdatei-Download vollstaendig ueber `forTenant()`. | | apps/api/src/dkv/dkv.service.ts | dkvInvoiceHistory | muss-mandantengebunden | gebunden | DKV-Rechnungshistorie je Mandant, `tenantId`-Spalte vorhanden. Seit 260909-mir (Aufgabe 3) laufen beide Historien-Schreibzugriffe der Verarbeitungsstrecke, beide parallelen Lesezugriffe von `getHistory` und der neue Riegel vor dem Ausfuhrdatei-Download vollstaendig ueber `forTenant()`. |
| apps/api/src/dkv/dkv.service.ts | dkvModuleConfig | muss-mandantengebunden | system-gebunden | Postfach-/Zugangsdaten des DKV-Moduls je Mandant. Seit 260909-mir (Aufgabe 2) laufen `loadConfig`, `getConfigForApi`, `saveConfig`, `testConnection` und der Konfigurations-Lesezugriff der Verarbeitungsstrecke ueber `forTenant()`. Seit 260914-eym liest der Planer-Startpfad `loadActiveConfigsForScheduler()` ueber `forSystem()` (alle aktiven Konfigurationen, nur lesend, `system_read_policy`) — kein ungebundener Zugriff mehr, WINDOWS #21 geschlossen; alle uebrigen Zugriffe bleiben mandantengebunden (Stand-Vorrang: system ohne ungebunden = `system-gebunden`). | | apps/api/src/dkv/dkv.service.ts | dkvModuleConfig | muss-mandantengebunden | system-gebunden | Postfach-/Zugangsdaten des DKV-Moduls je Mandant. Seit 260909-mir (Aufgabe 2) laufen `loadConfig`, `getConfigForApi`, `saveConfig`, `testConnection` und der Konfigurations-Lesezugriff der Verarbeitungsstrecke ueber `forTenant()`. Seit 260914-eym liest der Planer-Startpfad `loadActiveConfigsForScheduler()` ueber `forSystem()` (alle aktiven Konfigurationen, nur lesend, `system_read_policy`) — kein ungebundener Zugriff mehr, WINDOWS #21 geschlossen; alle uebrigen Zugriffe bleiben mandantengebunden (Stand-Vorrang: system ohne ungebunden = `system-gebunden`). |
| apps/api/src/dkv/dkv.service.ts | dkvVehicleMaster | muss-mandantengebunden | gebunden | Fahrzeugstammdaten des DKV-Moduls je Mandant. Seit 260909-mir (Aufgabe 3) laufen Fahrzeugliste, Anlegen, beide Paare aus Besitzpruefung und Schreibzugriff (Aendern/Loeschen), beide Zweige des CSV-Imports und der gebuendelte Lesezugriff beim Aufbau der Ausfuhrzeilen vollstaendig ueber `forTenant()`; die vorgeschalteten Besitzpruefungen bei Aendern/Loeschen bleiben zusaetzlich bestehen (Befund G — ein gebundenes UPDATE ueber die Kennung allein trifft eine fremde Zeile still, nicht laut). | | apps/api/src/dkv/dkv.service.ts | dkvVehicleMaster | muss-mandantengebunden | gebunden | Fahrzeugstammdaten des DKV-Moduls je Mandant. Seit 260909-mir (Aufgabe 3) laufen Fahrzeugliste, Anlegen, beide Paare aus Besitzpruefung und Schreibzugriff (Aendern/Loeschen), beide Zweige des CSV-Imports und der gebuendelte Lesezugriff beim Aufbau der Ausfuhrzeilen vollstaendig ueber `forTenant()`; die vorgeschalteten Besitzpruefungen bei Aendern/Loeschen bleiben zusaetzlich bestehen (Befund G — ein gebundenes UPDATE ueber die Kennung allein trifft eine fremde Zeile still, nicht laut). |
| apps/api/src/favorites/favorites.service.ts | favoriteLink | muss-mandantengebunden | gebunden | Favoriten-Links eines Nutzers, `tenantId`-Spalte vorhanden. Seit 260911-gwh (Aufgabe 2) laufen `list`, `create`, `update`, `remove`, `getIconBytes` vollstaendig ueber `forTenant()`, je Methode EIN Klient `tenantPrisma`; die Besitzpruefungen (`findUnique`, Vergleich `link.userId !== userId`, dann Schreibzugriff auf DEMSELBEN Klienten) bleiben zusaetzlich bestehen — die Regel auf `FavoriteLink` kennt keine Benutzerdimension (Aufgabe 1, Pruefung 4), die `userId`-Filter sind bis zur Etappe-3-Entscheidung (2) der einzige Schutz gegen Quer-Lesen zwischen Nutzern DESSELBEN Mandanten. Die Mandantenquelle ist dieselbe wie bei `dashboard` (`extractContext` im Controller), nicht das Claim wie bei `auth`. Benutzerdimension seit 20260911120000 (260911-nke). | | apps/api/src/favorites/favorites.service.ts | favoriteLink | muss-mandantengebunden | gebunden | Favoriten-Links eines Nutzers, `tenantId`-Spalte vorhanden. Seit 260911-gwh (Aufgabe 2) laufen `list`, `create`, `update`, `remove`, `getIconBytes` vollstaendig ueber `forTenant()`, je Methode EIN Klient `tenantPrisma`; die Besitzpruefungen (`findUnique`, Vergleich `link.userId !== userId`, dann Schreibzugriff auf DEMSELBEN Klienten) bleiben zusaetzlich bestehen — die Regel auf `FavoriteLink` kennt keine Benutzerdimension (Aufgabe 1, Pruefung 4), die `userId`-Filter sind bis zur Etappe-3-Entscheidung (2) der einzige Schutz gegen Quer-Lesen zwischen Nutzern DESSELBEN Mandanten. Die Mandantenquelle ist dieselbe wie bei `dashboard` (`extractContext` im Controller), nicht das Claim wie bei `auth`. Benutzerdimension seit 20260911120000 (260911-nke). Nachtrag (260917-jdd): `reorder()` laeuft als Mehrschritt ueber `withTenantTransaction()` (einzige gemessene atomare Form, siehe prisma-tenant.extension.ts) — diese Form setzt KEINE Benutzerdimension in der Sitzung, deshalb traegt jede Bedingung innerhalb der Transaktion `userId` UND `widgetId`; der Stand bleibt `gebunden` (Erkennungsform 2 des Detektors). |
| apps/api/src/favorites/favorites.service.ts | widgetInstance | muss-mandantengebunden | gebunden | NEUE Fundstelle (260911-gwh, Aufgabe 2): `create()` prueft ueber einen gebundenen `widgetInstance.findUnique` (`select: { userId: true }`), dass das Ziel-Widget (`dto.widgetId`) dem Aufrufer gehoert, BEVOR die Zeile angelegt wird — der Fremdschluessel `FavoriteLink.widgetId` prueft an der Zeilenschutz-Regel von `WidgetInstance` VORBEI (dokumentiertes PostgreSQL-Verhalten, Aufgabe 1 Pruefung 7 hat das GELINGEN eines gebundenen `create` mit einer fremdmandantigen `widgetId` bestaetigt); ohne den Riegel waere der Unterschied zwischen "Widget existiert nicht" (FK-Verletzung) und "gehoert einem fremden Mandanten" (gelingt) ein Existenzorakel ueber Mandantengrenzen (T-GWH-05). | | apps/api/src/favorites/favorites.service.ts | widgetInstance | muss-mandantengebunden | gebunden | NEUE Fundstelle (260911-gwh, Aufgabe 2): `create()` prueft ueber einen gebundenen `widgetInstance.findUnique` (`select: { userId: true }`), dass das Ziel-Widget (`dto.widgetId`) dem Aufrufer gehoert, BEVOR die Zeile angelegt wird — der Fremdschluessel `FavoriteLink.widgetId` prueft an der Zeilenschutz-Regel von `WidgetInstance` VORBEI (dokumentiertes PostgreSQL-Verhalten, Aufgabe 1 Pruefung 7 hat das GELINGEN eines gebundenen `create` mit einer fremdmandantigen `widgetId` bestaetigt); ohne den Riegel waere der Unterschied zwischen "Widget existiert nicht" (FK-Verletzung) und "gehoert einem fremden Mandanten" (gelingt) ein Existenzorakel ueber Mandantengrenzen (T-GWH-05). |
| apps/api/src/groups/groups.service.ts | group | muss-mandantengebunden | gebunden | Gruppen sind je Mandant, `tenantId`-Spalte vorhanden. Alle 12 Methoden laufen seit 260909-jts (Aufgabe 2) ueber `forTenant()` bzw. `withTenantTransaction()`. | | apps/api/src/groups/groups.service.ts | group | muss-mandantengebunden | gebunden | Gruppen sind je Mandant, `tenantId`-Spalte vorhanden. Alle 12 Methoden laufen seit 260909-jts (Aufgabe 2) ueber `forTenant()` bzw. `withTenantTransaction()`. |
| apps/api/src/groups/groups.service.ts | groupMembership | muss-mandantengebunden | gebunden | Kein eigenes `tenantId`, RLS über Join auf `Group` (Migration 20260618112133-Nachfolger) — braucht trotzdem `forTenant()`, damit der Join-Kontext gesetzt ist. Seit 260909-jts gebunden, einschliesslich der drei Zugriffe innerhalb des Standardgruppen-Aufbaus (`ensureDefaultGroup`), die zuvor ueber den Transaktionsparameter liefen und fuer keine Pruefung dieses Projekts sichtbar waren (Befund B). | | apps/api/src/groups/groups.service.ts | groupMembership | muss-mandantengebunden | gebunden | Kein eigenes `tenantId`, RLS über Join auf `Group` (Migration 20260618112133-Nachfolger) — braucht trotzdem `forTenant()`, damit der Join-Kontext gesetzt ist. Seit 260909-jts gebunden, einschliesslich der drei Zugriffe innerhalb des Standardgruppen-Aufbaus (`ensureDefaultGroup`), die zuvor ueber den Transaktionsparameter liefen und fuer keine Pruefung dieses Projekts sichtbar waren (Befund B). |