Add optional room areas

This commit is contained in:
Julian Appel 2026-09-27 20:00:03 +02:00
parent a6500e2b26
commit dce31ee143
26 changed files with 3669 additions and 78 deletions

View file

@ -13,6 +13,7 @@ zugeordneten Etagen lassen sich nicht löschen.
|---|---|---|
| `buildings` | `id`, `project_id` (FK, cascade), `name`, `sort_order` | Gebäude eines Projekts |
| `floors` | `building_id` (nullable FK → `buildings`, `ON DELETE set null`) | optionales übergeordnetes Gebäude |
| `rooms` | `area_m2` (nullable `REAL`) | optional gepflegte Netto-Raumfläche in m² |
Migration `0009_buildings`. Bestehende Etagen behalten `building_id = null` und
erscheinen in der Oberfläche als „Ohne Gebäude“. Es gibt keine Zwangsmigration
@ -20,6 +21,11 @@ und kein automatisch angelegtes Standardgebäude. Vor dem Anwenden der Migration
auf eine bestehende Datenbank ist wie bei jeder Schemamigration eine separate
Datenbanksicherung anzulegen.
Migration `0011_glossy_norman_osborn` ergänzt ausschließlich das nullable Feld
`rooms.area_m2`. Bestehende Räume behalten damit eine unbekannte Fläche. Vor
dem Anwenden auf eine bestehende Datenbank ist ebenfalls eine separate
Datenbanksicherung erforderlich.
## Befehle, Undo/Redo
Alle Änderungen laufen über die bestehende Orts-Struktur
@ -41,17 +47,22 @@ persistentem Undo/Redo:
Vorwärts- und Inversbefehle sind Version 2. Das ist bei linearer Historie
exakt: Wird ein Gebäude später zugeordnet, liegt dieser neuere Befehl im
Undo-Stapel vor dem alten Befehl und wird zuerst zurückgenommen.
- Raumbefehle bleiben unverändert (Version 1).
- Raumbefehle `project-room.insert/.update/.delete` haben seit Schema-Version
**2** das Feld `areaM2`. Version-1-Befehle werden beim Einlesen mit
`areaM2 = null` hochgestuft. Vor einem alten Update oder Delete wird weiterhin
der vollständige erwartete Datensatz verglichen; eine inzwischen vorhandene
Fläche kann deshalb nicht unbemerkt gelöscht werden.
## Snapshots und Transfer
Projekt-Snapshot-Schema **7** enthält `buildings` und `floors[].buildingId`.
Alle älteren unterstützten Versionen (1–6) werden beim Einlesen mit leerer
Gebäudeliste und `buildingId = null` hochgestuft; gespeicherte Sicherungspunkte
bleiben wiederherstellbar. Die Relationen werden geprüft (Gebäude gehört zum
Projekt, `buildingId` verweist auf ein Gebäude des Snapshots). Restore löscht
und schreibt Gebäude zusammen mit den Etagen; der Projekt-Transfer vergibt neue
Gebäude-IDs und stellt die Etagenverweise um.
Projekt-Snapshot-Schema **8** enthält zusätzlich `rooms[].areaM2`. Version 7
wird mit `areaM2 = null` hochgestuft; die älteren unterstützten Versionen 1–6
werden außerdem mit leerer Gebäudeliste und `buildingId = null` hochgestuft.
Gespeicherte Sicherungspunkte bleiben wiederherstellbar. Die Relationen werden
geprüft (Gebäude gehört zum Projekt, `buildingId` verweist auf ein Gebäude des
Snapshots). Restore und Projekttransfer erhalten die Raumfläche. Das portable
Transferformat bleibt Version 2, weil es den versionierten Projektsnapshot
bereits einbettet; beim Duplizieren werden nur IDs und Relationen umgesetzt.
## API
@ -63,6 +74,10 @@ Gebäude-IDs und stellt die Etagenverweise um.
- `POST/PUT /api/projects/:projectId/floors[/:floorId]` akzeptieren zusätzlich
optional `buildingId` (`string | null`). Fehlt das Feld, bleibt beim Bearbeiten
das bisherige Gebäude erhalten; ältere Clients funktionieren unverändert.
- `POST/PUT /api/projects/:projectId/rooms[/:roomId]` akzeptieren zusätzlich
optional `areaM2` (`number | null`, endlich und ≥ 0). Beim Anlegen bedeutet
ein fehlendes Feld `null`; beim Bearbeiten bleibt die vorhandene Fläche
erhalten, wenn das Feld fehlt.
## Oberfläche
@ -74,10 +89,24 @@ nur dessen Etagen, Klick auf eine Etage nur deren Räume, „Alle“ hebt den Fi
auf. Hinzufügen, Bearbeiten und Löschen laufen über Icon-Buttons wie in der
Stromkreisliste.
Der Raumdialog pflegt die optionale **Fläche [m²]** mit deutscher
Dezimaltrennzeichen-Eingabe. Gemeint ist die Netto-Raumfläche, wie sie der
Revit-Raumparameter „Fläche“ liefert, nicht eine Flächenart nach DIN 277. Die
Fläche wird ohne fachliche Rundung gespeichert und mit zwei Nachkommastellen
angezeigt. Die Gebäudestruktur zeigt die Fläche je Raum sowie Summen je Etage,
je Gebäude, „Ohne Etage“ und „Ohne Gebäude“. Fehlende Flächen werden nicht als
Nullfläche ausgegeben, sondern separat gezählt, damit Teilsummen erkennbar
bleiben.
## Offen
- Revit: Eine Übernahme des Gebäudes aus Revit ist derzeit nicht geplant. Die
Revit-Arbeit ist insgesamt zurückgestellt und läuft später über die
Linear-CSV; das Modell steht einer späteren CSV-Zuordnung nicht im Weg.
- Raumflächen werden derzeit ausschließlich manuell gepflegt. Ein späterer
Revit-Abgleich muss die Phase-14-Regeln mit externem Modellzustand, Vorschau
und ausdrücklicher Bestätigung einhalten.
- Eine W/m²-Auswertung oder Übernahme der Raumfläche in Flächenlasten ist nicht
Teil dieser Funktion; das ist Folgethema von Issue #20.
- Sortierung von Gebäuden per Drag-and-drop ist noch nicht umgesetzt; neue
Gebäude werden hinten angehängt.

View file

@ -67,6 +67,12 @@ Prüfsumme; Ersetzen übernimmt die Grobbilanzen atomar und mit Undo/Redo,
Duplizieren vergibt neue IDs. Transferformat 1 bleibt ohne Grobbilanzen
importierbar.
Das aktuelle Projekt-Snapshot-Schema 8 enthält außerdem die optionale
Netto-Raumfläche `rooms[].areaM2`. Snapshot-Versionen 1–7 werden dafür mit
`areaM2 = null` hochgestuft. Restore, Export und Import erhalten vorhandene
Flächen; eine eigene Transferformat-Version ist nicht nötig, weil Format 2 den
versionierten Projektsnapshot einbettet.
The public dispatcher supports the Circuit and CircuitDeviceRow commands
`circuit.update`, `circuit.insert`, `circuit.delete`,
`circuit-device-row.update`, `circuit-device-row.insert`,
@ -297,19 +303,26 @@ commands. Endpoints, payloads and calculation rules: `docs/estimate-balance.md`.
- rejects floors with assigned rooms or distribution boards
- `POST /projects/:projectId/rooms`
- body:
`{ "floorId": "floor_1", "roomNumber": "001", "roomName": "Technik", "expectedRevision": 14 }`
- executes `project-room.insert` with a stable room id; `floorId` is optional
and must belong to the project when present
`{ "floorId": "floor_1", "roomNumber": "001", "roomName": "Technik", "areaM2": 18.75, "expectedRevision": 14 }`
- executes `project-room.insert` command version 2 with a stable room id;
`floorId` and `areaM2` are optional. An omitted or explicit `null` area
creates a room without area; a value must be finite and non-negative.
- response: `{ "room": { ... }, "revision": { ... }, "history": { ... } }`
- `PUT /projects/:projectId/rooms/:roomId`
- body:
`{ "floorId": null, "roomNumber": "101", "roomName": "Büro", "expectedRevision": 16 }`
- updates number, name and optional floor as one persistent command
`{ "floorId": null, "roomNumber": "101", "roomName": "Büro", "areaM2": null, "expectedRevision": 16 }`
- updates number, name, optional floor and area as one persistent version-2
command. If `areaM2` is omitted, the current area is retained; `null`
explicitly clears it.
- `DELETE /projects/:projectId/rooms/:roomId`
- body: `{ "expectedRevision": 17 }`
- rejects rooms referenced by device rows
- Persistent Undo/Redo restores exact previous and target records without
silently clearing foreign keys.
- Stored version-1 room commands remain executable and are upgraded with
`areaM2 = null`. Exact expected-record checks reject an old update or delete
if a newer command has added an area, so legacy history cannot silently
erase it.
- Stale revisions return `409 PROJECT_REVISION_CONFLICT`.
### Tree Endpoint

View file

@ -464,8 +464,10 @@ Derselbe Befehl versioniert die Felder der Verteiler-Infokarte
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` ist vorerst Freitext.
Snapshot-Schema 7 enthält zusätzlich die Gebäude und die Gebäudezuordnung
der Etagen; ältere Versionen werden ohne Gebäude hochgestuft.
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
@ -483,7 +485,12 @@ Baseline-Version 1 wird zusätzlich mit `isPublicBuilding = false` hochgestuft;
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. Ein Geschoss wird nur
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