Features
DB-Migrationen ab bestimmter Version starten
deploy-migrations.ps1unddeploy-migrations-dev.ps1unterstützen jetzt den Parameter-StartVersion(z.B.-StartVersion 050), um Migrationsskripte mit einer kleineren Versionsnummer als angegeben zu überspringen, statt immer alle Skripte ab001_...erneut auszuführen.- Ohne Angabe von
-StartVersionverhalten sich beide Skripte wie bisher (alle Skripte werden ausgeführt).
Fixes
Backend-E2E-Test “Mandanten-Admin landet auf dem Dashboard” seit v0.59.6 rot
- Seit der persönlichen Begrüßung im Dashboard (
<h1>Willkommen, {{ auth.user()?.name }}</h1>statt<h1>Dashboard</h1>, v0.59.6, #133) suchtetenant-dashboard.spec.tsweiterhin nach einer Überschrift mit dem exakten (Teil-)Text “Dashboard” — die gab es seitdem nicht mehr, der Test schlug aufmasterseit diesem Commit durchgängig fehl. - Assertion angepasst auf
getByRole('heading', { name: /Willkommen/ }).
Fixes
Migration 036 (User-Dashboard-Layouts) schlug in Produktion an verwaisten Mandanten-Referenzen fehl
036_user_dashboard_layouts.sqlscheiterte beim nachträglichen Ergänzen der Fremdschlüssel- Constraint mitinsert or update ... violates foreign key constraint "fk_user_dashboard_layouts_tenant", weil bereits Layout-Zeilen für einen zwischenzeitlich gelöschten Mandanten existierten (mandant_id=6in Produktion, ohne passenden Eintrag intenant).- Die Migration räumt jetzt verwaiste Zeilen auf (
DELETE ... WHERE mandant_id NOT IN (SELECT id FROM tenant)), bevor die Fremdschlüssel-Constraint ergänzt wird — analog zum bisherigen Vorgehen bei der Schutzklasse-Spaltenbereinigung in009_dguv_v3.sql.
Fixes
Production-Deploy der Suite-Frontend schlug mit “Permission denied” fehl
/www-revuwork-suiteauf dem Produktivserver gehörtroot(einmalig manuell angelegt); derdeploy-User darf das Verzeichnis selbst nicht entfernen, nur seinen Inhalt..github/workflows/deploy-production.ymlundbuild-deploy-linux.ps1haben beim Deploy des Suite-Frontends bisherrm -rf $REMOTE_SUITE && mkdir -p $REMOTE_SUITEausgeführt — das scheiterte mitrm: cannot remove '/www-revuwork-suite': Permission denied, nachdem der Inhalt bereits gelöscht war, und brach den Deploy-Job ab.- Beide Stellen räumen jetzt nur noch den Inhalt des Verzeichnisses auf (
mkdir -p $REMOTE_SUITE && rm -rf $REMOTE_SUITE/*) statt das Verzeichnis selbst zu entfernen.
Features
revuwork.suite wird jetzt mit dem Kernprodukt zusammen deployt
build-deploy-linux.ps1deployt jetzt drei Bausteine statt zwei: Backend,revuwork-ui-Frontend undrevuwork-suite-ui-Frontend (neues-OnlySuite-Flag, kombinierbar mit-OnlyFrontend/-OnlyApi). Grund: revuwork.suite teilt sich dasselbe Backend mit dem Kernprodukt, ein separates Deploy-Skript hätte nur unnötig zwei Stellen für dieselbe Server-Zielumgebung gepflegt.- Das bisherige eigenständige Skript
build-deploy-suite-light-linux.ps1wurde entfernt. .github/workflows/deploy-production.yml(spiegeltbuild-deploy-linux.ps1für CI-basierte Deploys) baut und deploytrevuwork-suite-uijetzt im selben Lauf mit.build-deploy.ps1(separates Test-Zieltest.stratworx.de/ampvia IIS) bleibt unverändert — dort ist revuwork.suite nicht vorgesehen.
Fixes
Migration räumt verwaiste Inventory.Schutzklasse-Spalte aktiv auf
- Der Guard aus
0.59.14verhinderte nur, dassInventory.Schutzklassekünftig erneut angelegt wird — bereits betroffene Datenbanken (bei denen die Spalte durch frühere Deploys schon leer wieder da war) blieben davon unberührt, wodurch die Mandanten-Datenmigration weiterhin mitcolumn "schutzklasse" ... does not existfehlschlagen konnte 009_dguv_v3.sqldroppt die verwaiste Spalte jetzt aktiv, sofern sie nachweislich leer ist (keine Zeile mit einem Wert) undProtectionClass(das Umbenennungsziel) bereits existiert — kein Datenverlust möglich, da nur eine garantiert leere Spalte entfernt wird- Die Prüfung ist dabei verschachtelt statt mit
ANDkombiniert (analog040_manual_intervention_automation.sql), da Postgres Tabellenreferenzen einerWHERE-Bedingung beim Parsen auflöst, auch ohne Kurzschluss — auf einer frischen Datenbank ohne die Spalte wäre der erste Entwurf sonst schon beim Parsen mit “column does not exist” gescheitert (in CI durch densql-migrations-idempotency-Check aufgefallen, bevor es in Produktion landete)
Fixes
Migrationen: verwaiste Legacy-Spalte nach Umbenennung
009_dguv_v3.sqlfügteInventory.SchutzklasseperADD COLUMN IF NOT EXISTShinzu;028_english_rename.sqlbenennt sie später zuProtectionClassum. Da alle Migrationen bei jedem Deploy erneut vollständig durchlaufen, legte009die alte Spalte nach der Umbenennung bei jedem weiteren Lauf leer neu an — beide Spalten existierten dauerhaft nebeneinander. Das führte u.a. dazu, dass die Mandanten-Datenmigration (SuperAdmin → “Mandant migrieren”) mitcolumn "schutzklasse" ... does not existfehlschlug, sobald ein Mandant Inventardaten hatte009prüft jetzt zusätzlich, obProtectionClassbereits existiert, bevorSchutzklasseneu angelegt wird040_manual_intervention_automation.sqlverglichAspNetUsersgegeninformation_schema.tables.table_name='aspnetusers'(klein geschrieben) — die Tabelle wird von EF Core Identity aber case-preserved/quotiert angelegt, der Vergleich matchte nie und die komplette Anweisung (Tracking-Felder für den Bestätigungsmail-Retry) wurde stillschweigend übersprungen. Nutzt jetzt denselbenpg_class-basierten Vergleich wie die entsprechende Stelle in028_english_rename.sql
Fixes
Mandanten-Datenmigration: Verifikation ignorierte vorbefüllte Referenzdaten
TenantDataMigrationService.MigrateAsyncverglich nach dem Kopieren einer Tabelle die mandantenscoped Quellzahl mit der gesamten Zeilenzahl der Zieltabelle. Tabellen, die bei der Tenant-Provisionierung bereits mit globalen Standarddaten vorbefüllt werden (z.B.VisualCheckTemplatemit Standard-Prüfvorlagen ohne Mandantenbezug), hatten dadurch nie eine Zieldatenzahl von 0 — die Verifikation schlug fehl, obwohl der Kopiervorgang selbst korrekt war- Die Verifikation vergleicht jetzt die Differenz der Zielzeilenzahl vor/nach dem Kopieren mit der Quellzahl, statt der absoluten Zielzeilenzahl
Fixes
Deploy löschte serverseitige appsettings.Production.json/appsettings.Preview.json
- Der in
0.59.9eingeführterm -rfvor dem Kopieren des Backend-Builds hat auchappsettings.Production.jsonbzw.appsettings.Preview.jsonmitgelöscht — diese Dateien werden nur manuell auf dem Server gepflegt (Connection-Strings, Secrets), liegen aber im selben Verzeichnis wie der Build-Output. Jeder Deploy hat dadurch die Datenbankverbindung der Anwendung zerstört deploy-production.ymlunddeploy-preview.ymlsichern die jeweiligeappsettings.*.jsonjetzt vor demrm -rfnach/tmpund spielen sie danach zurück- Zusätzlich eine kurze Wartezeit vor dem abschließenden
systemctl is-active-Check ergänzt, damit ein gerade erst neu gestarteter Dienst nicht fälschlich als fehlgeschlagen gemeldet wird, während er sich noch im Übergangszustand “activating” befindet
Fixes
Mandanten-Datenmigration: Sequence-Reset schlug wegen Groß-/Kleinschreibung fehl
TenantDataMigrationService.CopyTableAsyncübergab den Spaltennamen der Primärschlüssel-Spalte unquotiert in PascalCase (z.B."Id") anpg_get_serial_sequence(). Postgres faltet das Tabellennamen-Argument dieser Funktion automatisch auf Kleinschreibung, gleicht das Spaltennamen-Argument intern aber exakt/case-sensitiv ab — da die Tenant-Datenbanken ihre Spalten tatsächlich kleingeschrieben anlegen, schlug der Abgleich fehl und die komplette Mandanten-Migration (SuperAdmin → “Mandant migrieren”) brach mit einem 500er ab, sobald eine Tabelle mit Daten befüllt war- Der Spaltenname wird beim Aufruf von
pg_get_serial_sequence()jetzt klein geschrieben übergeben
Fixes
Migrations-Deploy fragt DB-Passwort nur noch einmal ab
deploy-migrations.ps1unddeploy-migrations-dev.ps1fragen das DB-Passwort jetzt nur noch einmal zu Skriptstart interaktiv ab (falls nicht per-DbPasswordoder UmgebungsvariableAMV_DB_PASSWORDgesetzt), statt bei jeder einzelnen SQL-Datei/Datenbank erneut
Fixes
Veraltete Build-Artefakte im Deploy behoben
deploy/api/,deploy/webapi/,deploy/web/unddeploy/frontend/waren versehentlich als Build-Output ins Repo eingecheckt, darunter alteAmv.Api.*-Binaries von vor der Umbenennung des Projekts zuRevuwork.Api. Da der Deploy-Workflow das Zielverzeichnis auf dem Server bislang nicht vor dem Kopieren geleert hat, liefen diese veralteten Dateien bei jedem Deploy unbemerkt neben dem frischen Build weiter — der produktive Dienst startete dadurch tatsächlich weiterhin die alte, eingefrorene Binary statt der aktuell deployten Version- Die vier Build-Output-Ordner sind jetzt aus dem Git-Tracking entfernt und in
.gitignoreaufgenommen deploy-production.ymlunddeploy-preview.ymlleeren das Backend-Zielverzeichnis auf dem Server jetzt vor dem Kopieren (rm -rf+mkdir -p), analog zum bestehenden Verhalten beim Frontend-Deploy
Features
DB-Migrationen im Production-Deploy-Workflow
deploy-production.ymlführt jetzt analog zum Preview-Workflow automatisch die Postgres-Migrationen ausdatabase/migrations/gegen die Prod-Control-Plane-DB und alle Tenant-DBs aus, bevor der Backend-Dienst neu gestartet wird- Neues Repo-Secret
PROD_DB_PASSWORDerforderlich (analogPREVIEW_DB_PASSWORD); ist es nicht gesetzt, wird der Schritt übersprungen statt den Deploy zu blockieren - Der Workflow bleibt weiterhin ausschließlich manuell auslösbar (
workflow_dispatchmit Pflicht-Bestätigungconfirm: deploy), kein automatischer Trigger
Features
MandantenID in der Mandantenverwaltung
- In der Liste „Alle Mandanten” (SuperAdmin-Bereich) wird jetzt zusätzlich zu Slug und Datum auch die MandantenID angezeigt, um schnellere manuelle Datenbankabfragen zu ermöglichen
Features
Persönliche Begrüßung im Dashboard
- Die Dashboard-Überschrift zeigt jetzt „Willkommen, {Name}” statt eines statischen „Dashboard”-Titels
- Bezieht sich auf Issue #119 — Logo und Farbschema pro Mandant waren bereits über die bestehende Branding-/Theme-Einstellungen (Admin → Einstellungen) umsetzbar, nur die Begrüßung fehlte noch
Breaking
Umbenennung “ERP” auf “Suite”
Die interne ERP-Integration wurde von “ERP” auf “Suite” umbenannt (Backend-Klassen/DTOs, appsettings-Sektion, eigenständiges Frontend, Changelog- und Deploy-Skript-Namen). Verweise auf den echten externen Dienst ErpLicensePlugin (VEMAS NextGen) sowie Backend-Routen/-Methodennamen mit FromErp-Suffix bleiben unverändert.
ErpLicenseClient/IErpLicenseClient→SuiteLicenseClient/ISuiteLicenseClient; DTOsErpLicenseSnapshotDto,ErpLicenseTermsDto,ErpLicensePackageDto(samtErpLicenseFeatureDto,ErpLicenseQuantityAllowancesDto,ErpLicenseAllowanceDto,ErpLicenseLineItemDto),ErpTrialLicenseRequestDto→ jeweiligeSuite*-Pendants- appsettings-Sektion
ErpLicense→SuiteLicense - Eigenständiges Frontend
src/revuwork-erp-ui/→src/revuwork-suite-ui/, Produktnamerevuwork.erp→revuwork.suite - Changelog-Verzeichnis
changelog-erp/→changelog-suite/(siehe dort0.3.0.md) - Deploy-Skript
build-deploy-erp-light-linux.ps1→build-deploy-suite-light-linux.ps1, Live-Pfad/erp→/suite(erfordert manuellen Umzug auf dem Server, siehe Skript-Header)
Features
Transparenz & Warnung bei Speicherprovider-Berechtigungen
Beim Einrichten eines Speicherproviders war bisher nicht ersichtlich, welche Berechtigungen die Verbindung mit sich bringt — und wenn eine bestehende Verbindung durch spätere Erweiterungen (z.B. neue Scopes) nicht mehr ausreichte, bemerkte man das erst an einem kryptischen Fehler beim Import.
- Jede Provider-Karte unter Admin → Provider → Speicherprovider zeigt jetzt über ein Info-Icon (und beim unverbundenen Zustand direkt als Text), welche Zugriffe/Berechtigungen die Verbindung erteilt
- OAuth2-Verbindungen (Dropbox, OneDrive, SharePoint, Google Drive) speichern jetzt den beim Connect gewährten Scope. Fordert der Code inzwischen weitere Berechtigungen an als beim letzten Connect gespeichert, zeigt die Karte statt “Verbunden” ein Warnhinweis “Verbindung veraltet — bitte neu verbinden” mit einem direkten “Neu verbinden”-Button
- Neue Spalte
GrantedScopeinStorageProviderConnection(Migration 049), neues FeldneedsReconnectin der/admin/storage-providers-Antwort
Fixes
Dropbox-Import schlug mit “Status 400 (Bad Request)” fehl
Der Dropbox-OAuth2-Connect forderte keinen expliziten scope-Parameter an (im Unterschied zu OneDrive, SharePoint und Google Drive, die das bereits taten). Dadurch erhielt Dropbox beim Verbinden nur die im App Console als Default markierten Berechtigungen — meist ausreichend für den automatischen PDF-Backup-Upload, aber nicht für das Auflisten (files.metadata.read) oder Herunterladen (files.content.read) von Dateien beim Import.
DropboxStorageProviderClientfordert jetzt explizitaccount_info.read files.content.write files.content.read files.metadata.readan- Wichtig: Bereits bestehende Dropbox-Verbindungen müssen einmalig unter Admin → Provider → Speicherprovider getrennt und neu verbunden werden, damit ein Token mit den neuen Berechtigungen ausgestellt wird — der Scope eines bereits ausgestellten Tokens ändert sich nicht automatisch
- Zusätzlich prüfen: im Dropbox App Console (Permissions-Tab der App) müssen
files.metadata.read,files.content.read,files.content.writeundaccount_info.readaktiviert sein, sonst kann Dropbox den angeforderten Scope nicht erteilen
Fixes
Speicherprovider-Browse zeigt echte Fehlerursache
Der Datei-Browser “Aus Speicheranbieter importieren” (Import-Center) zeigte bei jedem Fehler nur die generische Meldung “Ordner konnte nicht geladen werden.” — unabhängig davon, ob z.B. ein Token abgelaufen war, die Verbindung zum Provider fehlschlug oder ein anderer Fehler auftrat.
- Backend (
StorageProvidersController.Browse): fängt jetzt auch nicht-InvalidOperationException-Fehler (z.B. fehlgeschlagene Token-Entschlüsselung, Netzwerkfehler zum Provider) ab, loggt sie serverseitig und liefert eine aussagekräftigeProblemDetails-Meldung statt eines opaken 500ers - Frontend (
StorageExplorerComponent): zeigt jetzt die vom Backend gelieferte Fehlermeldung (detail) an, statt sie durch einen hartkodierten Text zu ersetzen
Features
Hinweis zur Datenspeicherung im Browser
Beim ersten Aufruf der Anwendung erscheint jetzt ein dismissibarer Hinweis-Banner, der erklärt, dass die Anmeldesitzung sowie einige UI-Einstellungen (Theme, Seitenleisten-Status, Ansichtsmodus) lokal im Browser gespeichert werden und kein Tracking stattfindet. Der Hinweis wird nach Bestätigung (“Verstanden”) dauerhaft pro Browser unterdrückt.
Features
Verlinkung Kunden-Grid → Inventarliste
In der Kundenliste öffnet ein Klick auf die Spalte “Geräte” jetzt direkt die Inventarliste, gefiltert auf den jeweiligen Kunden. Der Filter wird als entfernbarer Chip angezeigt.
Features
Administration neu strukturiert
Die Admin-Startseite wurde neu gegliedert, damit verwandte Funktionen leichter zu finden sind:
- Logs: Audit-Log, Import-Journal
- Sicherheit: API-Keys, Webhooks
- Provider: Speicherprovider
- Einstellungen: aufgeteilt in eigene Unterseiten — Allgemein (Mandant, Inventar-Export, Inventar-Archivierung), Themes / Farben, Benachrichtigungen, Backups, Sichtprüfungen
- Verwaltung: Benutzer
Die bisherige Sammel-Seite /admin/settings wurde in vier eigenständige, per Rolle sichtbare Unterseiten aufgeteilt (/admin/settings/general, /admin/settings/theme, /admin/settings/notifications, /admin/settings/backups).
Fixes
Admin-Dashboard: doppelte Überschrift entfernt
- Im SuperAdmin-Bereich zeigte die Seite “Admin-Dashboard” zwei Überschriften an (“Admin-Dashboard” und “Dashboard”). Die redundante Seitenüberschrift wurde entfernt.
Features
Speicheranbieter: Azure Blob Storage (Beta)
- Neuer Speicherprovider Azure Blob Storage — authentifiziert über eine Azure-AD-App-Registrierung (Service Principal, Client-Credentials-Flow) statt statischem Access-Key/Secret-Key. Reiht sich als weiterer
IKeyBasedStorageProviderClientnebens3ein (siehe0-56-0.md): Tenant-ID/Client-ID/Client-Secret/Storage-Account/Container werden über dasselbe Zugangsdaten-Formular wie bei S3-Anbietern eingegeben (Feldbeschriftungen provider-spezifisch), intern auf die generischenStorageProviderKeyCredentials-Felder gemappt (Endpoint→Storage-Account-URL, Region→Tenant-ID, Bucket→Container, AccessKey→Client-ID, SecretKey→Client-Secret) und in derselbenStorageProviderKeyConnection-Tabelle gespeichert — keine neue Migration nötig. - Da Access-Keys bei Azure nicht existieren, holt der Client bei jedem Aufruf (Test/Upload/Listing/Download) einen frischen AAD-Bearer-Token per Client-Credentials-Flow; anders als bei den OAuth2-Providern gibt es dafür kein eigenes Token-Caching in
StorageProviderService. - Als Beta gekennzeichnet, solange noch keine Praxiserfahrung in Produktion vorliegt.
Import: Speicheranbieter als Quelle nutzen (interner Explorer)
- Im Import-Center kann eine Datei jetzt zusätzlich zum lokalen Upload aus einem verbundenen und aktivierten Speicheranbieter importiert werden — Auswahl über einen neuen, wiederverwendbaren Datei-/Ordner-Browser (
app-storage-explorer,shared/components/storage-explorer/). - Dafür wurden sowohl
IStorageProviderClient(OAuth2-Provider) als auchIKeyBasedStorageProviderClient(Access-Key-Provider) um Listing (ListAsync) und Download (DownloadFileAsync) erweitert und für alle sieben Provider implementiert (Dropbox, OneDrive, SharePoint, Google Drive, Box, S3, Azure Blob). - Neuer Endpunkt
GET /admin/storage-providers/{provider}/browseliefert Ordner-/Dateiinhalt für den Explorer;POST /import/upload-from-providerlädt die ausgewählte Datei serverseitig herunter und speist sie in dieselbe Import-Pipeline wie der lokale Datei-Upload ein.
Fixes
Sidebar: Position des “Administration”-Eintrags korrigiert
- Der Sidebar-Eintrag “Administration” (verlinkt auf die neue Card-Grid-Übersichtsseite aus #82) steht jetzt am Ende der Navigationsliste, direkt über “Hilfe & Support”, statt zwischen den Kernfunktionen und dem SuperAdmin-Bereich “Mandanten”.
Features
Administration: neue Card-Grid-Übersichtsseite
- Neue Seite unter
/adminfasst alle bisherigen Admin-Unterseiten (Benutzer, Einstellungen, Sicherheit, Import-Journal, Webhooks, Speicheranbieter, Sichtprüfungs-Vorlagen, Audit-Journal) in Kategorie-Cards (“Einstellungen”, “Verwaltung”) zusammen, inkl. Client-seitigem Suchfeld zum Filtern der Einträge. - Die zugehörigen Admin-Einträge wurden aus der globalen Sidebar entfernt; stattdessen gibt es dort nur noch einen einzelnen Eintrag “Administration”, der auf die neue Übersichtsseite verlinkt. Reines Layout-Redesign — Labels, Routen und Rollen-Sichtbarkeit der einzelnen Admin-Funktionen sind unverändert (1:1-Migration).
- Kategorien und Rollen-Sichtbarkeit kommen aus dem neuen, zentralen
AdminCategoriesService(features/admin/admin-categories.service.ts) statt aus verstreutengroup-Feldern, die zuvor pro Rolle (Admin/Editor/SuperAdmin) dupliziert waren. - Die zur Laufzeit editierbare Rollenzuordnung pro Eintrag durch SuperAdmins ist bewusst nicht Teil dieser Änderung — siehe Folge-Issue #81.
Features
Neuer Speicheranbieter: S3-kompatibler Objektspeicher (ohne OAuth2)
- Ergänzt die bisher rein OAuth2-basierte Speicherprovider-Verwaltung (Dropbox, OneDrive, SharePoint, Google Drive, Box) um einen Provider-Typ mit statischer Access-Key/Secret-Key-Authentifizierung:
s3. Deckt AWS S3, Hetzner Object Storage und MinIO ab, da alle das S3-Protokoll sprechen (ForcePathStyleaktiv für Anbieter ohne Wildcard-Subdomain-DNS). - Neues, paralleles Interface
IKeyBasedStorageProviderClient(Provider,TestConnectionAsync,UploadFileAsync) ergänztIStorageProviderClient, statt es zu verbiegen — OAuth2-spezifische Methoden (BuildAuthorizeUrl/ExchangeCodeAsync/RefreshAsync) passten nicht zu statischen Zugangsdaten (siehe bekannte Lücke in0-55-0.md).StorageProviderServiceverwaltet beide Provider-Arten transparent nebeneinander; neue Provider werden weiterhin einfach per DI registriert. StorageProviderDtohat ein neues FeldauthMode(oauth2|apikey), damit das Frontend pro Provider-Karte den passenden “Verbinden”-Fluss zeigt: Redirect zuGET /{provider}/connectbei OAuth2, ein Zugangsdaten-Formular mitPOST /{provider}/connect(Endpoint, Region, Bucket, Access Key, Secret Key) bei Access-Key-Providern. Die Zugangsdaten werden vor dem Speichern sofort getestet — bei Fehlschlag wird nichts persistiert.- Neue Tabelle
StorageProviderKeyConnection(Migration048_storage_provider_key_connections.sql, verschlüsselter Access-/Secret-Key analog zu den OAuth2-Tokens) statt Wiederverwendung vonStorageProviderConnection, da sich die Spalten nicht überschneiden (kein Refresh-Token/Ablaufzeit, dafür Endpoint/Bucket/Region). - Zielordner-Einstellung (
StorageProviderSettings) funktioniert unverändert auch fürs3(als Objekt-Präfix); der automatische Prüfbericht-PDF-Backup (PruefberichtBackupService) unterstützt S3 dadurch ohne eigene Anpassung, da er ausschließlich überIStorageProviderService.UploadFileAsyncläuft.
Features
Speicheranbieter: echte Provider-Logos statt generischer Icons
- Die Speicheranbieter-Karten (Dropbox, OneDrive, SharePoint, Google Drive, Box) zeigen jetzt das jeweilige Marken-Logo statt eines generischen Material-Icons — bessere Wiedererkennbarkeit auf einen Blick.
- Logos liegen als eigenständige SVG-Dateien unter
public/storage-provider-logos/, jeweils mit der offiziellen Markenfarbe eingefärbt (Dropbox#0061FF, OneDrive#0078D4, SharePoint#038387, Google Drive#2684FC, Box#0061D5). Dropbox/Google Drive/Box stammen aus dem aktuellen, CC0-lizenzierten Simple-Icons-Katalog; die OneDrive-/SharePoint-Formen wurden aus einer älteren Simple-Icons-Version übernommen, da Microsoft-Produkte dort inzwischen aus dem offiziellen Katalog entfernt wurden — die Nutzung als reine Wiedererkennungsmarke für den “Verbinden”-Button ist als nominativer Fair-Use-Verweis auf den Drittanbieter-Dienst unproblematisch.
Features
Automatisches PDF-Backup für Prüfberichte
- Neuer Schalter “Automatisches PDF-Backup” in den Mandanten-Einstellungen (Karte “Prüfbericht-Backup”): sichert einmal täglich im Hintergrund die PDFs aller finalisierten Prüfberichte beim aktivierten externen Speicherprovider des Mandanten.
- Der Schalter ist standardmäßig deaktiviert und lässt sich erst aktivieren, wenn mindestens ein Speicherprovider verbunden und eingeschaltet ist (
PUT /settingsliefert sonst422 Unprocessable Entity). Wird der Provider später wieder deaktiviert, überspringt der Hintergrunddienst den Mandanten mit einer Warnung, statt fehlzuschlagen. - Neuer manueller/externer Cron-Trigger-Endpunkt
POST /inspection/pdf-backup/run— verarbeitet unabhängig vom internen 24h-Hintergrunddienst alle Mandanten mit aktiviertem Schalter (analog zuPOST /inventory/archive/run). IStorageProviderClientkann jetzt Dateien hochladen (UploadFileAsync, überschreibend/idempotent) — implementiert für alle Provider (Dropbox, OneDrive, SharePoint, Google Drive, Box) in den in den jeweiligen Speicherprovider-Einstellungen konfigurierten Zielordner.
Features
Neue Speicheranbieter: OneDrive, SharePoint, Google Drive, Box
- Vier neue OAuth2-fähige Storage-Provider ergänzt (Speicheranbieter-Verwaltung unterstützte bisher nur Dropbox):
onedrive,sharepoint,googledrive,box. Verbinden, Trennen, Aktivieren/Deaktivieren, Testen und Zielordner-Einstellung funktionieren identisch zu Dropbox — kein Sonderfall im Frontend. - OneDrive und SharePoint teilen sich eine gemeinsame Microsoft-Graph-OAuth2-Basisklasse (
MicrosoftGraphStorageProviderClientBase), da beide über dieselbe Azure-AD-App-Registrierung laufen und sich nur in Scope (Files.ReadWritevs.Sites.ReadWrite.All) und Konto-Query unterscheiden. - SharePoint benötigt zusätzlich zur Zielordner-Einstellung die ID der SharePoint-Site (
/sites/{siteId}/drive) — dafür wurdeStorageProviderSettingsum eine optionaleSiteId-Spalte erweitert (Migration047_storage_provider_settings_site_id.sql). Bei allen anderen Providern bleibt sie ungenutzt/null. - Das bisher Dropbox-spezifische Settings-Formular (
dropbox-settings-form) wurde durch ein generisches, providerparametrisiertes Formular (storage-provider-settings-form) ersetzt, das das Site-ID-Feld nur für SharePoint einblendet. - Neue App-Zugangsdaten-Konfigurationssektionen
MicrosoftGraph,GoogleDrive,Boxinappsettings.json(analog zuDropbox) — müssen serverseitig mit den jeweiligen OAuth2-App-Credentials befüllt werden, bevor die Provider nutzbar sind.
Bekannt: Amazon S3 / S3-kompatible Speicher noch nicht unterstützt
- S3 und S3-kompatible Anbieter (Hetzner Object Storage, MinIO, Azure Blob im Access-Key-Modus) authentifizieren über Access-Key/Secret statt OAuth2 und passen nicht in das bestehende
IStorageProviderClient-Interface (BuildAuthorizeUrl/ExchangeCodeAsync/RefreshAsync). Das ist als separate Erweiterung vorgesehen.
Fixes
Storage-Provider-Callback: kein ungefangener 500 mehr, Fehler werden geloggt
StorageProviderService.HandleCallbackAsyncfing bisher nicht alle Fehlerfälle ab: schlug das Speichern der neu verbundenen Verbindung fehl, führte das zu einem ungefangenen500 Internal Server Errorauf/admin/storage-providers/{provider}/callbackstatt eines Redirects mit Fehlerstatus.- Alle Fehlerpfade im Callback (State ungültig, Code-Exchange fehlgeschlagen, Konto-Info nicht abrufbar, Speichern fehlgeschlagen) werden jetzt sauber abgefangen und mit
ILoggergeloggt (vorher:StorageProviderServicehatte kein Logging, Fehler waren nur über eine generische ProblemDetails-Antwort ohne Detail sichtbar).
StorageProviderConnection/-Settings: FK-Verletzung bei physisch getrennten Mandanten-DBs
- Ursache des zuvor beobachteten 500ers:
TenantId REFERENCES Tenant(Id)in den Migrationen 044/045 validierte bei physisch getrennten Tenant-DBs nur gegen die dortige leere Schatten-Kopie vonTenant(echte Tenant-Zeilen leben nur in der Control-Plane-DB) — jeder Schreibzugriff schlug mit23503 foreign key violationfehl. Exakt derselbe Fehler wurde für andere Tabellen bereits in Migration039_drop_cross_db_tenant_fks.sql(Issue #3) behoben; die neuen Tabellen haben ihn wiederholt. - Neue Migration
046_storage_provider_drop_tenant_fk.sqlentfernt die FK-Constraints (analog zu 039) —TenantIdbleibt erhalten, die Zugehörigkeit wird weiterhin überITenantContextsichergestellt.
Features
Eigenes Einstellformular pro Speicheranbieter
- Neues Einstellformular pro Provider: über das Zahnrad-Symbol auf der Karte (
/admin/storage-providers) hinterlegt jeder Mandant seine providerspezifischen Zusatz-Einstellungen — für Dropbox aktuell: optionaler Zielordner für Exporte. - Jeder Provider bekommt sein eigenes, dediziertes Formular (
DropboxSettingsFormComponent) statt eines generischen Einstellungs-Screens — weitere Provider ergänzen künftig ihr eigenes Formular, ohne bestehende anzufassen. - App Key/Secret für den Dropbox-OAuth2-Flow bleiben zentral in der Server-Konfiguration (
Dropbox:AppKey/Dropbox:AppSecret, per systemd-EnvDropbox__AppKey/Dropbox__AppSecret) — eine Dropbox-App für alle Mandanten, wie bisher.
Fixes
Dropbox-Verbindung als SuperAdmin blieb nach erfolgreichem Connect als “nicht verbunden” stehen
StorageProviderRepository/StorageProviderSettingsRepositorynutzten die ambientefactory.CreateConnection()-Überladung statt der explizitenfactory.CreateConnection(tenantId). Der Dropbox-OAuth2-Callback läuft ohne?tenantId=-Query-Parameter (Dropbox liefert nurcode/statezurück), wodurchITenantContext.TenantIdfür SuperAdmins dortnullwar und die neue Verbindung fälschlich in der Control-Plane-DB statt der Tenant-DB landete (analog zur Regression aus v0.42.1 beiAuditRepository).- Alle Storage-Provider-Repository-Methoden verwenden jetzt durchgängig die explizite Tenant-Verbindung; Regressionstests ergänzt (
StorageProviderRepositoryTests).
Fixes
Speicheranbieter: Karten-Layout verkleinert
- Provider-Kacheln auf der Seite „Speicheranbieter” (
/admin/storage-providers) von einer vollbreiten Zeilenliste auf ein kompaktes Karten-Grid umgestellt, sodass bei ausreichender Breite 4 Karten nebeneinander passen (responsiv: 3 / 2 / 1 Spalten auf kleineren Viewports).
Features
Einstellformular „Speicheranbieter” (Dropbox)
- Neuer Admin-Bereich Speicheranbieter (
/admin/storage-providers), in dem Mandanten externe Storage-Provider per OAuth2 verbinden können — erster Provider: Dropbox. - Backend-Architektur bewusst providerunabhängig gehalten (
IStorageProviderClient), damit weitere Provider (z.B. OneDrive, Google Drive) ohne Formular-Neubau ergänzt werden können. - Vollständiger OAuth2-Connect-Flow (
/admin/storage-providers/dropbox/connect→ Dropbox-Autorisierung →/admin/storage-providers/dropbox/callback), inkl. signiertem, zeitlich begrenztem State-Parameter gegen CSRF. - Zugangs- und Refresh-Token werden mandantenspezifisch verschlüsselt gespeichert (
IDataProtector) und bei Bedarf automatisch erneuert. - Verbindung kann pro Mandant aktiviert/deaktiviert, getestet (Konto-Info abrufen) und getrennt werden.
Features
Prüfberichte-Liste auf das neue Data-Grid umgestellt
Die Prüfberichte-Liste nutzt jetzt app-data-grid statt der handgebauten
Karten-Liste, analog zur Inventar- und Kundenliste: Drag&Drop-Spaltenreorder,
Gruppierung (persistiert pro Benutzer), sortierbare Spaltenköpfe für die
serverseitig unterstützten Felder (Gerät/Typ, Prüfdatum, nächste Prüfung,
Ergebnis, Importdatum) sowie eine funktionierende Mehrfachauswahl für den
Excel-Export.
Fixes
Inventarliste: weißer Bildschirm behoben
InventoryListComponent hat DatePipe per inject(DatePipe) verwendet,
ohne die Pipe als Provider zu registrieren. Das führte beim Öffnen der
Inventarliste zu einem NG0201-Fehler (“No provider found for DatePipe”),
der das Rendern der gesamten Komponente verhinderte — Seite blieb komplett
weiß, keine Toolbar, kein Grid. DatePipe ist jetzt Teil der
Komponenten-Provider.
Features
Inventar-Liste: Umstellung auf das neue Grid
Die Tabellenansicht der Inventarliste (inventory-list) nutzt jetzt die neue
app-data-grid-Komponente statt der bisherigen, handgebauten Karten-Liste —
analog zur bereits umgestellten Kundenliste (customer-list).
- Spalten lassen sich per Drag & Drop verschieben und in die Gruppierleiste ziehen; die bestehende Spalten-Konfiguration (Dialog „Spalten“) bleibt erhalten und bleibt die Quelle der Wahrheit für Sichtbarkeit/Reihenfolge
- Gruppierzustand wird jetzt (wie bei der Kundenliste) pro Benutzer
gespeichert (
UserPreferenceService.saveGroupLevels, Kontextinventory) - Sortierbare Spaltenköpfe für die serverseitig sortierbaren Felder (Typ, Art, Gebäude, Geschoss, Raum, Status, nächste/letzte Prüfung, Hersteller, Projektnummer)
- Mehrfachauswahl per Checkbox-Spalte für „Als MXP exportieren“ und „Kunde zuweisen“ ist jetzt tatsächlich nutzbar (vorher fehlte dafür die UI)
- Die Kartenansicht (
app-card-Grid) ist unverändert; die farbige Status-Markierung der ganzen Zeile (ok/warnung/fehler) entfällt in der Tabellenansicht, da das neue Grid keine zeilenweite Einfärbung unterstützt — die Fälligkeits-Hervorhebung bleibt auf die Spalte „Nächste Prüfung“ beschränkt
Features
Grid: Persistenz von Spalten- und Gruppierzustand
Die Kundenliste (customer-list) speichert Spalten-Sichtbarkeit/-Reihenfolge
und den Gruppierzustand (groupLevels) jetzt pro Benutzer statt nur im
Arbeitsspeicher der Komponente. Nach einem Neuladen der Seite bleibt sowohl
die Spaltenkonfiguration als auch eine aktive Gruppierung erhalten.
- Neue Backend-Endpunkte
GET/PUT /api/v1/user/group-state(analog zucolumn-preferences), gesichert über dieselbe Benutzer-Authentifizierung und ohne Mandantenbezug (reine UI-Präferenz, wie bei den Spalteneinstellungen) UserPreferenceService(Frontend) umgetGroupLevels/saveGroupLevelserweitert; Kontext-Parameter unterstützt jetzt zusätzlichcustomer- Neue Tabelle
UserGroupLevel(Control-Plane-DB), idempotent angelegt überUserPreferenceRepository.EnsureTableAsync()analog zuUserColumnPreference
Features
Grid: Aggregate/Summen in Gruppenzeilen
ColumnConfig bekommt ein neues optionales Feld aggregate?: 'sum' | 'avg'. Ist
es für eine Spalte gesetzt, zeigt die neue DataGridComponent-Gruppierung
(shared/components/data-grid) neben Gruppenwert und Anzahl jetzt auch die
Summe bzw. den Durchschnitt dieser Spalte über die Zeilen der jeweiligen Gruppe
an (Betrag: Σ 1.234,56) — sowohl in der Tabellen- als auch in der
Kartenansicht.
- Nicht-numerische Werte innerhalb einer Gruppe werden beim Aggregieren übersprungen; enthält eine Gruppe gar keinen numerischen Wert, wird für sie kein Aggregat angezeigt
- Betrifft nur Spalten mit explizit gesetztem
aggregate— unverändertes Verhalten für bestehende Konsumenten ohne diese Option
Features
Kundenliste: vollständige Toolbar wie bei Prüfberichten/Inventar
Die Kundenliste (Kunden) hat jetzt dieselbe Toolbar wie die Prüfberichte- und
Inventarliste:
- Filter: Suchfeld plus separates Filter-Menü über alle Kundenfelder (Name, Straße, PLZ, Ort, externe Referenz, Anzahl Geräte von/bis) mit entfernbaren Filter-Chips
- Spalten: “Spalten X/Y”-Button mit Badge öffnet dieselbe Spalten-Auswahl wie zuvor, jetzt nur optisch aufgewertet statt als reines Icon
- 3-Punkte-Menü: neuer Excel-Export der aktuell sichtbaren/gefilterten/ gruppierten Kundenliste
- Die bestehende Drag&Drop-Gruppierleiste des Grids bleibt unverändert erhalten
DataGridComponent (shared/components/data-grid) bekommt dafür ein neues
optionales Input columnMenuVisible (Default true, unverändertes Verhalten für
bestehende Konsumenten): darüber kann ein Konsument das eingebaute
Spalten-Icon-Menü ausblenden und über die bereits öffentliche
columnState/isColumnVisible/toggleColumnVisibility-API einen eigenen,
stärker gestalteten “Spalten”-Button bauen — wie jetzt bei den Kunden.
Backend: Excel-Export für Kunden (POST /customers/export/excel)
Neuer Endpoint exportiert die per customerIds übergebenen Kunden (oder alle
Kunden des Mandanten, wenn leer/weggelassen) als Excel-Datei. Respektiert wie
alle Endpunkte die Mandantengrenze — angefragte IDs aus einem fremden Mandanten
tauchen in der zugrunde liegenden, bereits mandantengefilterten Abfrage gar
nicht erst auf.
Fixes
Kundenliste: fehlende Toolbar ergänzt
Die Kundenliste (Kunden) wirkte nach der Migration auf shared/components/data-grid
im Vergleich zur Prüfberichte-Liste leer — es fehlte die gewohnte Toolbar mit
Suchfeld. Die Kundenliste hat jetzt dieselbe Toolbar-Optik wie die Prüfberichte-Liste
(Suchfeld über Name/Adresse/externe Referenz, Aktions-Button daneben); der
“Kunde anlegen”-Button ist aus der Kopfzeile in die Toolbar gewandert.
Features
Grid: Karte/Tabelle-View-Toggle für shared/components/data-grid
DataGridComponent unterstützt jetzt optional einen Karte/Tabelle-View-Toggle,
wie ihn die bisherige Card-List-Implementierung in inventory-list/inspection-list
schon kannte:
- Neues Input
cardViewEnabledblendet zwei Toggle-Buttons (“Tabellenansicht” / “Kartenansicht”) in der Toolbar ein — ohne dieses Input bleibt das Grid wie bisher tabellenbasiert (kein Verhaltensunterschied für bestehende Konsumenten) - Neues Two-Way-Model
viewMode('table' | 'card'), damit Konsumenten die gewählte Ansicht wie bisher persistieren können (z.B. inlocalStorage) - Kartenansicht nutzt dieselben
visibleRows()wie die Tabelle: Gruppierung (inkl. Ein-/Ausklappen) und Spalten-Sichtbarkeit wirken identisch in beiden Ansichten — die erste sichtbare Spalte wird zum Kartentitel, die übrigen zu Feld/Wert-Zeilen - Neue Direktive
appGridCardDefals Erweiterungspunkt für ein komplett eigenes Karten-Layout, falls die generische Fallback-Karte nicht reicht - Auswahl (Checkbox) funktioniert in der Kartenansicht genauso wie in der Tabelle
Features
Grid: Export-Extension-Point in shared/components/data-grid
DataGridComponent stellt jetzt einen öffentlichen exportRows()-Zugriff bereit, über den Konsumenten (z.B. inventory-list/inspection-list bei ihrer künftigen Migration auf die neue Grid-Komponente) an die aktuell sichtbaren/gefilterten/gruppierten Zeilen für einen eigenen Export (Excel/PDF/MXP) kommen:
exportRows()liefert die reinen Datenzeilen in der aktuellen Sortier-/Gruppierreihenfolge, über alle Seiten hinweg (nicht nur die paginierte Seite) und ohne Zeilen aus eingeklappten Gruppen- kombinierbar mit dem bereits über
exportAs="appDataGrid"öffentlichenselectedIds()für einen “nur Auswahl exportieren”-Flow - Export-Logik selbst (Backend-Aufrufe, Datei-Erzeugung) bleibt bewusst beim Konsumenten, nicht im Grid
Features
Kundenliste: Pagination aktiviert
Die Kundenliste (customer-list) nutzt jetzt die in 0.52.2 eingeführte Pagination-Unterstützung von DataGridComponent:
pagination="client"— die Grid-Komponente paginiert die vom Backend geladene Kundenliste selbst (kein neuer paginierter API-Endpunkt notwendig,CustomerService.getAllliefert weiterhin die volle Liste)- Funktioniert wie gewohnt in Kombination mit Sortierung und Gruppierung
Features
Grid: Pagination-Integration in shared/components/data-grid
Die wiederverwendbare Grid-Komponente (DataGridComponent) unterstützt jetzt optional Pagination, ohne die bestehende API für aktuelle Konsumenten (customer-list) zu ändern:
- Neuer Input
pagination:'none'(Standard, unverändertes Verhalten) |'client'(die Grid-Komponente slict die übergebenen Daten selbst) |'server'(dataenthält bereits nur die aktuelle Seite, die Gesamtanzahl kommt übertotalCount) - Neue Inputs/Outputs
pageIndex,pageSize,pageSizeOptions,totalCountundpageChange(analog zuMatPaginator/PageEvent, wie bereits ininventory-list/inspection-listverwendet) - Pagination funktioniert jetzt auch in Kombination mit Gruppierung (
groupLevels):- Client-seitig wird zuerst über den vollständigen Datensatz gruppiert, die Seite wird anschließend aus den geflatteten Zeilen (inkl. Gruppen-Header) geschnitten — eine Gruppe kann sich dadurch über zwei Seiten erstrecken
- Server-seitig gruppiert die Komponente weiterhin nur innerhalb der aktuell geladenen Seite (ein Hinweis macht das in der UI transparent)
Features
Superadmin-Kundenliste auf neue Grid-Komponente umgestellt
Die Kundenliste im Superadmin-Bereich (Superadmin > Kunden) nutzte bisher eine
unfertige Tabelle ganz ohne Sortieren, Gruppieren oder Spalten-Sichtbarkeit — anders
als die reguläre Kundenliste, die in 0.52.0 bereits auf die neue Grid-Komponente
umgestellt wurde. Jetzt nutzt auch die Superadmin-Ansicht shared/components/data-grid
mit denselben Funktionen: Sortieren per Klick auf den Spaltenkopf, Spalten
ein-/ausblenden, Spaltenreihenfolge per Drag & Drop sowie Gruppieren per Drag & Drop
in die Gruppierleiste.
Fixes
Ort-Spalte in der Superadmin-Kundenliste zeigte keine Adresse
Die Spalte “Ort” griff auf nicht existierende Felder (plz/ort) zu und blieb dadurch
immer leer. Sie greift jetzt korrekt auf postalCode/city zu.
Features
Neue Grid-Komponente mit Gruppieren per Drag & Drop
Neue wiederverwendbare Grid-Komponente (shared/components/data-grid) für Listenansichten:
Spalten ein-/ausblenden, Spaltenreihenfolge direkt im Tabellenkopf per Drag & Drop
ändern, Sortieren durch Klick auf den Spaltenkopf sowie Zeilen-Selektion.
Neu ist das Gruppieren per Drag & Drop: ein Spaltenkopf wird in die Gruppierleiste oberhalb der Tabelle gezogen, die Daten werden daraufhin nach dieser Spalte gruppiert (mehrstufig durch mehrere Spalten möglich). Gruppenzeilen zeigen den Gruppenwert und die Anzahl der enthaltenen Einträge und lassen sich ein-/ausklappen.
Die Kundenliste (Kunden-Verwaltung) wurde als erste Ansicht auf die neue Komponente umgestellt.
Features
Fehlerquote pro Projekt im Dashboard
Neues Dashboard-Widget “Fehlerquote pro Projekt”: zeigt je Projektnummer die Gesamtanzahl Einträge, die Anzahl mit Mängeln sowie die daraus berechnete Fehlerquote in Prozent (Einträge mit Mängel ÷ Gesamteinträge des Projekts × 100).
Die zehn Projekte mit der höchsten Fehlerquote werden farblich nach Ampelschema markiert (< 5 % grün, 5–20 % orange, ≥ 20 % rot). Archivierte Einträge und Einträge ohne Projektnummer fließen nicht in die Berechnung ein. Das Widget kann wie alle anderen Dashboard-Kacheln über “Bearbeiten” ein-/ausgeblendet und frei positioniert werden.
Features
Automatische Archivierung überfälliger Prüfungen
Inventar-Einträge, deren nächste Prüfung seit vielen Jahren überfällig ist, wurden bisher dauerhaft in den Fälligkeits-Listen und -Statistiken angezeigt — meist, weil das Arbeitsmittel inzwischen von einem anderen Anbieter geprüft wird und in revuwork eigentlich nicht mehr aktiv geführt werden sollte.
Solche Einträge lassen sich jetzt archivieren, statt sie zu löschen:
- Schwellenwert konfigurierbar: In den Einstellungen → Inventar-Archivierung lässt sich festlegen, ab wie vielen Jahren Überfälligkeit ein Eintrag als Archivierungs-Kandidat gilt (Standard: 5 Jahre).
- Automatischer Archivmodus: Ein Schalter in denselben Einstellungen aktiviert einen internen Hintergrunddienst, der Kandidaten einmal täglich automatisch archiviert — ganz ohne Server-Setup.
- Manueller/externer Trigger: Alternativ (oder zusätzlich) lässt sich der neue Endpunkt
POST /inventory/archive/runper Cron-Job aufrufen (Anleitung indeploy/setup-archive-cron.md); dieser verarbeitet unabhängig vom Schalter immer alle Mandanten. - Archiv-Ansicht: Im Reiter “Inventar” blendet der neue “Archiv”-Button in der Toolbar zwischen der normalen Liste und einer reinen Archiv-Ansicht um. Archivierte Einträge werden in der normalen Liste, im Export und in den Fälligkeits-Statistiken ab sofort nicht mehr mitgezählt.
- Reversibel: Über “Aus Archiv zurückholen” im Kontextmenü eines archivierten Eintrags lässt sich die Archivierung jederzeit rückgängig machen — es wird nichts gelöscht.
Fixes
Ort-Spalte in Gebäude, Geschoss und Raum aufgeteilt
Die Spalte “Ort” in der Inventar-Tabelle und im Excel-/PDF-Export kombinierte bisher Gebäude, Geschoss und Raum zu einem Text (z.B. “Halle A · EG · R001”). Das machte ein Filtern oder Sortieren nach Gebäude in Excel praktisch unmöglich, obwohl die Daten bereits als getrennte Felder vorliegen (wie in den Inventar-Filtern links).
Die Spaltenkonfiguration bietet jetzt standardmäßig drei separate, sichtbare Spalten Gebäude, Geschoss und Raum an — sowohl in der Tabellenansicht als auch im davon abgeleiteten Export. Die bisherige kombinierte “Ort”-Spalte bleibt als optionale, standardmäßig ausgeblendete Spalte weiterhin verfügbar.
Features
Konfigurierbare Spalten im Inventar-Export
Der Excel- und PDF-Export im Reiter “Inventar” exportierte bisher immer nur fünf fest codierte Spalten (Typ, Art, Ort, Status, Nächste Prüfung) — unabhängig davon, welche Spalten in der Tabellenansicht ein- oder ausgeblendet waren.
Der Export nutzt jetzt dieselbe Spaltenkonfiguration wie die Inventar-Tabelle (Dialog “Spalten konfigurieren”): Welche Spalten sichtbar sind und in welcher Reihenfolge sie stehen, bestimmt jetzt auch den Excel-/PDF-Export.
Außerdem stehen im Spalten-Dialog nun deutlich mehr Parameter zur Auswahl, die bisher weder in der Tabelle noch im Export verfügbar waren: Kostenstelle, Kostenstellen-Nr., Abteilung, Lieferant, Baujahr, Anschaffung, Ausmusterung, Gerätestatus, Benutzerstatus, Gefährdungsklasse, HW-/SW-Revision (inkl. Revisionsdatum) und letzte Änderung. Nicht benötigte Spalten lassen sich wie gewohnt über den Dialog ausblenden.
Die Inventar-Nr., die bereits standardmäßig sichtbar war, ist jetzt ebenfalls Teil des Exports (bisher fehlte sie dort trotz sichtbarer Spalte in der Tabelle).
Features
Manueller Anstoß der Trial-Lizenz-Anfrage ans ERP-System
Bisher wurde die Trial-Lizenz-Anfrage ans ErpLicensePlugin nur automatisch bei der
Erstfreigabe eines selbst registrierten Mandanten ausgelöst. Im SuperAdmin-Frontend gibt es
jetzt im Lizenz-Dialog zusätzlich zum “Von ERP synchronisieren”-Button einen Button
“Trial-Lizenz anfordern” (POST /superadmin/tenants/{id}/license/trial-request), mit dem
sich die ERP-Anbindung für einen beliebigen Mandanten manuell testen lässt — unabhängig
davon, ob bereits eine lokale Lizenz existiert. Im Gegensatz zur automatischen Anfrage bei
der Erstfreigabe (best-effort, Fehler werden nur geloggt) wird ein ERP-Fehler bei diesem
manuellen Anstoß direkt im Dialog angezeigt.
Fixes
”Von ERP synchronisieren”-Button im SuperAdmin-Frontend lieferte 403
Die Endpunkte GET /superadmin/tenants/{id}/license/erp und
POST /superadmin/tenants/{id}/license/sync-from-erp verlangten bisher zwingend
Authentifizierung per X-Api-Key (SuperAdminApiKeyOnly-Policy). Das SuperAdmin-Frontend
ruft diese beiden Pull-Endpunkte aber über die normale Browser-Session auf, wodurch der
Button “Von ERP synchronisieren” im Lizenz-Dialog immer mit 403 Forbidden fehlschlug.
Beide Endpunkte verwenden jetzt die neue Policy SuperAdminSessionOrApiKey, die sowohl
eine eingeloggte SuperAdmin-Session als auch den API-Key akzeptiert. Der reine Push-Endpunkt
POST /superadmin/tenants/{id}/license/push (für Aufrufe vom ErpLicensePlugin) bleibt
bewusst bei SuperAdminApiKeyOnly.
Fixes
ERP-Konfiguration in Preview-Deploy-Dokumentation ergänzt
build-deploy-preview-linux.ps1 dokumentiert jetzt zusätzlich ErpLicense__BaseUrl und
ErpLicense__ApiKey als Environment=-Zeilen der revuwork-preview.service-Unit
(analog zu den bestehenden Smtp__*-Zeilen). Der GitHub-Actions-Workflow
deploy-preview.yml setzt keine App-Umgebungsvariablen selbst — diese Werte müssen
weiterhin direkt in der systemd-Unit auf dem Server hinterlegt werden.
Fixes
ERP-Konfiguration im Server-Setup-Skript ergänzt
Install-linux-web-postgres-server.sh setzt jetzt auch ErpLicense__BaseUrl und
ErpLicense__ApiKey als Umgebungsvariablen der systemd-Unit (analog zu den bestehenden
Smtp__*-Variablen). Die Werte werden über ERP_LICENSE_BASE_URL und
ERP_LICENSE_API_KEY beim Aufruf des Skripts gesetzt und sind optional (leerer Default),
da die ERP-Anbindung nicht zwingend erforderlich ist.
Features
Neues ERP-Lizenzformat mit Paketpreisen und Kontingenten
Das ErpLicensePlugin (VEMAS NextGen) liefert Lizenzpakete jetzt in einem erweiterten, preisbehafteten Format (Paketname, Preis, Feature-Liste mit Einzelpreisen, Mengenkontingente für Inventar-Items, Prüfberichte und Benutzer, Addin- und API-Request-Kontingente – jeweils mit Preis pro Einheit oberhalb des Kontingents). Dieses Format wird jetzt vollständig auf die interne Lizenzverwaltung übernommen:
LicenseTemplateund die Lizenz eines Mandanten führen neu:Price,InventoryItemOveragePrice,MaxInspectionReports+InspectionReportOveragePrice,UserOveragePrice,MaxAddins+AddinOveragePrice,MaxApiRequestsPerMonth+ApiRequestOveragePrice.- Der ERP-Sync (
POST /superadmin/tenants/{id}/license/sync-from-erp) übernimmt Paket, Preise und Kontingente automatisch in die lokale Lizenz. Dieser Endpunkt ist jetzt auch im SuperAdmin-Frontend als “Von ERP synchronisieren”-Button im Lizenz-Dialog verfügbar – damit lässt sich ein im ERP-System vorgenommenes Tarif-Upgrade eines Mandanten mit einem Klick übernehmen. - Preise werden ausschließlich per ERP-Sync gepflegt; es gibt bewusst kein manuelles Preis-Eingabeformular in AMV.
- Überschreitungen von Mengenkontingenten (Inventar-Items, Benutzer, Prüfberichte, API-Requests/Monat)
sperren die Nutzung nicht mehr – da jedes Kontingent jetzt einen Overage-Preis hat, wird die
Überschreitung stattdessen abgerechnet. Der Verbrauch des Vormonats wird dafür weiterhin je
Mandant erfasst (u.a.
ApiAccessDailySummaryfür API-Requests) und steht für die Rechnungsstellung zur Verfügung. Eine grundsätzlich abgelaufene oder fehlende Lizenz sperrt die Nutzung weiterhin.
Trial-Lizenz-Anfrage beim ERP-System bei Mandanten-Registrierung
Bei der Erstfreigabe eines selbst registrierten Mandanten wird jetzt zusätzlich zur lokalen 7-Tage-Testlizenz automatisch eine Trial-Lizenz-Anfrage an das ErpLicensePlugin geschickt (inkl. Firmenname, Adresse und Admin-E-Mail-Adresse). Die Anfrage läuft best-effort: ist das ERP-System nicht erreichbar, wird die lokale Testlizenz trotzdem angelegt, der Fehler wird nur geloggt.
Features
Rückfrage vor Löschen zugehöriger Prüfberichte beim Import-Löschen
Beim Löschen eines Import-Eintrags (DELETE /api/v1/import/{id}) werden auch die zugehörigen
Inventareinträge entfernt. Waren zu diesen Inventareinträgen noch Prüfberichte vorhanden, wurden
sie bisher als verwaiste Datensätze zurückgelassen (kein Fremdschlüssel zwischen InspectionReport
und Inventory in der Datenbank).
Jetzt prüft der Endpunkt vorab, ob verknüpfte Prüfberichte existieren:
- Ohne Bestätigung antwortet er mit
409 Conflictund der Anzahl betroffener Prüfberichte (affectedInspectionReportCount). - Erst mit dem neuen Query-Parameter
confirmCascadeDelete=truewerden die Prüfberichte zusammen mit den Inventareinträgen und dem Import-Log in einer Transaktion gelöscht.
Im Import-Protokoll (Frontend) fragt der Lösch-Dialog entsprechend nach, wenn Prüfberichte betroffen sind, bevor der Löschvorgang bestätigt wird.
Fixes
ErpLicensePlugin-Endpunkte auf /api/revuwork/… umgestellt
Das ErpLicensePlugin (VEMAS NextGen) hat seine REST-Routen von /api/erplicense/... nach
/api/revuwork/... umbenannt. ErpLicenseClient ruft GetLicenseAsync/GetLicenseTermsAsync
jetzt unter dem neuen Pfad api/revuwork/distribution/{tenantId} bzw.
api/revuwork/distribution/{tenantId}/terms auf.
Feature
News-Anmeldung bei Registrierung und Profilseite für Benachrichtigungen & Theme
- Die Registrierung enthält jetzt eine Checkbox “Ich möchte per E-Mail über News und Updates informiert werden”, standardmäßig aktiviert. Der Wert wird beim Anlegen des Kontos in den neuen benutzerbezogenen
UserSettingsgespeichert. - Neue Profilseite (
/profile, erreichbar über das Benutzermenü) erlaubt es, die News-Benachrichtigung jederzeit ein-/auszuschalten sowie zwischen hellem und dunklem Theme zu wechseln. - Die Theme-Präferenz wird nun serverseitig über
GET/PUT /api/v1/user/settingsgespeichert und beim Login geräteübergreifend geladen (bisher nur pro Browser inlocalStorage).
Fixes
ErpLicenseClient deserialisierte case-sensitiv, ErpLicensePlugin liefert camelCase
IErpLicenseClient.GetLicenseAsync/GetLicenseTermsAsync lasen die JSON-Antwort ohne
PropertyNameCaseInsensitive. Revuwork.Api selbst liefert (wie die meisten ASP.NET-Core-APIs)
camelCase-JSON – dieselbe Konvention ist beim ErpLicensePlugin (eigener Host, VEMAS NextGen) zu
erwarten. Ohne case-insensitives Deserialisieren wären alle Felder still auf ihren Default-Wert
(0/""/null) gefallen, ohne Fehlermeldung. Betrifft direkt die neue Preiskalkulation im
ErpLicensePlugin, die auf korrekt gelesene Lizenzbedingungen angewiesen ist.
Features
Lizenz per REST vom ErpLicensePlugin abrufen und dorthin pushen
Die API kann jetzt mit dem ErpLicensePlugin (VEMAS-NextGen-Erweiterung) sprechen:
- Abrufen (Pull):
GET /api/v1/superadmin/tenants/{id}/license/erpruft die aktuelle Lizenz-Momentaufnahme eines Mandanten direkt vom ErpLicensePlugin ab (X-Api-Key-authentifizierter REST-Aufruf über den neuenIErpLicenseClient). - Pushen:
POST /api/v1/superadmin/tenants/{id}/license/pushnimmt eine Lizenz-Aktualisierung von einem externen System entgegen und legt sie an oder aktualisiert sie (Upsert) – für den Fall, dass das ErpLicensePlugin (oder ein anderes System) die Lizenz proaktiv nach revuwork überträgt, statt dass revuwork sie abholt. - Synchronisieren:
POST /api/v1/superadmin/tenants/{id}/license/sync-from-erpruft die aktuell gültigen Lizenzbedingungen (Features/Limits/Gültigkeit) vom neuen.../distribution/{tenantId}/terms-Endpunkt des ErpLicensePlugin ab und übernimmt sie in einem Schritt als lokale Lizenz (Pull + Upsert kombiniert).
Konfiguration über die neue appsettings-Sektion ErpLicense (BaseUrl, ApiKey).
Mandanten-Nutzungsmengen für die Rechnungsstellung
GET /api/v1/superadmin/tenants/{id}/quantities liefert die aktuellen Nutzungsmengen eines einzelnen Mandanten (Inventar-Anzahl, Prüfberichte, Benutzer, Benachrichtigungskonfiguration, Sichtprüfungs-Overrides) – dieselben Werte wie im Massenexport (export/tenants), aber gezielt für einen Mandanten und ohne die anderen Tenant-Datenbanken per Fan-out abzufragen. Basis für externe Abrechnungssysteme (z.B. den PriceCalculator oder das ErpLicensePlugin).
Integrationsendpunkte nur noch per SuperAdmin-API-Key
Die vier neuen Endpunkte (.../license/erp, .../license/push, .../license/sync-from-erp, .../quantities) sind für externe Systeme gedacht, nicht für eingeloggte Menschen. Die neue Policy SuperAdminApiKeyOnly lässt hier zusätzlich zur Rolle SuperAdmin nur den X-Api-Key-Pfad zu (AuthenticationType == "ApiKey") – eine SuperAdmin-Cookie-Session (Browser-Login) reicht dafür nicht mehr aus.
Fixes
Vemas-NextGen-Plugin (revuworkVFSPlugin) an aktuelle API angepasst
Das Plugin für das ERP-System Vemas NextGen rief die revuwork-API noch mit veralteten, deutschen Feldnamen ab. Da die API heute ausschließlich englische Property-Namen liefert, wurden viele Felder beim Deserialisieren stillschweigend auf ihren Default-Wert (""/0) gesetzt – ohne Fehlermeldung.
InventoryDto: alle Feldnamen auf die aktuellen API-Namen umgestellt (InventoryNumber,DeviceType,DeviceCategory,Manufacturer,Building,Floor,Room,Result,LastInspectionDate,NextInspectionDate,ProjectNumber,ProtectionClass,CableLength,SerialNumber) und um die inzwischen neuen Felder ergänzt (CostCenter,CostCenterNumber,Department,Supplier,YearOfManufacture,PurchaseDate,DecommissionDate,Status,UserStatus,AssignedPersonUserId,HazardClass,HardwareRevision(Date),SoftwareRevision(Date)). Damit funktioniert u. a. die Report-Anreicherung inRevuworkInventoryAfterLoadwieder korrekt (bisher liefen alle Items aufProjectNumber = 0).InspectionReportDto:MandantId→TenantId,Projektnummer→ProjectNumberkorrigiert.- Update-Request beim Verknüpfen/Trennen von NextGen-Assets (
RevuworkSyncService): der PUT-Body verwendete durchgehend falsche (deutsche) Feldnamen, wodurch jeder Link-/Unlink-Vorgang an der[Required]-Validierung der API mit400 Bad Requestscheiterte. Der Request enthält jetzt die korrekten Feldnamen und alle Felder des aktuellen Inventar-Eintrags, damit beim Schreiben keine Daten überschrieben werden. - Tenant-Filter: Query-Parameter beim Abruf von Inventar/Prüfberichten von
MandantIdaufTenantIdkorrigiert (relevant für den SuperAdmin-API-Key-Pfad). - Die SQL-Report-Prozedur
ReportrevuworkInventarwurde um die neuenamv_*-Spalten ergänzt, damit die zusätzlichen Inventarfelder im NextGen-Report zur Verfügung stehen.
Umbenennung Amv* → Revuwork*
Alle Klassen-, Interface- und Dateinamen im Plugin, die noch das alte Amv-Präfix trugen, wurden auf Revuwork umgestellt (RevuworkApiClient, IRevuworkApiClient, RevuworkApiClientOptions, RevuworkAuthException, RevuworkUpdateInventoryRequest, RevuworkSyncService, IRevuworkSyncService, RevuworkWebhookController, RevuworkWebhookReceiver, RevuworkWebhookEnvelope), passend zum sonst im Projekt verwendeten Namensschema. Die SQL-seitigen amv_*-Report-Spaltennamen (bestehender Vertrag mit den NextGen-Reports) bleiben unverändert.
Build-Warnungen behoben
Mit Zugriff auf den privaten Vemas-NuGet-Feed konnte revuworkVFSPlugin erstmals vollständig gebaut und getestet werden (0 Fehler, 15/15 Tests grün). Dabei behoben:
- Nullability-Warnings (CS8619/CS8601) in den
*AfterLoad-Workflows:ExpandoObjectimplementiert internIDictionary<string, object?>(nullable Value-Typ) – die Report-Zeilen wurden entsprechend alsIDictionary<string, object?>typisiert, statt die Warnung zu unterdrücken. - Bekannte Sicherheitslücken in transitiven Paketen von
Vemas.NextGEN.Core/Vemas.NextGen.PluginCoreper expliziter Versions-Overrides im.csprojgeschlossen:AutoMapper→ 16.1.1,MailKit/MimeKit→ 4.16.0, die kompletteMicrosoft.Kiota.*-Paketfamilie → 1.22.2 (im Lockstep, um Versionsinkompatibilitäten zu vermeiden).
Features
Self-Service-Beitritt zu bestehendem Mandanten
Mitarbeiter können sich jetzt per Einladungslink direkt einem bestehenden, aktiven Mandanten anschließen, statt wie bisher zwangsläufig einen neuen Mandanten anzulegen.
- Admin-Invite: Mandanten-Admins können unter „Benutzer” über „Einladungslink senden” eine E-Mail-gebundene Einladung mit frei wählbaren Rollen erstellen. Die Einladung ist 7 Tage gültig und einmal verwendbar.
- Registrierung: Der Registrierungsdialog hat einen neuen dritten Weg „Mitarbeiter (mit Einladung)” neben „Privatperson” und „Unternehmen”. Über den Einladungslink (
?type=Mitarbeiter&token=...) wird der Firmenname zur Bestätigung angezeigt. - Sicherheit: Der Einladungs-Token wird ausschließlich per E-Mail versendet und nur gehasht gespeichert; die Ziel-E-Mail-Adresse ist fest an das Token gebunden. Ein Token kann nicht erraten oder für einen anderen Mandanten verwendet werden.
- Offene Einladungen können vom Admin eingesehen und widerrufen werden.
Backend
- Neue Tabelle
TenantInvites(Control-Plane, idempotent perEnsureTableAsync) - Neue Endpunkte:
POST /api/v1/admin/invites— Einladung erstellen (Admin/SuperAdmin)GET /api/v1/admin/invites— Offene Einladungen des eigenen Mandanten auflistenDELETE /api/v1/admin/invites/{id}— Einladung widerrufenGET /api/v1/auth/invites/{token}— Firmenname zu einem Einladungs-Token abrufen (Prefill Registrierung)
POST /api/v1/auth/registerunterstützt neuenType: "Mitarbeiter"mitInviteToken-Feld
Fixes
Manueller Produktions-Deploy-Workflow
- Neuer GitHub-Actions-Workflow
deploy-production.yml: baut Frontend und Backend und deployt sie auf die Produktion (revuwork.stratworx.de), ausgelöst manuell über “Run workflow” mit Bestätigungsfeld — bewusst kein automatischer Zeitplan, damit ein fehlerhaftermaster-Stand nicht unbeaufsichtigt live geht. build-deploy-linux.ps1undbuild-deploy.ps1zeigten noch auf den alten Frontend-Ordnersrc\amv-ui, der beim Umbenennen aufsrc\revuwork-uinicht mitgezogen wurde — beide produktiven Deploy-Skripte waren dadurch defekt. Pfade korrigiert.
Features
Testabdeckung für fehlende Controller ergänzt
- Neue Unit-Tests für alle 14 Controller, die bisher keine direkten Controller-Tests hatten:
CustomerController,DashboardController,DashboardLayoutController,UserController,NotificationsController,DokumenteController,GefaehrdungsBeurteilungController,PruefvorschriftKatalogController,PruefvorschriftenController,TourController,SettingsController,ImportController,InspectionController,InventoryController. - Jeder Endpunkt hat mindestens einen Happy-Path-Test (korrekter
ActionResult-Typ + korrekter Service-/Repository-Aufruf). - Für alle Endpunkte mit
?tenantId=-Override oderITenantContext-Zugriff zusätzliche Mandantengrenz-Tests nach dem bestehenden Muster (WebhooksControllerTests,AuditControllerTests): normaler Admin ignoriert fremdetenantId, SuperAdmin-Override funktioniert, fehlender Mandantenkontext liefert 400/403. - Damit haben jetzt alle 21 API-Controller direkte Unit-Tests (insgesamt 171 neue Testfälle, Gesamt-Testsuite: 393 Tests, alle grün).
Features
Testabdeckung für Mandantengrenze im Webhook-Endpunkt ergänzt
- Neue Unit-Tests für
WebhooksControllersichern ab, dass ein normaler Admin über den?tenantId=-Query-Parameter niemals Webhooks eines fremden Mandanten lesen oder löschen kann — sowohl überResolveMandant(GetAll,Upsert,Test) als auch über die separat implementierte Logik inDelete. - Zusätzlich abgesichert: SuperAdmin-Tenant-Override sowie der 400-Fehlerfall für einen Aufrufer ohne Mandantenkontext.
Features
Testabdeckung für Mandantengrenze im Audit-Log-Endpunkt ergänzt
- Neue Unit-Tests für
AuditController.GetAuditLogsichern ab, dass ein normaler Admin über den?tenantId=-Query-Parameter niemals das Audit-Log eines fremden Mandanten abfragen kann — die eigeneTenantIdausITenantContexthat immer Vorrang vor dem Query-Parameter. - Zusätzlich abgesichert: SuperAdmin-Tenant-Override (
?tenantId=), SuperAdmin-Cross-Tenant-Ansicht ohne Override, sowie die Begrenzung vonpageSizeauf 200.
Features
Testabdeckung für AuditService.Log() ergänzt
- Neue Unit-Tests prüfen die Hintergrund-Verarbeitung von
AuditService.Log(): korrektes Mapping der Eingabeparameter aufAuditLog, Auflösung von API-Key-Aufrufern (apikey:{id},apikey:superadmin) zu einem lesbaren Anzeigenamen, sowie dass ein fehlgeschlagener DB-Insert den Datei-basierten Audit-Trail nicht mit sich reißt (zwei unabhängige try/catch-Blöcke).
Features
Testabdeckung für Mandantentrennung erweitert
- Neuer Unit-Test für
AuditRepository.InsertAsyncprüft explizit, dass die Verbindungsauswahl überentry.TenantIderfolgt (statt über den ambientenITenantContext) — Regressionsschutz für den in v0.42.1 behobenen Bug mit verlorenen AuditLog-Einträgen bei Hintergrund-Inserts. - Neue Tests für
TenantDatabaseNamingsichern ab, dass Prod- und Preview-Umgebung durchTenancy:EnvironmentSuffiximmer unterschiedliche physische Datenbanknamen erhalten. - Neue Tests für
TenantProvisioningServiceprüfen den Status-ÜbergangInProgress → Failedbei fehlgeschlagener Provisionierung sowie die Invariante, dass ein Mandant erst nach erfolgreicher Migration auf die neue Datenbank umgeschaltet wird.
Fixes
AuditLog-Einträge gingen bei Hintergrund-Inserts verloren
AuditService.Log()schreibt Einträge asynchron über einen fire-and-forgetTask.Run(), außerhalb des ursprünglichen Request-Kontexts.AuditRepository.InsertAsync()wählte die Ziel-DB bisher über das ambienteITenantContext.TenantId— das ist in diesem Hintergrund-Task nicht verlässlich (der HTTP-Request kann zu diesem Zeitpunkt schon abgeschlossen sein) und lieferte dannnull, wodurch der Insert auf die Control-Plane-DB umgeleitet wurde. Dort existiert keine roheAuditLog-Tabelle (nur in den physischen Tenant-Datenbanken angelegt), der Fehler wurde intern geloggt und der Eintrag ging stillschweigend verloren.InsertAsync()nutzt jetzt die bereits vom Aufrufer übergebeneentry.TenantIdzur expliziten Verbindungsauswahl (analog zuGetPagedAsync()), statt sich auf den Hintergrund-Kontext zu verlassen.- Neuer Regressionstest (
inventory-api.spec.ts) prüft, dass nachPOST /inventorytatsächlich eininventory.created-Eintrag überGET /auditsichtbar wird.
Features
REST-API-Checks nach SQL- und Endpunkt-Änderungen
- Neue Backend-E2E-Tests (
e2e/backend/inventory-api.spec.ts) prüfen die zuletzt geändertenPOST/GET-Endpunkte von/api/v1/inventorydirekt über die REST-API: Vertragsprüfung (Statuscodes, Response-Felder, Validierungsfehler, Duplikat-Konflikt) sowie ein expliziter Mandantentrennungs-Test — ein zweiter Test-Mandant darf einen fremden Inventar-Eintrag weder per ID noch über die Liste sehen. global-setup.tslegt dafür jetzt zwei getrennte Test-Mandanten mit eigener Tenant-DB an.- Neuer CI-Job
sql-migrations-idempotency(e2e-backend.yml) spielt alledatabase/migrations/*.sql-Dateien zweimal hintereinander gegen dieselbe frische Datenbank ein, damit nicht-idempotente Migrationen (fehlendesIF NOT EXISTSo.ä.) schon in der CI auffallen und nicht erst beim Produktions-Deploy.
Fixes
AssignedPersonUserId-Umbenennung lief nie
028_english_rename.sqlverglichcolumn_namegegen'benanntePersonuserid'(gemischte Schreibweise) statt'benanntepersonuserid'. Da Postgres unquoted Spaltennamen beim Anlegen immer klein schreibt, griff die Bedingung nie —BenanntePersonUserIdwurde nie zuAssignedPersonUserIdumbenannt. Dadurch schlugen INSERT/UPDATE auf jeder neu angelegten Mandanten-Datenbank mitcolumn "assignedpersonuserid" does not existfehl (u.a. das manuelle Anlegen von Inventar-Einträgen aus v0.41.2). Der neuesql-migrations-idempotency-Job und die neuen Inventory-API-Tests haben das aufgedeckt.
Fixes
Manuelles Anlegen von Inventar-Geräten
- Neuer Endpunkt
POST /api/v1/inventory(RollenAdmin,Editor) zum manuellen Anlegen einzelner Inventar-Einträge — bisher lieferte der Server hier ein405 Method Not Allowed, da nur der Import-Weg (XLS/JSON) unterstützt wurde. - Prüft Lizenzlimit (
MaxInventoryItems) sowie die Eindeutigkeit der Inventarnummer je Mandant (409 Conflictbei Duplikat). - Der „Neu”-Button im Inventar öffnet weiterhin den Formular-Drawer, jetzt aber mit Pflichtfeld für die Inventarnummer; der Speichern-Button ruft für neue Geräte den neuen Endpunkt auf, statt wirkungslos zu bleiben.
Fixes
Mandanten-Neuanlage schlug fehl (Regression aus v0.40.0)
- Die in v0.40.0 ergänzte Migration
040_manual_intervention_automation.sqlprüfte die Existenz der Control-Plane-TabelleLicenseTemplatefälschlich mitIF EXISTS(...) AND NOT EXISTS (SELECT ... FROM LicenseTemplate ...)in einem einzigen Ausdruck. Postgres muss beim Planen dieses Ausdrucks beide Seiten desANDauflösen — auch wenn die linke Bedingungfalseist — wodurch die Tabellenreferenz auf der frisch provisionierten Mandanten-Datenbank (woLicenseTemplatenicht existiert) mit einem Fehler abbrach. - Dadurch schlug jede Neuanlage eines Mandanten (SuperAdmin-Provisionierung wie Selbstregistrierung) mit
500 Internal Server Errorfehl. - Die Prüfung ist jetzt korrekt verschachtelt (analog zu den beiden anderen
DO-Blocks derselben Migration), sodass die Testlizenz-Seed-Logik auf Mandanten-Datenbanken sauber übersprungen wird, auf der Control-Plane-DB aber weiterhin greift.
Features
Pull-Request-Template
.github/pull_request_template.mdhinzugefügt, um PR-Beschreibungen zu vereinheitlichen.- Enthält eine Checkliste für projektspezifische Pflichtpunkte: Changelog-Eintrag, Versions-Update in den Environment-Dateien, Aktualisierung von
docs/api-v1.yaml, Mandantentrennung sowie sichere Fehler-Responses.
Features
Automatische Mandanten-Freischaltung bei E-Mail-Bestätigung
- Ein selbst registrierter Mandant wird nicht mehr manuell durch einen SuperAdmin freigeschaltet, sondern automatisch aktiviert, sobald der Admin-User seine E-Mail-Adresse über den Bestätigungslink verifiziert.
- Schlägt die Provisionierung dabei ausnahmsweise fehl, bleibt der Mandant weiterhin “Pending” und kann wie bisher manuell über die Mandantenverwaltung freigeschaltet werden (Fallback).
Automatische 7-Tage-Testlizenz
- Bei der automatischen Freischaltung eines neuen Mandanten wird sofort eine 7-Tage-Testlizenz vergeben (PDF-Export, Inspektionen, Import; bis zu 100 Inventar-Items, 5 Benutzer).
- Die Testlizenz ist in der Mandantenverwaltung im Lizenz-Drawer als solche gekennzeichnet.
Automatischer Retry für Bestätigungsmails
- Fehlgeschlagene Sendeversuche der Bestätigungsmail werden nun mit Zeitpunkt und Fehlermeldung je Benutzer gespeichert, statt nur im Server-Log zu verschwinden.
- Ein Hintergrunddienst versucht fehlgeschlagene Sendungen automatisch erneut (alle 10 Minuten, bis zu 5 Versuche).
- In der Benutzerverwaltung sieht der SuperAdmin je Benutzer den Mailstatus (Anzahl Versuche, letzter Fehler) auf einen Blick.
Fixes
Zu großer Abstand zwischen Sidebar-Einträgen
- Die Navigationseinträge in der Sidebar hatten durch zu viel vertikales Padding und Mindesthöhe unnötig viel Abstand zueinander.
- Padding und Mindesthöhe der Einträge verkleinert, dadurch wirkt die Navigation kompakter.
Features
Mandanten-Auswahl aus der Sidebar entfernt
- Die Mandanten-Combobox (GodMode) steht jetzt ausschließlich in der SuperAdmin-Toolbar zur Verfügung (siehe v0.39.7) und wurde aus der Sidebar entfernt, um die doppelte Anzeige zu vermeiden.
Features
Mandanten-Auswahl auch in der SuperAdmin-Toolbar
- Die Combobox zur Mandanten-Auswahl (GodMode) war bisher nur in der Sidebar verfügbar und verschwand beim Einklappen der Sidebar vollständig.
- Die Combobox ist jetzt zusätzlich in der SuperAdmin-Toolbar sichtbar, unabhängig vom Sidebar-Zustand.
Fixes
Bestätigungslink in E-Mails zeigte auf falsche URL
- Der Link zur E-Mail-Bestätigung wurde aus
App:ApiUrl+/auth/confirm-emailgebaut, ohne das/v1-Segment der tatsächlichen Route (/api/v1/auth/confirm-email). Dadurch verwies der Link auf einen nicht existierenden Pfad (z.B.http://localhost:4201/api/auth/confirm-emailstatt.../api/v1/auth/confirm-email). - Betroffen:
AuthController.BuildConfirmUrl(Registrierung) undAdminService.ResendConfirmationEmailAsync(“Bestätigung senden”-Button in der Benutzerverwaltung). App:ApiUrlinappsettings.jsonsowie die Code-Fallbacks korrigiert, jetzt einheitlich mit/v1.
Fixes
SuperAdmin-Toolbar: nur “Global”-Einträge verschoben, Rest zurück in die Sidebar
Korrektur zu 0.38.0: Es sollten nur die drei “Global”-Einträge (Dashboard, Mandanten, Benutzer) in die neue Top-Toolbar wandern. Die Gruppen “Mandanten” (Admin-Dashboard, Kunden, Inventar, Prüfberichte, Import) und “Einstellungen” (Einstellungen, Sicherheit, Import-Journal, Webhooks, Sichtprüfungs-Vorlagen, Audit-Journal) bleiben für SuperAdmins wie zuvor in der Sidebar.
app-superadmin-toolbarzeigt jetzt nur noch die drei “Global”-Einträge als flache Links (kein Dropdown-Menü mehr nötig).- Der Mandanten-Umschalter (Tenant-Select) ist zurück in die Sidebar gewandert (Teil der “Mandanten”-Gruppe, wie vor 0.38.0).
Fixes
Login blieb bei ausbleibender Server-Antwort im Spinner hängen
AuthService.login()hatte keinen Timeout: Antwortete der Server aufPOST /auth/logingar nicht (z.B. Preview-System, SuperAdmin-Login), drehte sich der Spinner im Login-Button unendlich weiter, ohne dass der Benutzer eine Fehlermeldung sah oder den Vorgang abbrechen konnte. Die Anfrage bricht jetzt nach 20 Sekunden mit einer verständlichen Fehlermeldung ab.LoginComponent.onSubmit()setzteloadingbei erfolgreichem Login nie zurück, sondern verließ sich darauf, dass die Navigation zu/dashboarddie Komponente zerstört. Schlug die Weiterleitung fehl oder wurde abgebrochen, blieb der Benutzer mit drehendem Spinner auf dem Login-Screen stehen. Navigation wird jetzt ausgewertet (inkl. Fehlerfall) und der Spinner bei Fehlschlag zurückgesetzt.
Fixes
SMTP-Konfiguration im Server-Setup ergänzt
- Registrierungs- und andere System-E-Mails wurden nicht versendet, weil
Smtp:Hostinappsettings.jsonstandardmäßig auflocalhost:25zeigt und auf frisch aufgesetzten Servern kein lokaler Mail-Relay läuft. Fehlschläge beim Senden wurden bisher nur geloggt, ohne dass die Registrierung selbst fehlschlägt. Install-linux-web-postgres-server.shlegt dieamv.service-systemd-Unit jetzt mitSmtp__Host,Smtp__Port,Smtp__EnableSsl,Smtp__User,Smtp__PasswordundSmtp__Fromals Umgebungsvariablen an (perSMTP_*-Env-Vars beim Setup gesetzt, nie im Repo hardcodiert), analog zum bestehendenConnectionStrings__Default-Muster.build-deploy-preview-linux.ps1: Beispiel-Unit fürrevuwork-preview.serviceum dieselbenSmtp__*-Umgebungsvariablen als Dokumentation ergänzt, da der Preview-Dienst manuell auf dem Server eingerichtet wird.
Fixes
Branding-Texte: “AMV” durch “revuwork” ersetzt
- Registrierungsseite: Untertitel “Beantragen Sie Zugang zum AMV-Portal” → “Beantragen Sie Zugang zum revuwork-Portal”
BrandingService: Default-Firmenname (Fallback, falls kein Mandanten-Branding hinterlegt ist) vonAMVaufrevuworkgeändert- Admin-Einstellungen: gleicher Fallback-Firmenname beim Speichern der Branding-Einstellungen angepasst
- API-Dokumentation (Scalar): Titel “AMV API” → “revuwork API”
Fixes
Firmenregistrierung schlug immer fehl
Auth:AllowPublicRegistrationwar in allenappsettings.json(lokal und Deploy-Artefakte) auffalsegesetzt, wodurchPOST /api/v1/auth/registerjede Registrierung mit403 Forbiddenablehnte, bevor überhaupt Daten geprüft wurden. Flag auftruegesetzt, damit die Registrierung wieder funktioniert.- Falls die Registrierung künftig wieder deaktiviert wird, liefert der Endpunkt jetzt
ProblemDetailsmit verständlicher Fehlermeldung statt eines leeren403-Bodys, damit das Frontend keine generische Fehlermeldung mehr anzeigt.
Features
Node.js 24.18.0
- CI/CD-Workflows (
ci.yml,deploy-preview.yml,e2e-backend.yml,e2e-ui.yml) verwenden jetzt Node.js24.18.0statt20für Build, Tests und Deployment des Angular-Frontends
Features
SuperAdmin-Navigation: Sidebar-Einträge in eine Top-Toolbar umgezogen
Die SuperAdmin-spezifischen Sidebar-Einträge (bisher als eigene “Global”/“Mandanten”/“Einstellungen”-Gruppen in der linken Sidebar) sind für die Rolle SuperAdmin in eine neue horizontale Toolbar oberhalb des Inhaltsbereichs umgezogen. Für alle anderen Rollen (Admin, Editor, Viewer) ändert sich nichts — deren Sidebar-Einträge bleiben unverändert.
- Neue Komponente
app-superadmin-toolbar(shared/components/superadmin-toolbar/): zeigt die bisherigen drei Gruppen als Dropdown-Menüs (“Global”, “Mandanten”, “Einstellungen”) mit den jeweiligen Navigationspunkten. - Der aktive Menüpunkt wird über die aktuelle Route erkannt und farblich hervorgehoben (Akzentfarbe
--color-role-superadmin). - Der Mandanten-Umschalter (Tenant-Select) ist aus der Sidebar in die neue Toolbar gewandert und dort immer sichtbar (rechts, unabhängig von der Gruppenauswahl).
- Die Sidebar selbst (Logo, Hilfe, Theme-Umschalter, Benutzermenü) bleibt für SuperAdmins bestehen, zeigt aber keine eigenen Navigationspunkte mehr.
Features
Backfill: Bestandsmandanten auf eigene Datenbank migrieren
Aufbauend auf 0.32.0: Bisher bekamen nur neu freigegebene Mandanten automatisch eine eigene physische Datenbank (TenantProvisioningService). Bestandsmandanten blieben dauerhaft auf der geteilten amv-Datenbank, ohne einen Weg, das nachträglich zu ändern.
- Neuer Endpoint
POST /superadmin/tenants/{id}/migrate-database: kopiert die Bestandsdaten eines Altmandanten in eine neue, eigene Datenbank, verifiziert die Kopie (Zeilenzahl-Abgleich je Tabelle) und schaltet danach den DB-Pointer um. - Während der Migration wird der Mandant in einen Wartungsmodus versetzt (
Status = "Migrating"): Lesezugriffe bleiben möglich, Schreibzugriffe werden mit503abgelehnt, damit die Kopie konsistent bleibt. - Die alten Zeilen bleiben nach der Migration zunächst redundant in der geteilten Datenbank liegen. Ein separater, bewusst auszulösender Endpoint
POST /superadmin/tenants/{id}/purge-legacy-dataentfernt sie erst nach expliziter Bestätigung — kein automatisches Löschen von Produktionsdaten in einem Aufwasch. TenantProvisioningServiceist jetzt idempotent und retry-sicher:CREATE DATABASEwird übersprungen, falls die Datenbank aus einem vorherigen, fehlgeschlagenen Versuch bereits existiert; Fehler werden je Mandant nachvollziehbar getrackt (ProvisioningStatus/ProvisioningError).- Mandantenverwaltung im SuperAdmin-Bereich: neue Buttons „Auf eigene Datenbank migrieren” und „Altdaten bereinigen” pro Mandant, sichtbar je nach Migrationsstatus (
MandantDtoliefert dafür jetztdatabaseName/dataMigratedAt/legacyDataPurgedAtmit). - Neuer Config-Wert
Tenancy:EnvironmentSuffix(appsettings.json): Prod und Preview laufen auf derselben Postgres-Instanz und würden ohne Unterscheidung identisch benannte Tenant-Datenbanken anlegen wollen. Neue Tenant-Datenbanken heißen jetztrevuwork_{id}(Prod, ohne Suffix — keine Änderung am Server nötig) bzw.revuwork_{id}_preview(Preview, Suffix inappsettings.Preview.jsonauf dem Server, nicht im Repo). ITenantDatabaseNaming.ControlPlaneDatabaseNameliest den Namen der geteilten Datenbank jetzt direkt ausConnectionStrings:ControlPlanestatt ihn hart zu codieren — Prod (amv, künftigrevuwork) und Preview (eigenständige DBrevuwork-preview) haben unterschiedliche, unabhängige Control-Plane-Datenbanken. Neue Mandanten (MandantRepository.CreateAsync) bekommenDatabaseNamejetzt ebenfalls explizit aus dieser Quelle statt aus dem umgebungsunabhängigen Spalten-Default in038_tenant_database_name.sql.- Runbook für die geplante Umbenennung der Prod-Datenbank
amv→revuwork:docs/sql/rename-prod-database-amv-to-revuwork.sql+docs/sql/backfill-tenant-databasename-revuwork.sql(manuell auszuführen, nicht automatisiert — siehe Kommentare in den Dateien für die genaue Reihenfolge inkl. Wartungsfenster).
Fixes
SupportRepository/HistoryRepositoryjoinen nicht mehr direkt gegenAspNetUsers/AspNetRolesüber die Tenant-Connection (funktioniert nicht mehr, sobald eine Tenant-DB physisch getrennt ist) — Benutzername/Rolle werden jetzt über den neuenIUserNameLookupServicegegen die Control-Plane-DB nachgeladen.- Der Datenkopiervorgang beim Backfill überspringt
LicenseTemplate,MandantLicense,TenantSettings,ApiKeysundApiAccessDailySummary— diese Tabellen sind seit 0.36.0/0.36.4 bewusst Control-Plane-only (EnsureTableAsyncbeim App-Start) und existieren in keiner Tenant-Datenbank. - Migration
039_tenant_provisioning_tracking.sqlund der „Auf eigene Datenbank migrieren”-Button prüften bisher hartcodiert gegen den Prod-DB-Namenamv— auf Preview (geteilte DBrevuwork-preview) hätten dadurch bereits migrierte bzw. noch nicht migrierte Mandanten falsch erkannt werden können. Beide prüfen jetzt umgebungsunabhängig (current_database()serverseitig, Tenant-DB-Namensschemarevuwork_{id}clientseitig). TenantProvisioningService.EnsureDatabaseAsync(läuft bei jeder Neuanlage eines Mandanten, nicht nur beim Backfill) schrieb nachTenant.ProvisioningStatus— diese Spalte existierte auf einer frisch gebooteten Control-Plane-DB nicht, da039_tenant_provisioning_tracking.sql(wie alledatabase/migrations/*.sql) nie automatisch dagegen ausgeführt wird, sondern nur gegen neu provisionierte Tenant-DBs bzw. manuell perdeploy-migrations.ps1.MandantRepository.EnsureTableAsync()legt die Tracking-Spalten jetzt beim App-Start zusätzlich selbst an (analog zuDatabaseName), damit die Mandanten-Neuanlage auch auf einer frischen DB (E2E-Tests, lokale Erstinstallation) funktioniert.
Noch offen (bewusst zurückgestellt): Massen-Rollout des Backfills über alle Bestandsmandanten (aktuell einzeln pro Mandant auszulösen), generelle Trennung von Control-Plane- und Tenant-Migrationsskripten (weiterhin laufen alle Migrationen inkl. nie genutzter Kopien von Tenant/AspNetUsers/ApiKeys in jede Tenant-DB mit).
Fixes
Control-Plane vs. Tenant-DB: falsche Connection-Factory in mehreren Repositories (#3)
Mehrere Repositories griffen auf die falsche physische Datenbank zu — ein Bug, der bislang folgenlos blieb, weil bisher nur ein Mandant mit produktiven Daten existiert und dessen Tenant-DB noch identisch mit der Control-Plane-DB ist. Sobald ein Mandant über die automatische Provisionierung eine eigene physische Datenbank erhält, hätte dies zu falsch abgelegten bzw. unauffindbaren Daten geführt.
TenantSettingsRepository: CRUD-Methoden (GetAsync,SetAsync,GetAllAsync) nutzten die Tenant-Verbindung, obwohlTenantSettingsnur in der Control-Plane-DB existiert — jetzt konsistent auf Control-Plane-Verbindung umgestellt.ApiKeyRepository: bleibt bewusst auf der Control-Plane-DB (ApiKeyMiddlewaremuss einen Schlüssel per Hash auflösen können, bevor der Mandant überhaupt bekannt ist — eine Tenant-DB-Trennung würde diese Auflösung unmöglich machen). Tabelle wird nicht mehr überdatabase/migrations/angelegt (würde sonst als leere Schattenkopie in jede neue Tenant-DB repliziert), sondern perEnsureTableAsync()beim App-Start.TourRepositoryundUserPreferenceRepository: bleiben bewusst auf der Control-Plane-DB (reine UI-Präferenzen/Fortschrittsdaten, über die global eindeutige Benutzer-ID bereits korrekt isoliert). Tabellen ebenfalls perEnsureTableAsync()statt über replizierte Migrationsdateien angelegt.CustomerRepository: geprüft — nutzt bereits durchgängig die Tenant-Verbindung, kein Änderungsbedarf.- Cross-Datenbank-Fremdschlüssel auf
Tenant(Id)entfernt (AuditLog,Customer,user_dashboard_layouts,support_tickets,VisualCheckTemplate,NotificationConfig,Webhook): eine FK-Referenz über physisch getrennte Datenbanken hinweg ist in PostgreSQL nicht möglich und hätte bei getrennten Tenant-DBs nur gegen eine leere lokale Schattenkopie vonTenantvalidiert — Schreibzugriffe wären mit einer FK-Verletzung fehlgeschlagen. Die Zugehörigkeit wird weiterhin anwendungsseitig überITenantContextsichergestellt. Die letzten vier Tabellen waren im Issue nicht explizit genannt, zeigten beim Nachstellen einer frischen Tenant-DB-Provisionierung (database/migrations/*.sqlzweimal gegen eine Scratch-Datenbank ausgeführt) aber dasselbe Muster.
Verifiziert durch tatsächliches Ausführen aller 39 database/migrations/*.sql-Dateien gegen zwei Scratch-Datenbanken (Control-Plane- und frisches-Tenant-Szenario, je zweimal für Idempotenz) sowie dotnet test (183/183 grün).
Keine API-Endpunkt-Änderungen.
Fixes
Backend-E2E-Test scheiterte an mehrdeutigem Locator
tenant-dashboard.spec.ts prüfte page.getByText(TEST_TENANT_ADMIN_NAME) — der Name steht aber doppelt auf der Seite (Header-Badge und Sidebar), Playwright verweigert im Strict Mode die Auswertung bei mehreren Treffern. Alle vorherigen Backend-Fixes (Tenant-Tabelle, EmailConfirmed, Migrations-Reihenfolge) haben tatsächlich gegriffen — die App bootstrapped, der Mandant wird angelegt, Login und Dashboard-Rendering funktionieren. .first() ergänzt, um den ersten Treffer zu prüfen.
Fixes
Tenant-Provisionierung schlug auf einer wirklich frischen Datenbank fehl
Beim Anlegen eines neuen Mandanten (TenantProvisioningService.ProvisionAsync) werden alle database/migrations/*.sql-Dateien nacheinander gegen die neue, dedizierte Tenant-Datenbank ausgeführt. Das lief bisher nie gegen eine wirklich leere Datenbank durch (bestehende Umgebungen hatten die betroffenen Tabellen/Spalten schon aus älteren, kombinierten Deployments) — durch den neuen Backend-E2E-Test kam das jetzt erstmals zum Vorschein. Reproduziert und verifiziert mit einer lokalen Postgres-Instanz, alle 38 Migrationen laufen jetzt sauber und idempotent gegen eine frische Datenbank durch.
028_english_rename.sql: Index-Neuanlage aufInventory (TenantId, InventoryNumber)stand vor der UmbenennungMandantId → TenantIdin derselben Datei — auf einer frischen DB existierteTenantIdan der Stelle noch nicht. Statement hinter die Umbenennung verschoben.029_license_external_integration.sql/030_license_addins.sql: mehrereALTER TABLE-Anweisungen aufTenantSettings/LicenseTemplate/MandantLicense(Control-Plane-Tabellen) waren nicht dagegen abgesichert, dass diese Tabellen in einer frisch provisionierten Tenant-Datenbank gar nicht existieren. Alle betroffenen Statements jetzt mitIF EXISTS (... information_schema.tables ...)-Guards versehen, analog zum bereits bestehenden Muster in derselben Datei.
Fixes
Automatisch angelegter Bootstrap-SuperAdmin konnte sich nie einloggen
Beim ersten Start auf einer leeren Datenbank legt Program.cs automatisch einen SuperAdmin (admin@amv.local) an — ohne EmailConfirmed zu setzen. Da AuthController.Login unbestätigte E-Mail-Adressen blockt, konnte sich dieser Account nie einloggen. Betraf bisher nur frische Umgebungen (siehe 0.36.0), da bestehende Installationen ihren SuperAdmin-Zugang längst anders eingerichtet haben.
EmailConfirmed = truebeim Bootstrap-Admin gesetzt, analog zu den bereits behobenen Fällen inMandantService/AdminService
Fixes
Seed-Script des Backend-E2E-Tests rief falsche URLs auf
e2e/backend/global-setup.ts nutzte request.newContext({ baseURL: 'http://localhost:5000/api/v1' }) zusammen mit Pfaden wie /auth/login (führender Slash). Laut URL-Standard ersetzt ein Pfad mit führendem Slash den kompletten Pfad der Basis-URL, statt ihn zu ergänzen — die Requests gingen dadurch an http://localhost:5000/auth/login statt .../api/v1/auth/login und schlugen mit 404 fehl.
API_BASE_URLendet jetzt mit einem trailing slash- Pfade in
global-setup.tsohne führenden Slash (auth/login,superadmin/tenants)
Reiner Test-Infrastruktur-Fix, keine Auswirkung auf Produktionscode.
Fixes
App-Start scheiterte auf einer wirklich frischen Datenbank
Der neue Backend-E2E-Test-Workflow (0.35.0) deckte einen bestehenden Bootstrap-Bug auf: TenantSettingsRepository.EnsureTableAsync() und LicenseRepository.EnsureTablesAsync() legen Tabellen mit Fremdschlüssel auf Tenant(Id) an — aber nirgendwo im Code oder in database/migrations/ wurde die Tenant-Tabelle selbst erzeugt. Auf bestehenden Servern (Dev/Preview/Prod) fiel das nie auf, da die Tabelle dort bereits vorhanden ist; eine wirklich leere, neue Postgres-Instanz konnte die API aber nie hochfahren.
MandantRepositorybekommt ein neuesEnsureTableAsync()(CREATE TABLE IF NOT EXISTS Tenant (...)), analog zu den bereits bestehendenEnsureTableAsync/EnsureTablesAsync-Methoden der anderen Control-Plane-Repositories- Wird in
Program.csvorTenantSettingsRepository/LicenseRepositoryaufgerufen - Da
IF NOT EXISTS: auf bestehenden Umgebungen ein No-Op, behebt aber den Bootstrap dauerhaft für jede neue/frische Umgebung
Fixes
Backend-E2E-Tests brauchen kein Docker mehr
Die in 0.35.0 eingeführten Backend-E2E-Tests setzten eine per docker-compose.e2e.yml gestartete Postgres-Instanz voraus — im Team wird lokal aber ausschließlich mit direkt installiertem Postgres gearbeitet, nicht mit Docker.
docker-compose.e2e.ymlentferntnpm run e2e:db:resetlegt stattdessen perpsqleine eigene Datenbankrevuwork_e2eauf der bestehenden lokalen Postgres-Instanz an (Drop + Create, idempotent)playwright.backend.config.tsliest Host/Port/User/Passwort für diese Verbindung aus den UmgebungsvariablenE2E_DB_HOST/E2E_DB_PORT/E2E_DB_USER/E2E_DB_PASSWORD(Default:localhost:5432,postgres/postgres) — überschreibbar, falls die lokale Instanz andere Zugangsdaten nutzt- CI (
e2e-backend.yml) ist unverändert, da dort bereits ein GitHub-Actions-Service-Container statt Docker Compose verwendet wurde
Features
Echte Backend-E2E-Tests für die Oberfläche
Zusätzlich zu den gemockten Playwright-Tests aus 0.34.0 gibt es jetzt eine zweite E2E-Testreihe, die gegen die echte API und eine frische, wegwerfbare Postgres-Instanz läuft — inklusive echter Mandanten-Provisionierung.
- Neues
docker-compose.e2e.yml: startet eine tmpfs-basierte Postgres-Instanz für E2E-Läufe (kein persistenter State) - Neue
playwright.backend.config.ts+ Testverzeichnise2e/backend/: startet API (dotnet run) und Frontend (ng serve) gegen diese Postgres-Instanz e2e/backend/global-setup.tsseedet vor dem Testlauf einen Test-Mandanten über die echte API (Login als bootstrapper SuperAdmin →POST /superadmin/tenants), statt Testdaten per SQL an der Business-Logik vorbei einzuspielen- Erster Test
tenant-dashboard.spec.ts: loggt sich als frisch angelegter Mandanten-Admin ein und prüft das Dashboard - Neue npm-Skripte
npm run e2e:backend,npm run e2e:db:up/e2e:db:down - Neuer CI-Workflow
.github/workflows/e2e-backend.ymlmit Postgres-Service-Container
Fixes
Mandanten-Admin konnte sich nach direkter Anlage durch SuperAdmin nie einloggen
MandantService.CreateDirectAsync (der Weg, wie ein SuperAdmin über POST /superadmin/tenants einen Mandanten direkt anlegt) hat beim neuen Admin-User EmailConfirmed nicht gesetzt. Da AuthController.Login unbestätigte E-Mail-Adressen blockt, konnte sich der Admin eines so angelegten Mandanten nie einloggen. Jetzt wird EmailConfirmed = true gesetzt, analog zum bereits bestehenden Verhalten bei admin-angelegten Nutzern in AdminService.
Features
E2E-Tests für die Oberfläche (Playwright)
Das Frontend hat jetzt neben den bestehenden Vitest-Komponententests auch echte End-to-End-Tests, die durch den Browser gegen die gerenderte Oberfläche laufen.
- Neues Testverzeichnis
src/revuwork-ui/e2e/mitplaywright.config.ts - Playwright startet den Angular-Dev-Server automatisch und mockt Backend-Aufrufe über
page.route(...), damit Tests ohne laufendes Backend/DB ausführbar sind - Erster Beispieltest
e2e/login.spec.ts: Login-Formular mit falschen Zugangsdaten (Fehlermeldung) und erfolgreicher Anmeldung (Weiterleitung zum Dashboard) - Neue npm-Skripte
npm run e2e/npm run e2e:ui - Neuer CI-Workflow
.github/workflows/e2e-ui.yml, der die E2E-Tests bei Änderungen am Frontend ausführt - Fix:
angular.jsonverwies imserve-Target noch auf den alten Projektnamenamv-uistattrevuwork-ui, wodurchng serve(und damit auchnpm start) nicht mehr startete
Features
SuperAdmin-Cross-Tenant-Aggregation per Fan-out (Phase 3 der DSGVO-Datentrennung)
Aufbauend auf 0.31.0: SuperAdmin-Statistiken und der Mandanten-Export aggregierten bisher per einzelner SQL-Query über alle Mandanten hinweg — das funktioniert nicht mehr, sobald Mandanten (ab Phase 2) eigene physische Datenbanken bekommen.
- Neuer
ITenantFanOutService: führt eine Abfrage parallel gegen die Datenbank jedes aktiven Mandanten aus (Task.WhenAll) und liefert die Ergebnisse als Dictionary, keyed by Mandant-ID. InventoryRepository.GetGlobalStatsAsync()/GetCountPerMandantAsync()(SuperAdmin-Dashboard-Kacheln) auf Fan-out umgestellt.MandantExportRepository(GET /superadmin/export/tenants) auf Fan-out umgestellt — außerGetUserCountsAsync(), da Benutzerkonten weiterhin zentral in der Control-Plane-DB liegen.
Fixes
MandantExportRepository.GetVisualCheckOverrideCountsAsync()filterte aufSource = 'Tenant', der tatsächliche Wert ist aber'Mandant'— der Zähler für mandantenspezifische Prüfschritt-Overrides im Export lieferte dadurch seit jeher immer 0.
Noch offen (bewusst zurückgestellt): Fan-out-Schreiben für globale Prüfschritt-Vorlagen (VisualCheckTemplateRepository.ReplaceGlobalAsync), Aufräumen der Cross-Tenant-Ansichten ohne Mandantenauswahl in AuditRepository/CustomerRepository, sowie die physische Migration des Bestandsmandanten (Wartungsfenster).
Features
Automatische Mandanten-DB-Provisionierung (Phase 2 der DSGVO-Datentrennung)
Aufbauend auf 0.30.0: Neue Mandanten bekommen ab sofort automatisch eine eigene physische Postgres-Datenbank, sobald sie freigegeben werden.
- Neuer
ITenantProvisioningService: legt beiApprove(und beim direkten SuperAdmin-Create) eine Datenbankamv_tenant_{Id}an und spielt die komplette Migrationshistorie ein.Tenant.DatabaseNamewird erst nach erfolgreicher Migration umgestellt — schlägt die Provisionierung fehl, bleibt der Mandant auf der bisherigen DB geroutet undPending, statt auf eine halb migrierte Datenbank zu zeigen. - Provisionierung erfolgt nur bei echter Erstfreigabe (
Status: Pending → Active), nicht bei erneutem Freigeben eines bereits aktiven Mandanten. database/migrations/*.sqlwerden jetzt mit ins Deploy-Artefakt der API übernommen (Amv.Api.csproj), damit die laufende API zur Laufzeit auf die Migrationsskripte zugreifen kann.deploy-migrations.ps1/deploy-migrations-dev.ps1: ermitteln jetzt automatisch alle existierenden Mandanten-Datenbanken (Control-Plane + jede Tenant-DB) und wenden neue Migrationen auf alle an, statt nur auf eine feste DB.
Manueller Vorschritt (einmalig, außerhalb des Repos): pguser benötigt CREATEDB-Recht auf dem Postgres-Server (ALTER ROLE pguser CREATEDB;), da die API selbst neue Datenbanken anlegt.
Features
Fundament für Mandanten-Datentrennung (DSGVO): Control-Plane/Tenant-DB-Architektur
Erster Schritt einer mehrphasigen Umstellung von einer gemeinsamen Datenbank (Zeilenfilterung per TenantId) auf eine eigene Postgres-Datenbank pro Mandant — physische statt logischer Trennung der Mandantendaten. Diese Phase ändert das Laufzeitverhalten nicht: der Bestandsmandant zeigt weiterhin auf dieselbe Datenbank wie zuvor.
- Neue Spalte
Tenant.DatabaseName(Migration038_tenant_database_name.sql), Default'amv'für alle bestehenden und neu angelegten Mandanten. IDbConnectionFactorydurch zwei spezialisierte Factories ersetzt:IControlPlaneDbConnectionFactory— zentrale DB für Identity/Login,ApiKey,Tenant,LicenseTemplate/MandantLicense, globale Produkt-Tours, globale User-Präferenzen, App-Settings.ITenantDbConnectionFactory— löst die DB des aktuellen Mandanten dynamisch auf (ITenantContext.TenantId, gecacht viaITenantDatabaseResolver); OverloadCreateConnection(tenantId)für Pre-Auth-Pfade (z.B.ApiKeyMiddleware) und explizite Cross-Tenant-Zugriffe.- Ohne Tenant im Kontext (z.B. SuperAdmin ohne
?tenantId=-Auswahl) fälltITenantDbConnectionFactoryin dieser Phase auf die Control-Plane-Verbindung zurück — identische physische DB, daher keine Verhaltensänderung.
- Alle ~25 Repositories sowie
AmvIdentityDbContextundApplicationUserClaimsPrincipalFactoryauf die passende Factory umgestellt. appsettings*.json:ConnectionStrings:Defaultersetzt durchControlPlaneundTenantTemplate.
Folgephasen (noch nicht umgesetzt): automatische DB-Provisionierung neuer Mandanten, Migrations-Pipeline über mehrere Tenant-DBs, SuperAdmin-Cross-Tenant-Aggregation per Fan-out, physische Migration des Bestandsmandanten in einem Wartungsfenster.
Fixes
Test-Suite: Backend kompilierte nicht mehr
Amv.Api.Tests ließ sich nicht mehr bauen (100 Compile-Fehler in 14 Dateien). Ursache: Die Tests waren nicht mit früheren Refaktorierungen im Hauptprojekt synchron gehalten worden — u.a. die Umbenennung deutscher Property-Namen ins Englische (Raum→Room, Ergebnis→Result, Projektnummer→ProjectNumber, MandantId→TenantId u.v.m.), die Einführung von IAuditService in AdminService/AuthController, die Umstellung von CreateUserRequest/UserAdminDto.Role (string) auf Roles (List<string>), sowie der Umzug der API-Key-Endpunkte vom AdminController in einen eigenen ApiKeyController.
- Betroffene Test-Dateien an aktuelle Signaturen/Property-Namen angepasst;
GefaehrdungsBeurteilungRepositoryTests.cs,InventoryDokumentEntityTests.csundPruefvorschriftEntityTests.csinHazardAssessmentTests.cs,InventoryDocumentEntityTests.csundInspectionRegulationEntityTests.csumbenannt (Entities heißen inzwischenHazardAssessment,InventoryDocument,InspectionRegulation). - Neue
ApiKeyControllerTests.csfür die beiden API-Key-Tests, die zuvor fälschlich gegen denAdminControllerliefen. - Zwei echte Verhaltensabweichungen aufgedeckt und in den Tests nachgezogen:
AuthController.ConfirmEmailliefert inzwischen einen Redirect auf das Frontend statt eines JSON-Ergebnisses; der Import-Rate-Limiting-Test zielte auf die inzwischen entfernte Route/api/v1/inspection/import/jsonstatt auf/api/v1/import/inspections/json. PdfReportServiceTestsfehlte die QuestPDF-Lizenzinitialisierung (QuestPDF.Settings.License = LicenseType.Community), wodurch der Test je nach Ausführungsreihenfolge fehlschlug.
Frontend: veraltete Unit-Test-Erwartungen
AuthService-Spec erwartete noch/api/auth/...statt der versionierten Route/api/v1/auth/....ChartColorService-Spec erwartete die alte Fallback-Primärfarbe#0891b2statt der aktuellen#0369a1.UserBadgeComponent-Spec nutzte noch das alte FeldmandantNamestatttenantNameund fand dadurch die Mandanten-Pille nicht.
Kein produktiver Code geändert — reine Testkorrekturen. Alle 168 Backend- und 43 Frontend-Unit-Tests sind wieder grün.
Features
Neue Tests für Tenant-Isolation und Lizenz-Endpunkt-Schutz
Coverage-Lücken bei zwei laut CLAUDE.md sicherheitskritischen Regeln geschlossen:
InventoryServiceTests.cs: Der zentrale CRUD-Service für Arbeitsmittel hatte bisher keinen Unit-Test. Neu abgedeckt: Mapping vonGetPagedAsync/GetByIdAsync,UpdateAsync(inkl.KeyNotFoundExceptionund History-Aufzeichnung),GetFilterOptionsAsync, sowie insbesondere die In-Memory-Tenant-Filterung inGetAssignableUsersAsync/GetInspectorUsersAsync(Admin sieht nur eigene Mandanten-User, SuperAdmin sieht alle, Duplikate über Admin+Editor-Rollen werden dedupliziert).LicenseControllerTests.cs: Test verifiziert per Reflection, dassLicenseControllerweiterhin[Authorize(Roles = "Admin")]trägt — schützt vor versehentlichem Entfernen/Aufweichen des Rollen-Schutzes im Code. Hinweis: Der externe Zugriffsschutz auf nginx-Ebene (deploy/nginx-revuwork.confproxied aktuell den gesamten/api/-Pfad ohne Ausschluss für/api/v1/admin/license) ist damit nicht abgedeckt und bleibt eine offene manuelle Aufgabe.
Fixes
Import-Endpunkt: Fehlerbehandlung und Datenqualität
Ausgelöst durch Auswertung eines Prod-Errorlogs des SharePoint-Import-Scrapers (POST /api/v1/import/json):
- Lizenz-Limit-Fehler (403) gaben bislang ein rohes
{"message": "..."}zurück statt des projektweitenProblemDetails-Formats (RFC 9457).LicenseExceptionMiddlewareliefert jetzt konsistenteapplication/problem+json-Antworten mittitle,detailundtraceId. Raumist beim Import kein Pflichtfeld mehr (DB-Spalte, EntityInventoryItem.RoomundInventoryItemImportDto.Raumnullable, analog zuManufacturer/Building/Floor). Bisher führte ein fehlender Raumwert in nur einer Zeile dazu, dass der komplette Datei-Import mit400abgelehnt wurde.- Datumsfelder (
letztePruefung/naechstePruefung): leere oder fehlende Werte erzeugen jetzt eine eindeutige Fehlermeldung (“Datumsfeld ist leer oder fehlt…”) statt der irreführenden Kombination aus"request field is required"und einem kryptischen Parse-Fehler.
Große Importe wurden von nginx blockiert (413)
client_max_body_size war für /api/ nicht gesetzt, sodass nginx’ Default von 1 MB griff. Große Inventarlisten (mehrere tausend Positionen als JSON) erreichten die API nie und scheiterten mit 413. Limit in deploy/nginx-revuwork.conf auf 50 MB angehoben (muss auf dem Prod-Server manuell nachgezogen werden, siehe deploy/nginx-revuwork.conf).
Inventarliste: Paginierung (“Weiter”) funktionierte nicht
Der mat-paginator stand im Template innerhalb des Ladezustand-Blocks (@if (loading()) {...} @else {...}) und wurde bei jedem Seitenwechsel kurz aus dem DOM entfernt und neu erzeugt. Da pageIndex nicht gebunden war, sprang die Paginierung dabei intern immer auf Seite 1 zurück — ein Weiterblättern über Seite 2 hinaus war nicht möglich. pageIndex ist jetzt explizit an das Signal currentPage gebunden. Zusätzlich steht die Seitengröße 100 als Option zur Verfügung.
SuperAdmin-Prüfberichte: Stat-Kacheln zeigten immer 0
InspectionService.getStats() im Frontend hat den tenantId-Query-Parameter nicht mitgeschickt (im Gegensatz zu getFilterOptions() und der Report-Liste). Für SuperAdmins ohne eigenen mandant_id-Claim fiel ITenantContext.TenantId dadurch auf 0 zurück, sodass /inspection/stats immer für einen nicht existierenden Mandanten berechnet wurde (“Gesamt”, “Bestanden”, “Fehler/Mangel”, “Überfällig” = 0). tenantId wird jetzt an getStats() übergeben und die Kacheln aktualisieren sich zudem beim Wechsel des Mandanten in der SuperAdmin-Seitenleiste (ngOnChanges).
Audit-Log: fehlender Benutzername bei API-Key-Aufrufen
Aktionen über einen API-Key (z.B. POST /api/v1/import/json) protokollierten im Audit-Log keinen Benutzernamen — UserEmail blieb leer, da nur wenige Controller (AuthController) diesen Wert überhaupt setzen und die NameIdentifier-Claim bei API-Key-Requests ohnehin nur apikey:{id} enthält, keinen echten Benutzer. AuditService.Log(...) löst UserEmail jetzt zentral auf, wenn sie nicht explizit übergeben wurde:
- Bei API-Key-Aufrufen wird der Benutzer angezeigt, der den API-Key angelegt hat (
ApiKey.CreatedByUserId), z.B.max.mustermann@firma.de (API-Key "SharePoint Import"). - Bei normalen Logins wird die E-Mail/UserName über
UserManageraufgelöst, falls ein Controller nuruserIdübergeben hat. - Neue Repository-Methode
IApiKeyRepository.FindByIdAsync(nur für diese interne Anzeige-Auflösung, kein Tenant-Filter).
Neue Migration
037_inventory_room_nullable.sql— machtInventory.Roomnullable (muss perdeploy-migrations.ps1ausgerollt werden).
Fixes
Import-Endpunkte verschoben
POST /api/v1/inspection/import/mxp→POST /api/v1/import/inspections/uploadPOST /api/v1/inspection/import/json→POST /api/v1/import/inspections/json
Beide Endpunkte sind in den ImportController verschoben worden, um eine konsistente Routenstruktur unter /import/ zu gewährleisten.
Features
Audit-Journal: Filterfunktion
- Suchfeld (E-Mail, Detail, IP-Adresse) mit Debounce
- Filter-Dialog (Aktion, Datum von/bis) analog zur Inventar-Ansicht
- Aktive Filter werden als entfernbare Chips in der Toolbar angezeigt
- Backend: neue Query-Parameter
search,dateFrom,dateToam/audit-Endpunkt
Fixes
Build-Warnungen bereinigt
- CS0108:
InventoryListRequest.Searchentfernt — wurde bereits vonPagedRequestgeerbt - CS9113: Ungenutzten
IAuditService audit-Parameter ausAdminController-Konstruktor entfernt - CS8601: Nullable-Zuweisung in
InventoryService.ToDtofürManufacturer,BuildingundFloormit?? string.Emptyabgesichert - CS8602: Mögliche Nullverweis-Dereferenzierung in
InspectionServicebeipdfServicemit Null-Forgiving-Operator (!) behoben
Features
Anpassbares Dashboard
Jeder Benutzer kann sein Dashboard individuell gestalten: Widgets per Drag & Drop im 12-Spalten-Grid anordnen, hinzufügen oder entfernen. Das Layout wird pro Benutzer in der Datenbank gespeichert.
- 9 verfügbare Widgets: KPI-Karten (Geräte, Mängel, Fälligkeit), Lizenzstatus, Sofortbedarf-Liste, Fälligkeits-Chart, Inventar-nach-Typ-Chart, Dringende Fristen, Zuletzt importiert
- Neuer Bearbeitungsmodus (✏️ Bearbeiten) mit Widget-Bibliothek, Drag-Handle und Entfernen-Button
- Speichern und Abbrechen im Edit-Modus
- Neues API-Endpunkt-Paar
GET /dashboard/layout/PUT /dashboard/layout - Datenbankmigraton
036_user_dashboard_layouts.sql
Fixes
Inventory: Nullable Felder
- Migration
035_inventory_nullable_fields.sql:Manufacturer,Building,Floorerlauben jetzt NULL - Entity und DTO entsprechend auf
string?umgestellt
Features
Kundennummer (CustomerNo)
- Neues Feld
CustomerNoauf derCustomer-Entität und im DTO - Migration
034_customer_no.sql: SpalteCustomerNo VARCHAR(50)mit Index - JSON-Import:
customernowird als dedizierte Kundennummer gespeichert - Lookup-Reihenfolge beim Import: erst per
CustomerNo, dann per Name; bei Treffer per Name wirdCustomerNonachgepflegt
Fixes
JSON-Import: Deutsches Datumsformat unterstützt
POST /api/v1/import/json-dataakzeptiert jetzt Datumsfelder im Formatdd.MM.yyyy(z.B.01.10.2024) zusätzlich zu ISO 8601 (yyyy-MM-dd)- Neuer
DateOnlyJsonConverterregistriert — gilt global für alleDateOnly-Felder in der API
Fixes
Dashboard-500 für Editor (und Admin) behoben
DashboardService.GetStatsAsync: In der “Dringende Fristen”-Query fehlte der Tabellenalias beiTenantId— das JOIN vonInventory iundCustomer cführte zu “column reference is ambiguous” in PostgreSQL → 500- Fix:
WHERE TenantId = @TenantId→WHERE i.TenantId = @TenantId
API-Tokens für alle Mitarbeiter (Editor) verfügbar
- Neuer
ApiKeyControllermit[Authorize]auf Klassen-Ebene — Editors sehen und verwalten ausschließlich ihre eigenen Keys (User-ID-Filter im Service) - Admins sehen alle Keys ihres Mandanten, SuperAdmins mandantenübergreifend
- Endpoints aus
AdminControllerentfernt (Route bleibt identisch:/admin/apikeys) - Nav-Item “Sicherheit” für Editor wieder aktiviert
Import-Journal: ewiger Spinner behoben
ImportLogsComponentlud Daten nur inngOnChanges— beim direkten Aufruf via Router werden keine Inputs gesetzt, daher wurdeload()nie aufgerufenngOnInitergänzt, ruftload()beim erstmaligen Rendern auf
Einstellungen-Menüeintrag für Editor entfernt
- Nav-Item “Einstellungen” war für Editor sichtbar, obwohl der Route-Guard (
/admin/settings) bereits nur Admin/SuperAdmin erlaubt - Eintrag auf
roles: ['Admin']beschränkt
Prüfberichte für Editor freigegeben
InspectionControllerhatte Controller-weites[Authorize(Roles = "Admin,ApiClient,SuperAdmin")]— Editor wurde blockiert- Fix: Controller auf
[Authorize]geändert; Import-Endpoints (import/mxp,import/json) undlogsbehalten eigene Admin-Restriction - Betroffene Endpoints für Editor:
GET /inspection/reports/paged,GET /inspection/reports/filter-options,GET /inspection/stats,GET /inspection/reports,GET /inspection/reports/{id}
Rate-Limit-Partition auf User-ID umgestellt
- Rate-Limiter partitioniert jetzt nach User-ID (statt IP-Adresse) für authentifizierte Requests
- Verhindert, dass Nutzer hinter demselben Proxy (nginx/NAT) das gemeinsame IP-Limit erschöpfen
- Limit auf 60 Requests/Minute erhöht (Dashboard allein: ~4–5 Calls)
- Unauthentifizierte Requests bleiben weiterhin IP-basiert limitiert
Features
Editor-Rolle: Einstellungen, Sicherheit & Import-Journal
- Editor-Benutzer sehen jetzt die Navigationseinträge Einstellungen, Sicherheit und Import-Journal
- Einstellungen: Editor hat vollen Lesezugriff und kann Mandanteneinstellungen bearbeiten
- Import-Journal (
/import/logs): Editor sieht die Import-Logs des eigenen Mandanten (lesend) - Sicherheit (
/admin/security): Editor sieht und verwaltet ausschließlich eigene API-Keys; Admin sieht alle Keys des Mandanten - Ownership-Prüfung beim Deaktivieren: Editor kann nur eigene API-Keys deaktivieren (403 bei fremden Keys)
Fixes
Farbdesign-Einstellungen werden jetzt korrekt gespeichert und geladen
- Alle Farb-Tokens (Statusfarben, Rollenfarben) werden nun in der Datenbank persistiert, nicht nur im localStorage
- Nach einem Seiten-Reload oder auf anderen Geräten/Browsern werden die gespeicherten Farben korrekt wiederhergestellt
- Beim Laden der Settings-Seite werden die Farbpicker jetzt direkt mit den serverseitig gespeicherten Werten synchronisiert
Features
Prüfimport: Projekt löschen
- Im Aktionsmenü der Prüfberichtsliste kann ein importiertes Projekt (alle Berichte einer Projektnummer) gelöscht werden
- Die Option erscheint nur, wenn der Filter “Projektnummer” aktiv ist
- Bestätigungsdialog vor dem unwiderruflichen Löschen
- Nach dem Löschen wird der Filter zurückgesetzt und die Liste neu geladen
Features
UI-Modernisierung — Foundation (Plan 1)
- Design Tokens: Primärfarbe auf Sky Blue (
#0369a1) aktualisiert, neue Tokens--color-primary-hover,--color-primary-subtle, Spacing-Skala (--space-xsbis--space-xl), dunkle Sidebar-Tokens (--color-sidebar-bg: #0f172a) - Dark Mode: Vollständige Token-Abdeckung —
--color-primary-hover,--color-bg-card,--color-sidebar-textkorrigiert - Collapsible Sidebar: Sidebar klappt auf Klick zwischen 52px (Icons-only) und 220px (Icons + Labels) zusammen; Zustand wird in
localStoragepersistiert; Tooltips auf Nav-Items im eingeklappten Zustand; dunkles Theme (#0f172a) - Custom Toast Stack:
MatSnackBardurch eigene Signal-basierte Komponente ersetzt — volle Breite am unteren Rand, stapelbar, farbiger linker Balken nach Typ (ok/warn/error/info), Fortschrittsbalken bei Auto-close, Dark-Mode-Unterstützung via CSS Custom Properties - HelpTooltipComponent: Neue Shared-Komponente
<app-help-tooltip text="...">für Inline-Felderklärungen mit?-Button und Tooltip - WizardStepperComponent: Neue Shared-Komponente
<app-wizard-stepper>für horizontale Schritt-Anzeige mit Verbindungslinien und Done/Active-States - EditDrawer Readonly-Modus:
EditDrawerComponenterhältmode,editRouteundeditClicked-Inputs; im Readonly-Modus zeigt der Footer einen „Bearbeiten”-Button statt Speichern/Abbrechen - Tour-Styling: Tour-Panel modernisiert — kein farbiger Border mehr, neutrales Card-Design, CSS Custom Properties
- AppCard-Styling: Status-Indikatoren als
border-left: 3px solidstattborder-color, Border-Radius und Schatten auf Design-Tokens ausgerichtet
Features
Zentrale Mandantenauswahl im SuperAdmin-Bereich
- Mandanten-Selector wurde aus allen SuperAdmin-Seiten entfernt und zentral in die Sidebar integriert
- Die Auswahl bleibt beim Navigieren zwischen SuperAdmin-Seiten erhalten
- Die Auswahl wird im Browser-LocalStorage gespeichert und überlebt einen Seiten-Reload
- Beim Abmelden wird die gespeicherte Auswahl automatisch geleert
- Audit-Journal reagiert automatisch auf Änderungen des globalen Mandanten
Features
Lizenz – Add-ins
- Neue Spalte
Addins(text[]) aufLicenseTemplateundMandantLicense - Lizenz kann nun eine Liste von Add-in-Bezeichnern (z. B. Plugin-IDs) enthalten
- API:
LicenseFeaturesDtoenthält neues Feldaddins: string[] - Migration 030: Spalte
Addinsin beiden Lizenz-Tabellen (idempotent)
Breaking Changes
Umbenennung auf Englisch (API-Felder)
Alle Datenbank-Tabellen und -Spalten, C#-Entitäten, DTOs sowie Angular-Modelle und -Komponenten wurden von deutschen auf englische Bezeichnungen umgestellt. Bestehende Integrationen müssen ihre Feldnamen anpassen.
- Tabellen umbenannt:
mandant→Tenant,pruefvorschriften→InspectionRegulations,inventory_pruefvorschrift→InventoryInspectionRegulation,inventory_dokumente→InventoryDocument,GefaehrdungsBeurteilung→HazardAssessment - Inventory-Spalten umbenannt (z. B.
Arbeitsmitteltyp→DeviceType,Gebaeude→Building,Ergebnis→Result, u. v. m.) - JSON-Antworten liefern nun englische Feldnamen
- Migration 028 ist idempotent (mehrfach ausführbar)
Features
Drittanbindung (Lizenz-Feature)
- Neues Lizenz-Feature
ExternalIntegration(LicenseFeature-Enum) - Entitäten
MandantLicenseundLicenseTemplate: neues FeldFeatureExternalIntegration - DTOs (
LicenseFeaturesDto,LicenseTemplateDto,CreateLicenseRequest,CreateTemplateRequest): neues Feld ergänzt LicenseService: Feature-Prüfung, Mapping und Fehlermeldung für Drittanbindung implementiert- Migration 029: Spalte
FeatureExternalIntegrationinLicenseTemplateundMandantLicense
Frontend
- Angular-Modelle, Services und Komponenten auf englische Feldnamen aktualisiert
- Import-Center: Serververzeichnis-Scan-Karte entfernt
- Admin-Sidebar neu strukturiert (Gruppen-Überschriften angepasst)
Migrationen
- Migration 026: Unique-Constraint-Erstellung idempotent gemacht
- Migration 028 (neu): Alle Umbenennungen via DO-Blöcke (idempotent)
- Migration 029 (neu):
FeatureExternalIntegration-Spalte in Lizenz-Tabellen
Features
Inventar-Detailansicht – Prüfvorschriften
- Neuer Tab “Prüfvorschriften” in der Inventar-Detailansicht
- Zuweisung von Prüfvorschriften aus zentralem Katalog per Autocomplete
- Sortierung per Hoch/Runter-Buttons
- Aktivieren/Deaktivieren per Checkbox
- Löschen mit Bestätigung
Inventar-Detailansicht – Dokumente
- Neuer Tab “Dokumente” in der Inventar-Detailansicht
- Datei-Upload auf Server
- Metadaten je Dokument: Beschreibung und Kategorie
- Listenansicht aller hochgeladenen Dokumente
- Löschen mit Bestätigung
Features
Webhooks & Sicherheit (Superadmin)
- Webhooks- und Sicherheits-Seite zeigen jetzt eine Mandantenauswahl wenn der Benutzer als SuperAdmin angemeldet ist
- Inhalte werden erst nach Auswahl eines Mandanten geladen
- Alle Aktionen (Speichern, Löschen, Testen, API-Key erstellen/deaktivieren) arbeiten mit dem gewählten Mandanten
- Superadmin-Navigation: Eintrag “Sicherheit” unter Einstellungen hinzugefügt
- Backend:
WebhooksControllerundAdminController(apikeys) akzeptieren optionalenmandantIdQuery-Parameter für SuperAdmin-Zugriff
Features
AMV API Integration (revuworkVFSPlugin)
AmvApiClient— typisierter HTTP-Client gegen AMV REST API mit Polly-8-Retry (3x exponentieller Backoff) und HMAC-SignaturprüfungIAmvApiClient— Interface für TestbarkeitAmvSyncService— Link/Unlink von NextGen-Datensätzen mit AMV-Inventareinträgen viaExternalReferenceAmvWebhookReceiver— Empfänger für AMV-Webhooks (inspection.report.finalized,inventory.updated) mit HMAC-SHA256-Signaturprüfung- Konfiguration via Umgebungsvariablen:
AMV_API_BASEURL,AMV_API_KEY,AMV_API_WEBHOOK_SECRET
Changes
AMV API
UpdateInventoryRequestumExternalReference(optional, max. 200 Zeichen) ergänztInventoryRepository.UpdateAsyncpersistiertExternalReferencein der Datenbank
Features
Prüfberichte-Report
Der Prüfberichte-Report wurde vollständig überarbeitet und bringt die gleichen Features wie der Inventarreport:
- Server-seitige Filterung & Paginierung: Die Liste lädt Daten paginiert vom Server statt alle Berichte auf einmal
- Chip-Filter: Aktive Filter werden als Chips in der Toolbar angezeigt und können einzeln entfernt werden. Neue Filter über den „Filter”-Button im Popover hinzufügbar
- Filtermöglichkeiten: Freitextsuche, Projektnummer, Ergebnis, Prüfer, Standort, Hersteller, Prüfdatum (von/bis), Nächste Prüfung (von/bis), Finalisiert
- Spalten-Konfigurator: Über den Button „Spalten” können sichtbare Spalten und deren Reihenfolge angepasst werden. Einstellungen werden pro Benutzer gespeichert
- Standard-Spalten: Gerät / Typ, Standort, Prüfdatum, Nächste Prüfung, Ergebnis, Finalisiert
- Optionale Spalten: Prüfer, Projektnummer, Inventarnummer, Berichtsnummer, Norm / Standard, Externe Referenz, Hersteller, Gerätekategorie, Importiert am
- Checkbox-Selektion: Einzelne oder alle Berichte auswählen für selektiven Export
- Excel-Export: Export aller gefilterten Berichte oder nur der ausgewählten Berichte
Backend
- Neue DB-Migration 023:
Context-Spalte inUserColumnPreferencezur Trennung von Inventar- und Prüfberichte-Spalteneinstellungen - Neue API-Endpunkte:
GET /api/v1/inspection/reports/paged— Paginierte, gefilterte Prüfberichte-ListeGET /api/v1/inspection/reports/filter-options— Dropdown-Werte für alle FilterGET /api/v1/inspection/reports/export/excel— Excel-Export mit Filterunterstützung
GET/PUT /api/v1/user/column-preferencesumcontext-Parameter erweitert (inventory/inspection)
Features
Inventarliste - Dynamische Spaltenauswahl
- Inventarliste (Tabellenansicht): Benutzer können über den Button „Spalten” in der Toolbar selbst festlegen, welche Spalten sichtbar sind und in welcher Reihenfolge sie erscheinen. Die Konfiguration wird pro Benutzer serverseitig gespeichert.
- Neue optionale Spalten verfügbar: Hersteller, Seriennummer, Letzte Prüfung, Schutzklasse, Leitungslänge, Projektnummer, Externe Referenz
Plugin: Neue Reports für Prüfberichte und Wiederholungsprüfungen
- Neuer Report
ReportrevuworkPruefberichte: zeigt alle Prüfberichte aus der AMV API, angereichert viaRevuworkPruefberichteAfterLoad-Workflow - Neuer Report
ReportrevuworkWiederholungspruefungen: zeigt Prüfberichte mit gesetztem Wiederholungstermin (NextInspectionDate), angereichert viaRevuworkWiederholungspruefungenAfterLoad-Workflow - SQL-Skripte zur Registrierung der Datasources und Workflows (
04–07) - AMV API-Konfiguration (
BaseUrl,ApiKey) kommt jetzt aus dem GlobalSettings-EintragrevuworkVFSconfigstatt aus Umgebungsvariablen; SQL-Skript08_register_globalsettings.sqllegt den Eintrag an - Neues Formular
GLOBALSETTINGS.REVUWORKVFSCONFIG(09_form_globalsettings_revuworkvfsconfig.sql) zur Pflege von BaseUrl und ApiKey direkt in der NextGen-Administration
Backend
- Neue DB-Tabelle
UserColumnPreference(Migration 022) für Speicherung der Spalteneinstellungen pro Benutzer - Neuer Controller-Endpunkt
PUT/GET /api/v1/users/column-preferencesfür Speicherung und Abruf der Spalteneinstellungen UserPreferenceServiceverwaltet die Spalten-Konfiguration pro Benutzer
Features
Webhook-Konfiguration
- Neue Seite
/admin/webhooks: Konfigurierbare ausgehende Webhooks für alle Systemereignisse - Unterstützte Event-Typen: Inventar angelegt/bearbeitet/gelöscht, Prüfeintrag angelegt/bearbeitet/gelöscht, Prüf-PDF erstellt, Wiederholungsprüfung fällig
- Beliebig viele Custom HTTP-Header pro Webhook (Key-Value-Paare, Values verschlüsselt gespeichert)
- Test-Button zum Prüfen der Erreichbarkeit — zeigt HTTP-Statuscode als Rückmeldung
- Navigation im Admin-Bereich um „Webhooks” ergänzt
Backend
- Neue DB-Tabellen
WebhookundWebhookHeader(Migration 021) - Bestehender Notification-Webhook aus
NotificationConfigin neue Tabelle migriert - Neuer Controller
WebhooksController(GET/PUT/DELETE /api/v1/admin/webhooks/{eventType},POST .../test) WebhookServicemit ASP.NET Data Protection für verschlüsselte Header-ValuesNotificationServiceliestnotification.due-Webhook aus neuerWebhook-Tabelle (unterstützt jetzt beliebig viele Header)
Fehlerbehebungen
Inventar
- Inventory-API: SQL-Fehler
column reference "mandantid" is ambiguousnach Einführung des Customer-Joins behoben — alle WHERE-Bedingungen und ORDER BY mit Tabellen-Aliasi.prefixiert - Inventory-API:
COUNT-Query fehlte JOIN-Aliasi→missing FROM-clause entry for table "i"behoben
Features
ExternalReference
- Neue Spalte
ExternalReference(VARCHAR 200, nullable) auf den TabellenCustomer,InventoryundInspectionReport - Backend: Entitäten, DTOs, Repository-INSERTs und CustomerService aktualisiert
- Frontend: Modelle
customer.model.ts,inventory.model.tsundinspection.model.tsaktualisiert
Features
Kunden
- Neue Tabelle
Customer(mandantenabhängig) mit Name, Straße, PLZ, Ort - XLS-Import erkennt Kundenanschrift aus Zeile 3 und legt Kunden automatisch an (find-or-create per MandantId + Name)
Inventory.CustomerIdFK — Geräte sind einem Kunden zugeordnet- CRUD-Verwaltung unter
/customers(Admin) und/superadmin/customers - Bulk-Zuweisung: Altdaten über Multi-Select in der Inventarliste einem Kunden zuweisen
- Löschen mit Cascade (nur wenn keine abgeschlossenen Prüfergebnisse)
Import
- XLS-Import liest jetzt Kundenanschrift aus Zeile 3 (z. B. “Debnar Messtechnik GmbH, …”) und speichert sie im ImportLog
ImportResultDtoenthält neues FeldKundenanschrift- DB-Migration 017: Spalte
Kundenanschriftin TabelleImportLog
Features
Superadmin Import
- Neue Seite
/superadmin/import: Mandanten-Dropdown + vollständige Import-UI (Datei-Upload & Server-Ordner-Scan) für beliebigen Mandanten - Neue Seite
/superadmin/import/logs: Globale Import-Log-Übersicht über alle Mandanten mit Filter (Mandant, Datumsbereich) - Navigation im Superadmin-Bereich um “Import” und “Import-Logs” ergänzt
Backend
ImportController: Alle Endpoints unterstützen nunAuthorize(Roles = "Admin,SuperAdmin")mit optionalem?mandantIdQuery-ParameterImportService: MethodenImportFromStreamAsync,ImportFolderAsync,ScanFoldersAsync,GetConfigAsyncundGetLogsAsyncerhalten optionalenmandantId-Override für SuperAdmin-Kontext- Neues
GetSuperAdminLogsAsyncmit Mandant-JOIN für globale Log-Ansicht ImportRepository:GetLogsForSuperAdminAsync(LEFT JOIN auf Mandant-Tabelle, optionale Filter),GetLogsByMandantIdAsync(mandantenspezifischer Scan)MandantRepository: neuesGetSlugByIdAsyncfür Pfadberechnung im SuperAdmin-Kontext- Neues DTO
ImportLogWithMandantDtomitMandantName-Feld
Frontend
ImportCenterComponent: neuer optionaler@Input() tenantId— alle API-Calls ergänzen?mandantIdwenn gesetztImportLogsComponent: neue InputstenantId,showMandantColumn,showFilters,backLink— reaktiv viangOnChanges
Hilfe & Support-Dashboard
Support-Tickets für alle Benutzer
- Neuer Sidebar-Eintrag „Hilfe & Support” — sichtbar für alle angemeldeten Rollen, Route
/help, visuell durch einen Trennstrich vom Hauptmenü abgesetzt - Zwei Ticket-Typen wählbar per Toggle oben im Formular:
- Fehlermeldung (Bug): Felder Fehlerstelle, Fehlermeldung, Erwartungen
- Änderungsanforderung (CR): Felder Featurename, Beschreibung
- Datum wird automatisch serverseitig gesetzt — kein Datumsfeld im Formular
- Dateianhänge: Drag & Drop, bis zu 3 Dateien pro Nachricht, erlaubte Formate PNG / JPG / GIF / PDF, max. 10 MB je Datei — Validierung client- und serverseitig
Ticket-Liste (rollenbasiert)
Viewer/Editorsehen nur eigene TicketsAdminsieht alle Tickets des eigenen Mandanten- Filterung nach Status, Typ und Freitext-Suche; Paginierung via
PagedResult<T>
Chatverlauf & Status-Workflow
- Ticket-Detailansicht zeigt alle Nachrichten im Chat-Stil (eigene Nachrichten rechts, andere links)
- Anhänge erscheinen als Download-Links unter jeder Nachricht
- Status-Übergänge:
Offen→InBearbeitung→Geloest→Geschlossen; zusätzlichAbgelehntundZurueckgestellt - Ersteller darf ein Ticket nur wieder öffnen (Status
Offensetzen); Admin / SuperAdmin dürfen alle Status setzen - Antworten sind gesperrt, wenn das Ticket
GeschlossenoderAbgelehntist
E-Mail-Benachrichtigungen
- Erstellungs-E-Mail: Bestätigung an den Ersteller + Kopie an
support@stratworx-solutions.demit allen Ticket-Feldern als HTML-Tabelle - Antwort-E-Mail: Benachrichtigung an alle Beteiligten (Ersteller + bisherige Antwortgeber + alle SuperAdmins) außer dem Autor der neuen Nachricht
Neue API-Endpunkte (api/v1/support)
| Methode | Pfad | Beschreibung |
|---|---|---|
GET | /tickets | Liste (rollenbasiert gefiltert, paginiert) |
GET | /tickets/{id} | Detail mit Chatverlauf |
POST | /tickets | Ticket + erste Nachricht anlegen |
PATCH | /tickets/{id}/status | Status ändern |
POST | /tickets/{id}/messages | Antwort schreiben |
POST | /tickets/{id}/messages/{messageId}/attachments | Datei hochladen |
GET | /tickets/{id}/messages/{messageId}/attachments/{fileName} | Datei herunterladen |
Datenbank
- Neue Migration
009_support.sqlmit Tabellensupport_ticketsundsupport_ticket_messages - Anhang-Metadaten werden als JSONB in
support_ticket_messages.attachmentsgespeichert; Dateien liegen im konfigurierten Upload-Verzeichnis
SuperAdmin Mandanten-Export
Export-Button im SuperAdmin-Dashboard
- Neuer Button „Export JSON” im SuperAdmin-Dashboard-Header
- Ein Klick lädt eine JSON-Datei
mandanten-export-YYYY-MM-DD.jsonherunter - Enthält alle Mandanten mit Lizenzstatus, Datenmengen und API-Zugriffszahlen
- Während des Downloads: Spinner im Button, Button deaktiviert
Lizenzdaten pro Mandant
- Für jeden Mandanten: welche Features lizenziert sind (
mxpExport,pdfExport,inspections,import) - Limits (
maxInventoryItems,maxUsers), Gültigkeitszeitraum, Grace-Days - Berechneter Lizenzstatus:
Active,Grace(innerhalb Grace-Period) oderExpired - Mandanten ohne Lizenzeintrag:
license: null
Datenmengen pro Mandant
- Anzahl Inventar-Artikel, Prüfberichte, Benutzer
- Benachrichtigungskonfiguration: ob konfiguriert, Anzahl Schwellwerte, Anzahl Empfänger-E-Mails, ob Webhook hinterlegt
- Anzahl mandantenspezifischer Sichtprüfungs-Vorlagen (Overrides)
Tägliche API-Zugriffszahlen (Migration 015)
- Neue Tabelle
ApiAccessDailySummary— speichert pro Mandant und Tag die Summe der API-Anfragen ApiKeyMiddlewareinkrementiert bei jedem authentifizierten API-Key-Request den Tageszähler des Mandanten via UPSERT (race-safe)- Export enthält für jeden Mandanten:
totalRequests(Gesamtsumme) +dailySummaries(Datum + Anzahl pro Tag) - Neue API-Endpunkte:
GET /api/v1/superadmin/export/mandanten
Inventarnummer, Qualifikationsprüfung, Templates & Benachrichtigungen
Inventarnummer als Geschäftsschlüssel (Migration 012)
- Neues Feld
InventarnummeraufInventory— eindeutiger Geschäftsschlüssel pro Mandant (VARCHAR(100) NOT NULL UNIQUE per MandantId) - Bestehende Einträge werden mit
CAST(Id AS VARCHAR)migriert - XLS-Import liest Inventar-Nr. jetzt als Zeichenkette statt numerische ID
- Auto-Erstellung: Beim MXP-JSON-Import wird ein neues
InventoryItemautomatisch angelegt, wenn die Inventarnummer im System nicht bekannt ist — kein manuelles Vorerfassen nötig - Import-Center zeigt nach dem Import einen blauen Hinweis mit der Anzahl automatisch angelegter Geräte
- Inventarliste zeigt neue Spalte „Inventar-Nr.”
Harte Qualifikationsvalidierung
- Regel: SK I und SK III erfordern zwingend die Qualifikation „Elektrofachkraft” beim Prüfer; SK II erlaubt auch „Unterwiesene Person”
FinalizeAsyncprüft vor der PDF-Erstellung: (1) Bericht muss einem Inventargerät zugeordnet sein, (2) Qualifikation muss zur Schutzklasse passen- Controller gibt bei Verstoß HTTP 422 Unprocessable Entity mit dem Fehlertext aus
ProblemDetails.detailzurück - Prüfformular zeigt einen roten Warnhinweis und deaktiviert den Finalisieren-Button, solange die Kombination ungültig ist
- Detailansicht zeigt den 422-Fehlertext direkt im Snackbar
PDF neu generieren
- Neuer API-Endpunkt
POST /api/v1/inspection/reports/{id}/regenerate-pdf(Admin und SuperAdmin) - Generiert die PDF eines bereits finalisierten Berichts neu und überschreibt die bestehende Datei — nützlich nach Stammdatenänderungen
- Gibt HTTP 409 zurück, wenn der Bericht noch nicht finalisiert ist
- Detailansicht zeigt für Admins einen neuen Button „PDF neu generieren” (nur bei finalisierten Berichten)
Konfigurierbare Sichtprüfungs-Vorlagen (Migration 013)
- Neue Tabelle
VisualCheckTemplatemit 5 globalen DGUV-V3-Standardschritten (Gehäuse, Kabel, Stecker, Beschriftung, Sicherheitseinrichtungen) - Merge-Logik: Mandant-Admin kann einzelne Schritte per
Orderüberschreiben; nicht überschriebene Global-Schritte bleiben erhalten; neue Mandant-Orders werden angehängt - Manuelles Prüfformular lädt die Vorlage beim Start per API statt aus hartkodierter Konstante — eigene Mandanten-Schritte greifen automatisch
- Admin-Bereich (neue Seite „Sichtprüfungs-Vorlage”): Globale Schritte können per Klick überschrieben werden, eigene Schritte hinzugefügt, Zurücksetzen auf Global möglich
- SuperAdmin-Bereich: Globale Standardvorlage direkt bearbeitbar
- Neue API-Endpunkte:
GET /PUT /DELETE /api/v1/admin/visual-check-templatesGET /PUT /api/v1/superadmin/visual-check-templates
E-Mail- und Webhook-Benachrichtigungen (Migration 014)
- Neuer Scheduler-Endpunkt
POST /api/v1/notifications/run(API-Key-geschützt) — externer Cron-Job triggert die Benachrichtigungsläufe - Fälligkeits-Benachrichtigung: System prüft pro Mandant alle konfigurierten Schwellwerte (z. B. 30 Tage vorher, am Fälligkeitstag, 1 Tag überfällig) und sendet E-Mails
- Empfänger: Admin-Benutzer des Mandanten (automatisch) + optional hinterlegte Zusatz-E-Mails
- Duplikat-Guard: Für jede Kombination aus Gerät + Schwellwert wird maximal eine Benachrichtigung pro Tag versendet
- Ausgehender Webhook: Optional konfigurierbare URL mit verschlüsseltem Authorization-Header (AES via
IDataProtectionProvider) - Admin-Einstellungen (neue Card „Benachrichtigungen” in den Admin-Einstellungen):
- Warnstufen als Chips hinzufügen/entfernen (positive = Tage vorher, 0 = Fälligkeitstag, negativ = überfällig)
- Zusätzliche Empfänger-E-Mails als Chips
- Webhook-URL und Authorization-Header (Passwort-Feld, leer lassen = bestehenden Wert behalten)
- Neue API-Endpunkte:
GET /PUT /api/v1/admin/notification-config
DGUV V3 Compliance — Normkonforme Elektroprüfungsdokumentation
Datenmodell
- Inventar: Neue Felder
Schutzklasse(SK I/II/III),Leitungslaenge(Meter),Seriennummer— per XLS-Import befüllbar - Prüfbericht: Neue Felder
InspectorQualification,PruefartNorm(VDE0701/VDE0702),PruefplaketteFaellig,IsFinalized,FinalizedAt,PdfPath - Messwerte: Neue Felder
MeasurementType,CalculatedLimitValue,MeasuredValue - Migration
009_dguv_v3.sql: Alle neuen Spalten; importierte Prüfberichte werden rückwirkend als finalisiert markiert
Grenzwert-Service
- Automatische Berechnung der VDE 0702 Grenzwerte nach Schutzklasse und Leitungslänge
- SK I: Schutzleiterwiderstand ≤ 0,3 Ω (≤ 5 m) / +0,1 Ω je 7,5 m bis max. 1,0 Ω; bei Heizelement Isolationswiderstand ≥ 0,3 MΩ statt ≥ 1,0 MΩ
- SK II: Isolationswiderstand ≥ 2,0 MΩ, kein Schutzleiterwiderstand
- SK III: Isolationswiderstand ≥ 0,25 MΩ, kein Schutzleiterwiderstand
- Ableitstrom ≤ 3,5 mA für alle Schutzklassen
- Neuer API-Endpunkt
GET /api/v1/inspection/grenzwerte?schutzklasse=SK_I&leitungslaenge=10
Manuelles Prüfformular
- Neuer 4-Schritt-Wizard (
/inspections/new) mit Angular Material Stepper:- Gerät & Prüfdaten — Schutzklasse, Leitungslänge, Prüfdatum, Prüfer, Qualifikation, Prüfnorm
- Sichtprüfung — 5 vordefinierte Prüfpunkte mit Bestanden/Nicht bestanden
- Messwerte — Schutzleiterwiderstand, Isolationswiderstand, Ableitstrom mit automatischem Grenzwertvergleich + Funktionsprüfung
- Abschluss — Gesamtergebnis, Entwurf speichern oder Bericht abschließen
- Hinweis bei inkompatiblen Prüfer-Qualifikationen (VDE 0701 erfordert Elektrofachkraft)
- Dezimalkomma-Direktive: Deutsche Komma-Eingabe wird automatisch zu Dezimalpunkt normalisiert
- Neue API-Endpunkte:
POST /api/v1/inspection/reports— Entwurf anlegenPUT /api/v1/inspection/reports/:id— Entwurf bearbeitenPOST /api/v1/inspection/reports/:id/finalize— Bericht abschließen (nur nicht-finalisierte Berichte)
PDF-Export
- Server-seitige PDF-Erzeugung — normkonformes A4-Prüfprotokoll nach DIN EN 50699
- PDF wird vor dem Datenbank-Update generiert (atomisch: bei PDF-Fehler bleibt Bericht als Entwurf)
- Dateispeicherung auf dem Server unter
/mnt/amv-pdfs/{mandantId}/{jahr}/{berichtId}.pdf PdfPathwird nie an das Frontend übermittelt — Frontend erhält nurhasPdf: boolean- Neuer API-Endpunkt
GET /api/v1/inspection/reports/:id/pdf— liefert PDF als Download (nur für finalisierte Berichte) - Download-Button in der Prüfbericht-Detailansicht (nur sichtbar wenn
isFinalized && hasPdf)
Fehlerbehebungen
- API-Routen im Frontend korrigiert: doppeltes
/api/-Präfix nach Migration auf/api/v1entfernt (betraf alle Endpoints – Login, Inventar, Admin, Import u.v.m.) - Mandantenverwaltung: Route
/superadmin/mandantenauf/superadmin/tenantsaktualisiert - Benutzerverwaltung: Deaktivieren nutzt jetzt
POST .../deactivatestattDELETE - Benutzer- und API-Key-Listen verarbeiten jetzt das
PagedResult-Wrapper-Format des Backends korrekt - Fehlermeldungen aus dem Backend (Problem Details) werden nun korrekt angezeigt (
.detailstatt.message)
Neu
- SuperAdmin Benutzerliste: Button zum erneuten Senden der Bestätigungs-E-Mail
Verbesserungen
- Browser-Tab zeigt “revuwork” statt “AmvUi”
- Favicon auf revuwork-Cube aktualisiert
Neu
- E-Mail-Verifikation bei Registrierung: Bestätigungslink per E-Mail, Login erst nach Bestätigung möglich
- Benachrichtigung an register@stratworx-solutions.de bei jeder Neuregistrierung
- SuperAdmin: Benutzer dauerhaft löschen
- SuperAdmin Benutzerliste: Badge zeigt ob E-Mail bestätigt ist
- Bestätigungs-E-Mail mit professioneller HTML-Vorlage (revuwork-Logo, orangener Header, Bestätigungs-Button)
Verbesserungen
- Login-Screen zeigt aktuelle Versionsnummer
- Login-Screen: revuwork-Logo, zentrierter Titel, Material Icons fix
- Registrierung: Fehlermeldung wird beim erneuten Absenden korrekt zurückgesetzt
- Deploy: appsettings.Production.json wird jetzt aus dem Repo deployed
Neu
- REST API mit Bearer-Token-Authentifizierung
- PDF-Export für Prüfprotokolle
- Inspektionsmodul: Ergebnisse erfassen und signieren
Verbesserungen
- Dashboard-Performance bei großen Inventaren optimiert
- Import-Assistent mit Vorschau vor dem Import
Neu
- Multi-Tenant-Unterstützung: mehrere Mandanten in einer Installation
- Audit-Trail für alle Datenänderungen
- JSON-Import-Endpoint
Fehlerbehebungen
- Datumsformat-Fehler beim XLS-Import behoben
- Sortierung in der Inventarliste korrigiert
Version 0.1.1
Was ist neu
- Mehrere Betriebe in einer Installation — revuwork unterstützt jetzt mehrere unabhängige Organisationen in einem System, ohne dass sich die Daten gegenseitig beeinflussen.
Fehlerbehebungen
- Datumsangaben wurden beim Excel-Import in bestimmten Fällen falsch erkannt — das ist nun behoben.
- Die Sortierung in der Inventarliste funktioniert wieder korrekt.
Initial Release
- Inventarverwaltung mit Volltextsuche
- Prüffristen und Fälligkeitsübersicht
- XLS-Import aus bestehenden Listen
- Benutzerverwaltung mit Rollensystem
Auth
Anmeldung, Registrierung und Benutzerinfo
Inventar
Inventareinträge lesen, bearbeiten und exportieren
Prüfberichte
Prüfergebnisse importieren und abrufen (MXP / JSON)
Import
Inventardaten per XLS-Datei oder JSON importieren
Dashboard
Statistiken für die Startseite
Einstellungen
Mandantenspezifische Einstellungen und Branding
Admin – Benutzer
Benutzerverwaltung innerhalb eines Mandanten (Rolle Admin)
Admin – API-Keys
Verwaltung von API-Keys (Rolle Admin)
Admin – Visual-Check-Vorlagen
Verwaltung der Sichtprüfungs-Vorlagen (Rolle Admin)
Admin – Benachrichtigungskonfiguration
Benachrichtigungsschwellwerte und E-Mail-Adressen konfigurieren (Rolle Admin)
Benachrichtigungen
Benachrichtigungsläufe anstoßen (E-Mail / Webhook) – für Cron-Jobs
Kunden
Kundenverwaltung (Zuordnung von Inventareinträgen zu Kunden)
Custom Fields
Benutzerdefinierte Felder (revuwork.suite) — mandantenspezifische Feld-Definitionen für Customer/Contact, verwaltet über die Suite-Administration "Entitäten" (Rolle Admin)
Lizenz
Lizenzstatus des eigenen Mandanten (Rolle Admin)
Audit
Audit-Log für Änderungen (Rolle Admin/SuperAdmin)
Admin – Webhooks
Webhook-Konfiguration (Rolle Admin)
Support
Support-Tickets und Nachrichten
Tour
Geführte Onboarding-Touren für Benutzer