forked from jappel/leistungsbilanz-ts
842 lines
54 KiB
Markdown
842 lines
54 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.
|
||
`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`.
|
||
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.
|
||
- 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 9. 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-Grundlage nach R3
|
||
|
||
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 noch sichtbare alte
|
||
Projektseiten-Oberfläche ist bis R4 ohne Backend-Anbindung und wird dort durch
|
||
die Route `/projects/:projectId/revit` ersetzt.
|
||
|
||
### Historische Ausgangslage vor R3
|
||
|
||
Der folgende Text beschreibt den Stand nach R1 und ist keine aktuelle
|
||
Laufzeitdokumentation mehr.
|
||
|
||
Der nachfolgende Abschnitt beschreibt den am Ende von R1 noch vorhandenen
|
||
Legacy-Laufzeitpfad. Er verwendet CSV und IfcGUID und ist die geprüfte
|
||
Ausgangslage für den schrittweisen Cutover; er darf nicht erweitert werden.
|
||
Das verbindliche Ziel ist der XLSX-Austausch des Linear-Plugins mit exakter,
|
||
case-sensitiver Worksharing-ID. Details:
|
||
[Excel-Anforderungen](spec/revit-excel-integration-requirements.md) und
|
||
[R0–R10-Plan](spec/revit-excel-phase-14-plan.md).
|
||
|
||
Der reine Adapter unter `src/external-model/csv` validiert die projektweite
|
||
Transport- und Mappingkonfiguration und parst UTF-8-CSV verlustarm in eine
|
||
klassifizierte Zellmatrix. Er erkennt Metadaten, Header, Passthrough-, Objekt-
|
||
und Verdachtszeilen, prüft IFCGUIDs und serialisiert unveränderte Revit-Exporte
|
||
einschließlich BOM, CRLF und Quotierung bytegleich zurück. Der lokale Befehl
|
||
`npm run revit:verify-reference` prüft die bereitgestellte Referenzdatei.
|
||
|
||
`external_csv_configurations` enthält höchstens eine versionierte Konfiguration
|
||
je Projekt. `external-csv-configuration.update` schreibt Anlage, Änderung oder
|
||
Entfernung zusammen mit Revision und persistentem Undo/Redo. Snapshot-Schema 5–8,
|
||
Restore und portabler Projekttransfer enthalten diesen Zustand; beim Duplizieren
|
||
werden Konfigurations-UUID und Projektlink remapped.
|
||
|
||
Migration `0003` ergänzt die relationale Grundlage für genau eine implizite
|
||
`revit_csv`-Quelle je Projekt, geordnete Importbatches, normalisierte
|
||
Quellraummappings und über `(sourceId, ifcGuid)` eindeutige externe Objekte.
|
||
Batches halten Konfigurationssnapshot, Originalbytes und die klassifizierte
|
||
Zellmatrix zusammen. Migration `0004` ergänzt die monotone projektweite
|
||
Konfigurationsversion am Batch, getrennt von der Schema-Version des
|
||
Konfigurationsformats. Quellwerte, lokale Planungswerte und Override-Metadaten
|
||
der Objekte sind getrennt; optionale Links auf Raum, Verteilung, ProjectDevice
|
||
und CircuitDeviceRow bleiben nullable. `ExternalModelStateRepository` liest den
|
||
vollständigen Zustand deterministisch und unterscheidet unbekannte von noch
|
||
nicht importierten Projekten. Der letzte bestätigte Batch wird aus der
|
||
Batchreihenfolge abgeleitet statt redundant an der Quelle gespeichert.
|
||
Snapshot/Transfer v5 erfasst zusätzlich `manualQuantity`; Version 4 wird mit
|
||
dem bisherigen Gesamtwert als manuellem Anteil hochgestuft. Originalbytes,
|
||
Matrix und alle internen Links werden weiterhin vollständig erfasst,
|
||
stellt sie in Fremdschlüsselreihenfolge wieder her und remappt beim
|
||
Projektduplikat jede interne UUID; IFCGUID und Quelltransport bleiben
|
||
unverändert. `external-import.apply-initial` ist die atomare Schreibgrenze für
|
||
einen bestätigten Erstimport. Vor dem Insert prüft sie den aktuellen
|
||
Konfigurationsstand, SHA-256 und Base64-Kanonizität, parst die Originalbytes
|
||
erneut, vergleicht Matrix, vollständige IFCGUID-Menge und Quellwerte und
|
||
validiert alle Raum-, Verteiler- und ProjectDevice-Links gegen das Projekt.
|
||
Nicht explizit als Override markierte Planungswerte müssen dem regelbasierten
|
||
Vorschlag entsprechen. Der Command erzeugt und verknüpft ausdrücklich keine
|
||
CircuitDeviceRow; Undo entfernt den vollständigen unveränderten Importzustand,
|
||
Redo verwendet dieselben UUIDs und Bytes. Bloße Vorschau und Importplanung
|
||
erzeugen weiterhin keine dieser Zeilen.
|
||
|
||
Migration `0005` trennt bei `CircuitDeviceRow` den manuellen Mengenanteil vom
|
||
weiterhin materialisierten Gesamtwert. Bestehende Daten werden mit
|
||
`manualQuantity = quantity` übernommen. Die Fachregel für spätere externe
|
||
Zuordnungs-Commands lautet `quantity = manualQuantity + Summe der
|
||
effectiveQuantity aller verknüpften ExternalModelObjects`; Link und beide
|
||
Mengenwerte müssen innerhalb desselben Projekt-Commands wechseln.
|
||
`external-object.update-row-assignment` ist diese atomare Schreibgrenze für
|
||
bestehende Gerätezeilen. Sie weist mehrere externe Einzelobjekte zu, verschiebt
|
||
sie zwischen vorhandenen Rows oder löst ihre Links. Vor dem Schreiben werden
|
||
die vollständigen erwarteten Row- und Objektsnapshots, Projektzugehörigkeit,
|
||
Verteilung, Gruppenkategorie, Raum und Selektionskennzeichen geprüft.
|
||
Abweichende lokale Planungswerte benötigen eine explizite Konfliktbestätigung;
|
||
unterschiedliche Räume, Kategorien oder Selektionskennzeichen werden nicht
|
||
still zusammengeführt. Alle betroffenen materialisierten Mengen werden aus dem
|
||
unveränderten manuellen Anteil und sämtlichen Ziel-Objektlinks validiert. Die
|
||
Gegenrichtung enthält dieselben vollständigen Snapshots und funktioniert nach
|
||
Neustart; beim Lösen des letzten Objekts bleibt die Row bestehen.
|
||
`external-object.assign-to-new-row` fügt unzugeordnete Objekte atomar als neue
|
||
Gerätezeile in einen vorhandenen Stromkreis ein. Der manuelle Anteil dieser Row
|
||
ist null; Gesamtmenge, Raum, Verteilung, Kategorie, Selektionskennzeichen und
|
||
Planungswerte werden vor dem Schreiben geprüft. Undo löst exakt diese Objekte
|
||
und löscht die erzeugte Row nur, wenn sie und ihre Linkmenge unverändert sind.
|
||
Der Löschbefehl ist ausschließlich für die Projekthistorie zugelassen; eine
|
||
normale spätere Trennung lässt die Row gemäß der allgemeinen Zuordnungsregel
|
||
bestehen.
|
||
`external-object.assign-to-new-circuit` erzeugt einen neuen Stromkreis mit
|
||
vorab festgeschriebenem BMK, abgeleiteter Projektspannung, genau einer
|
||
externen Row und verpflichtendem Standardschutz zusammen mit den Objektlinks.
|
||
Verteilungs-, Gruppen-, Raum-, Mengen- und Planungsregeln werden vor dem Insert
|
||
gegen den aktuellen Projektstand geprüft. Der historische Gegenbefehl ist kein
|
||
allgemeiner Löschpfad: Er entfernt den vollständigen Circuit-Teilbaum nur bei
|
||
unverändertem Snapshot und unveränderter Objektlinkmenge. Redo verwendet exakt
|
||
dieselben IDs, dasselbe BMK und denselben Schutzsnapshot.
|
||
Die drei Zuordnungsadapter teilen sich
|
||
`external-object-assignment.persistence.ts` für Snapshotvergleich,
|
||
Kompatibilitätsregeln und link-sichere Updates.
|
||
`GET /api/projects/:projectId/circuit-lists/:circuitListId/external-objects`
|
||
liefert die verteilungsbezogene Read-Projektion für den späteren Editor-Drawer.
|
||
Die CircuitList bestimmt den Verteiler serverseitig; die Antwort enthält nur
|
||
dessen externe Objekte, sortiert unzugeordnet vor zugeordnet, sowie kompakte
|
||
Quell-, Raum-, Planungs- und Linkangaben. Originalbytes und vollständige
|
||
Command-Snapshots werden nicht an diese Oberfläche gegeben. Ohne optionalen
|
||
Revit-Import ist die Projektion leer.
|
||
`GET` und `PUT /api/projects/:projectId/external-csv/configuration` lesen oder
|
||
ändern die Konfiguration; der PUT plant Identität und nächsten
|
||
Konfigurationsstand serverseitig und verwendet den typisierten Command.
|
||
`POST /api/projects/:projectId/external-csv/preview` nimmt Dateiname und
|
||
Base64-kodierte CSV bis 18 MB entgegen. Der zustandsfreie Service liefert Hash,
|
||
Dialekt, Header, Klassifikationszahlen, Verdachtszeilen und gemappte
|
||
Objektquellwerte. Er schreibt weder Entwurf noch Projektdaten.
|
||
`POST /api/projects/:projectId/external-csv/initial-import/plan` verwendet
|
||
denselben begrenzten Transport, setzt eine gespeicherte Konfiguration und einen
|
||
noch leeren externen Projektzustand voraus und bleibt ebenfalls zustandsfrei.
|
||
Die Antwort gruppiert Quellräume, schlägt bei genau einer identischen
|
||
Raumnummer den vorhandenen Raum vor, gruppiert exakte Familie-und-Typ-Werte,
|
||
liefert regelbasierte Objektplanungswerte und Konfliktzahlen und enthält die
|
||
vorhandenen Ebenen, Räume, Verteilungen und ProjectDevices als Auswahlkataloge.
|
||
Unbekannte Familien, ungültige Leistungen/Mengen und Objekte ohne Raum bleiben
|
||
sichtbare Warnungen; es wird keine Entscheidung automatisch bestätigt.
|
||
`POST /api/projects/:projectId/external-csv/initial-import/apply` nimmt Datei,
|
||
erwarteten Hash, Konfigurationsversion und Projektrevision erneut entgegen.
|
||
Vollständige Entscheidungen ordnen jeden Quellraum optional einem vorhandenen
|
||
Raum und einer Standardverteilung sowie jede exakte Familiengruppe optional
|
||
einem vorhandenen ProjectDevice zu. Unklassifizierte Familien oder fehlende
|
||
Gruppenentscheidungen blockieren. Der Server parst und plant erneut, erzeugt
|
||
stabile interne UUIDs und übergibt den vollständigen Zustand an
|
||
`external-import.apply-initial`; CSV-Stromkreiswerte bleiben reine Quellwerte
|
||
und alle `circuitDeviceRowId` bleiben `null`.
|
||
Die Projektseite öffnet über „Revit-CSV“ ein deutsches Modal für Dialekt,
|
||
Spaltenzuordnung, zusätzliche Quellfelder und exakte Familie-und-Typ-Regeln.
|
||
Nach dem revisionierten Speichern kann eine lokale CSV gewählt und über den
|
||
zustandsfreien Endpunkt geprüft werden. Die Oberfläche zeigt Kennzahlen und nur
|
||
die ersten 25 erkannten Objekte; die vollständigen Objekte bleiben in der
|
||
Response verfügbar, ohne alle Zeilen in den DOM einzufügen. Der anschließende
|
||
Erstimport-Wizard gruppiert Quellräume und exakte Familie-und-Typ-Werte. Er
|
||
zeigt eindeutige, mehrdeutige und neue Raumtreffer, lässt vorhandene Räume,
|
||
Standardverteilungen und ProjectDevices explizit zuordnen und blockiert die
|
||
Übernahme bei unklassifizierten Familien. Erst nach einem Bestätigungsdialog
|
||
wird der vollständige Import als eine persistente, rückgängig machbare
|
||
Projektrevision angewendet. Dabei entstehen keine Stromkreise oder Gerätezeilen.
|
||
Fehlende Räume und ProjectDevices können direkt aus der jeweiligen Gruppe über
|
||
die regulären Projektformulare angelegt werden. Revit-Werte befüllen diese
|
||
Formulare vor, werden aber vor dem Speichern sichtbar vom Nutzer bestätigt.
|
||
Die Kataloganlage ist jeweils ein eigener Projekt-Command; der Wizard übernimmt
|
||
den neuen Datensatz anschließend in seinen lokalen Auswahlkatalog und markiert
|
||
ihn für die betroffene Gruppe.
|
||
|
||
### Freigegebener Zielpfad ab R2
|
||
|
||
R2 ersetzt zunächst nur den Transportadapter und endet mit einem realen
|
||
Linear-Rückimporttest. R3 bleibt bis zu dessen Freigabe gesperrt. Scheitert der
|
||
ExcelJS-Roundtrip, wird vor R3 ein Adapter vorgeschlagen, der die Original-XLSX
|
||
direkt auf OOXML-Ebene ändert.
|
||
|
||
Der Zielzustand verwendet Worksharing-ID als exakten, case-sensitiven externen
|
||
Schlüssel; IfcGUID bleibt optional. Die Konfiguration hat keinen eigenen
|
||
Versionszähler. `expectedRevision` bindet Vorschau und Apply, Importbatches
|
||
speichern eine Konfigurationskopie. Nur der letzte Batch hält Workbook und
|
||
Matrix vollständig, ältere Batches nur Metadaten. Apply parst die erneut
|
||
übertragene Datei, Undo/Redo arbeitet nur auf persistierten Zuständen.
|
||
|
||
Die eigene Route `/projects/:projectId/revit` lädt eine kompakte Objektliste
|
||
einmal und stellt sie clientseitig gefiltert, sortiert und virtualisiert dar.
|
||
Verknüpfte Objekte folgen dem Verteiler ihrer Row. Row-, Circuit- und
|
||
Gruppenlöschungen lösen externe Links nach Warnung im selben Command; Undo
|
||
stellt sie wieder her. Raum- und Verteilerlöschungen bleiben bei externen
|
||
Referenzen gesperrt.
|
||
|
||
## Noch nicht unterstützt
|
||
|
||
- Mehrbenutzerbetrieb und Konfliktauflösung
|
||
- Linear-XLSX-Transport, eigene Revit-Seite, Editor-Oberfläche für die
|
||
Zuordnungslogik, Folgeimport und strukturtreuer Revit-Rückexport; Umsetzung
|
||
folgt dem R0–R10-Plan
|
||
- vollständige elektrische Dimensionierung
|
||
- Produktionsdeployment
|
||
|
||
Details: [Bekannte Einschränkungen](circuit-list-editor-known-limitations.md) und
|
||
[Zukunftsarchitektur](project-history-and-external-model-architecture.md).
|