diff --git a/compose.yaml b/compose.yaml index 6a5ec7c..d8d787e 100644 --- a/compose.yaml +++ b/compose.yaml @@ -13,7 +13,6 @@ services: CHOKIDAR_USEPOLLING: "true" LOG_LEVEL: "${LOG_LEVEL:-info}" init: true - restart: unless-stopped logging: driver: json-file options: @@ -54,7 +53,6 @@ services: NEXT_TELEMETRY_DISABLED: "1" LOG_LEVEL: "${LOG_LEVEL:-info}" init: true - restart: unless-stopped logging: driver: json-file options: diff --git a/docs/circuit-list-editor-api.md b/docs/circuit-list-editor-api.md index 9ed9e0a..4ed696b 100644 --- a/docs/circuit-list-editor-api.md +++ b/docs/circuit-list-editor-api.md @@ -359,9 +359,8 @@ Response sketch: ### Circuit Structure -- `GET /projects/:projectId/circuit-sections/:sectionId/next-identifier` +- `GET /circuit-sections/:sectionId/next-identifier` - preview next identifier for section (`prefix + maxSuffix + 1`) - - returns 404 if the section does not belong to the given project Circuit and device-row field updates, standalone insertions/deletions, single or bulk device-row moves, circuit reorders and explicit renumbering are diff --git a/docs/current-architecture.md b/docs/current-architecture.md index 92efc98..32f6814 100644 --- a/docs/current-architecture.md +++ b/docs/current-architecture.md @@ -476,28 +476,16 @@ Kopieren in ein Projekt erzeugt ein eigenständiges Projektgerät. 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, + Ein triggergeführtes Register erzwingt bereits 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 5. 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. + Verteilerkomponenten. Snapshot- und Transfer-Integration verwenden aktuell + Snapshot-Schema 5. 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. PostgreSQL ist bewusst nicht implementiert. Die Domainregeln und Transaktionsgrenzen sollen portabel bleiben; Schema und Betriebsmodell benötigen diff --git a/docs/deployment.md b/docs/deployment.md index dfbb3f1..5458a69 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -58,15 +58,6 @@ mit Laufzeit und Speicherverbrauch – nützlich, um Speicherlecks oder Hänger einem 502 über einen längeren Zeitraum nachzuvollziehen. Für die Detailsuche `LOG_LEVEL=debug` setzen; das protokolliert zusätzlich den Start jeder API-Anfrage und macht damit hängende (nie abgeschlossene) Requests sichtbar. -Ein `close`-Ereignis ohne vorheriges `finish` wird als `request aborted before -response finished` (`warn`) geloggt und zeigt damit vom Client oder einem -vorgeschalteten Proxy abgebrochene Verbindungen. - -Eine unbehandelte Exception oder Promise-Rejection wird geloggt und beendet -den jeweiligen Prozess anschließend bewusst (`process.exit(1)`), statt in -einem unbekannten Zustand weiterzulaufen. Beide Dienste laufen deshalb mit -`restart: unless-stopped`, damit Docker sie danach automatisch neu startet; -ohne diese Policy würde ein Crash den Dienst dauerhaft unerreichbar lassen. ## Voraussetzungen für ein späteres Produktionssetup diff --git a/src/app/projects/[projectId]/page.tsx b/src/app/projects/[projectId]/page.tsx index fa342ea..c675340 100644 --- a/src/app/projects/[projectId]/page.tsx +++ b/src/app/projects/[projectId]/page.tsx @@ -17,13 +17,13 @@ import { deleteDistributionBoard, disconnectProjectDeviceRows, exportProjectTransfer, - getProject, getProjectDeviceSyncPreview, listCircuitLists, listDistributionBoards, listFloors, listGlobalDevices, listProjectDevices, + listProjects, listRooms, importProjectTransfer, synchronizeProjectDeviceRows, @@ -129,7 +129,7 @@ export default function ProjectDetailPage() { return; } Promise.all([ - getProject(projectId), + listProjects(), listDistributionBoards(projectId), listCircuitLists(projectId), listFloors(projectId), @@ -138,7 +138,7 @@ export default function ProjectDetailPage() { listGlobalDevices(), ]) .then(([ - currentProject, + projects, distributionBoards, loadedCircuitLists, loadedFloors, @@ -146,6 +146,7 @@ export default function ProjectDetailPage() { loadedProjectDevices, loadedGlobalDevices, ]) => { + const currentProject = projects.find((item) => item.id === projectId) ?? null; setProject(currentProject); setBoards(distributionBoards); setCircuitLists(loadedCircuitLists); diff --git a/src/db/migrations/0006_damp_skrulls.sql b/src/db/migrations/0006_damp_skrulls.sql deleted file mode 100644 index 3f6badf..0000000 --- a/src/db/migrations/0006_damp_skrulls.sql +++ /dev/null @@ -1,2 +0,0 @@ -CREATE INDEX `circuit_device_rows_circuit_id_idx` ON `circuit_device_rows` (`circuit_id`);--> statement-breakpoint -CREATE INDEX `circuits_section_id_idx` ON `circuits` (`section_id`); \ No newline at end of file diff --git a/src/db/migrations/meta/0006_snapshot.json b/src/db/migrations/meta/0006_snapshot.json deleted file mode 100644 index 84f5229..0000000 --- a/src/db/migrations/meta/0006_snapshot.json +++ /dev/null @@ -1,2419 +0,0 @@ -{ - "version": "6", - "dialect": "sqlite", - "id": "b25e1a40-c400-4848-bc24-a04bda7a8209", - "prevId": "b79934fe-5d05-49da-b7c4-95c954e5fec2", - "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 - }, - "manual_quantity": { - "name": "manual_quantity", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": 0 - }, - "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": { - "circuit_device_rows_circuit_id_idx": { - "name": "circuit_device_rows_circuit_id_idx", - "columns": [ - "circuit_id" - ], - "isUnique": false - } - }, - "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_section_id_idx": { - "name": "circuits_section_id_idx", - "columns": [ - "section_id" - ], - "isUnique": false - }, - "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": {} - }, - "external_csv_configurations": { - "name": "external_csv_configurations", - "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 - }, - "configuration_version": { - "name": "configuration_version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "configuration": { - "name": "configuration", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "external_csv_configurations_project_id_unique": { - "name": "external_csv_configurations_project_id_unique", - "columns": [ - "project_id" - ], - "isUnique": true - } - }, - "foreignKeys": { - "external_csv_configurations_project_id_projects_id_fk": { - "name": "external_csv_configurations_project_id_projects_id_fk", - "tableFrom": "external_csv_configurations", - "tableTo": "projects", - "columnsFrom": [ - "project_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "external_import_batches": { - "name": "external_import_batches", - "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_id": { - "name": "source_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "import_kind": { - "name": "import_kind", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "imported_at_iso": { - "name": "imported_at_iso", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "file_name": { - "name": "file_name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "sha256": { - "name": "sha256", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "applied_project_revision": { - "name": "applied_project_revision", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "configuration_version": { - "name": "configuration_version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "configuration_snapshot": { - "name": "configuration_snapshot", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "original_bytes": { - "name": "original_bytes", - "type": "blob", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "document": { - "name": "document", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "external_import_batches_project_revision_idx": { - "name": "external_import_batches_project_revision_idx", - "columns": [ - "project_id", - "applied_project_revision" - ], - "isUnique": false - }, - "external_import_batches_source_imported_idx": { - "name": "external_import_batches_source_imported_idx", - "columns": [ - "source_id", - "imported_at_iso" - ], - "isUnique": false - } - }, - "foreignKeys": { - "external_import_batches_project_id_projects_id_fk": { - "name": "external_import_batches_project_id_projects_id_fk", - "tableFrom": "external_import_batches", - "tableTo": "projects", - "columnsFrom": [ - "project_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "external_import_batches_source_id_external_model_sources_id_fk": { - "name": "external_import_batches_source_id_external_model_sources_id_fk", - "tableFrom": "external_import_batches", - "tableTo": "external_model_sources", - "columnsFrom": [ - "source_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "external_model_objects": { - "name": "external_model_objects", - "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_id": { - "name": "source_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "ifc_guid": { - "name": "ifc_guid", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "last_seen_import_batch_id": { - "name": "last_seen_import_batch_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "last_accepted_import_batch_id": { - "name": "last_accepted_import_batch_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "accepted_source_values": { - "name": "accepted_source_values", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "planning_values": { - "name": "planning_values", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "overridden_fields": { - "name": "overridden_fields", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "external_room_mapping_id": { - "name": "external_room_mapping_id", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "distribution_board_id": { - "name": "distribution_board_id", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "linked_project_device_id": { - "name": "linked_project_device_id", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "circuit_device_row_id": { - "name": "circuit_device_row_id", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "presence_status": { - "name": "presence_status", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'present'" - } - }, - "indexes": { - "external_model_objects_source_ifc_guid_unique": { - "name": "external_model_objects_source_ifc_guid_unique", - "columns": [ - "source_id", - "ifc_guid" - ], - "isUnique": true - }, - "external_model_objects_project_presence_idx": { - "name": "external_model_objects_project_presence_idx", - "columns": [ - "project_id", - "presence_status" - ], - "isUnique": false - }, - "external_model_objects_distribution_board_idx": { - "name": "external_model_objects_distribution_board_idx", - "columns": [ - "distribution_board_id" - ], - "isUnique": false - }, - "external_model_objects_circuit_device_row_idx": { - "name": "external_model_objects_circuit_device_row_idx", - "columns": [ - "circuit_device_row_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "external_model_objects_project_id_projects_id_fk": { - "name": "external_model_objects_project_id_projects_id_fk", - "tableFrom": "external_model_objects", - "tableTo": "projects", - "columnsFrom": [ - "project_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "external_model_objects_source_id_external_model_sources_id_fk": { - "name": "external_model_objects_source_id_external_model_sources_id_fk", - "tableFrom": "external_model_objects", - "tableTo": "external_model_sources", - "columnsFrom": [ - "source_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "external_model_objects_last_seen_import_batch_id_external_import_batches_id_fk": { - "name": "external_model_objects_last_seen_import_batch_id_external_import_batches_id_fk", - "tableFrom": "external_model_objects", - "tableTo": "external_import_batches", - "columnsFrom": [ - "last_seen_import_batch_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "no action" - }, - "external_model_objects_last_accepted_import_batch_id_external_import_batches_id_fk": { - "name": "external_model_objects_last_accepted_import_batch_id_external_import_batches_id_fk", - "tableFrom": "external_model_objects", - "tableTo": "external_import_batches", - "columnsFrom": [ - "last_accepted_import_batch_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "no action" - }, - "external_model_objects_external_room_mapping_id_external_room_mappings_id_fk": { - "name": "external_model_objects_external_room_mapping_id_external_room_mappings_id_fk", - "tableFrom": "external_model_objects", - "tableTo": "external_room_mappings", - "columnsFrom": [ - "external_room_mapping_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - }, - "external_model_objects_distribution_board_id_distribution_boards_id_fk": { - "name": "external_model_objects_distribution_board_id_distribution_boards_id_fk", - "tableFrom": "external_model_objects", - "tableTo": "distribution_boards", - "columnsFrom": [ - "distribution_board_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - }, - "external_model_objects_linked_project_device_id_project_devices_id_fk": { - "name": "external_model_objects_linked_project_device_id_project_devices_id_fk", - "tableFrom": "external_model_objects", - "tableTo": "project_devices", - "columnsFrom": [ - "linked_project_device_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - }, - "external_model_objects_circuit_device_row_id_circuit_device_rows_id_fk": { - "name": "external_model_objects_circuit_device_row_id_circuit_device_rows_id_fk", - "tableFrom": "external_model_objects", - "tableTo": "circuit_device_rows", - "columnsFrom": [ - "circuit_device_row_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "external_model_sources": { - "name": "external_model_sources", - "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 - }, - "source_type": { - "name": "source_type", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'revit_csv'" - } - }, - "indexes": { - "external_model_sources_project_type_unique": { - "name": "external_model_sources_project_type_unique", - "columns": [ - "project_id", - "source_type" - ], - "isUnique": true - } - }, - "foreignKeys": { - "external_model_sources_project_id_projects_id_fk": { - "name": "external_model_sources_project_id_projects_id_fk", - "tableFrom": "external_model_sources", - "tableTo": "projects", - "columnsFrom": [ - "project_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "external_room_mappings": { - "name": "external_room_mappings", - "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_id": { - "name": "source_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "normalized_source_room_key": { - "name": "normalized_source_room_key", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "source_floor_name": { - "name": "source_floor_name", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "source_room_number": { - "name": "source_room_number", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "source_room_name": { - "name": "source_room_name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "room_id": { - "name": "room_id", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "default_distribution_board_id": { - "name": "default_distribution_board_id", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": { - "external_room_mappings_source_key_unique": { - "name": "external_room_mappings_source_key_unique", - "columns": [ - "source_id", - "normalized_source_room_key" - ], - "isUnique": true - }, - "external_room_mappings_project_room_idx": { - "name": "external_room_mappings_project_room_idx", - "columns": [ - "project_id", - "room_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "external_room_mappings_project_id_projects_id_fk": { - "name": "external_room_mappings_project_id_projects_id_fk", - "tableFrom": "external_room_mappings", - "tableTo": "projects", - "columnsFrom": [ - "project_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "external_room_mappings_source_id_external_model_sources_id_fk": { - "name": "external_room_mappings_source_id_external_model_sources_id_fk", - "tableFrom": "external_room_mappings", - "tableTo": "external_model_sources", - "columnsFrom": [ - "source_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "external_room_mappings_room_id_rooms_id_fk": { - "name": "external_room_mappings_room_id_rooms_id_fk", - "tableFrom": "external_room_mappings", - "tableTo": "rooms", - "columnsFrom": [ - "room_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - }, - "external_room_mappings_default_distribution_board_id_distribution_boards_id_fk": { - "name": "external_room_mappings_default_distribution_board_id_distribution_boards_id_fk", - "tableFrom": "external_room_mappings", - "tableTo": "distribution_boards", - "columnsFrom": [ - "default_distribution_board_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 eedfc7a..5a65548 100644 --- a/src/db/migrations/meta/_journal.json +++ b/src/db/migrations/meta/_journal.json @@ -43,13 +43,6 @@ "when": 1785687503453, "tag": "0005_stale_gorilla_man", "breakpoints": true - }, - { - "idx": 6, - "version": "6", - "when": 1786043080323, - "tag": "0006_damp_skrulls", - "breakpoints": true } ] } \ No newline at end of file diff --git a/src/db/repositories/circuit-device-row.persistence.ts b/src/db/repositories/circuit-device-row.persistence.ts index 4a32623..c075f61 100644 --- a/src/db/repositories/circuit-device-row.persistence.ts +++ b/src/db/repositories/circuit-device-row.persistence.ts @@ -43,6 +43,12 @@ export interface CircuitDeviceRowPatchInput { overriddenFields?: string | null; } +export interface CircuitDeviceRowCreateInput extends CircuitDeviceRowUpdateInput { + circuitId: string; + linkedProjectDeviceId?: string; + sortOrder: number; +} + export function toCircuitDeviceRowUpdateValues(input: CircuitDeviceRowUpdateInput) { return { linkedProjectDeviceId: input.linkedProjectDeviceId ?? null, @@ -99,3 +105,15 @@ export function toCircuitDeviceRowPatchValues(input: CircuitDeviceRowPatchInput) return values; } + +export function toCircuitDeviceRowCreateValues( + id: string, + input: CircuitDeviceRowCreateInput +) { + return { + id, + circuitId: input.circuitId, + sortOrder: input.sortOrder, + ...toCircuitDeviceRowUpdateValues(input), + }; +} diff --git a/src/db/repositories/circuit-project-command.repository.ts b/src/db/repositories/circuit-project-command.repository.ts index bbd6543..6f04ffb 100644 --- a/src/db/repositories/circuit-project-command.repository.ts +++ b/src/db/repositories/circuit-project-command.repository.ts @@ -1,4 +1,4 @@ -import { and, eq } from "drizzle-orm"; +import { and, eq, ne } from "drizzle-orm"; import { assertCircuitUpdateProjectCommand, createCircuitUpdateProjectCommand, @@ -21,7 +21,6 @@ import { import { executeProjectCommandTransactionWithAppliedForward } from "./project-command-transaction.persistence.js"; import { resolveCircuitVoltage } from "./project-voltage.persistence.js"; import { circuitDeviceRows } from "../schema/circuit-device-rows.js"; -import { assertEquipmentIdentifierAvailable } from "./equipment-identifier-uniqueness.persistence.js"; type CircuitRow = typeof circuits.$inferSelect; @@ -167,12 +166,20 @@ export class CircuitProjectCommandRepository ) { return; } - assertEquipmentIdentifierAvailable( - database, - circuit.circuitListId, - equipmentIdentifier, - circuit.id - ); + const duplicate = database + .select({ id: circuits.id }) + .from(circuits) + .where( + and( + eq(circuits.circuitListId, circuit.circuitListId), + eq(circuits.equipmentIdentifier, equipmentIdentifier), + ne(circuits.id, circuit.id) + ) + ) + .get(); + if (duplicate) { + throw new Error("Duplicate equipmentIdentifier in circuit list."); + } } } diff --git a/src/db/repositories/circuit-structure-project-command.repository.ts b/src/db/repositories/circuit-structure-project-command.repository.ts index 9b8941d..454028e 100644 --- a/src/db/repositories/circuit-structure-project-command.repository.ts +++ b/src/db/repositories/circuit-structure-project-command.repository.ts @@ -27,7 +27,6 @@ import { } from "./circuit-device-row-structure.persistence.js"; import { executeProjectCommandTransaction } from "./project-command-transaction.persistence.js"; import { resolveCircuitVoltage } from "./project-voltage.persistence.js"; -import { assertEquipmentIdentifierAvailable } from "./equipment-identifier-uniqueness.persistence.js"; export class CircuitStructureProjectCommandRepository implements CircuitStructureProjectCommandStore @@ -104,11 +103,24 @@ export class CircuitStructureProjectCommandRepository if (existingCircuit) { throw new Error("Circuit id already exists."); } - assertEquipmentIdentifierAvailable( - database, - snapshot.circuitListId, - snapshot.equipmentIdentifier - ); + const duplicateIdentifier = database + .select({ id: circuits.id }) + .from(circuits) + .where( + and( + eq(circuits.circuitListId, snapshot.circuitListId), + eq( + circuits.equipmentIdentifier, + snapshot.equipmentIdentifier + ) + ) + ) + .get(); + if (duplicateIdentifier) { + throw new Error( + "Duplicate equipmentIdentifier in circuit list." + ); + } if (snapshot.deviceRows.length > 0) { const rowIds = snapshot.deviceRows.map((row) => row.id); diff --git a/src/db/repositories/distribution-board-component-structure-project-command.repository.ts b/src/db/repositories/distribution-board-component-structure-project-command.repository.ts index 3ea4291..2dc3bba 100644 --- a/src/db/repositories/distribution-board-component-structure-project-command.repository.ts +++ b/src/db/repositories/distribution-board-component-structure-project-command.repository.ts @@ -22,7 +22,6 @@ import { circuitSections } from "../schema/circuit-sections.js"; import { distributionBoardComponentProtectionDevices } from "../schema/distribution-board-component-protection-devices.js"; import { distributionBoardComponents } from "../schema/distribution-board-components.js"; import { executeProjectCommandTransaction } from "./project-command-transaction.persistence.js"; -import { assertEquipmentIdentifierAvailable } from "./equipment-identifier-uniqueness.persistence.js"; export class DistributionBoardComponentStructureProjectCommandRepository implements DistributionBoardComponentStructureProjectCommandStore @@ -95,11 +94,6 @@ export class DistributionBoardComponentStructureProjectCommandRepository if (existing) { throw new Error("Distribution-board component id already exists."); } - assertEquipmentIdentifierAvailable( - database, - snapshot.component.circuitListId, - snapshot.component.equipmentIdentifier - ); database .insert(distributionBoardComponents) .values(snapshot.component) @@ -193,17 +187,6 @@ export class DistributionBoardComponentStructureProjectCommandRepository "Distribution-board component changed before update." ); } - if ( - target.component.equipmentIdentifier !== - expected.component.equipmentIdentifier - ) { - assertEquipmentIdentifierAvailable( - database, - expected.component.circuitListId, - target.component.equipmentIdentifier, - expected.component.id - ); - } database .update(distributionBoardComponents) .set(target.component) diff --git a/src/db/repositories/equipment-identifier-uniqueness.persistence.ts b/src/db/repositories/equipment-identifier-uniqueness.persistence.ts deleted file mode 100644 index 43b63ad..0000000 --- a/src/db/repositories/equipment-identifier-uniqueness.persistence.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { eq } from "drizzle-orm"; -import type { AppDatabase } from "../database-context.js"; -import { circuitListEquipmentIdentifiers } from "../schema/circuit-list-equipment-identifiers.js"; - -export function normalizeEquipmentIdentifier(value: string): string { - return value.trim().toLowerCase(); -} - -/** - * The DB-level normalized unique index uses SQLite's built-in lower(), - * which only folds ASCII a-z and leaves German characters (Ä/Ö/Ü/ß/…) - * untouched, so it alone would let e.g. "Ä1" and "ä1" coexist. This check - * normalizes with JS's Unicode-aware toLowerCase() against every - * identifier already registered for the circuit list (circuits and - * distribution-board components share one BMK namespace via - * circuit_list_equipment_identifiers), catching what the DB index cannot. - */ -export function assertEquipmentIdentifierAvailable( - database: AppDatabase, - circuitListId: string, - equipmentIdentifier: string, - excludeOwnerId?: string -): void { - const candidate = normalizeEquipmentIdentifier(equipmentIdentifier); - const existing = database - .select({ - ownerId: circuitListEquipmentIdentifiers.ownerId, - equipmentIdentifier: circuitListEquipmentIdentifiers.equipmentIdentifier, - }) - .from(circuitListEquipmentIdentifiers) - .where(eq(circuitListEquipmentIdentifiers.circuitListId, circuitListId)) - .all(); - const duplicate = existing.some( - (row) => - row.ownerId !== excludeOwnerId && - normalizeEquipmentIdentifier(row.equipmentIdentifier) === candidate - ); - if (duplicate) { - throw new Error("Duplicate equipmentIdentifier in circuit list."); - } -} diff --git a/src/db/repositories/external-initial-import-project-command.repository.ts b/src/db/repositories/external-initial-import-project-command.repository.ts index 1f2281e..9d2895a 100644 --- a/src/db/repositories/external-initial-import-project-command.repository.ts +++ b/src/db/repositories/external-initial-import-project-command.repository.ts @@ -212,9 +212,7 @@ function replaceExternalState( if (target.roomMappings.length) { database.insert(externalRoomMappings).values(target.roomMappings).run(); } - if (target.objects.length) { - database.insert(externalModelObjects).values(target.objects).run(); - } + database.insert(externalModelObjects).values(target.objects).run(); } function decodeCanonicalBase64(value: string) { diff --git a/src/db/repositories/external-object-new-circuit-project-command.repository.ts b/src/db/repositories/external-object-new-circuit-project-command.repository.ts index bea64ff..c25ac76 100644 --- a/src/db/repositories/external-object-new-circuit-project-command.repository.ts +++ b/src/db/repositories/external-object-new-circuit-project-command.repository.ts @@ -32,7 +32,6 @@ import { loadExpectedExternalObjectTransitions, snapshotsEqual, } from "./external-object-assignment.persistence.js"; -import { assertEquipmentIdentifierAvailable } from "./equipment-identifier-uniqueness.persistence.js"; export class ExternalObjectNewCircuitProjectCommandRepository implements ExternalObjectNewCircuitProjectCommandStore @@ -91,11 +90,12 @@ export class ExternalObjectNewCircuitProjectCommandRepository .where(eq(circuits.id, circuit.id)).get()) { throw new Error("External circuit id already exists."); } - assertEquipmentIdentifierAvailable( - database, - circuit.circuitListId, - circuit.equipmentIdentifier - ); + if (database.select({ id: circuits.id }).from(circuits).where(and( + eq(circuits.circuitListId, circuit.circuitListId), + eq(circuits.equipmentIdentifier, circuit.equipmentIdentifier) + )).get()) { + throw new Error("Duplicate equipmentIdentifier in circuit list."); + } if (database.select({ id: circuitDeviceRows.id }).from(circuitDeviceRows) .where(eq(circuitDeviceRows.id, row.id)).get()) { throw new Error("External device-row id already exists."); diff --git a/src/db/schema/circuit-device-rows.ts b/src/db/schema/circuit-device-rows.ts index f718c5d..a5da01e 100644 --- a/src/db/schema/circuit-device-rows.ts +++ b/src/db/schema/circuit-device-rows.ts @@ -1,38 +1,35 @@ -import { index, integer, real, sqliteTable, text } from "drizzle-orm/sqlite-core"; +import { integer, real, sqliteTable, text } from "drizzle-orm/sqlite-core"; import { circuits } from "./circuits.js"; import { projectDevices } from "./project-devices.js"; import { rooms } from "./rooms.js"; -export const circuitDeviceRows = sqliteTable( - "circuit_device_rows", - { - id: text("id").primaryKey(), - circuitId: text("circuit_id") - .notNull() - .references(() => circuits.id, { onDelete: "cascade" }), - linkedProjectDeviceId: text("linked_project_device_id").references(() => projectDevices.id, { - onDelete: "set null", - }), - sortOrder: integer("sort_order").notNull().default(0), - name: text("name").notNull(), - displayName: text("display_name").notNull(), - phaseType: text("phase_type"), - connectionKind: text("connection_kind"), - costGroup: text("cost_group"), - category: text("category"), - level: text("level"), - roomId: text("room_id").references(() => rooms.id, { - onDelete: "set null", - }), - roomNumberSnapshot: text("room_number_snapshot"), - roomNameSnapshot: text("room_name_snapshot"), - quantity: integer("quantity").notNull(), - manualQuantity: integer("manual_quantity").notNull().default(0), - powerPerUnit: real("power_per_unit").notNull(), - simultaneityFactor: real("simultaneity_factor").notNull(), - cosPhi: real("cos_phi"), - remark: text("remark"), - overriddenFields: text("overridden_fields"), - }, - (table) => [index("circuit_device_rows_circuit_id_idx").on(table.circuitId)] -); +export const circuitDeviceRows = sqliteTable("circuit_device_rows", { + id: text("id").primaryKey(), + circuitId: text("circuit_id") + .notNull() + .references(() => circuits.id, { onDelete: "cascade" }), + linkedProjectDeviceId: text("linked_project_device_id").references(() => projectDevices.id, { + onDelete: "set null", + }), + sortOrder: integer("sort_order").notNull().default(0), + name: text("name").notNull(), + displayName: text("display_name").notNull(), + phaseType: text("phase_type"), + connectionKind: text("connection_kind"), + costGroup: text("cost_group"), + category: text("category"), + level: text("level"), + roomId: text("room_id").references(() => rooms.id, { + onDelete: "set null", + }), + roomNumberSnapshot: text("room_number_snapshot"), + roomNameSnapshot: text("room_name_snapshot"), + quantity: integer("quantity").notNull(), + manualQuantity: integer("manual_quantity").notNull().default(0), + powerPerUnit: real("power_per_unit").notNull(), + simultaneityFactor: real("simultaneity_factor").notNull(), + cosPhi: real("cos_phi"), + remark: text("remark"), + overriddenFields: text("overridden_fields"), +}); + diff --git a/src/db/schema/circuits.ts b/src/db/schema/circuits.ts index 6e3ed3e..c0c733c 100644 --- a/src/db/schema/circuits.ts +++ b/src/db/schema/circuits.ts @@ -1,4 +1,4 @@ -import { index, integer, real, sqliteTable, text, unique } from "drizzle-orm/sqlite-core"; +import { integer, real, sqliteTable, text, unique } from "drizzle-orm/sqlite-core"; import { circuitLists } from "./circuit-lists.js"; import { circuitSections } from "./circuit-sections.js"; @@ -26,9 +26,6 @@ export const circuits = sqliteTable( isReserve: integer("is_reserve").notNull().default(0), remark: text("remark"), }, - (table) => [ - unique("circuits_list_equipment_identifier_unique").on(table.circuitListId, table.equipmentIdentifier), - index("circuits_section_id_idx").on(table.sectionId), - ] + (table) => [unique("circuits_list_equipment_identifier_unique").on(table.circuitListId, table.equipmentIdentifier)] ); diff --git a/src/domain/models/circuit-device-row-project-command.model.ts b/src/domain/models/circuit-device-row-project-command.model.ts index 8e949a1..825701f 100644 --- a/src/domain/models/circuit-device-row-project-command.model.ts +++ b/src/domain/models/circuit-device-row-project-command.model.ts @@ -144,9 +144,6 @@ function assertCircuitDeviceRowUpdateFieldValue( if (typeof value !== "number" || !Number.isFinite(value) || value < 0) { throw new Error(`${field} must be a non-negative finite number.`); } - if (field === "simultaneityFactor" && value > 1) { - throw new Error("simultaneityFactor must not exceed 1."); - } return; } if (field === "cosPhi") { diff --git a/src/domain/models/circuit-device-row-structure-project-command.model.ts b/src/domain/models/circuit-device-row-structure-project-command.model.ts index 2464a27..d6e3cf6 100644 --- a/src/domain/models/circuit-device-row-structure-project-command.model.ts +++ b/src/domain/models/circuit-device-row-structure-project-command.model.ts @@ -134,9 +134,6 @@ export function assertCircuitDeviceRowInsertProjectCommand( row.simultaneityFactor, "row.simultaneityFactor" ); - if (row.simultaneityFactor > 1) { - throw new Error("row.simultaneityFactor must not exceed 1."); - } if (row.cosPhi !== null) { assertFiniteNumber(row.cosPhi, "row.cosPhi"); if (row.cosPhi <= 0) { diff --git a/src/domain/models/circuit-device-row.model.ts b/src/domain/models/circuit-device-row.model.ts new file mode 100644 index 0000000..f18c63c --- /dev/null +++ b/src/domain/models/circuit-device-row.model.ts @@ -0,0 +1,24 @@ +export interface CircuitDeviceRow { + id: string; + circuitId: string; + linkedProjectDeviceId?: string; + sortOrder: number; + name: string; + displayName: string; + phaseType?: string; + connectionKind?: string; + costGroup?: string; + category?: string; + level?: string; + roomId?: string; + roomNumberSnapshot?: string; + roomNameSnapshot?: string; + quantity: number; + manualQuantity: number; + powerPerUnit: number; + simultaneityFactor: number; + cosPhi?: number; + remark?: string; + overriddenFields?: string; +} + diff --git a/src/domain/models/circuit-protection-project-command.model.ts b/src/domain/models/circuit-protection-project-command.model.ts index 0c8a19e..2e8e6f4 100644 --- a/src/domain/models/circuit-protection-project-command.model.ts +++ b/src/domain/models/circuit-protection-project-command.model.ts @@ -71,33 +71,15 @@ export function assertCircuitProtectionUpdateProjectCommand( if (target !== null) { assertCircuitProtectionSnapshot(target, circuitId); } - if (circuitProtectionSnapshotsEqual(expected, target)) { + if (JSON.stringify(expected) === JSON.stringify(target)) { throw new Error("Circuit protection update must change state."); } } -function circuitProtectionSnapshotsEqual( - left: CircuitProtectionSnapshot | null, - right: CircuitProtectionSnapshot | null -): boolean { - if (left === null || right === null) { - return left === right; - } - return ( - left.circuitId === right.circuitId && - left.type === right.type && - left.ratedCurrentA === right.ratedCurrentA && - left.fuseUtilizationCategory === right.fuseUtilizationCategory && - left.tripCharacteristic === right.tripCharacteristic && - left.rcdType === right.rcdType && - left.ratedResidualCurrentMa === right.ratedResidualCurrentMa - ); -} - export function assertCircuitProtectionSnapshot( value: unknown, circuitId: string -): asserts value is CircuitProtectionSnapshot { +) { if ( !isPlainObject(value) || Object.keys(value).length !== 7 || diff --git a/src/domain/models/circuit-section.model.ts b/src/domain/models/circuit-section.model.ts new file mode 100644 index 0000000..bba2c4e --- /dev/null +++ b/src/domain/models/circuit-section.model.ts @@ -0,0 +1,9 @@ +export interface CircuitSection { + id: string; + circuitListId: string; + key: string; + displayName: string; + prefix: string; + sortOrder: number; +} + diff --git a/src/domain/models/circuit.model.ts b/src/domain/models/circuit.model.ts new file mode 100644 index 0000000..3efe12c --- /dev/null +++ b/src/domain/models/circuit.model.ts @@ -0,0 +1,19 @@ +export interface Circuit { + id: string; + circuitListId: string; + sectionId: string; + equipmentIdentifier: string; + displayName?: string; + sortOrder: number; + cableType?: string; + cableCrossSection?: string; + cableLength?: number; + rcdAssignment?: string; + terminalDesignation?: string; + voltage?: number; + controlRequirement?: string; + status?: string; + isReserve: boolean; + remark?: string; +} + diff --git a/src/domain/models/project-state-snapshot.model.ts b/src/domain/models/project-state-snapshot.model.ts index 7172cb7..060c795 100644 --- a/src/domain/models/project-state-snapshot.model.ts +++ b/src/domain/models/project-state-snapshot.model.ts @@ -130,7 +130,7 @@ const circuitDeviceRowSchema = z.preprocess( quantity: finiteNumberSchema.nonnegative(), manualQuantity: finiteNumberSchema.nonnegative(), powerPerUnit: finiteNumberSchema.nonnegative(), - simultaneityFactor: finiteNumberSchema.min(0).max(1), + simultaneityFactor: finiteNumberSchema.nonnegative(), cosPhi: finiteNumberSchema.positive().nullable(), remark: nullableStringSchema, overriddenFields: nullableStringSchema, diff --git a/src/frontend/components/circuit-tree-editor.tsx b/src/frontend/components/circuit-tree-editor.tsx index 03aa38d..b63cd8e 100644 --- a/src/frontend/components/circuit-tree-editor.tsx +++ b/src/frontend/components/circuit-tree-editor.tsx @@ -268,10 +268,6 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str const [editingCell, setEditingCell] = useState(null); const [activeSectionId, setActiveSectionId] = useState(null); const [isSaving, setIsSaving] = useState(false); - // Synchronous re-entry guard: isSaving is React state and only reflects - // reality after the next render, so a second click/drop fired within the - // same tick could otherwise race past it and double-submit a command. - const commandInFlightRef = useRef(false); const [componentEditorIntent, setComponentEditorIntent] = useState(null); const [circuitGroupEditorIntent, setCircuitGroupEditorIntent] = @@ -727,27 +723,6 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str ); }, [data]); - // Clears the sidebar's target selection once it no longer resolves to a - // real section/circuit (e.g. deleted, moved or renumbered elsewhere) - // instead of silently holding a stale id after a tree reload. - useEffect(() => { - if (!data) { - return; - } - if ( - targetSectionId && - !data.sections.some((section) => section.id === targetSectionId) - ) { - setTargetSectionId(null); - } - if ( - targetCircuitId && - !circuitOptions.some((option) => option.id === targetCircuitId) - ) { - setTargetCircuitId(null); - } - }, [data, circuitOptions, targetSectionId, targetCircuitId]); - const allCircuits = useMemo( () => data?.sections.flatMap((section) => section.circuits) ?? [], [data] @@ -1071,10 +1046,6 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str // Runs a normal command. The server records it in project-wide history. async function runCommand(command: HistoryCommand) { - if (commandInFlightRef.current) { - return; - } - commandInFlightRef.current = true; try { setError(null); setIsSaving(true); @@ -1085,7 +1056,6 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str await loadTree({ showLoading: false }); setError(message); } finally { - commandInFlightRef.current = false; setIsSaving(false); } } @@ -1093,10 +1063,6 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str // Applies the next eligible project-wide history operation. Selection is only // a best-effort local hint; command eligibility and data changes stay server-owned. async function applyHistory(mode: "undo" | "redo") { - if (commandInFlightRef.current) { - return; - } - commandInFlightRef.current = true; try { setError(null); setHistoryBusy(true); @@ -1122,7 +1088,6 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str await loadTree({ showLoading: false }); setError(message); } finally { - commandInFlightRef.current = false; setIsSaving(false); setHistoryBusy(false); } @@ -1779,7 +1744,7 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str if (!section) { throw new Error("Bereich wurde nicht gefunden."); } - const next = await getNextCircuitIdentifier(projectId, sectionId); + const next = await getNextCircuitIdentifier(sectionId); const sortOrder = section.circuits.length > 0 ? Math.max(...section.circuits.map((circuit) => circuit.sortOrder)) + 10 : 10; const isDeviceField = deviceFieldKeys.has(key); @@ -1968,7 +1933,7 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str await runCommand({ label: "Stromkreis hinzufügen", redo: async () => { - const next = await getNextCircuitIdentifier(projectId, sectionId); + const next = await getNextCircuitIdentifier(sectionId); const sortOrder = getInsertionSortOrder(section.circuits, afterCircuitId); const circuit = createCircuitSnapshot({ sectionId, @@ -2162,7 +2127,7 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str if (!section) { throw new Error("Der Zielbereich ist ungültig."); } - const next = await getNextCircuitIdentifier(projectId, sectionId); + const next = await getNextCircuitIdentifier(sectionId); const sortOrder = section.circuits.length > 0 ? Math.max(...section.circuits.map((circuit) => circuit.sortOrder)) + 10 : 10; const circuit = createCircuitSnapshot( @@ -2573,7 +2538,7 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str await runCommand({ label: newCircuitLabel, redo: async () => { - const next = await getNextCircuitIdentifier(projectId, intent.sectionId); + const next = await getNextCircuitIdentifier(intent.sectionId); const sortOrder = intent.targetCircuitId && intent.placement ? getAdjacentInsertionSortOrder( @@ -3734,7 +3699,6 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str type="button" tabIndex={-1} disabled={ - isSaving || !buildCircuitGroupReorderAssignments( data.sections, section.id, @@ -3752,7 +3716,6 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str type="button" tabIndex={-1} disabled={ - isSaving || !buildCircuitGroupReorderAssignments( data.sections, section.id, @@ -3770,7 +3733,6 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str type="button" tabIndex={-1} disabled={ - isSaving || hasActiveSortOrFilter || !section.category || !canRenumberCircuitGroups( @@ -3796,7 +3758,6 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str - ) : null} diff --git a/src/frontend/components/form-modal.tsx b/src/frontend/components/form-modal.tsx index d893690..05e18eb 100644 --- a/src/frontend/components/form-modal.tsx +++ b/src/frontend/components/form-modal.tsx @@ -1,6 +1,6 @@ "use client"; -import React, { type FormEvent, type ReactNode, useEffect, useRef } from "react"; +import React, { type FormEvent, type ReactNode } from "react"; interface FormModalProps { children: ReactNode; @@ -14,9 +14,6 @@ interface FormModalProps { title: string; } -const FOCUSABLE_SELECTOR = - 'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])'; - export function FormModal({ children, description, @@ -28,56 +25,11 @@ export function FormModal({ submitLabel, title, }: FormModalProps) { - const dialogRef = useRef(null); - - // Focuses the dialog on open and returns focus to the element that - // triggered it on close, so keyboard users never lose their place in the - // grid behind the backdrop. - useEffect(() => { - const previouslyFocused = document.activeElement as HTMLElement | null; - const firstFocusable = - dialogRef.current?.querySelector(FOCUSABLE_SELECTOR); - firstFocusable?.focus(); - return () => { - previouslyFocused?.focus(); - }; - }, []); - - function handleKeyDown(event: React.KeyboardEvent) { - if (event.key === "Escape") { - if (!isSaving) { - event.stopPropagation(); - onClose(); - } - return; - } - if (event.key !== "Tab" || !dialogRef.current) { - return; - } - const focusable = Array.from( - dialogRef.current.querySelectorAll(FOCUSABLE_SELECTOR) - ); - if (focusable.length === 0) { - return; - } - const first = focusable[0]; - const last = focusable[focusable.length - 1]; - if (event.shiftKey && document.activeElement === first) { - event.preventDefault(); - last.focus(); - } else if (!event.shiftKey && document.activeElement === last) { - event.preventDefault(); - first.focus(); - } - } - return ( <>
diff --git a/src/frontend/components/project-settings-modal.tsx b/src/frontend/components/project-settings-modal.tsx index 4af751c..77295df 100644 --- a/src/frontend/components/project-settings-modal.tsx +++ b/src/frontend/components/project-settings-modal.tsx @@ -7,7 +7,6 @@ import { distributionBoardSupplyTypes, type DistributionBoardSupplyType, } from "../../shared/constants/distribution-board"; -import { FormModal } from "./form-modal"; export interface ProjectSettingsInput { name: string; @@ -132,278 +131,319 @@ export function ProjectSettingsModal({ } return ( - -
-
- - setName(event.target.value)} - required - value={name} - /> -
-
- - - setInternalProjectNumber(event.target.value) - } - value={internalProjectNumber} - /> -
-
- - - setExternalProjectNumber(event.target.value) - } - value={externalProjectNumber} - /> -
-
-
- - Verwendete Netzarten - -

- Nur ausgewählte Netzarten stehen bei Verteilungen zur - Auswahl. Bereits verwendete Netzarten können nicht - deaktiviert werden. -

-
- {distributionBoardSupplyTypes.map((supplyType) => ( -
+ <> +
+
+
+
+
+

+ Projekteinstellungen +

+

+ Stammdaten und elektrische Standardwerte des Projekts +

+
+
+
+
+
+ + setName(event.target.value)} + required + value={name} + /> +
+
+ + + setInternalProjectNumber(event.target.value) + } + value={internalProjectNumber} + /> +
+
+ + + setExternalProjectNumber(event.target.value) + } + value={externalProjectNumber} + /> +
+
+
+ + Verwendete Netzarten + +

+ Nur ausgewählte Netzarten stehen bei Verteilungen zur + Auswahl. Bereits verwendete Netzarten können nicht + deaktiviert werden. +

+
+ {distributionBoardSupplyTypes.map((supplyType) => ( +
+ +
+ ))} +
+ {enabledDistributionBoardSupplyTypes.length === 0 ? ( +
+ Mindestens eine Netzart muss aktiviert sein. +
+ ) : null} +
+
+
+ + setBuildingOwner(event.target.value)} + value={buildingOwner} + /> +
+
+ +