A board with its complete circuit list can be downloaded as a leistungsbilanz.distribution-board file and imported into any project as a new board. The import reuses distribution-board.insert-subtree, so it is one revision with restart-safe Undo/Redo. All ids are new; project-bound references (project devices with override metadata, room ids, floor, logical feed, a disabled supply type and the Revit quantity share) are dropped, circuit voltages follow the target project, and the response lists these adjustments. The project page offers export per board and import in the board card header. Refs #26 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
745 lines
48 KiB
Markdown
745 lines
48 KiB
Markdown
# Aktuelle Systemarchitektur
|
||
|
||
## Status und unterstützter Pfad
|
||
|
||
Der unterstützte Editor ist Circuit-First:
|
||
|
||
`Project → DistributionBoard → CircuitList → CircuitSection → Circuit → CircuitDeviceRow`
|
||
|
||
Ein Stromkreis ist nicht dasselbe wie eine Gerätezeile. BMK, Schutz- und Kabeldaten
|
||
gehören zum Stromkreis; Last-, Raum- und Kategoriedaten gehören zur Gerätezeile.
|
||
|
||
Die frühere Consumer-Oberfläche, ihre API, Tabellen und Upgrade-Werkzeuge sind
|
||
entfernt. Neue Funktionen bauen ausschließlich auf dem Circuit-First-Modell auf.
|
||
|
||
## Laufzeit
|
||
|
||
```text
|
||
Browser :3001
|
||
│
|
||
▼
|
||
Next.js App Router ── /api/* Rewrite ──▶ Express API :3000
|
||
│
|
||
▼
|
||
Repository / Transaktion
|
||
│
|
||
▼
|
||
data/leistungsbilanz.db (SQLite)
|
||
```
|
||
|
||
In beiden Docker-Stacks laufen Frontend und API in getrennten Containern.
|
||
`compose.yaml` startet den gebauten Stand, `compose.dev.yaml` die Entwicklung
|
||
mit Hot Reload. Das Frontend leitet `/api/*` über `API_INTERNAL_URL` an die API
|
||
weiter; beim gebauten Stand wird das Ziel zur Build-Zeit festgelegt. Die
|
||
Datenbank liegt über einen Host-Mount außerhalb des Containers.
|
||
|
||
Vor allen fachlichen `/api/*`-Routen liegt die Cookie-basierte Anmeldung aus
|
||
`src/server/auth`. Nur Login, Status und Logout sind ohne Session erreichbar.
|
||
Schreibzugriffe verlangen zusätzlich den Anwendungsheader
|
||
`X-Leistungsbilanz-Request: 1`, damit der Schutz auch beim HTTP-Zugriff im LAN
|
||
ohne Fetch-Metadata-Header greift. `/api/users/*` ist Administratoren
|
||
vorbehalten. Konten sind globale Betriebsdaten und gehören nicht zu
|
||
Projektkommandos, Snapshots oder Projekttransfers. Sessions liegen im Speicher
|
||
des einzelnen API-Prozesses und enden bei dessen Neustart.
|
||
|
||
Neue IDs für Stromkreise, Gerätezeilen, Gruppen und Verteilerkomponenten erzeugt
|
||
der Editor über `src/frontend/utils/client-id.ts` (`createClientId`). Die
|
||
Hilfsfunktion nutzt `crypto.randomUUID()`, sofern verfügbar, andernfalls
|
||
`crypto.getRandomValues()` mit UUID-v4-Format. Damit funktioniert das Anlegen
|
||
auch beim HTTP-Zugriff im LAN ohne sicheren Browserkontext. Die IDs entstehen
|
||
vor dem Command und bleiben über Undo/Redo unverändert.
|
||
|
||
## Wichtige Einstiegspunkte
|
||
|
||
- `src/app/projects/page.tsx` – Projektliste und globale Gerätebibliothek
|
||
- `src/app/projects/[projectId]/page.tsx` – Projektstammdaten, Verteilungen
|
||
und Projektgeräte
|
||
- `src/app/projects/[projectId]/struktur/page.tsx` – Gebäude, Etagen, Räume
|
||
und Raumflächen
|
||
- `src/app/projects/[projectId]/grobbilanz/` – Stände und Editor der groben
|
||
Leistungsbilanz
|
||
- `src/app/projects/[projectId]/print/page.tsx` – Seitenansicht und PDF-Export
|
||
- `src/app/projects/[projectId]/circuit-lists/[circuitListId]/tree-edit/page.tsx`
|
||
– unterstützte Editorroute
|
||
- `src/frontend/components/circuit-tree-editor.tsx` – Editorzustand,
|
||
Befehlsausführung, Drag-and-drop und der schrittweise Historien-Cutover
|
||
- `src/frontend/components/circuit-grid-*.ts` – reine Grid-Projektion,
|
||
Zellbesitz, Einfügen und Sicherheitsregeln
|
||
- `src/frontend/utils/api.ts` – typisierte Frontend-API-Aufrufe
|
||
- `src/frontend/components/AuthProvider.tsx` und `LoginGate.tsx` – Anmeldung
|
||
und erzwungener Wechsel eines Startpassworts
|
||
- `src/server/auth/` – Passwort-Hashing, Sessions und Zugriffsschutz
|
||
- `src/domain/services/user.service.ts` und `src/domain/ports/user.store.ts` –
|
||
kontobezogene Regeln und schmale Persistenzgrenze
|
||
- `src/server/index.ts` und `src/server/routes/` – API-Komposition
|
||
- `src/domain/services/` – fachliche Command- und Synchronisierungsregeln
|
||
- `src/server/composition/` – Verdrahtung fachlicher Services mit konkreten
|
||
SQLite-Repositories
|
||
- `src/db/repositories/` – Abfragen, Persistenzmapper und Transaktionsadapter
|
||
- `src/db/schema/` und `src/db/migrations/` – SQLite-Schema und Migrationen
|
||
|
||
## Daten- und Befehlsfluss
|
||
|
||
1. Das Grid projiziert den geladenen Circuit-Tree in sichtbare Zeilen.
|
||
2. Eine Benutzeraktion wird im Frontend validiert und als API-Befehl gesendet.
|
||
3. Controller validieren Requestdaten mit Zod.
|
||
4. Domain-Services prüfen fachliche Regeln wie BMK-Eindeutigkeit,
|
||
Abschnittszuordnung und Reserveverhalten.
|
||
5. Repositories schreiben Daten. Kritische Mehrfachschreibvorgänge besitzen
|
||
explizite SQLite-Transaktionsadapter mit Commit-/Rollback-Integrationstests.
|
||
6. Das Frontend lädt den Circuit-Tree neu und stellt Auswahl beziehungsweise
|
||
Viewport soweit möglich wieder her.
|
||
|
||
Der Editor besitzt keinen sitzungslokalen Undo-/Redo-Stapel mehr. Beim initialen
|
||
Laden und nach jedem Tree-Reload liest er den persistenten History-Status und
|
||
gleicht dessen Revision mit `currentRevision` des Trees ab. Das Datenmodell
|
||
besitzt einen projektbezogenen Revisionszähler sowie getrennte Revision-/Change-Set-
|
||
Tabellen. Eine direkt getestete Persistence-Funktion schreibt diese
|
||
Historienmetadaten innerhalb der zentralen Command-Transaktion optimistisch und
|
||
atomar fort. Vorwärts- und Rückwärtskommandos besitzen einen
|
||
versionierten, JSON-sicheren Umschlag; Typ und Payload können dadurch nach
|
||
einem Neustart verlustfrei rekonstruiert werden. Alle aktuell unterstützten
|
||
Circuit-, Gerätezeilen-, Projektgeräte-, Projektstruktur- und
|
||
Projekteinstellungsänderungen
|
||
verwenden typisierte Command-Stores, die Fachänderung, automatisch erzeugtes
|
||
inverses Kommando, Revision und Historienstapel gemeinsam committen
|
||
beziehungsweise zurückrollen.
|
||
Gerätezeilen-Kommandos bewahren dabei auch lokale ProjectDevice-Overrides und
|
||
prüfen Projektzugehörigkeit von Verknüpfungen und Räumen. Projektweite,
|
||
persistente Undo-/Redo-Stacks verwalten die zulässige LIFO-Reihenfolge und
|
||
verwerfen den Redo-Zweig bei einem neuen Benutzerkommando. Ihr Status ist über
|
||
`GET /api/projects/:projectId/history` lesbar. Die unveränderliche,
|
||
absteigend paginierte Revisions-Timeline ist ohne Befehls-Payloads über
|
||
`GET /api/projects/:projectId/history/revisions` lesbar. Ein zentraler
|
||
Dispatcher führt die nachfolgend beschriebenen typisierten Kommandos über
|
||
öffentliche Command-, Undo- und Redo-Endpunkte aus.
|
||
Alle unterstützten Runtime-Project-Command-Stores einschließlich der
|
||
vollständigen Snapshot-Wiederherstellung verwenden dabei
|
||
`project-command-transaction.persistence.ts` als gemeinsame äußere
|
||
Transaktionsgrenze. Sie führt Fachänderung, Revisions-Append und
|
||
History-Transition in fester Reihenfolge innerhalb derselben SQLite-Transaktion
|
||
aus. Für CircuitDeviceRow-Feldänderungen übernimmt eine typisierte Variante den
|
||
während der Fachänderung um Override-Metadaten ergänzten Forward-Command. Die
|
||
fachlichen Validierungs-, Forward- und Inversenregeln bleiben im jeweiligen
|
||
Store.
|
||
Die Low-Level-Funktion `appendProjectRevision` bleibt ein internes Detail dieser
|
||
Persistenzgrenze und wird direkt mit einer realen SQLite-Transaktion getestet.
|
||
Ein eigenständiges Runtime-Revisions-Repository existiert nicht.
|
||
`ProjectDeviceSyncService` und `CircuitNumberingService` kennen nur schmale,
|
||
fachlich benannte Reader-Interfaces. Ihre SQLite-Repositories werden
|
||
ausschließlich in `src/server/composition/` erzeugt und injiziert.
|
||
Auch die allgemeinen Projekt-, Geräte-, Raum-, Geschoss- und Circuit-
|
||
Repositories verlangen einen expliziten `AppDatabase`-Kontext. Ihre
|
||
Anwendungsinstanzen werden zentral in
|
||
`src/server/composition/application-repositories.ts` erzeugt; Controller
|
||
importieren weder den globalen SQLite-Client noch konkrete Repository-Klassen.
|
||
`circuit-device-row.insert` und `circuit-device-row.delete` sind atomare
|
||
Strukturkommandos. Beim Löschen wird die vollständige Zeile im inversen
|
||
Kommando gesichert, sodass Undo dieselbe UUID und alle Fachwerte wiederherstellt.
|
||
`circuit.insert` und `circuit.delete` behandeln einen
|
||
Stromkreis mit null, einer oder mehreren Gerätezeilen als vollständigen Block.
|
||
Undo bewahrt dabei sämtliche Circuit-/Row-UUIDs und ändert keine
|
||
Betriebsmittelkennzeichen. Bestehende Circuit- und Gerätezeilen-Zelländerungen
|
||
sowie eigenständiges Einfügen und Löschen verwendet das Grid bereits über die
|
||
öffentliche Command-Grenze. Neue Circuits und Gerätezeilen erhalten ihre stabile
|
||
UUID vor dem Command; Löschen und Undo bewahren diese Identität. Der Tree liefert
|
||
dazu `currentRevision`; Undo/Redo für diese Aktionen läuft über die projektweite
|
||
Serverhistorie. Direkte Feld-PATCH-, Struktur-POST-, Move-, Circuit- und
|
||
Gerätezeilen-DELETE-Endpunkte sind entfernt. Gerätezeilen-Moves,
|
||
Stromkreis-Reorders und die explizite Neunummerierung im Grid verwenden die
|
||
persistenten Kommandos. Die Toolbar leitet ihre Undo-/Redo-Verfügbarkeit direkt
|
||
aus den serverseitigen Stack-Tiefen ab, sodass ein Reload die Bedienbarkeit
|
||
nicht unterbricht.
|
||
|
||
Benannte logische Projektstände werden in `project_snapshots` getrennt von
|
||
Datenbank-Backups gespeichert. `POST /api/projects/:projectId/snapshots`
|
||
erzeugt bei passender erwarteter Revision transaktional einen vollständigen,
|
||
schema-versionierten Projektzustand mit SHA-256-Prüfwert. Enthalten sind
|
||
Projekteinstellungen, Verteiler, Stromkreislisten, Bereiche, Stromkreise und
|
||
Gerätezeilen sowie Projektgeräte, Geschosse und Räume. Globale Geräte sind nicht
|
||
Teil des Projekt-Snapshots.
|
||
Create/List verändern weder Projektrevision noch Undo-/Redo-Stapel.
|
||
`kind` unterscheidet benannte und automatische Stände. Die zentrale
|
||
Revisionspersistenz erzeugt nach jeweils 25 weiteren Projektänderungen
|
||
transaktional einen automatischen Stand. Pro Projekt bleiben die neuesten 12
|
||
automatischen Stände erhalten; ältere automatische Stände werden in derselben
|
||
Transaktion entfernt. Benannte Stände und die unveränderliche Revisionshistorie
|
||
sind von dieser Aufbewahrung ausdrücklich ausgeschlossen.
|
||
`POST /api/projects/:projectId/snapshots/:snapshotId/restore` prüft Payload,
|
||
Prüfsumme, erwartete Revision und den unmittelbar zuvor gelesenen
|
||
Projektzustand. Der Restore ersetzt alle unterstützten Projektdaten in einer
|
||
Transaktion und schreibt dabei eine neue Revision mit Quelle `restore` sowie
|
||
ein vollständiges inverses Kommando. Undo und Redo können deshalb auch einen
|
||
Restore nach einem Neustart exakt zurücknehmen oder wiederholen.
|
||
Die Projektseite bindet diese APIs in einem einklappbaren Bereich
|
||
„Versionen und Sicherungspunkte“ ein. Dort können Benutzer Sicherungspunkte
|
||
benennen, jeden aufgeführten benannten oder automatischen Stand nach
|
||
expliziter Bestätigung wiederherstellen und die paginierte Revisions-Timeline
|
||
mit deutschen Quellen- und Änderungsbezeichnungen lesen. Die Snapshot-Liste
|
||
ordnet jede positive `sourceRevision` serverseitig ihren unveränderlichen
|
||
Revisionsmetadaten zu. Dadurch zeigt auch ein älterer automatischer Stand die
|
||
auslösende Änderung, ohne eine zweite frei formulierte Beschreibung zu
|
||
speichern oder von den zuletzt paginiert geladenen Timeline-Einträgen
|
||
abzuhängen. Revision null wird als Projektstart dargestellt.
|
||
Projektweites Rückgängig/Wiederholen bleibt im Kopf dieses Bereichs auch im
|
||
eingeklappten Zustand erreichbar. Die Verfügbarkeit stammt direkt aus den
|
||
persistierten Server-Stacks; nach einer Historienaktion oder einem Restore lädt
|
||
die Seite sämtliche Projektdaten neu.
|
||
`circuit-device-row.move` verschiebt oder sortiert eine oder mehrere Zeilen
|
||
zwischen vorhandenen Stromkreisen derselben Liste. Erwartete und neue
|
||
Stromkreis-/Sortierpositionen machen Forward und Inverse deterministisch;
|
||
Reservewerte aller beteiligten Stromkreise werden atomar neu abgeleitet. Das
|
||
Kompositkommando `circuit-device-row.move-with-new-circuit` bildet auch das
|
||
Verschieben auf einen freien Platz ab: Ein Zielstromkreis mit stabiler UUID und
|
||
BMK wird zusammen mit allen Zeilenbewegungen erzeugt. Undo stellt die exakten
|
||
Quellpositionen wieder her und löscht den erzeugten Stromkreis nur, wenn dessen
|
||
Felder und vollständiger Zeilenbestand unverändert sind.
|
||
Der Editor erzeugt die vollständigen Move-Zuweisungen aus dem geladenen Tree,
|
||
vergibt für neue Ziele vor dem Kommando eine stabile UUID und führt das
|
||
Toolbar-Undo/Redo über die projektweite Historie aus.
|
||
Die Projektgerätepalette belegt keine permanente Layoutspalte mehr. Sie wird
|
||
über die Editor-Toolbar als überlagernder Drawer geöffnet, während das
|
||
Stromkreis-Grid standardmäßig die gesamte verfügbare Breite nutzt. Auswahl,
|
||
Schnelleinfügen und die vorhandenen Drag-and-drop-Payloads bleiben im Drawer
|
||
unverändert verfügbar; während eines aktiven Projektgeräte-Drags kann er nicht
|
||
geschlossen werden. Der Drawer liegt am rechten Fensterrand und verdeckt damit
|
||
nicht die führenden BMK- und Anzeigenamenspalten. Das Grid zeigt Geräte- und
|
||
Stromkreisleistungen in einer gemeinsamen Spalte `Gesamtsumme`: Gerätezeilen
|
||
verwenden `rowTotalPower`, Stromkreis-Sammelzeilen `circuitTotalPower`.
|
||
Zahlen werden ausschließlich für die Anzeige deutsch und begrenzt formatiert;
|
||
gespeicherte Werte und Bearbeitungsentwürfe behalten ihre volle Genauigkeit.
|
||
Jeder Abschnitt liefert und zeigt seine aufsummierte Stromkreisleistung. Die
|
||
Stromkreisliste zeigt außerdem die ungefilterte Gesamtleistung des Verteilers,
|
||
den am Verteiler gespeicherten Gleichzeitigkeitsfaktor und die daraus
|
||
abgeleitete Gesamtleistung unter Berücksichtigung dieses Faktors. Sortierung
|
||
und Filter verändern diese fachlichen Summen nicht.
|
||
Reihenfolge und Sichtbarkeit der Grid-Spalten sind reine UI-Präferenzen. Sie
|
||
werden im Browser unter einem projektspezifischen Schlüssel gespeichert und
|
||
deshalb beim Wechsel zwischen Verteilern desselben Projekts wiederverwendet,
|
||
ohne Projektrevisionen oder fachliche Snapshots zu erzeugen.
|
||
`circuit.reorder-section` speichert die erwartete und neue Sortierposition
|
||
jedes Stromkreises eines vollständigen Abschnitts. Forward, Undo und Redo
|
||
ändern ausschließlich `sortOrder`; Stromkreisblöcke, Gerätezeilen und BMKs
|
||
bleiben unverändert. `circuit.renumber-section` bildet die getrennte,
|
||
ausdrücklich ausgelöste Neunummerierung ab. Es speichert alle erwarteten und
|
||
neuen BMKs des Abschnitts, löst Tauschkollisionen über temporäre Werte und
|
||
ändert weder Sortierung noch Gerätezeilen.
|
||
Die Bereichsaktion erzeugt die vollständigen erwarteten und neuen BMKs aus
|
||
Präfix und aktueller Stromkreisreihenfolge und verwendet das persistente
|
||
Kommando für Toolbar-Undo/Redo. Die früheren direkten Renumber- und
|
||
Identifier-Restore-Endpunkte sowie ihr separater Transaktionsadapter sind
|
||
entfernt.
|
||
Drag-and-drop verwendet `circuit.reorder-section`. Die explizite Übernahme
|
||
einer sortierten Ansicht verwendet `circuit.reorder-sections`, damit alle
|
||
betroffenen Bereiche in einer Transaktion und als ein Undo-Schritt gespeichert
|
||
werden. Direkte Reorder-Endpunkte existieren nicht mehr.
|
||
`project-device.sync-rows` persistiert Synchronisierung, Trennen und erneutes
|
||
Verknüpfen als atomaren Mehrzeilen-Command. Jede betroffene Zeile enthält den
|
||
vollständigen erwarteten und neuen Stand aller synchronisierbaren Felder,
|
||
einschließlich ProjectDevice-Verknüpfung und lokaler Override-Metadaten. Damit
|
||
werden stille Überschreibungen veralteter Zeilen verhindert und Undo/Redo stellt
|
||
exakt die vorherigen lokalen Werte wieder her. Die Synchronisieren- und
|
||
Trennen-Endpunkte sowie die Projektseite verwenden diesen Command mit
|
||
optimistischer Revisionsprüfung. Rückgängig/Wiederholen erfolgt dort
|
||
einheitlich über die persistente projektweite Historie; der frühere
|
||
sitzungslokale Spezial-Undo und separate direkte Restore-/Reconnect-Schreibwege
|
||
existieren nicht mehr.
|
||
`project-device.update` versioniert Änderungen an den kanonischen
|
||
Projektgerätefeldern unabhängig davon. Der Store erzeugt die Inverse aus dem
|
||
gespeicherten Gerät und schreibt Geräteänderung, Revision und Historienstapel
|
||
atomar. Verknüpfte Stromkreiszeilen werden dabei bewusst nicht automatisch
|
||
synchronisiert. Der ProjectDevice-`PUT`-Endpunkt und die Projektseite verwenden
|
||
diesen Command mit optimistischer Revisionsprüfung.
|
||
`project-device.insert` und `project-device.delete` versionieren außerdem den
|
||
Lebenszyklus eines Projektgeräts mit stabiler UUID. Beim Löschen speichert das
|
||
inverse Insert den vollständigen Gerätestand sowie vollständige, nach dem
|
||
Löschen erwartete Snapshots aller zuvor verknüpften Gerätezeilen. Undo setzt
|
||
die Links nur zurück, wenn diese Zeilen weiterhin zum Projekt gehören,
|
||
unverknüpft und vollständig unverändert sind. Gerät, Linkänderungen, Revision
|
||
und Historienstapel teilen dieselbe Transaktion. Create, Import aus der globalen
|
||
Gerätebibliothek und Delete laufen über dieselbe Command-Grenze; ihre Antworten
|
||
liefern Gerät und aktualisierten Historienstand an die Projektseite zurück.
|
||
`project.update-settings` versioniert Projektname, interne und externe
|
||
Projektnummer, Bauherr, Beschreibung, die Deckblattangaben (Anschrift,
|
||
Leistungsphase, Projektleitung, Bearbeitung; Schema-Version 3, Migration
|
||
`0008`), die Kennzeichnung als öffentliches Gebäude, beide Standardspannungen sowie die im Projekt freigeschalteten
|
||
Verteiler-Netzarten als eine atomare Änderung. Die Gebäudekennzeichnung ist
|
||
der spätere Eingang für die Auswahl halogenfreier Kabel und Leitungen. Der
|
||
Systemkatalog besteht aus `AV`, `SV`, `EV`, `USV`, `MSR` und `SiBe`; mindestens
|
||
eine Netzart muss aktiv bleiben und eine bereits von einer Verteilung
|
||
verwendete Netzart kann nicht deaktiviert werden. Die Projektseite bearbeitet diese Angaben in einem
|
||
beschrifteten Einstellungsmodal statt in einer permanenten Formularkarte. Der
|
||
Store leitet das inverse Kommando aus dem gespeicherten Projekt ab und schreibt
|
||
Werte, Revision und Historienstapel gemeinsam. `PUT /api/projects/:projectId`
|
||
verlangt deshalb `expectedRevision`, liefert Projekt plus aktualisierten
|
||
Historienstand und besitzt keinen separaten direkten Settings-Schreibweg mehr.
|
||
Kommando- und Snapshot-Payloads müssen dem aktuellen Baseline-Schema entsprechen.
|
||
Die Projektseite zeigt Verteilungen als kompakte Bestandsübersicht; ihre
|
||
versionierten Erstellwege öffnen beschriftete Modals statt dauerhafter
|
||
Eingabezeilen. Gebäude, Etagen und Räume werden auf der eigenen Seite
|
||
„Gebäudestruktur“ (`/projects/:projectId/struktur`) gepflegt; Etagen können
|
||
einem Gebäude zugeordnet sein (`docs/building-structure.md`). Projektgeräte werden als durchsuchbare,
|
||
fünfspaltige Übersicht dargestellt. Manuelle Anlage, Übernahme aus der globalen
|
||
Bibliothek und vollständige Bearbeitung erfolgen in einem gemeinsamen Modal.
|
||
Die anschließende Vorschau verknüpfter Stromkreiszeilen bleibt davon getrennt,
|
||
damit Änderungen weiterhin niemals still synchronisiert werden.
|
||
Die Karten Verteilungen und Projektgeräte besitzen je eine einklappbare
|
||
Filterleiste im Stil der PM-App (Suche, Mehrfachauswahl für Etage/Netzart bzw.
|
||
Kategorie/Phasenzahl/Kostengruppe, „Zurücksetzen“, Anzeige „x von y“); die
|
||
Filterlogik liegt rein in `src/frontend/utils/overview-filters.ts`, der
|
||
Einklappzustand je Karte in `localStorage`. Aktionen verwenden wie der Editor
|
||
`IconButton`/`IconLink`.
|
||
Die allgemeinen Anwendungskarten verwenden in `src/app/globals.css` einen
|
||
gleichmäßigen Rahmen, 16-px-Rundungen und einen Schatten mit eigener
|
||
Dark-Mode-Abstimmung. Bootstrap-Karten mit `shadow-sm` nutzen denselben
|
||
Schatten; der Stromkreis-Grid bleibt eine eigenständige Komponente.
|
||
Die Verteilersuche berücksichtigt Name, Verteilernummer, Örtlichkeit und
|
||
Etagenname; die Gerätesuche
|
||
Name, Anzeigename, Kategorie einschließlich deutscher Bezeichnung, Anschlussart
|
||
und Kostengruppe. Der Gerätefilter „Anschluss“ wählt zwischen 1- und 3-phasig.
|
||
Mehrere Werte innerhalb eines Filters gelten alternativ, verschiedene Filter
|
||
und die Suche gemeinsam. Eine leere Auswahl bedeutet „Alle“; fehlende
|
||
Zuordnungen sind über „Ohne Etage“, „Nicht festgelegt“ beziehungsweise
|
||
„Ohne Kostengruppe“ auswählbar. Nicht mehr angebotene Auswahlwerte werden bei
|
||
der Filterung ignoriert. „Zurücksetzen“ leert die Filter der jeweiligen Karte.
|
||
Beim Einklappen bleiben aktive Filter wirksam und durch „Filter aktiv“ sichtbar.
|
||
Nur der Einklappzustand wird je Kartentyp im Browser gespeichert; Suchbegriffe
|
||
und Filterauswahl werden nicht dauerhaft gespeichert. Die Filter ändern keine
|
||
Projektdaten und erzeugen keine Revisionen. Das Löschen eines Projektgeräts
|
||
erfordert eine Bestätigung mit dessen Namen und verwendet weiterhin den
|
||
bestehenden persistenten Projektbefehl.
|
||
|
||
`GET /api/projects/:projectId/export` verpackt den vollständigen portablen
|
||
Projektzustand in ein format- und schema-versioniertes JSON-Dokument mit
|
||
SHA-256-Prüfsumme. Transferformat 2 enthält zusätzlich alle Grobbilanzen,
|
||
ohne sie in das logische Snapshot-Schema aufzunehmen; Format 1 bleibt ohne
|
||
Grobbilanzen importierbar. `POST /api/projects/:projectId/import` prüft Format,
|
||
Snapshot-Relationen, Grobbilanzen und Prüfsumme vor jedem Schreibzugriff. Der
|
||
Modus `replace` läuft über `project.restore-state` Version 2 und ersetzt den
|
||
Projektzustand und die Grobbilanzen atomar und dauerhaft rückgängig machbar.
|
||
Normale Snapshot-Restores verwenden weiterhin Version 1 und lassen
|
||
Grobbilanzen unverändert. Der Modus `duplicate` ordnet Projekt-,
|
||
Struktur-, Gruppen-, Verteilerkomponenten-, Raum-, Stromkreis- und
|
||
Gerätezeilen-UUIDs sowie Schutzgeräte-Referenzen und die IDs aller Grobbilanzen
|
||
vollständig neu zu und legt die Kopie mit Revision `0` in einer Transaktion an.
|
||
Fachliche Verknüpfungen
|
||
innerhalb des unterstützten Laufzeitmodells bleiben
|
||
erhalten. Die
|
||
Projektübersicht verwendet dafür den separaten Collection-Endpunkt
|
||
`POST /api/projects/import`, der ausschließlich eine neue Kopie anlegt und
|
||
deshalb weder eine bestehende Projekt-ID noch `expectedRevision` annimmt. Das
|
||
Ersetzen eines Projekts bleibt auf dessen Einstellungsmodal und den
|
||
projektgebundenen Endpunkt beschränkt.
|
||
`distribution-board.insert` versioniert die Anlage einer Verteilung mit
|
||
Command-Schema 1 als vollständigen Block aus Verteilung, Stromkreisliste, den
|
||
drei Gruppen `Beleuchtung 1`, `1-phasig 1`, `3-phasig 1`, Hauptschalter `-Q0`
|
||
und Überspannungsableiter `-FA`.
|
||
Alle UUIDs entstehen vor dem Command und bleiben über Undo/Redo stabil.
|
||
`distribution-board.delete` ist die persistierte Inverse und entfernt nur den
|
||
vollständig unveränderten, weiterhin stromkreislosen Block. Controller und
|
||
Projektseite übergeben die erwartete Projektrevision; der frühere direkte
|
||
Controller-Schreibweg ist entfernt. Verteilungen besitzen eine optionale
|
||
Etagenreferenz sowie eine Netzart aus dem Projektkatalog. Anlage und
|
||
nachträgliche Bearbeitung prüfen die Projektzugehörigkeit der Etage und die
|
||
Freigabe der Netzart in den Projekteinstellungen.
|
||
Bereits befüllte Verteilungen werden über
|
||
`distribution-board.insert-subtree` und `distribution-board.delete-subtree`
|
||
als vollständiger Unterbaum kopiert beziehungsweise gelöscht. Der Snapshot
|
||
umfasst Verteilung, Stromkreisliste, Gruppen, Stromkreise, Gerätezeilen,
|
||
Verteilerkomponenten und alle zugehörigen Schutzgeräte. Beim Kopieren werden
|
||
sämtliche besitzenden UUIDs neu vergeben, während fachliche Verknüpfungen zu
|
||
Projektgeräten und Räumen erhalten bleiben. Beide Aktionen sind atomare
|
||
Projektrevisionen und bleiben nach einem Neustart über Undo/Redo umkehrbar. Die
|
||
Projektseite bietet sie im Einstellungsmodal der jeweiligen Verteilung an;
|
||
Löschen verlangt dort eine ausdrückliche Bestätigung.
|
||
Derselbe Unterbaum ist auch als portable Datei verfügbar:
|
||
`GET /api/projects/:projectId/distribution-boards/:distributionBoardId/export`
|
||
liefert das Format `leistungsbilanz.distribution-board` (Version 1,
|
||
`src/domain/models/distribution-board-transfer.model.ts`), und
|
||
`POST /api/projects/:projectId/distribution-boards/import` (`expectedRevision`,
|
||
optionaler Name, Datei) legt ihn über `distribution-board.insert-subtree` als
|
||
neue Verteilung an – eine Revision, Undo/Redo wie beim Kopieren. Beim Import
|
||
werden alle UUIDs neu vergeben und projektgebundene Bezüge entfernt:
|
||
Projektgeräte-Verknüpfungen samt Override-Metadaten, Raum-IDs (Raumnummer und
|
||
-name bleiben als Text), Etage, logische Einspeisung, eine im Zielprojekt nicht
|
||
freigegebene Netzart und der Revit-Anteil der Anzahl (`quantity =
|
||
manualQuantity`). Stromkreisspannungen folgen den Spannungen des Zielprojekts.
|
||
Die Antwort nennt diese Anpassungen in `adjustments`; die Projektseite bietet
|
||
Export je Verteilung und Import im Kopf der Verteilerkarte an.
|
||
`distribution-board-component.insert` und
|
||
`distribution-board-component.delete` sowie
|
||
`distribution-board-component.update` versionieren Anlage, Entfernung,
|
||
Bearbeitung und Sortierung veränderlicher Gruppen-Schutzgeräte und zusätzlicher
|
||
Verteilergeräte. Die vollständigen Snapshots enthalten stabile
|
||
Komponenten-UUIDs und, für Gruppen-Schutzgeräte, die getrennte
|
||
1:1-Schutzgerätekonfiguration. Updates tauschen einen exakt erwarteten gegen
|
||
einen vollständigen Zielzustand aus; ID, Eigentum, Rolle und Platzierungszone
|
||
bleiben dabei unverändert. Projekt-, Listen- und Gruppenzugehörigkeit sowie
|
||
veraltete Zustände werden innerhalb derselben Transaktion geprüft. Die festen
|
||
Kopfkomponenten Hauptschalter und Überspannungsableiter sind von diesen
|
||
allgemeinen Commands ausgeschlossen. Veränderliche Gruppen- und Fußkomponenten
|
||
werden im Editor über eigene Modale und diese Commands verwaltet.
|
||
Leere Stromkreisgruppen werden über `circuit-group.insert`,
|
||
`circuit-group.update` und `circuit-group.delete` verwaltet. Ihr vollständiger
|
||
Snapshot koppelt Kategorie, positive Gruppennummer und daraus abgeleiteten
|
||
BMK-Präfix; allgemeine Updates ändern ausschließlich den Anzeigenamen.
|
||
Löschen verlangt einen exakt unveränderten Zustand ohne Stromkreise und ohne
|
||
Gruppenkomponenten. Befüllte Gruppen verwenden getrennt davon den vollständigen
|
||
`circuit-group.delete-subtree`-Command mit ausdrücklicher Warnung.
|
||
`circuit-group.reorder` sortiert Gruppen als vollständige Stromkreislisten-
|
||
Zuordnung. Jede vorhandene Gruppe muss mit erwarteter und neuer Position
|
||
enthalten sein. Der Command verändert ausschließlich `sortOrder`; Nummern,
|
||
Präfixe und Stromkreis-BMKs bleiben stabil. Die gesamte Sortierung bildet eine
|
||
Revision und einen Undo/Redo-Schritt.
|
||
`src/domain/services/circuit-group-renumbering.ts` plant explizite
|
||
Gruppennummerierungen deterministisch. Der reine Plan erhält
|
||
Stromkreis-Endnummern, leitet Gruppenpräfixe und optionale `.0`-Komponenten-BMKs
|
||
neu ab und prüft Nummerntausch sowie Kollisionen mit unveränderten Gruppen.
|
||
`circuit-group.renumber` führt diesen Plan über die gemeinsame
|
||
Projekt-Command-Transaktion aus. Vor dem Schreiben müssen alle betroffenen
|
||
Gruppen, Stromkreise und Gruppenkomponenten exakt dem erwarteten Zustand
|
||
entsprechen. Nummerntausch verwendet kollisionsfreie temporäre Präfixe und
|
||
BMKs über beide BMK-Tabellen; anschließend werden alle Zielwerte finalisiert.
|
||
Der vollständige inverse Plan ermöglicht dauerhaftes Undo/Redo.
|
||
`src/domain/services/circuit-group-move-planning.ts` plant den Wechsel eines
|
||
vollständigen Stromkreises zwischen zwei verschiedenen Gruppen derselben
|
||
Kategorie. Die Ziel-BMK verwendet die höchste vorhandene Stromkreis-Endnummer
|
||
der Zielgruppe plus eins; Lücken werden nicht gefüllt. Quell-/Zielgruppe,
|
||
Quell-/Zielposition und beide BMKs werden festgeschrieben, damit eine spätere
|
||
Wiederholung nichts neu berechnet.
|
||
`circuit.move-group` führt den geplanten Wechsel als eine Projekt-Revision aus.
|
||
Nur `sectionId`, `equipmentIdentifier` und `sortOrder` des Stromkreises ändern
|
||
sich. Gerätezeilen und das getrennte 1:1-Schutzgerät bleiben über die stabile
|
||
Stromkreis-UUID verbunden. Der inverse Wechsel speichert Quellgruppe, BMK und
|
||
Position vollständig für dauerhaftes Undo/Redo.
|
||
`CircuitGroupSubtreeSnapshot` ist der kanonische Vertrag für destruktive
|
||
Gruppenoperationen. Er umfasst Gruppe, optionale Gruppenkomponenten samt
|
||
1:1-Schutzdaten, vollständige Stromkreise samt 1:1-Schutzdaten und sämtliche
|
||
Gerätezeilen einschließlich Link- und Override-Metadaten. Seine
|
||
Warnzusammenfassung liefert Vorsicherung/FI sowie Stromkreis- und
|
||
Gerätezeilenanzahl.
|
||
`circuit-group.delete-subtree` nimmt den aktuellen Unterbaum unmittelbar vor
|
||
dem Löschen erneut auf und verlangt exakte Übereinstimmung mit dem bestätigten
|
||
Snapshot. `circuit-group.restore-subtree` schreibt ihn in FK-sicherer Reihenfolge
|
||
mit denselben UUIDs zurück. Beide Richtungen laufen als vollständige inverse
|
||
Projekt-Commands; späte Fehler hinterlassen weder Teillöschungen noch
|
||
Teilwiederherstellungen.
|
||
Der Circuit-Tree liefert zusätzlich `headerComponents`,
|
||
gruppenbezogene `components` und `footerComponents`. Abschnitte enthalten
|
||
optionale Gruppenkategorie und -nummer; Stromkreise und Gruppenkomponenten
|
||
enthalten ihre getrennte Schutzgerätekonfiguration. Die
|
||
1:1-Schutzgerätetabellen sind die einzige unterstützte Quelle dieser Daten.
|
||
Separate aktive Read-Repositories lesen Komponenten- und beide
|
||
Schutzgerätetabellen; Schreibzugriffe bleiben ausschließlich in Commands.
|
||
`src/frontend/utils/circuit-structure-projection.ts` projiziert diesen Tree
|
||
rein und deterministisch in die drei Zonen Kopf, vollständige Gruppen und Fuß.
|
||
Innerhalb einer Gruppe folgen auf die Überschrift optionale Schutzkomponenten,
|
||
vollständige Stromkreisblöcke und der freie Einfügeplatz. Leere Gruppen bleiben
|
||
sichtbar; Komponenten werden stabil nach `sortOrder` und ID sortiert.
|
||
`circuit-grid-projection.ts` normalisiert diese Struktur zusammen mit den
|
||
bisherigen kompakten und mehrzeiligen Stromkreisblöcken für den Editor. Kopf-,
|
||
Gruppen- und Fußkomponenten erscheinen als Zeilen über die volle Tabellenbreite
|
||
und nehmen nicht an Zellauswahl oder Tastatur-Einfügen teil. Hauptschalter und
|
||
Überspannungsableiter bleiben feste, schreibgeschützte Kopfkomponenten.
|
||
Optionale Gruppenvorsicherungen, Gruppen-FIs und zusätzliche Verteilergeräte
|
||
werden über beschriftete Modale angelegt, bearbeitet und nach Bestätigung
|
||
entfernt. Der Frontend-API-Adapter serialisiert dafür die vorhandenen
|
||
`distribution-board-component.*`-Commands; die verbindliche
|
||
geräteabhängige Validierung und atomare Revision bleiben serverseitig.
|
||
Neue Stromkreisgruppen erhalten im Editor die höchste vorhandene
|
||
Gruppennummer ihrer Kategorie plus eins und ein daraus abgeleitetes Präfix.
|
||
Das Bearbeiten ändert ausschließlich den Anzeigenamen. Nur vollständig leere
|
||
Gruppen können über `circuit-group.delete` entfernt werden; für befüllte
|
||
Gruppen bleibt die Aktion bis zum gesonderten Warn- und Unterbaumdialog
|
||
gesperrt.
|
||
`circuit-protection.update` bildet die eigene persistente Schreibgrenze für
|
||
die neue 1:1-Stromkreisschutztabelle. Der Command vergleicht den vollständigen
|
||
erwarteten Datensatz, validiert Typ und abhängige Felder, prüft die
|
||
Projektzugehörigkeit und schreibt Schutzgerät, Revision und Historienübergang
|
||
atomar. Benutzer können Schutzdaten anlegen oder ändern, aber nicht entfernen;
|
||
Undo einer erstmaligen Anlage darf den zuvor fehlenden Datensatz exakt
|
||
wiederherstellen.
|
||
Der vollständige `CircuitSnapshot` enthält den zugehörigen
|
||
`protectionDevice`-Datensatz, sofern einer vorhanden ist. Neue Stromkreise und
|
||
durch Geräteverschiebung erzeugte Zielstromkreise verwenden die vereinbarten
|
||
Kategorie-Standardwerte und schreiben Schutzgerät, Stromkreis sowie
|
||
Gerätezeilen atomar. Delete/Undo erfasst denselben Datensatz vollständig.
|
||
Im Editor sind die Schutzspalten deshalb eine
|
||
schreibgeschützte Projektion der 1:1-Daten; Änderungen erfolgen über ein
|
||
geräteabhängiges Schutzgeräte-Modal und `circuit-protection.update`.
|
||
Mehrfachänderungen und Einfügen im Editor laufen über
|
||
`circuit-list.bulk-update`: Der Befehl enthält vollständige Einzelbefehle
|
||
`circuit.update`, `circuit-device-row.update` und `circuit-protection.update`
|
||
(je Ziel höchstens einer, maximal 2000) und wendet sie mit denselben Regeln wie
|
||
die Einzelbefehle in einer Transaktion an
|
||
(`circuit-list-bulk-update-project-command.repository.ts`). Die Inverse ist die
|
||
umgekehrte Liste der angewendeten Inversen, sodass Undo/Redo nichts neu
|
||
plant. Die reine Planung im Client liegt in
|
||
`src/frontend/utils/circuit-grid-clipboard.ts`: Strg+C/Strg+V über die
|
||
`copy`/`paste`-Ereignisse (funktioniert auch über HTTP im LAN), Schutz- und
|
||
Kabelzusammenfassung werden als vollständiger Datensatz des Stromkreises
|
||
kopiert, Tabulator-Blöcke aus Excel laufen über sichtbare Zeilen und Spalten.
|
||
Bei einer Mehrfachauswahl (Strg/Umschalt) gelten Eingabe, Einfügen und das
|
||
Schutzgeräte-Modal für alle passenden ausgewählten Zeilen; nicht passende
|
||
Zellen werden übersprungen und gemeldet. Ein Rechtsklick-Kontextmenü
|
||
(`grid-context-menu.tsx`, auch Kontextmenütaste/Umschalt+F10) bündelt
|
||
Kopieren, Einfügen, Bearbeiten, Schutzgerät, Leitungsberechnung, Stromkreis
|
||
unterhalb einfügen, Gerät hinzufügen und Löschen.
|
||
Gruppen lassen sich im Editor schrittweise nur gegenüber einer benachbarten
|
||
Gruppe derselben Kategorie verschieben. Der Client sendet dabei immer die
|
||
vollständige erwartete und gewünschte Sortierreihenfolge über
|
||
`circuit-group.reorder`; Gruppennummer, Präfix und Kind-BMK bleiben unverändert.
|
||
Eine separate, ausdrücklich bestätigte Editoraktion erzeugt dagegen einen
|
||
vollständigen `circuit-group.renumber`-Plan für genau eine Kategorie. Sie
|
||
nummeriert deren Gruppen gemäß aktueller Reihenfolge ab eins und ändert
|
||
Gruppenpräfixe, optionale Gruppen-Schutz-BMK und Stromkreis-BMK gemeinsam;
|
||
die Stromkreisnummer hinter dem letzten Punkt bleibt erhalten.
|
||
Das vorhandene Stromkreis-Drag-Handle kann außerdem genau einen vollständigen
|
||
Stromkreis in eine andere Gruppe derselben Kategorie verschieben. Der atomare
|
||
`circuit.move-group`-Befehl erhält Gerätezeilen und Schutzdaten, setzt die
|
||
Zielposition und vergibt dort die höchste vorhandene Stromkreisnummer plus eins;
|
||
Lücken werden nicht automatisch gefüllt.
|
||
Befüllte Gruppen werden im Editor erst nach einer Warnung mit Anzahl der
|
||
enthaltenen Stromkreise, Gerätezeilen und Gruppenschutzgeräte gelöscht. Der
|
||
Client sendet dafür den vollständigen aktuellen Unterbaum an
|
||
`circuit-group.delete-subtree`; der Server vergleicht ihn innerhalb derselben
|
||
Transaktion mit dem Datenbankstand. Undo stellt Gruppe, Schutzgeräte,
|
||
Stromkreise, Gerätereihen und Verknüpfungs-/Override-Metadaten vollständig
|
||
wieder her.
|
||
`distribution-board.update` versioniert Etage, Netzart und den
|
||
verteilerweiten Gleichzeitigkeitsfaktor gemeinsam und stellt alle Werte über
|
||
dauerhaftes Undo/Redo wieder her. Der Faktor liegt zwischen `0` und `1` und
|
||
ist für bestehende sowie neu angelegte Verteilungen standardmäßig `1`.
|
||
Derselbe Befehl versioniert die Felder der Verteiler-Infokarte
|
||
(`boardNumber`, `location`, `fedFrom`, `remark`, Migration `0007`). Die
|
||
Infokarte liegt im Bearbeiten-Dialog der Verteilung; die Werte sind lokale
|
||
Planungswerte und sollen später über den Revit-Abgleich nur nach Vorschau und
|
||
ausdrücklicher Bestätigung vorbelegt werden. `fedFrom` bleibt Freitext für
|
||
externe Einspeisungen und Hinweise.
|
||
`distribution-board.update` verwendet Schema-Version 2 für die optionalen
|
||
logischen Verweise `feedingDistributionBoardId` und `feedingCircuitId`.
|
||
Version-1-Befehle aus der Historie bleiben ausführbar und verändern diese
|
||
Verweise nicht. Der speisende Stromkreis muss zur speisenden Verteilung gehören;
|
||
Selbstverweise und Zyklen werden abgelehnt. Löschbefehle für referenzierte
|
||
Verteilungen und Stromkreise, einschließlich Unterbaum- und Undo-Pfaden, werden
|
||
vor der Datenbankänderung fachlich gesperrt.
|
||
Im Bearbeiten-Dialog kann nach Auswahl der speisenden Verteilung direkt ein
|
||
neuer Einspeiseabgang geplant werden. Die Oberfläche schlägt die dreiphasige
|
||
Gruppe und das nächste BMK vor. Der Befehl
|
||
`distribution-board.create-feeder-circuit` legt den leeren Stromkreis an und
|
||
wendet die vollständige Verteilungsänderung in derselben Transaktion und
|
||
Revision an; es entsteht keine Gerätezeile. Schutz und Leitung bleiben bewusst
|
||
offen, weil sie aus Last und Verlegebedingungen geplant werden müssen. Undo
|
||
stellt zuerst die vorherigen Verteilungswerte wieder her und entfernt danach
|
||
nur den unveränderten erzeugten Stromkreis; Redo verwendet UUID und BMK erneut.
|
||
Bei genau einer gespeisten Unterverteilung ohne Gerätezeilen projiziert das
|
||
Grid nur eine kompakte Stromkreiszeile mit dem über die ID aufgelösten
|
||
Verteilernamen. Erst mehrere Unterverteilungen oder zusätzliche Geräte erzeugen
|
||
Sammel- und Unterzeilen. Anzeigename und Bemerkung eines leeren
|
||
Reservestromkreises gehören zum Stromkreis und erzeugen beim Bearbeiten keine
|
||
Gerätezeile.
|
||
Die projektweite Circuit-Tree-Projektion berechnet rekursiv die Lasten aller
|
||
Verteilungen. Die Leistung einer Unterverteilung nach ihrem eigenen
|
||
Verteiler-GZF wird als virtuelle, schreibgeschützte Zeile genau dem gewählten
|
||
Abgang zugerechnet. Eine Verknüpfung ohne Stromkreis bildet nur die Hierarchie.
|
||
Snapshot-Schema 10 enthält zusätzlich die Linear-XLSX-Konfiguration und den
|
||
externen Zustand mit Worksharing-ID-Identität. Leere Versionen 1–9 werden
|
||
hochgestuft; Versionen mit tatsächlichem Legacy-CSV-Zustand werden abgelehnt.
|
||
Snapshot-Schema 9 enthält die beiden Einspeisungsverweise; Versionen 1–8 werden
|
||
mit `null` hochgestuft. Restore und Projektduplizierung erhalten beziehungsweise
|
||
remappen die Verweise. Snapshot-Schema 8 enthält zusätzlich die optionale Netto-Raumfläche
|
||
`rooms[].areaM2`; Versionen 1–7 werden dafür mit `null` hochgestuft.
|
||
Snapshot-Schema 7 enthält die Gebäude und die Gebäudezuordnung der Etagen;
|
||
ältere Versionen werden ohne Gebäude hochgestuft.
|
||
Snapshot-Schema 6 enthält diese Felder und die Deckblattangaben des
|
||
Projekts; ältere Versionen werden mit `null`
|
||
hochgestuft. Snapshot-Schema 5 enthält zusätzlich den manuellen Mengenanteil jeder
|
||
Gerätezeile. Version 4 wird mit `manualQuantity = quantity` hochgestuft und
|
||
enthielt bereits den damaligen externen CSV-Modellzustand
|
||
aus Quelle, Batches, Raum-Mappings und Objekten. Es enthielt außerdem die
|
||
damalige projektweite Revit-CSV-Konfiguration, die Gebäudekennzeichnung sowie den Gleichzeitigkeitsfaktor,
|
||
Gruppenkategorie und -nummer, Verteilerkomponenten sowie die getrennten
|
||
1:1-Schutzgerätedaten. Capture,
|
||
benannte und automatische Snapshots, Wiederherstellung, Undo/Redo und beide
|
||
JSON-Importmodi verwenden denselben vollständigen Zustand. Die unterstützte
|
||
Version 3 wird mit leerem externem Modellzustand und Version 2 zusätzlich mit
|
||
leerer externer Konfiguration hochgestuft. Die unterstützte
|
||
Baseline-Version 1 wird zusätzlich mit `isPublicBuilding = false` hochgestuft; Formate vor
|
||
dieser Baseline werden nicht eingelesen.
|
||
Persistente Insert-, Update- und Delete-Commands versionieren Anlage,
|
||
Bearbeitung und Löschung von Geschossen und Räumen mit stabilen UUIDs und
|
||
exakten Vorher-/Nachher-Snapshots. Raumbefehle verwenden Version 2 und führen
|
||
die nullable, nicht negative Netto-Raumfläche `areaM2`; Version-1-Befehle
|
||
werden mit `null` hochgestuft und bleiben durch den vollständigen
|
||
Snapshotvergleich konfliktfest. Der Raumdialog pflegt die Fläche manuell mit
|
||
deutscher Dezimaleingabe, die Gebäudestruktur zeigt zwei Nachkommastellen sowie
|
||
Summen und die Anzahl fehlender Flächen je Etage und Gebäude. Ein Geschoss wird nur
|
||
entfernt, solange ihm weder ein Raum noch eine Verteilung zugeordnet wurde. Ein
|
||
Raum wird nur entfernt, solange keine CircuitDeviceRow auf ihn verweist. Alle
|
||
Schreibendpunkte verlangen `expectedRevision` und liefern den aktualisierten
|
||
Historienstand.
|
||
|
||
## Projektgeräte
|
||
|
||
Die Projektgerätekategorie `lighting`, `single_phase` oder `three_phase` ist die
|
||
fachliche Klassifikation und Zielgruppe. `phaseType` ist keine separate
|
||
Benutzereingabe, sondern wird daraus für Spannung, Verknüpfung und spätere
|
||
Dimensionierung abgeleitet.
|
||
|
||
Beim Einfügen entsteht eine verknüpfte `CircuitDeviceRow`. Der Anzeigename wird
|
||
kopiert, aber nicht still synchronisiert. Spätere Änderungen am Projektgerät
|
||
werden als Diff angezeigt und nur für ausdrücklich gewählte Felder und Zeilen
|
||
übernommen.
|
||
|
||
Die globale Gerätebibliothek ist ein einfacher, datenbankweiter Vorlagenbestand.
|
||
Kopieren in ein Projekt erzeugt ein eigenständiges Projektgerät.
|
||
|
||
## Leitungsberechnung
|
||
|
||
Das optionale Modul `src/cable-sizing` berechnet auf Anforderung eine
|
||
Querschnittsempfehlung für einen Stromkreis. Strombelastbarkeit, Temperatur
|
||
und Häufung beruhen auf DIN VDE 0298-4:2023-06 einschließlich
|
||
Berichtigung 1:2023-10; Spannungsfall und Schutzorgan werden ergänzend und mit
|
||
den in der Moduldokumentation beschriebenen Grenzen geprüft. Die
|
||
Domänenlogik ist rein, `POST /api/cable-sizing/calculate` ist zustandslos und
|
||
es gibt keine eigene Tabelle. Der Editor öffnet den Dialog über den Button
|
||
„Leitungsberechnung“ in der Aktionsspalte; „Übernehmen“ schreibt Kabeltyp,
|
||
Querschnitt und Länge über den regulären `circuit.update`-Befehl, also mit
|
||
Revision und Undo/Redo. Details, Rechenweg und Prüfnachweis der Tabellen:
|
||
`docs/cable-sizing-module.md`.
|
||
|
||
## Grobe Leistungsbilanz
|
||
|
||
Das Modul `src/estimate-balance` mit der importfreien Rechnung in
|
||
`src/shared/estimate-balance` führt Leistungsbilanzen für LPH 2/3 nach
|
||
DIN-276-Kostengruppen in eigenen Tabellen (`estimate_*`, Migration `0010`).
|
||
Es ist nicht mit der Stromkreisstruktur verknüpft und liest Gerätezeilen nur
|
||
für den Vergleich „geschätzt ↔ detailliert“ über das längste passende
|
||
Kostengruppen-Präfix. Schreibzugriffe laufen über `estimate-balance.insert`,
|
||
`.update` und `.delete` mit vollständigen Dokumenten, also mit Revision und
|
||
Undo/Redo. Grobbilanzen sind Teil von Transferformat 2, aber bewusst nicht Teil
|
||
der logischen Snapshots; ein Restore lässt sie unverändert. Einzelne Zeilen
|
||
lassen sich über den vorbelegten Projektgeräte-Dialog ausdrücklich als
|
||
Projektgerät kopieren (`project-device.insert`, ohne gespeicherte Verknüpfung).
|
||
Oberfläche: `/projects/:projectId/grobbilanz`. Details:
|
||
`docs/estimate-balance.md`.
|
||
|
||
## Persistenz und Migration
|
||
|
||
- SQLite ist die aktuell unterstützte Datenbank.
|
||
- Fremdschlüssel werden für jeden Datenbankkontext aktiviert.
|
||
- `npm run db:migrate` richtet mit Baseline-Migration `0000` und den folgenden
|
||
additiven Migrationen das aktuelle Schema ein.
|
||
- `npm run db:verify:circuit-schema` prüft erforderliche und entfernte Spalten.
|
||
- `npm run db:backup` erzeugt ein konsistentes und verifiziertes Online-Backup.
|
||
- `npm run typecheck:scripts` prüft die TypeScript-Wartungsskripte, ohne Code zu
|
||
erzeugen.
|
||
- `npm run typecheck:tests` prüft alle Tests und Testhilfen gegen die aktuellen
|
||
Typen; `tsx` führt Tests sonst ohne Typprüfung aus.
|
||
- Die Baseline ersetzt alle vorigen Entwicklungsmigrationen. Datenbanken,
|
||
Snapshots und persistierte Commands aus Vor-Baseline-Ständen werden bewusst
|
||
nicht unterstützt. Ab der ersten veröffentlichten Version sind angewendete
|
||
Migrationen unveränderlich und Änderungen erfolgen additiv.
|
||
- Allgemeine Circuit-, Gerätezeilen-, CircuitList- und DistributionBoard-
|
||
Repositories stellen im Anwendungspfad nur noch benötigte Leseabfragen bereit.
|
||
Fachliche Schreibvorgänge liegen in den typisierten Command-Repositories.
|
||
- Die vollständige Verteilungs-Testfixture liegt unter
|
||
`tests/support/distribution-board-fixture.ts` und ist kein exportierter
|
||
Produktions-Schreibweg.
|
||
- Die Baseline bildet die relationale Grundlage für geschützte Stromkreisgruppen
|
||
und Verteilerkomponenten. Separate
|
||
1:1-Tabellen halten Stromkreis- und Komponenten-Schutzgeräte. Die früheren
|
||
flachen Stromkreis-Schutzfelder sind aus der Baseline entfernt.
|
||
Ein triggergeführtes Register erzwingt eine normalisierte,
|
||
stromkreislistenweite BMK-Eindeutigkeit über Stromkreise und
|
||
Verteilerkomponenten. Der DB-Index normalisiert dabei nur über SQLites
|
||
eingebautes `lower()` (rein ASCII), erkennt also z.B. `"Ä1"` und `"ä1"` nicht
|
||
als denselben Wert. Die gemeinsame Prüfung
|
||
`src/db/repositories/equipment-identifier-uniqueness.persistence.ts`
|
||
schließt diese Lücke: Sie normalisiert mit JavaScripts Unicode-fähigem
|
||
`toLowerCase()` gegen das vollständige Register der Stromkreisliste und wird
|
||
von jedem Anlage-/Umbenennungspfad für Stromkreise und Verteilerkomponenten
|
||
aufgerufen, auch dort, wo zuvor kein Vorab-Check existierte. Snapshot- und
|
||
Transfer-Integration verwenden aktuell Snapshot-Schema 10. Persistente
|
||
Insert/Delete/Update-Commands für veränderliche Verteilerkomponenten,
|
||
Gruppen einschließlich befüllter Unterbäume sowie vollständige
|
||
Gruppensortierung sind integriert. Der Editor zeigt die geschützte Struktur
|
||
an und bearbeitet veränderliche Gruppen- und Fußkomponenten über dedizierte
|
||
Command-Modale. Gruppenanlage, -umbenennung, -sortierung, explizite
|
||
Neunummerierung, Same-Category-Stromkreiswechsel, geschütztes
|
||
Unterbaumlöschen und Stromkreisschutz sind integriert.
|
||
Migration `0006` ergänzt additiv Indizes auf `circuits.section_id` und
|
||
`circuit_device_rows.circuit_id`, den beiden am häufigsten gefilterten
|
||
Fremdschlüsselspalten sowie den Cascade-Delete-Pfaden von Abschnitten und
|
||
Stromkreisen.
|
||
|
||
PostgreSQL ist bewusst nicht implementiert. Die Domainregeln und
|
||
Transaktionsgrenzen sollen portabel bleiben; Schema und Betriebsmodell benötigen
|
||
bei einem späteren Wechsel trotzdem einen eigenen PostgreSQL-Adapter.
|
||
|
||
## Revit-Anbindung: Linear-XLSX nach R10
|
||
|
||
R3 hat den Backend- und Persistenz-Cutover abgeschlossen. Migration `0014`
|
||
entfernt die alten externen CSV-Entwicklungsdaten, nachdem verknüpfte
|
||
Gerätezeilen auf `manualQuantity` zurückgesetzt wurden. Die neue
|
||
`external_model_configurations`-Tabelle besitzt keinen eigenen Versionszähler;
|
||
Änderungen laufen als Projekt-Commands über `expectedRevision`.
|
||
|
||
Externe Objekte werden je Quelle durch `worksharing_id` exakt und
|
||
case-sensitiv identifiziert. `ifc_guid` ist nullable und nicht eindeutig.
|
||
Importbatches speichern eine Konfigurationskopie; nur der neueste Batch darf
|
||
Originalbytes und die vollständige XLSX-Matrix enthalten, ältere Batches nur
|
||
Datum, Dateiname, SHA-256 und Objektzahl. Snapshot-Schema 10, Restore und
|
||
Projekttransfer verwenden diesen Zustand und remappen alle internen
|
||
Konfigurations-, Gerätearten-, Regel- und Objekt-UUIDs. Leere Snapshots 1–9
|
||
werden hochgestuft; alte Snapshots mit CSV-Konfiguration oder externem
|
||
CSV-Zustand sowie alte CSV-History-Commands werden mit einer deutschen Meldung
|
||
abgelehnt.
|
||
|
||
Der XLSX-Adapter liegt unter `src/external-model/xlsx`. Der Backend-CSV-Parser,
|
||
seine Endpunkte und Laufzeitverträge sind entfernt. Die Route
|
||
`/projects/:projectId/revit` enthält Einstellungen, Importstatus und den
|
||
sechsstufigen Erstimportwizard.
|
||
|
||
`POST /api/projects/:projectId/external-model/initial-import/plan` liest eine
|
||
lokale XLSX zustandslos, gruppiert Quellräume und exakte Familie-und-Typ-Werte
|
||
und liefert die vorhandenen Raum-, Verteiler- und ProjectDevice-Kataloge.
|
||
Unbekannte Familien bleiben blockierend; fehlender GZF wird sichtbar mit 1
|
||
vorbelegt. `apply` erhält die Datei erneut, parst sie erneut und vergleicht den
|
||
Vorschau-SHA-256. Der Command `external-model.apply-initial-import` speichert
|
||
die dabei verwendete Konfiguration und den vollständigen externen Zustand in
|
||
derselben Transaktion. Er erzeugt weder CircuitDeviceRows noch Circuits oder
|
||
BMKs. Seine vollständige Inverse macht Undo/Redo nach Neustart möglich, ohne
|
||
Workbookbytes erneut einzulesen oder zu parsen.
|
||
|
||
R6 bis R9 ergänzen die einmal geladene, clientseitig gefilterte und
|
||
virtualisierte Projektobjektliste, revisionierte Sammelplanung, atomare
|
||
Zuordnung im Stromkreislisteneditor, strukturtreue Rückgabe und den
|
||
vollständigen Folgeimport. Der Folgeimport trennt fachliche Quelländerungen
|
||
von noch nicht nach Revit übernommenen Rückgabewerten, aktualisiert akzeptierte
|
||
Mengen samt Row-Summe und markiert fehlende Objekte ohne ihren Link zu lösen.
|
||
Eine gesondert bestätigte Aktion löscht fehlende Objekte samt Mengenübergang;
|
||
Undo stellt Objekt, Link und Menge wieder her.
|
||
|
||
R10 weist den Ablauf über Rückgabe, Folgeimport, Snapshot-Restore,
|
||
Projektduplikat und Undo/Redo nach Datenbankneustart nach. Persistenzpfade für
|
||
vollständige externe Zustände schreiben große Mengen in begrenzten
|
||
SQLite-Blöcken. Der synthetische Abnahmetest verarbeitet 5.000 Objekte; Details
|
||
und Messwerte stehen in
|
||
[R10-Abnahme](spec/revit-excel-r10-validation.md).
|
||
|
||
## Historische CSV-Unterlagen
|
||
|
||
Der vor R3 vorhandene CSV-/IfcGUID-Laufzeitpfad ist entfernt. Seine damalige
|
||
Ausgangslage bleibt ausschließlich im [R0-Audit](spec/revit-excel-r0-audit.md)
|
||
und in den als abgelöst markierten CSV-Spezifikationen nachvollziehbar. Alte
|
||
CSV-Snapshots und CSV-History werden weiterhin ausdrücklich abgelehnt.
|
||
|
||
## Noch nicht unterstützt
|
||
|
||
- Mehrbenutzerbetrieb und Konfliktauflösung
|
||
- vollständige elektrische Dimensionierung
|
||
- Produktionsdeployment
|
||
|
||
Details: [Bekannte Einschränkungen](circuit-list-editor-known-limitations.md) und
|
||
[Zukunftsarchitektur](project-history-and-external-model-architecture.md).
|