From 44b6fde940bc2ee2d13bee47b0d8d8df8246c77e Mon Sep 17 00:00:00 2001 From: Julian Appel Date: Fri, 31 Jul 2026 17:41:30 +0200 Subject: [PATCH] Add public building project setting --- AGENTS.md | 16 +- README.md | 7 +- docs/circuit-list-editor-api.md | 14 +- docs/current-architecture.md | 21 +- ...history-and-external-model-architecture.md | 5 +- docs/revit-requirements-llm-context.md | 2 +- .../spec/06-future-sizing-and-calculations.md | 6 + docs/spec/07-implementation-phases-todo.md | 6 +- docs/spec/08-current-product-backlog.md | 15 +- ...-board-components-and-protection-groups.md | 12 +- scripts/db-verify-circuit-schema.js | 1 + .../0001_add_public_building_setting.sql | 1 + src/db/migrations/meta/0001_snapshot.json | 1748 +++++++++++++++++ src/db/migrations/meta/_journal.json | 7 + ...ect-settings-project-command.repository.ts | 38 +- ...roject-state-restore-command.repository.ts | 1 + .../project-state-snapshot.persistence.ts | 2 + src/db/repositories/project.repository.ts | 1 + src/db/schema/projects.ts | 3 + .../project-settings-project-command.model.ts | 43 +- .../models/project-state-snapshot.model.ts | 43 +- .../components/project-settings-modal.tsx | 26 + src/frontend/types.ts | 1 + src/frontend/utils/api.ts | 1 + .../validation/project-structure.schemas.ts | 2 + tests/project-command.service.test.ts | 5 + ...ettings-project-command.repository.test.ts | 68 + tests/project-state-snapshot.test.ts | 14 +- tests/project-version-history.test.ts | 2 + 29 files changed, 2057 insertions(+), 54 deletions(-) create mode 100644 src/db/migrations/0001_add_public_building_setting.sql create mode 100644 src/db/migrations/meta/0001_snapshot.json diff --git a/AGENTS.md b/AGENTS.md index f3bdd53..8499827 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -250,9 +250,11 @@ not change the project revision or undo/redo stacks. Restoring a server-stored snapshot verifies its checksum and the current-state hash, replaces supported project data atomically and records a new `restore` revision with a complete inverse command. Restore can therefore be undone and redone after a restart. -Baseline snapshot schema version 1 contains circuit-group identity, -distribution-board components and the separate circuit/component protection -records. Pre-baseline snapshots are unsupported. Portable duplicate imports +Current snapshot schema version 2 additionally stores whether the project is a +public building. Supported baseline version 1 snapshots are upgraded with +`isPublicBuilding = false`; pre-baseline snapshots remain unsupported. Both +versions contain circuit-group identity, distribution-board components and the +separate circuit/component protection records. Portable duplicate imports remap component ids and protection-owner references together with the existing project graph. The central revision boundary creates an automatic logical snapshot after each @@ -284,9 +286,11 @@ global-to-project copy API/UI paths use these persistent commands and track the returned project revision. ProjectDevice synchronization/disconnect API and UI paths do the same; their undo action uses the project-wide history endpoint. Project settings use the persistent `project.update-settings` command. Project -metadata, both voltage defaults and the enabled distribution-board supply types -change in one revision and Undo/Redo restores them together; the project PUT -route requires `expectedRevision`. The system catalog is `AV`, `SV`, `EV`, +metadata, the public-building classification, both voltage defaults and the +enabled distribution-board supply types change in one revision and Undo/Redo +restores them together; the project PUT route requires `expectedRevision`. +The public-building flag is the future sizing input for requiring halogen-free +cables and lines. The system catalog is `AV`, `SV`, `EV`, `USV`, `MSR`, `SiBe`; at least one must be enabled and a type used by a board cannot be disabled. Distribution-board setup uses `distribution-board.insert` schema version 1 diff --git a/README.md b/README.md index 1b60bcd..c333917 100644 --- a/README.md +++ b/README.md @@ -108,9 +108,10 @@ npm run db:generate ``` `db:backup` verwendet die SQLite-Online-Backup-API und prüft das Ergebnis auf -Integrität und Fremdschlüsselverletzungen. Die Migration `0000` ist die -Release-Baseline und richtet ausschließlich eine neue, leere Datenbank ein. -Datenbanken aus Entwicklungsständen vor dieser Baseline werden nicht unterstützt. +Integrität und Fremdschlüsselverletzungen. Migration `0000` ist die +Release-Baseline für eine leere Datenbank; nachfolgende nummerierte Migrationen +entwickeln diese Baseline additiv weiter. Datenbanken aus Entwicklungsständen +vor der Baseline werden nicht unterstützt. ## Dokumentation diff --git a/docs/circuit-list-editor-api.md b/docs/circuit-list-editor-api.md index bd975d1..13afbe9 100644 --- a/docs/circuit-list-editor-api.md +++ b/docs/circuit-list-editor-api.md @@ -73,9 +73,11 @@ Protected-board structure uses `circuit-protection.update`. A subtree restore is normally emitted only as the persisted inverse of a delete. The dispatcher also supports the documented project-device, project settings, distribution-board and project-location -commands. The generic editor commands listed here use schema version `1`; -the clean pre-release baseline does not accept older development formats. -Unsupported types or schema versions are rejected. Insert commands contain the complete entity or circuit block +commands. The generic editor commands listed here use schema version `1`. +`project.update-settings` uses version `2` and still accepts supported baseline +version `1` commands without changing the newer building classification. +Pre-baseline formats remain unsupported. Unsupported types or schema versions +are rejected. Insert commands contain the complete entity or circuit block with stable ids; delete commands include the expected parent identity. Circuit snapshots contain zero, one or multiple complete device rows. Move commands contain each row's expected and target circuit plus its exact expected and @@ -170,12 +172,12 @@ synchronizes linked circuit rows implicitly. - `PUT /projects/:projectId` - request: - `{ "expectedRevision": 12, "singlePhaseVoltageV": 230, "threePhaseVoltageV": 400, "enabledDistributionBoardSupplyTypes": ["AV", "MSR", "SiBe"], ...projectMetadata }` + `{ "expectedRevision": 12, "isPublicBuilding": true, "singlePhaseVoltageV": 230, "threePhaseVoltageV": 400, "enabledDistributionBoardSupplyTypes": ["AV", "MSR", "SiBe"], ...projectMetadata }` - executes `project.update-settings` as one atomic revision - response: `{ "project": { ... }, "revision": { ... }, "history": { ... } }` - - persistent Undo/Redo restores metadata, voltage values and the enabled - distribution-board supply types together; project-device and circuit + - persistent Undo/Redo restores metadata, the public-building flag, voltage + values and enabled distribution-board supply types together; project-device and circuit voltages are recalculated from the restored project settings in the same transaction - project-device and circuit voltage are derived values and are not accepted diff --git a/docs/current-architecture.md b/docs/current-architecture.md index 42f4adc..5e9ce1d 100644 --- a/docs/current-architecture.md +++ b/docs/current-architecture.md @@ -232,8 +232,10 @@ 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, beide Standardspannungen sowie die im -Projekt freigeschalteten Verteiler-Netzarten als eine atomare Änderung. Der +Projektnummer, Bauherr, Beschreibung, 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 @@ -414,12 +416,13 @@ wieder her. 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`. -Das aktuelle Baseline-Snapshot-Schema enthält den Gleichzeitigkeitsfaktor, +Snapshot-Schema 2 enthält zusätzlich 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. Vorherige -Entwicklungsschemas werden nicht mehr eingelesen. +JSON-Importmodi verwenden denselben vollständigen Zustand. Die unterstützte +Baseline-Version 1 wird 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. Ein Geschoss wird nur @@ -447,8 +450,8 @@ Kopieren in ein Projekt erzeugt ein eigenständiges Projektgerät. - SQLite ist die aktuell unterstützte Datenbank. - Fremdschlüssel werden für jeden Datenbankkontext aktiviert. -- `npm run db:migrate` richtet mit der einzelnen Migration `0000` das komplette - aktuelle Schema einer leeren Datenbank ein. +- `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 @@ -469,8 +472,8 @@ Kopieren in ein Projekt erzeugt ein eigenständiges Projektgerät. flachen Stromkreis-Schutzfelder sind aus der Baseline entfernt. Ein triggergeführtes Register erzwingt bereits eine normalisierte, stromkreislistenweite BMK-Eindeutigkeit über Stromkreise und - Verteilerkomponenten. Snapshot- und Transfer-Integration verwenden - Snapshot-Schema 1 der Baseline. Persistente Insert/Delete/Update-Commands für + Verteilerkomponenten. Snapshot- und Transfer-Integration verwenden aktuell + Snapshot-Schema 2. 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 diff --git a/docs/project-history-and-external-model-architecture.md b/docs/project-history-and-external-model-architecture.md index a4d4cbd..4c0a8ea 100644 --- a/docs/project-history-and-external-model-architecture.md +++ b/docs/project-history-and-external-model-architecture.md @@ -262,8 +262,9 @@ Completed foundation: - `project-device.insert` and `project-device.delete` preserve stable device ids; delete undo restores links only from complete unchanged disconnected-row snapshots in the same transaction -- `project.update-settings` persists both project voltage defaults, its exact - inverse, revision and history transition atomically; the project page and +- `project.update-settings` persists the public-building classification and + both project voltage defaults with their exact inverse, revision and history + transition atomically; the project page and existing PUT route require the current expected revision; the same transaction derives every project-device and circuit voltage again, so Undo/Redo cannot leave mixed voltage states diff --git a/docs/revit-requirements-llm-context.md b/docs/revit-requirements-llm-context.md index 965eb47..314820b 100644 --- a/docs/revit-requirements-llm-context.md +++ b/docs/revit-requirements-llm-context.md @@ -52,7 +52,7 @@ Wichtige fachliche Grenzen: Für einen späteren Revit-Austausch besonders relevante Daten sind: - Projekt: Name, interne/externe Projektnummer, Bauherr, Beschreibung, - Projektspannungen. + Kennzeichnung als öffentliches Gebäude und Projektspannungen. - Geschoss und Raum: interne UUID, Name beziehungsweise Raumnummer und Raumname. - Verteilung: interne UUID, Name, Etage, Netzart und Gleichzeitigkeitsfaktor. diff --git a/docs/spec/06-future-sizing-and-calculations.md b/docs/spec/06-future-sizing-and-calculations.md index b9a5c24..f2bb232 100644 --- a/docs/spec/06-future-sizing-and-calculations.md +++ b/docs/spec/06-future-sizing-and-calculations.md @@ -14,12 +14,18 @@ Future suggestions or checks may include: - cable type - number of cores - cable cross-section +- halogen-free cable or line requirement for public buildings - minimum cable cross-section based on current - maximum permissible cable length based on voltage drop - warning messages if cable length, cross-section or protection device do not match Users must remain able to manually override suggestions. +The project-owned `isPublicBuilding` setting is the authoritative input for +the later halogen-free selection rule. The setting is persisted now; this +document does not yet define or implement the complete cable catalog or sizing +algorithm. + ## Initial Design Rule Examples These are common planning defaults, not a replacement for full norm-compliant calculation. diff --git a/docs/spec/07-implementation-phases-todo.md b/docs/spec/07-implementation-phases-todo.md index 3683534..4ebae4f 100644 --- a/docs/spec/07-implementation-phases-todo.md +++ b/docs/spec/07-implementation-phases-todo.md @@ -465,9 +465,9 @@ Implemented foundation: - ProjectDevice synchronization and disconnect endpoints also require `expectedRevision`; their UI undo uses project-wide persistent history and the obsolete direct restore/reconnect write paths are removed -- project voltage settings use `project.update-settings`; both values, inverse, - revision and history transition commit atomically and the former direct - settings write is removed +- project settings use `project.update-settings`; metadata, public-building + classification, voltage values, inverse, revision and history transition + commit atomically and the former direct settings write is removed - distribution-board setup uses `distribution-board.insert` with a complete stable board/list/default-section snapshot; Undo removes only an unchanged empty setup and the former direct controller write is removed diff --git a/docs/spec/08-current-product-backlog.md b/docs/spec/08-current-product-backlog.md index 4a81bb5..c90dd24 100644 --- a/docs/spec/08-current-product-backlog.md +++ b/docs/spec/08-current-product-backlog.md @@ -54,7 +54,7 @@ requirements and intended sequencing, not proof of implementation. The pre-release cleanup established the following compatibility boundary: -- [x] Pre-release databases, portable JSON exports, logical snapshots and +- [x] Pre-baseline databases, portable JSON exports, logical snapshots and persisted command histories are explicitly unsupported. - [x] Migration `0000` builds the complete supported schema on an empty database. @@ -109,9 +109,16 @@ The pre-release cleanup established the following compatibility boundary: device rows with a valid phase are three-phase. - [x] Changing project voltage settings updates all project devices and circuits atomically in the same persistent Undo/Redo step. -- [x] Baseline migration `0000` and project-state schema version `1` store only - the canonical derived values. Pre-release imports are intentionally - unsupported. +- [x] Baseline migration `0000` stores only canonical derived values. + Pre-baseline imports are intentionally unsupported. + +## Building Classification + +- [x] Store whether a project represents a public building. +- [x] Edit the value in the project settings and preserve it through persistent + Undo/Redo, snapshots and portable project transfers. +- [x] Provide the value as an explicit future sizing input for selecting + halogen-free cables and lines; automatic sizing remains deferred. ## Circuit List Power Summary diff --git a/docs/spec/09-distribution-board-components-and-protection-groups.md b/docs/spec/09-distribution-board-components-and-protection-groups.md index a8fe03f..9c7ee11 100644 --- a/docs/spec/09-distribution-board-components-and-protection-groups.md +++ b/docs/spec/09-distribution-board-components-and-protection-groups.md @@ -467,11 +467,11 @@ The supported logical project snapshot and portable JSON transfer must include: - circuit protection-device state - all new component relationships and sort positions -The clean pre-release baseline uses project-state schema version `1`. It -contains the complete supported structure above and is shared by logical -snapshots, restore, Undo/Redo and portable JSON transfer. Payloads from earlier -development schemas are intentionally unsupported and rejected before any -write. +Current project-state schema version `2` contains the complete supported +structure above and the project building classification. It is shared by +logical snapshots, restore, Undo/Redo and portable JSON transfer. Supported +baseline version `1` is upgraded with the non-public default; payloads from +before that baseline are rejected before any write. Migration `0000` creates the complete relational model on an empty database. New defaults apply when boards, groups or circuits are created; they never @@ -545,7 +545,7 @@ Implemented persistence: one-to-one ownership boundaries - a trigger-maintained circuit-list registry rejects normalized BMK collisions across circuits and distribution-board components -- snapshot schema version `1` includes group identity, distribution-board +- snapshot schema version `2` includes group identity, distribution-board components and both one-to-one protection collections - capture, named and automatic snapshots, restore, Undo/Redo and portable JSON transfers preserve the complete new relational state diff --git a/scripts/db-verify-circuit-schema.js b/scripts/db-verify-circuit-schema.js index 6d291f0..318e549 100644 --- a/scripts/db-verify-circuit-schema.js +++ b/scripts/db-verify-circuit-schema.js @@ -78,6 +78,7 @@ const requiredProjectColumns = [ "external_project_number", "building_owner", "description", + "is_public_building", "enabled_distribution_board_supply_types", ]; const projectColumns = new Set( diff --git a/src/db/migrations/0001_add_public_building_setting.sql b/src/db/migrations/0001_add_public_building_setting.sql new file mode 100644 index 0000000..c646f8d --- /dev/null +++ b/src/db/migrations/0001_add_public_building_setting.sql @@ -0,0 +1 @@ +ALTER TABLE `projects` ADD `is_public_building` integer DEFAULT false NOT NULL; \ No newline at end of file diff --git a/src/db/migrations/meta/0001_snapshot.json b/src/db/migrations/meta/0001_snapshot.json new file mode 100644 index 0000000..3cac431 --- /dev/null +++ b/src/db/migrations/meta/0001_snapshot.json @@ -0,0 +1,1748 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "62320e94-1976-4288-8640-d37cfc07f1ee", + "prevId": "2f464eb0-b1c4-4d2d-bfc8-a1466788c961", + "tables": { + "circuit_device_rows": { + "name": "circuit_device_rows", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "circuit_id": { + "name": "circuit_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "linked_project_device_id": { + "name": "linked_project_device_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "phase_type": { + "name": "phase_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "connection_kind": { + "name": "connection_kind", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cost_group": { + "name": "cost_group", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "room_id": { + "name": "room_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "room_number_snapshot": { + "name": "room_number_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "room_name_snapshot": { + "name": "room_name_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "quantity": { + "name": "quantity", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "power_per_unit": { + "name": "power_per_unit", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "simultaneity_factor": { + "name": "simultaneity_factor", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "cos_phi": { + "name": "cos_phi", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "remark": { + "name": "remark", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "overridden_fields": { + "name": "overridden_fields", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "circuit_device_rows_circuit_id_circuits_id_fk": { + "name": "circuit_device_rows_circuit_id_circuits_id_fk", + "tableFrom": "circuit_device_rows", + "tableTo": "circuits", + "columnsFrom": [ + "circuit_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "circuit_device_rows_linked_project_device_id_project_devices_id_fk": { + "name": "circuit_device_rows_linked_project_device_id_project_devices_id_fk", + "tableFrom": "circuit_device_rows", + "tableTo": "project_devices", + "columnsFrom": [ + "linked_project_device_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "circuit_device_rows_room_id_rooms_id_fk": { + "name": "circuit_device_rows_room_id_rooms_id_fk", + "tableFrom": "circuit_device_rows", + "tableTo": "rooms", + "columnsFrom": [ + "room_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "circuit_list_equipment_identifiers": { + "name": "circuit_list_equipment_identifiers", + "columns": { + "owner_type": { + "name": "owner_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "circuit_list_id": { + "name": "circuit_list_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "equipment_identifier": { + "name": "equipment_identifier", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "circuit_list_equipment_identifiers_owner_unique": { + "name": "circuit_list_equipment_identifiers_owner_unique", + "columns": [ + "owner_type", + "owner_id" + ], + "isUnique": true + }, + "circuit_list_equipment_identifiers_list_identifier_unique": { + "name": "circuit_list_equipment_identifiers_list_identifier_unique", + "columns": [ + "circuit_list_id", + "equipment_identifier" + ], + "isUnique": true + } + }, + "foreignKeys": { + "circuit_list_equipment_identifiers_circuit_list_id_circuit_lists_id_fk": { + "name": "circuit_list_equipment_identifiers_circuit_list_id_circuit_lists_id_fk", + "tableFrom": "circuit_list_equipment_identifiers", + "tableTo": "circuit_lists", + "columnsFrom": [ + "circuit_list_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "circuit_lists": { + "name": "circuit_lists", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "distribution_board_id": { + "name": "distribution_board_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "circuit_lists_distribution_board_id_unique": { + "name": "circuit_lists_distribution_board_id_unique", + "columns": [ + "distribution_board_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "circuit_lists_project_id_projects_id_fk": { + "name": "circuit_lists_project_id_projects_id_fk", + "tableFrom": "circuit_lists", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "circuit_lists_distribution_board_id_distribution_boards_id_fk": { + "name": "circuit_lists_distribution_board_id_distribution_boards_id_fk", + "tableFrom": "circuit_lists", + "tableTo": "distribution_boards", + "columnsFrom": [ + "distribution_board_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "circuit_protection_devices": { + "name": "circuit_protection_devices", + "columns": { + "circuit_id": { + "name": "circuit_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "rated_current_a": { + "name": "rated_current_a", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "fuse_utilization_category": { + "name": "fuse_utilization_category", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "trip_characteristic": { + "name": "trip_characteristic", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rcd_type": { + "name": "rcd_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rated_residual_current_ma": { + "name": "rated_residual_current_ma", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "circuit_protection_devices_circuit_id_circuits_id_fk": { + "name": "circuit_protection_devices_circuit_id_circuits_id_fk", + "tableFrom": "circuit_protection_devices", + "tableTo": "circuits", + "columnsFrom": [ + "circuit_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "circuit_sections": { + "name": "circuit_sections", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "circuit_list_id": { + "name": "circuit_list_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "prefix": { + "name": "prefix", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "group_number": { + "name": "group_number", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "circuit_sections_list_key_unique": { + "name": "circuit_sections_list_key_unique", + "columns": [ + "circuit_list_id", + "key" + ], + "isUnique": true + }, + "circuit_sections_list_prefix_unique": { + "name": "circuit_sections_list_prefix_unique", + "columns": [ + "circuit_list_id", + "prefix" + ], + "isUnique": true + }, + "circuit_sections_list_category_group_unique": { + "name": "circuit_sections_list_category_group_unique", + "columns": [ + "circuit_list_id", + "category", + "group_number" + ], + "isUnique": true + } + }, + "foreignKeys": { + "circuit_sections_circuit_list_id_circuit_lists_id_fk": { + "name": "circuit_sections_circuit_list_id_circuit_lists_id_fk", + "tableFrom": "circuit_sections", + "tableTo": "circuit_lists", + "columnsFrom": [ + "circuit_list_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "circuits": { + "name": "circuits", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "circuit_list_id": { + "name": "circuit_list_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "section_id": { + "name": "section_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "equipment_identifier": { + "name": "equipment_identifier", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "cable_type": { + "name": "cable_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cable_cross_section": { + "name": "cable_cross_section", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cable_length": { + "name": "cable_length", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rcd_assignment": { + "name": "rcd_assignment", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "terminal_designation": { + "name": "terminal_designation", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "voltage": { + "name": "voltage", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "control_requirement": { + "name": "control_requirement", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_reserve": { + "name": "is_reserve", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "remark": { + "name": "remark", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "circuits_list_equipment_identifier_unique": { + "name": "circuits_list_equipment_identifier_unique", + "columns": [ + "circuit_list_id", + "equipment_identifier" + ], + "isUnique": true + } + }, + "foreignKeys": { + "circuits_circuit_list_id_circuit_lists_id_fk": { + "name": "circuits_circuit_list_id_circuit_lists_id_fk", + "tableFrom": "circuits", + "tableTo": "circuit_lists", + "columnsFrom": [ + "circuit_list_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "circuits_section_id_circuit_sections_id_fk": { + "name": "circuits_section_id_circuit_sections_id_fk", + "tableFrom": "circuits", + "tableTo": "circuit_sections", + "columnsFrom": [ + "section_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "distribution_board_component_protection_devices": { + "name": "distribution_board_component_protection_devices", + "columns": { + "component_id": { + "name": "component_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "rated_current_a": { + "name": "rated_current_a", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "fuse_utilization_category": { + "name": "fuse_utilization_category", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "trip_characteristic": { + "name": "trip_characteristic", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rcd_type": { + "name": "rcd_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rated_residual_current_ma": { + "name": "rated_residual_current_ma", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "distribution_board_component_protection_devices_component_id_distribution_board_components_id_fk": { + "name": "distribution_board_component_protection_devices_component_id_distribution_board_components_id_fk", + "tableFrom": "distribution_board_component_protection_devices", + "tableTo": "distribution_board_components", + "columnsFrom": [ + "component_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "distribution_board_components": { + "name": "distribution_board_components", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "circuit_list_id": { + "name": "circuit_list_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "section_id": { + "name": "section_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "equipment_identifier": { + "name": "equipment_identifier", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "placement": { + "name": "placement", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + } + }, + "indexes": { + "distribution_board_components_list_identifier_unique": { + "name": "distribution_board_components_list_identifier_unique", + "columns": [ + "circuit_list_id", + "equipment_identifier" + ], + "isUnique": true + }, + "distribution_board_components_section_role_unique": { + "name": "distribution_board_components_section_role_unique", + "columns": [ + "section_id", + "role" + ], + "isUnique": true + } + }, + "foreignKeys": { + "distribution_board_components_circuit_list_id_circuit_lists_id_fk": { + "name": "distribution_board_components_circuit_list_id_circuit_lists_id_fk", + "tableFrom": "distribution_board_components", + "tableTo": "circuit_lists", + "columnsFrom": [ + "circuit_list_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "distribution_board_components_section_id_circuit_sections_id_fk": { + "name": "distribution_board_components_section_id_circuit_sections_id_fk", + "tableFrom": "distribution_board_components", + "tableTo": "circuit_sections", + "columnsFrom": [ + "section_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "distribution_boards": { + "name": "distribution_boards", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "floor_id": { + "name": "floor_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "supply_type": { + "name": "supply_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "simultaneity_factor": { + "name": "simultaneity_factor", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + } + }, + "indexes": {}, + "foreignKeys": { + "distribution_boards_project_id_projects_id_fk": { + "name": "distribution_boards_project_id_projects_id_fk", + "tableFrom": "distribution_boards", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "distribution_boards_floor_id_floors_id_fk": { + "name": "distribution_boards_floor_id_floors_id_fk", + "tableFrom": "distribution_boards", + "tableTo": "floors", + "columnsFrom": [ + "floor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "floors": { + "name": "floors", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "floors_project_id_projects_id_fk": { + "name": "floors_project_id_projects_id_fk", + "tableFrom": "floors", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "global_devices": { + "name": "global_devices", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "quantity": { + "name": "quantity", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "installed_power_per_unit_kw": { + "name": "installed_power_per_unit_kw", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "demand_factor": { + "name": "demand_factor", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "voltage_v": { + "name": "voltage_v", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "phase_count": { + "name": "phase_count", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "power_factor": { + "name": "power_factor", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "note": { + "name": "note", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "project_change_sets": { + "name": "project_change_sets", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "project_revision_id": { + "name": "project_revision_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "command_type": { + "name": "command_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "payload_schema_version": { + "name": "payload_schema_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "forward_payload_json": { + "name": "forward_payload_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inverse_payload_json": { + "name": "inverse_payload_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "project_change_sets_revision_unique": { + "name": "project_change_sets_revision_unique", + "columns": [ + "project_revision_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "project_change_sets_project_revision_id_project_revisions_id_fk": { + "name": "project_change_sets_project_revision_id_project_revisions_id_fk", + "tableFrom": "project_change_sets", + "tableTo": "project_revisions", + "columnsFrom": [ + "project_revision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "project_devices": { + "name": "project_devices", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "phase_type": { + "name": "phase_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'single_phase'" + }, + "connection_kind": { + "name": "connection_kind", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cost_group": { + "name": "cost_group", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "quantity": { + "name": "quantity", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "power_per_unit": { + "name": "power_per_unit", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "simultaneity_factor": { + "name": "simultaneity_factor", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "cos_phi": { + "name": "cos_phi", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "remark": { + "name": "remark", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "voltage_v": { + "name": "voltage_v", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "project_devices_project_id_projects_id_fk": { + "name": "project_devices_project_id_projects_id_fk", + "tableFrom": "project_devices", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "project_history_stack_entries": { + "name": "project_history_stack_entries", + "columns": { + "change_set_id": { + "name": "change_set_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "stack": { + "name": "stack", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "project_history_stack_entries_position_unique": { + "name": "project_history_stack_entries_position_unique", + "columns": [ + "project_id", + "stack", + "position" + ], + "isUnique": true + } + }, + "foreignKeys": { + "project_history_stack_entries_change_set_id_project_change_sets_id_fk": { + "name": "project_history_stack_entries_change_set_id_project_change_sets_id_fk", + "tableFrom": "project_history_stack_entries", + "tableTo": "project_change_sets", + "columnsFrom": [ + "change_set_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "project_history_stack_entries_project_id_projects_id_fk": { + "name": "project_history_stack_entries_project_id_projects_id_fk", + "tableFrom": "project_history_stack_entries", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": { + "project_history_stack_entries_stack_check": { + "name": "project_history_stack_entries_stack_check", + "value": "\"project_history_stack_entries\".\"stack\" in ('undo', 'redo')" + } + } + }, + "project_revisions": { + "name": "project_revisions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "revision_number": { + "name": "revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at_iso": { + "name": "created_at_iso", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "project_revisions_project_number_unique": { + "name": "project_revisions_project_number_unique", + "columns": [ + "project_id", + "revision_number" + ], + "isUnique": true + } + }, + "foreignKeys": { + "project_revisions_project_id_projects_id_fk": { + "name": "project_revisions_project_id_projects_id_fk", + "tableFrom": "project_revisions", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "project_snapshots": { + "name": "project_snapshots", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source_revision": { + "name": "source_revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "schema_version": { + "name": "schema_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'named'" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "payload_json": { + "name": "payload_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "payload_sha256": { + "name": "payload_sha256", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at_iso": { + "name": "created_at_iso", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_by_actor_id": { + "name": "created_by_actor_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "project_snapshots_project_created_idx": { + "name": "project_snapshots_project_created_idx", + "columns": [ + "project_id", + "created_at_iso" + ], + "isUnique": false + }, + "project_snapshots_project_kind_revision_idx": { + "name": "project_snapshots_project_kind_revision_idx", + "columns": [ + "project_id", + "kind", + "source_revision" + ], + "isUnique": false + }, + "project_snapshots_project_name_unique": { + "name": "project_snapshots_project_name_unique", + "columns": [ + "project_id", + "name" + ], + "isUnique": true + } + }, + "foreignKeys": { + "project_snapshots_project_id_projects_id_fk": { + "name": "project_snapshots_project_id_projects_id_fk", + "tableFrom": "project_snapshots", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "projects": { + "name": "projects", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "internal_project_number": { + "name": "internal_project_number", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "external_project_number": { + "name": "external_project_number", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "building_owner": { + "name": "building_owner", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_public_building": { + "name": "is_public_building", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "single_phase_voltage_v": { + "name": "single_phase_voltage_v", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 230 + }, + "three_phase_voltage_v": { + "name": "three_phase_voltage_v", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 400 + }, + "enabled_distribution_board_supply_types": { + "name": "enabled_distribution_board_supply_types", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[\"AV\",\"SV\",\"EV\",\"USV\",\"MSR\",\"SiBe\"]'" + }, + "current_revision": { + "name": "current_revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "rooms": { + "name": "rooms", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "floor_id": { + "name": "floor_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "room_number": { + "name": "room_number", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "room_name": { + "name": "room_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "rooms_project_id_projects_id_fk": { + "name": "rooms_project_id_projects_id_fk", + "tableFrom": "rooms", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "rooms_floor_id_floors_id_fk": { + "name": "rooms_floor_id_floors_id_fk", + "tableFrom": "rooms", + "tableTo": "floors", + "columnsFrom": [ + "floor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/src/db/migrations/meta/_journal.json b/src/db/migrations/meta/_journal.json index 44a9552..63252a5 100644 --- a/src/db/migrations/meta/_journal.json +++ b/src/db/migrations/meta/_journal.json @@ -8,6 +8,13 @@ "when": 1785499119431, "tag": "0000_whole_surge", "breakpoints": true + }, + { + "idx": 1, + "version": "6", + "when": 1785511894897, + "tag": "0001_add_public_building_setting", + "breakpoints": true } ] } \ No newline at end of file diff --git a/src/db/repositories/project-settings-project-command.repository.ts b/src/db/repositories/project-settings-project-command.repository.ts index 9ff2405..01a38f3 100644 --- a/src/db/repositories/project-settings-project-command.repository.ts +++ b/src/db/repositories/project-settings-project-command.repository.ts @@ -40,22 +40,33 @@ export class ProjectSettingsProjectCommandRepository if (!current) { throw new Error("Project not found."); } - const target = input.command.payload; + const target: ProjectSettingsValues = + input.command.schemaVersion === 1 + ? { + ...input.command.payload, + isPublicBuilding: current.isPublicBuilding, + } + : input.command.payload; if (projectSettingsEqual(current, target)) { throw new Error("Project settings did not change."); } - const inverse = createProjectSettingsUpdateProjectCommand({ + const inverseValues: ProjectSettingsValues = { name: current.name, internalProjectNumber: current.internalProjectNumber, externalProjectNumber: current.externalProjectNumber, buildingOwner: current.buildingOwner, description: current.description, + isPublicBuilding: current.isPublicBuilding, singlePhaseVoltageV: current.singlePhaseVoltageV, threePhaseVoltageV: current.threePhaseVoltageV, enabledDistributionBoardSupplyTypes: current.enabledDistributionBoardSupplyTypes, - }); + }; + const inverse = + input.command.schemaVersion === 1 + ? createBaselineProjectSettingsUpdateCommand(inverseValues) + : createProjectSettingsUpdateProjectCommand(inverseValues); assertDisabledSupplyTypesAreUnused(tx, input.projectId, target); const updated = tx .update(projects) @@ -71,6 +82,26 @@ export class ProjectSettingsProjectCommandRepository } } +function createBaselineProjectSettingsUpdateCommand( + values: ProjectSettingsValues +) { + return { + schemaVersion: 1 as const, + type: "project.update-settings" as const, + payload: { + name: values.name, + internalProjectNumber: values.internalProjectNumber, + externalProjectNumber: values.externalProjectNumber, + buildingOwner: values.buildingOwner, + description: values.description, + singlePhaseVoltageV: values.singlePhaseVoltageV, + threePhaseVoltageV: values.threePhaseVoltageV, + enabledDistributionBoardSupplyTypes: + values.enabledDistributionBoardSupplyTypes, + }, + }; +} + function projectSettingsEqual( current: ProjectSettingsValues, target: ProjectSettingsValues @@ -81,6 +112,7 @@ function projectSettingsEqual( current.externalProjectNumber === target.externalProjectNumber && current.buildingOwner === target.buildingOwner && current.description === target.description && + current.isPublicBuilding === target.isPublicBuilding && current.singlePhaseVoltageV === target.singlePhaseVoltageV && current.threePhaseVoltageV === target.threePhaseVoltageV && sameSupplyTypes( diff --git a/src/db/repositories/project-state-restore-command.repository.ts b/src/db/repositories/project-state-restore-command.repository.ts index aa0f05d..e2fde68 100644 --- a/src/db/repositories/project-state-restore-command.repository.ts +++ b/src/db/repositories/project-state-restore-command.repository.ts @@ -208,6 +208,7 @@ function replaceProjectState( externalProjectNumber: state.project.externalProjectNumber, buildingOwner: state.project.buildingOwner, description: state.project.description, + isPublicBuilding: state.project.isPublicBuilding, singlePhaseVoltageV: state.project.singlePhaseVoltageV, threePhaseVoltageV: state.project.threePhaseVoltageV, enabledDistributionBoardSupplyTypes: diff --git a/src/db/repositories/project-state-snapshot.persistence.ts b/src/db/repositories/project-state-snapshot.persistence.ts index f82702c..bb5606d 100644 --- a/src/db/repositories/project-state-snapshot.persistence.ts +++ b/src/db/repositories/project-state-snapshot.persistence.ts @@ -39,6 +39,7 @@ export function readProjectStateSnapshot( externalProjectNumber: projects.externalProjectNumber, buildingOwner: projects.buildingOwner, description: projects.description, + isPublicBuilding: projects.isPublicBuilding, singlePhaseVoltageV: projects.singlePhaseVoltageV, threePhaseVoltageV: projects.threePhaseVoltageV, enabledDistributionBoardSupplyTypes: @@ -233,6 +234,7 @@ export function readProjectStateSnapshot( externalProjectNumber: project.externalProjectNumber, buildingOwner: project.buildingOwner, description: project.description, + isPublicBuilding: project.isPublicBuilding, singlePhaseVoltageV: project.singlePhaseVoltageV, threePhaseVoltageV: project.threePhaseVoltageV, enabledDistributionBoardSupplyTypes: diff --git a/src/db/repositories/project.repository.ts b/src/db/repositories/project.repository.ts index bc96bee..171290e 100644 --- a/src/db/repositories/project.repository.ts +++ b/src/db/repositories/project.repository.ts @@ -25,6 +25,7 @@ export class ProjectRepository { externalProjectNumber: input.externalProjectNumber ?? null, buildingOwner: input.buildingOwner ?? null, description: input.description ?? null, + isPublicBuilding: input.isPublicBuilding ?? false, singlePhaseVoltageV: input.singlePhaseVoltageV ?? 230, threePhaseVoltageV: input.threePhaseVoltageV ?? 400, enabledDistributionBoardSupplyTypes: [ diff --git a/src/db/schema/projects.ts b/src/db/schema/projects.ts index 04671d1..d694258 100644 --- a/src/db/schema/projects.ts +++ b/src/db/schema/projects.ts @@ -9,6 +9,9 @@ export const projects = sqliteTable("projects", { externalProjectNumber: text("external_project_number"), buildingOwner: text("building_owner"), description: text("description"), + isPublicBuilding: integer("is_public_building", { mode: "boolean" }) + .notNull() + .default(false), singlePhaseVoltageV: integer("single_phase_voltage_v").notNull().default(230), threePhaseVoltageV: integer("three_phase_voltage_v").notNull().default(400), enabledDistributionBoardSupplyTypes: text( diff --git a/src/domain/models/project-settings-project-command.model.ts b/src/domain/models/project-settings-project-command.model.ts index 37bb132..69e9c5c 100644 --- a/src/domain/models/project-settings-project-command.model.ts +++ b/src/domain/models/project-settings-project-command.model.ts @@ -6,7 +6,8 @@ import { export const projectSettingsUpdateCommandType = "project.update-settings" as const; -export const projectSettingsUpdateCommandSchemaVersion = 1 as const; +export const projectSettingsUpdateCommandSchemaVersion = 2 as const; +const previousProjectSettingsUpdateCommandSchemaVersion = 1 as const; export interface ProjectSettingsValues { name: string; @@ -14,21 +15,37 @@ export interface ProjectSettingsValues { externalProjectNumber: string | null; buildingOwner: string | null; description: string | null; + isPublicBuilding: boolean; singlePhaseVoltageV: number; threePhaseVoltageV: number; enabledDistributionBoardSupplyTypes: DistributionBoardSupplyType[]; } -export interface ProjectSettingsUpdateProjectCommand +type PreviousProjectSettingsValues = Omit< + ProjectSettingsValues, + "isPublicBuilding" +>; + +export interface CurrentProjectSettingsUpdateProjectCommand extends SerializedProjectCommand { schemaVersion: typeof projectSettingsUpdateCommandSchemaVersion; type: typeof projectSettingsUpdateCommandType; } +interface PreviousProjectSettingsUpdateProjectCommand + extends SerializedProjectCommand { + schemaVersion: typeof previousProjectSettingsUpdateCommandSchemaVersion; + type: typeof projectSettingsUpdateCommandType; +} + +export type ProjectSettingsUpdateProjectCommand = + | CurrentProjectSettingsUpdateProjectCommand + | PreviousProjectSettingsUpdateProjectCommand; + export function createProjectSettingsUpdateProjectCommand( values: ProjectSettingsValues -): ProjectSettingsUpdateProjectCommand { - const command: ProjectSettingsUpdateProjectCommand = { +): CurrentProjectSettingsUpdateProjectCommand { + const command: CurrentProjectSettingsUpdateProjectCommand = { schemaVersion: projectSettingsUpdateCommandSchemaVersion, type: projectSettingsUpdateCommandType, payload: { ...values }, @@ -42,7 +59,8 @@ export function assertProjectSettingsUpdateProjectCommand( ): asserts command is ProjectSettingsUpdateProjectCommand { if ( command.type !== projectSettingsUpdateCommandType || - command.schemaVersion !== projectSettingsUpdateCommandSchemaVersion + (command.schemaVersion !== projectSettingsUpdateCommandSchemaVersion && + command.schemaVersion !== previousProjectSettingsUpdateCommandSchemaVersion) ) { throw new Error("Unsupported project settings update command."); } @@ -58,8 +76,7 @@ export function assertProjectSettingsUpdateProjectCommand( !isNullableTrimmedString(command.payload.buildingOwner, 200) || !isNullableTrimmedString(command.payload.description, 2000) || !isPositiveFiniteNumber(command.payload.singlePhaseVoltageV) || - !isPositiveFiniteNumber(command.payload.threePhaseVoltageV) || - Object.keys(command.payload).length !== 8 + !isPositiveFiniteNumber(command.payload.threePhaseVoltageV) ) { throw new Error( "Project settings update command contains invalid values." @@ -74,6 +91,18 @@ export function assertProjectSettingsUpdateProjectCommand( "Project settings update command contains invalid supply types." ); } + const isPreviousSchema = + command.schemaVersion === previousProjectSettingsUpdateCommandSchemaVersion; + if ( + (isPreviousSchema && Object.keys(command.payload).length !== 8) || + (!isPreviousSchema && + (typeof command.payload.isPublicBuilding !== "boolean" || + Object.keys(command.payload).length !== 9)) + ) { + throw new Error( + "Project settings update command contains invalid values." + ); + } } function isValidSupplyTypes( diff --git a/src/domain/models/project-state-snapshot.model.ts b/src/domain/models/project-state-snapshot.model.ts index 53e2ed2..86d84c4 100644 --- a/src/domain/models/project-state-snapshot.model.ts +++ b/src/domain/models/project-state-snapshot.model.ts @@ -19,13 +19,14 @@ import { resolveProjectVoltage, } from "../services/project-voltage.service.js"; -export const projectStateSnapshotSchemaVersion = 1 as const; +export const projectStateSnapshotSchemaVersion = 2 as const; +const previousProjectStateSnapshotSchemaVersion = 1 as const; const idSchema = z.string().trim().min(1); const nullableStringSchema = z.string().nullable(); const finiteNumberSchema = z.number().finite(); -const projectSchema = z +const previousProjectSchema = z .object({ id: idSchema, name: z.string().trim().min(1), @@ -42,6 +43,10 @@ const projectSchema = z }) .strict(); +const projectSchema = previousProjectSchema + .extend({ isPublicBuilding: z.boolean() }) + .strict(); + const distributionBoardSchema = z .object({ id: idSchema, @@ -267,6 +272,15 @@ export const projectStateSnapshotSchema = z }) .strict(); +const previousProjectStateSnapshotSchema = z + .object({ + schemaVersion: z.literal(previousProjectStateSnapshotSchemaVersion), + project: previousProjectSchema, + distributionBoards: z.array(distributionBoardSchema), + ...projectStateSnapshotContents, + }) + .strict(); + export type ProjectStateSnapshot = z.infer< typeof projectStateSnapshotSchema >; @@ -274,12 +288,35 @@ export type ProjectStateSnapshot = z.infer< export function parseProjectStateSnapshot( value: unknown ): ProjectStateSnapshot { - const parsedSnapshot = projectStateSnapshotSchema.parse(value); + const parsedSnapshot = projectStateSnapshotSchema.parse( + upgradePreviousProjectStateSnapshot(value) + ); const snapshot = normalizeSnapshotPhaseTypes(parsedSnapshot); assertProjectStateSnapshotRelations(snapshot); return snapshot; } +function upgradePreviousProjectStateSnapshot(value: unknown): unknown { + if ( + value === null || + typeof value !== "object" || + Array.isArray(value) || + (value as { schemaVersion?: unknown }).schemaVersion !== + previousProjectStateSnapshotSchemaVersion + ) { + return value; + } + const previous = previousProjectStateSnapshotSchema.parse(value); + return { + ...previous, + schemaVersion: projectStateSnapshotSchemaVersion, + project: { + ...previous.project, + isPublicBuilding: false, + }, + }; +} + function normalizeSnapshotPhaseTypes( snapshot: ProjectStateSnapshot ): ProjectStateSnapshot { diff --git a/src/frontend/components/project-settings-modal.tsx b/src/frontend/components/project-settings-modal.tsx index 228f600..77295df 100644 --- a/src/frontend/components/project-settings-modal.tsx +++ b/src/frontend/components/project-settings-modal.tsx @@ -14,6 +14,7 @@ export interface ProjectSettingsInput { externalProjectNumber: string | null; buildingOwner: string | null; description: string | null; + isPublicBuilding: boolean; singlePhaseVoltageV: number; threePhaseVoltageV: number; enabledDistributionBoardSupplyTypes: DistributionBoardSupplyType[]; @@ -54,6 +55,9 @@ export function ProjectSettingsModal({ const [description, setDescription] = useState( project.description ?? "" ); + const [isPublicBuilding, setIsPublicBuilding] = useState( + project.isPublicBuilding + ); const [singlePhaseVoltageV, setSinglePhaseVoltageV] = useState( String(project.singlePhaseVoltageV) ); @@ -82,6 +86,7 @@ export function ProjectSettingsModal({ externalProjectNumber: toNullableString(externalProjectNumber), buildingOwner: toNullableString(buildingOwner), description: toNullableString(description), + isPublicBuilding, singlePhaseVoltageV: Number(singlePhaseVoltageV), threePhaseVoltageV: Number(threePhaseVoltageV), enabledDistributionBoardSupplyTypes, @@ -269,6 +274,27 @@ export function ProjectSettingsModal({ value={description} /> +
+ +
+ Wird bei der späteren Leitungsauslegung berücksichtigt, + insbesondere bei der Auswahl halogenfreier Kabel und + Leitungen. +
+