diff --git a/AGENTS.md b/AGENTS.md index b3fd1d5..69de0d1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -283,17 +283,23 @@ that have remained unchanged. ProjectDevice create, update, delete and 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 voltage settings use the persistent `project.update-settings` command. -Both voltage defaults change in one revision and Undo/Redo restores them -together; the project PUT route requires `expectedRevision`. +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`, +`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` with a complete stable snapshot of the board, circuit list and four default sections. Its inverse removes only the same unchanged and still-empty structure; the POST route requires `expectedRevision` and returns the updated history state. +Distribution-board floor assignment and a project-enabled supply type use +`distribution-board.update`; both values are snapshot/export fields and one +persistent undo step. Floor and room setup use `project-floor.insert` and `project-room.insert` with complete stable snapshots. Their inverses remove only unchanged records and -reject floors with assigned rooms or rooms referenced by device rows or -retained upgrade data. Both POST routes require `expectedRevision` and return +reject floors with assigned rooms/distribution boards or rooms referenced by +device rows or retained upgrade data. Both POST routes require `expectedRevision` and return the updated history state. Required operations: diff --git a/docs/circuit-list-editor-api.md b/docs/circuit-list-editor-api.md index ea952cb..ac87c28 100644 --- a/docs/circuit-list-editor-api.md +++ b/docs/circuit-list-editor-api.md @@ -145,11 +145,14 @@ synchronizes linked circuit rows implicitly. - `PUT /projects/:projectId` - request: - `{ "expectedRevision": 12, "singlePhaseVoltageV": 230, "threePhaseVoltageV": 400 }` + `{ "expectedRevision": 12, "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 both voltage values together + - persistent Undo/Redo restores metadata, voltage values and the enabled + distribution-board supply types together + - at least one of `AV`, `SV`, `EV`, `USV`, `MSR`, `SiBe` must be enabled; + a supply type currently used by a distribution board cannot be disabled - unchanged values are rejected without creating a revision; a stale revision returns `409 PROJECT_REVISION_CONFLICT` @@ -181,7 +184,11 @@ returns HTTP `409` with `PROJECT_HISTORY_OPERATION_UNAVAILABLE`. ### Distribution Board Setup - `POST /projects/:projectId/distribution-boards` - - body: `{ "name": "UV-02", "expectedRevision": 12 }` + - body: + `{ "name": "UV-02", "floorId": "floor_1", "supplyType": "SV", "expectedRevision": 12 }` + - `floorId` may be `null`; a non-null floor must belong to the project + - `supplyType` is one of `AV`, `SV`, `EV`, `USV`, `MSR` or `SiBe` and must + be enabled in the project settings - executes `distribution-board.insert` with stable ids for the distribution board, its circuit list and all four default circuit sections - response: @@ -189,6 +196,11 @@ returns HTTP `409` with `PROJECT_HISTORY_OPERATION_UNAVAILABLE`. - persistent Undo removes only the unchanged and still-empty generated structure; Redo restores the same ids - stale revisions return `409 PROJECT_REVISION_CONFLICT` +- `PUT /projects/:projectId/distribution-boards/:distributionBoardId` + - body: + `{ "floorId": null, "supplyType": "AV", "expectedRevision": 13 }` + - executes `distribution-board.update`; floor and supply type are restored + together by persistent Undo/Redo ### Project Floors and Rooms @@ -203,8 +215,9 @@ returns HTTP `409` with `PROJECT_HISTORY_OPERATION_UNAVAILABLE`. and must belong to the project when present - response: `{ "room": { ... }, "revision": { ... }, "history": { ... } }` - Persistent Undo removes only unchanged, unreferenced records. A floor with - assigned rooms and a room referenced by device rows or retained upgrade data - are rejected instead of silently clearing foreign keys. + assigned rooms or distribution boards and a room referenced by device rows + or retained upgrade data are rejected instead of silently clearing foreign + keys. - Stale revisions return `409 PROJECT_REVISION_CONFLICT`. ### Tree Endpoint diff --git a/docs/current-architecture.md b/docs/current-architecture.md index c30c00e..56df731 100644 --- a/docs/current-architecture.md +++ b/docs/current-architecture.md @@ -221,8 +221,11 @@ 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 sowie beide Standardspannungen als eine -atomare Änderung. Die Projektseite bearbeitet diese Angaben in einem +Projektnummer, Bauherr, Beschreibung, beide Standardspannungen sowie die im +Projekt freigeschalteten Verteiler-Netzarten als eine atomare Änderung. Der +Systemkatalog besteht aus `AV`, `SV`, `EV`, `USV`, `MSR` und `SiBe`; mindestens +eine Netzart muss aktiv bleiben und eine bereits von einer Verteilung +verwendete Netzart kann nicht deaktiviert werden. Die Projektseite bearbeitet diese Angaben in einem beschrifteten Einstellungsmodal statt in einer permanenten Formularkarte. Der Store leitet das inverse Kommando aus dem gespeicherten Projekt ab und schreibt Werte, Revision und Historienstapel gemeinsam. `PUT /api/projects/:projectId` @@ -258,11 +261,21 @@ Alle UUIDs entstehen vor dem Command und bleiben über Undo/Redo stabil. `distribution-board.delete` ist die persistierte Inverse und entfernt nur den vollständig unveränderten, weiterhin stromkreislosen Block. Controller und Projektseite übergeben die erwartete Projektrevision; der frühere direkte -Controller-Schreibweg ist entfernt. +Controller-Schreibweg ist entfernt. Verteilungen besitzen eine optionale +Etagenreferenz sowie eine Netzart aus dem Projektkatalog. Anlage und +nachträgliche Bearbeitung prüfen die Projektzugehörigkeit der Etage und die +Freigabe der Netzart in den Projekteinstellungen. +`distribution-board.update` versioniert Etage und Netzart gemeinsam und stellt +beide Werte über dauerhaftes Undo/Redo wieder her. Snapshot-Schema 4 und der +portable Projekttransfer enthalten diese Felder; Schema 1/2 sowie gespeicherte +Version-1-Strukturcommands werden ohne erfundene Zuordnung hochgestuft. +Snapshot-Schema 3 wird mit allen sechs Netzarten als Projektauswahl +hochgestuft. `project-floor.insert` und `project-room.insert` versionieren die Anlage von Geschossen und Räumen mit stabilen UUIDs. Die vollständigen Datensätze bilden jeweils die persistierte Inverse für Undo/Redo. Ein Geschoss wird durch Undo nur -entfernt, solange ihm kein Raum zugeordnet wurde. Ein Raum wird nur entfernt, +entfernt, solange ihm weder ein Raum noch eine Verteilung zugeordnet wurde. Ein +Raum wird nur entfernt, solange weder eine CircuitDeviceRow noch ein aufbewahrter Upgrade-Datensatz auf ihn verweist. Beide POST-Endpunkte verlangen `expectedRevision`, liefern den aktualisierten Historienstand und besitzen keinen direkten Create-Schreibweg diff --git a/docs/spec/08-current-product-backlog.md b/docs/spec/08-current-product-backlog.md index c24bd5b..9c54f76 100644 --- a/docs/spec/08-current-product-backlog.md +++ b/docs/spec/08-current-product-backlog.md @@ -25,6 +25,11 @@ requirements and intended sequencing, not proof of implementation. - `SV` (Sicherheitsstromversorgung) - `EV` (Ersatzstromversorgung) - `USV` (Unterbrechungsfreie Stromversorgung) + - `MSR` (Mess-, Steuerungs- und Regelungstechnik) + - `SiBe` (Sicherheitsbeleuchtung) +- Let project settings select which catalog supply types are used in a + project. Distribution-board dialogs offer only that selection; a type in + use cannot be disabled. - Include both fields in project snapshots and portable project export/import. - Persist changes through project commands so they remain undoable after a diff --git a/scripts/db-verify-circuit-schema.js b/scripts/db-verify-circuit-schema.js index 2159898..0a811c2 100644 --- a/scripts/db-verify-circuit-schema.js +++ b/scripts/db-verify-circuit-schema.js @@ -53,6 +53,7 @@ const requiredProjectColumns = [ "external_project_number", "building_owner", "description", + "enabled_distribution_board_supply_types", ]; const projectColumns = new Set( db.prepare("PRAGMA table_info(projects)").all().map((column) => column.name) @@ -60,6 +61,19 @@ const projectColumns = new Set( const missingProjectColumns = requiredProjectColumns.filter( (name) => !projectColumns.has(name) ); +const requiredDistributionBoardColumns = ["floor_id", "supply_type"]; +const distributionBoardColumns = new Set( + db + .prepare("PRAGMA table_info(distribution_boards)") + .all() + .map((column) => column.name) +); +const missingDistributionBoardColumns = + requiredDistributionBoardColumns.filter( + (name) => !distributionBoardColumns.has(name) + ); +const integrityCheck = db.pragma("integrity_check", { simple: true }); +const foreignKeyViolations = db.pragma("foreign_key_check"); console.log("Database:", dbPath); console.log("Required tables:", requiredTables.join(", ")); @@ -67,6 +81,12 @@ console.log("Existing tables:", [...existing].join(", ") || "(none)"); console.log("Required project-device columns:", requiredProjectDeviceColumns.join(", ")); console.log("Required circuit columns:", requiredCircuitColumns.join(", ")); console.log("Required project columns:", requiredProjectColumns.join(", ")); +console.log( + "Required distribution-board columns:", + requiredDistributionBoardColumns.join(", ") +); +console.log("Integrity:", integrityCheck); +console.log("Foreign-key violations:", foreignKeyViolations.length); if (missing.length > 0) { console.error("Missing tables:", missing.join(", ")); @@ -96,4 +116,17 @@ if (missingProjectColumns.length > 0) { process.exit(1); } +if (missingDistributionBoardColumns.length > 0) { + console.error( + "Missing distribution-board columns:", + missingDistributionBoardColumns.join(", ") + ); + process.exit(1); +} + +if (integrityCheck !== "ok" || foreignKeyViolations.length > 0) { + console.error("Database integrity or foreign-key verification failed."); + process.exit(1); +} + console.log("Circuit-first schema verification passed."); diff --git a/src/app/projects/[projectId]/page.tsx b/src/app/projects/[projectId]/page.tsx index 32feec3..915b6fb 100644 --- a/src/app/projects/[projectId]/page.tsx +++ b/src/app/projects/[projectId]/page.tsx @@ -23,6 +23,7 @@ import { listRooms, importProjectTransfer, synchronizeProjectDeviceRows, + updateDistributionBoard, updateProjectDevice, updateProjectSettings, } from "../../../frontend/utils/api"; @@ -41,6 +42,10 @@ import { projectDeviceSyncFields, type ProjectDeviceSyncField, } from "../../../shared/constants/project-device-sync-fields"; +import { + distributionBoardSupplyTypeLabels, + type DistributionBoardSupplyType, +} from "../../../shared/constants/distribution-board"; import { ProjectVersionHistory } from "../../../frontend/components/project-version-history"; import { ProjectSettingsModal, @@ -74,6 +79,14 @@ export default function ProjectDetailPage() { const [projectDevices, setProjectDevices] = useState([]); const [globalDevices, setGlobalDevices] = useState([]); const [boardName, setBoardName] = useState(""); + const [boardFloorId, setBoardFloorId] = useState(""); + const [boardSupplyType, setBoardSupplyType] = + useState("AV"); + const [editingBoard, setEditingBoard] = + useState(null); + const [editingBoardFloorId, setEditingBoardFloorId] = useState(""); + const [editingBoardSupplyType, setEditingBoardSupplyType] = + useState("AV"); const [floorName, setFloorName] = useState(""); const [roomNumber, setRoomNumber] = useState(""); const [roomName, setRoomName] = useState(""); @@ -158,6 +171,24 @@ export default function ProjectDetailPage() { () => new Map(circuitLists.map((circuitList) => [circuitList.distributionBoardId, circuitList])), [circuitLists] ); + const enabledBoardSupplyTypes = + project?.enabledDistributionBoardSupplyTypes ?? []; + const usedBoardSupplyTypes = useMemo( + () => + [ + ...new Set( + boards + .map((board) => board.supplyType) + .filter( + ( + supplyType + ): supplyType is DistributionBoardSupplyType => + supplyType !== null + ) + ), + ], + [boards] + ); function applyProjectRevision(currentRevision: number) { setProject((current) => @@ -175,7 +206,11 @@ export default function ProjectDetailPage() { try { const result = await createDistributionBoard( projectId, - boardName.trim(), + { + name: boardName.trim(), + floorId: boardFloorId || null, + supplyType: boardSupplyType, + }, project.currentRevision ); setBoards((current) => [ @@ -185,6 +220,10 @@ export default function ProjectDetailPage() { setCircuitLists(await listCircuitLists(projectId)); applyProjectRevision(result.history.currentRevision); setBoardName(""); + setBoardFloorId(""); + setBoardSupplyType( + project.enabledDistributionBoardSupplyTypes[0] + ); setStructureModal(null); } catch (err) { setError(err instanceof Error ? err.message : "Verteilung konnte nicht erstellt werden."); @@ -351,6 +390,60 @@ export default function ProjectDetailPage() { } } + function openBoardEditor(board: DistributionBoardDto) { + setEditingBoard(board); + setEditingBoardFloorId(board.floorId ?? ""); + setEditingBoardSupplyType( + board.supplyType ?? + project?.enabledDistributionBoardSupplyTypes[0] ?? + "AV" + ); + } + + function openBoardCreator() { + setBoardSupplyType( + project?.enabledDistributionBoardSupplyTypes[0] ?? "AV" + ); + setStructureModal("board"); + } + + async function handleUpdateBoard(event: FormEvent) { + event.preventDefault(); + if (!projectId || !project || !editingBoard) { + return; + } + setIsSaving(true); + setError(null); + try { + const result = await updateDistributionBoard( + projectId, + editingBoard.id, + { + floorId: editingBoardFloorId || null, + supplyType: editingBoardSupplyType, + }, + project.currentRevision + ); + setBoards((current) => + current.map((board) => + board.id === result.distributionBoard.id + ? result.distributionBoard + : board + ) + ); + applyProjectRevision(result.history.currentRevision); + setEditingBoard(null); + } catch (err) { + setError( + err instanceof Error + ? err.message + : "Verteilung konnte nicht bearbeitet werden." + ); + } finally { + setIsSaving(false); + } + } + async function handleExportProject() { if (!project) { return; @@ -596,7 +689,7 @@ export default function ProjectDetailPage() { {circuitList ? ( - + Noch keine Verteilungen vorhanden. @@ -967,6 +1082,112 @@ export default function ProjectDetailPage() { required value={boardName} /> +
+
+ + +
+
+ + +
+
+ + ) : null} + + {editingBoard ? ( + setEditingBoard(null)} + onSubmit={handleUpdateBoard} + submitLabel="Änderungen speichern" + title="Verteilung bearbeiten" + > +
+
+ + +
+
+ + +
+
) : null} @@ -1082,6 +1303,7 @@ export default function ProjectDetailPage() { onImport={handleImportProject} onSave={handleSaveProjectSettings} project={project} + usedDistributionBoardSupplyTypes={usedBoardSupplyTypes} /> ) : null} diff --git a/src/db/migrations/0017_vengeful_romulus.sql b/src/db/migrations/0017_vengeful_romulus.sql new file mode 100644 index 0000000..8198de7 --- /dev/null +++ b/src/db/migrations/0017_vengeful_romulus.sql @@ -0,0 +1,2 @@ +ALTER TABLE `distribution_boards` ADD `floor_id` text REFERENCES floors(id);--> statement-breakpoint +ALTER TABLE `distribution_boards` ADD `supply_type` text; \ No newline at end of file diff --git a/src/db/migrations/0018_fancy_argent.sql b/src/db/migrations/0018_fancy_argent.sql new file mode 100644 index 0000000..feab398 --- /dev/null +++ b/src/db/migrations/0018_fancy_argent.sql @@ -0,0 +1 @@ +ALTER TABLE `projects` ADD `enabled_distribution_board_supply_types` text DEFAULT '["AV","SV","EV","USV","MSR","SiBe"]' NOT NULL; \ No newline at end of file diff --git a/src/db/migrations/meta/0017_snapshot.json b/src/db/migrations/meta/0017_snapshot.json new file mode 100644 index 0000000..1ec7207 --- /dev/null +++ b/src/db/migrations/meta/0017_snapshot.json @@ -0,0 +1,1877 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "f885efbf-98a4-4977-8416-9cc890cf64df", + "prevId": "8502c7d2-e925-42c3-8044-3e965c170f97", + "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 + }, + "legacy_consumer_id": { + "name": "legacy_consumer_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_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_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 + } + }, + "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 + } + }, + "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 + }, + "protection_type": { + "name": "protection_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "protection_rated_current": { + "name": "protection_rated_current", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "protection_characteristic": { + "name": "protection_characteristic", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "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": {} + }, + "consumers": { + "name": "consumers", + "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": false, + "autoincrement": false + }, + "circuit_list_id": { + "name": "circuit_list_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "project_device_id": { + "name": "project_device_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_linked_to_device": { + "name": "is_linked_to_device", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "room_id": { + "name": "room_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "circuit_number": { + "name": "circuit_number", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "device_type": { + "name": "device_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "phase_type": { + "name": "phase_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "trade_or_cost_group": { + "name": "trade_or_cost_group", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "group_name": { + "name": "group_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "protection_type": { + "name": "protection_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "protection_rated_current": { + "name": "protection_rated_current", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "protection_characteristic": { + "name": "protection_characteristic", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "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 + }, + "comment": { + "name": "comment", + "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": { + "consumers_project_id_projects_id_fk": { + "name": "consumers_project_id_projects_id_fk", + "tableFrom": "consumers", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "consumers_distribution_board_id_distribution_boards_id_fk": { + "name": "consumers_distribution_board_id_distribution_boards_id_fk", + "tableFrom": "consumers", + "tableTo": "distribution_boards", + "columnsFrom": [ + "distribution_board_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "consumers_circuit_list_id_circuit_lists_id_fk": { + "name": "consumers_circuit_list_id_circuit_lists_id_fk", + "tableFrom": "consumers", + "tableTo": "circuit_lists", + "columnsFrom": [ + "circuit_list_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "consumers_project_device_id_project_devices_id_fk": { + "name": "consumers_project_device_id_project_devices_id_fk", + "tableFrom": "consumers", + "tableTo": "project_devices", + "columnsFrom": [ + "project_device_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "consumers_room_id_rooms_id_fk": { + "name": "consumers_room_id_rooms_id_fk", + "tableFrom": "consumers", + "tableTo": "rooms", + "columnsFrom": [ + "room_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "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 + } + }, + "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": {} + }, + "legacy_consumer_circuit_migrations": { + "name": "legacy_consumer_circuit_migrations", + "columns": { + "consumer_id": { + "name": "consumer_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "circuit_id": { + "name": "circuit_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "circuit_device_row_id": { + "name": "circuit_device_row_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "circuit_list_id": { + "name": "circuit_list_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at_iso": { + "name": "created_at_iso", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "legacy_consumer_circuit_migrations_circuit_id_circuits_id_fk": { + "name": "legacy_consumer_circuit_migrations_circuit_id_circuits_id_fk", + "tableFrom": "legacy_consumer_circuit_migrations", + "tableTo": "circuits", + "columnsFrom": [ + "circuit_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "legacy_consumer_circuit_migrations_circuit_device_row_id_circuit_device_rows_id_fk": { + "name": "legacy_consumer_circuit_migrations_circuit_device_row_id_circuit_device_rows_id_fk", + "tableFrom": "legacy_consumer_circuit_migrations", + "tableTo": "circuit_device_rows", + "columnsFrom": [ + "circuit_device_row_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "legacy_consumer_circuit_migrations_circuit_list_id_circuit_lists_id_fk": { + "name": "legacy_consumer_circuit_migrations_circuit_list_id_circuit_lists_id_fk", + "tableFrom": "legacy_consumer_circuit_migrations", + "tableTo": "circuit_lists", + "columnsFrom": [ + "circuit_list_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "legacy_consumer_migration_reports": { + "name": "legacy_consumer_migration_reports", + "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 + }, + "legacy_consumer_count": { + "name": "legacy_consumer_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_circuit_count": { + "name": "created_circuit_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_device_row_count": { + "name": "created_device_row_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "duplicate_grouped_count": { + "name": "duplicate_grouped_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "generated_identifier_count": { + "name": "generated_identifier_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "unassigned_row_count": { + "name": "unassigned_row_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "warnings_json": { + "name": "warnings_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "generated_identifiers_json": { + "name": "generated_identifiers_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "duplicate_groups_json": { + "name": "duplicate_groups_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at_iso": { + "name": "created_at_iso", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "legacy_consumer_migration_reports_list_unique": { + "name": "legacy_consumer_migration_reports_list_unique", + "columns": [ + "circuit_list_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "legacy_consumer_migration_reports_circuit_list_id_circuit_lists_id_fk": { + "name": "legacy_consumer_migration_reports_circuit_list_id_circuit_lists_id_fk", + "tableFrom": "legacy_consumer_migration_reports", + "tableTo": "circuit_lists", + "columnsFrom": [ + "circuit_list_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "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 + }, + "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 + }, + "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/0018_snapshot.json b/src/db/migrations/meta/0018_snapshot.json new file mode 100644 index 0000000..b463316 --- /dev/null +++ b/src/db/migrations/meta/0018_snapshot.json @@ -0,0 +1,1885 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "6297060c-1a3f-4f7c-867f-7c3e96c34645", + "prevId": "f885efbf-98a4-4977-8416-9cc890cf64df", + "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 + }, + "legacy_consumer_id": { + "name": "legacy_consumer_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_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_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 + } + }, + "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 + } + }, + "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 + }, + "protection_type": { + "name": "protection_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "protection_rated_current": { + "name": "protection_rated_current", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "protection_characteristic": { + "name": "protection_characteristic", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "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": {} + }, + "consumers": { + "name": "consumers", + "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": false, + "autoincrement": false + }, + "circuit_list_id": { + "name": "circuit_list_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "project_device_id": { + "name": "project_device_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_linked_to_device": { + "name": "is_linked_to_device", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "room_id": { + "name": "room_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "circuit_number": { + "name": "circuit_number", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "device_type": { + "name": "device_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "phase_type": { + "name": "phase_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "trade_or_cost_group": { + "name": "trade_or_cost_group", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "group_name": { + "name": "group_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "protection_type": { + "name": "protection_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "protection_rated_current": { + "name": "protection_rated_current", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "protection_characteristic": { + "name": "protection_characteristic", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "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 + }, + "comment": { + "name": "comment", + "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": { + "consumers_project_id_projects_id_fk": { + "name": "consumers_project_id_projects_id_fk", + "tableFrom": "consumers", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "consumers_distribution_board_id_distribution_boards_id_fk": { + "name": "consumers_distribution_board_id_distribution_boards_id_fk", + "tableFrom": "consumers", + "tableTo": "distribution_boards", + "columnsFrom": [ + "distribution_board_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "consumers_circuit_list_id_circuit_lists_id_fk": { + "name": "consumers_circuit_list_id_circuit_lists_id_fk", + "tableFrom": "consumers", + "tableTo": "circuit_lists", + "columnsFrom": [ + "circuit_list_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "consumers_project_device_id_project_devices_id_fk": { + "name": "consumers_project_device_id_project_devices_id_fk", + "tableFrom": "consumers", + "tableTo": "project_devices", + "columnsFrom": [ + "project_device_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "consumers_room_id_rooms_id_fk": { + "name": "consumers_room_id_rooms_id_fk", + "tableFrom": "consumers", + "tableTo": "rooms", + "columnsFrom": [ + "room_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "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 + } + }, + "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": {} + }, + "legacy_consumer_circuit_migrations": { + "name": "legacy_consumer_circuit_migrations", + "columns": { + "consumer_id": { + "name": "consumer_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "circuit_id": { + "name": "circuit_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "circuit_device_row_id": { + "name": "circuit_device_row_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "circuit_list_id": { + "name": "circuit_list_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at_iso": { + "name": "created_at_iso", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "legacy_consumer_circuit_migrations_circuit_id_circuits_id_fk": { + "name": "legacy_consumer_circuit_migrations_circuit_id_circuits_id_fk", + "tableFrom": "legacy_consumer_circuit_migrations", + "tableTo": "circuits", + "columnsFrom": [ + "circuit_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "legacy_consumer_circuit_migrations_circuit_device_row_id_circuit_device_rows_id_fk": { + "name": "legacy_consumer_circuit_migrations_circuit_device_row_id_circuit_device_rows_id_fk", + "tableFrom": "legacy_consumer_circuit_migrations", + "tableTo": "circuit_device_rows", + "columnsFrom": [ + "circuit_device_row_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "legacy_consumer_circuit_migrations_circuit_list_id_circuit_lists_id_fk": { + "name": "legacy_consumer_circuit_migrations_circuit_list_id_circuit_lists_id_fk", + "tableFrom": "legacy_consumer_circuit_migrations", + "tableTo": "circuit_lists", + "columnsFrom": [ + "circuit_list_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "legacy_consumer_migration_reports": { + "name": "legacy_consumer_migration_reports", + "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 + }, + "legacy_consumer_count": { + "name": "legacy_consumer_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_circuit_count": { + "name": "created_circuit_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_device_row_count": { + "name": "created_device_row_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "duplicate_grouped_count": { + "name": "duplicate_grouped_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "generated_identifier_count": { + "name": "generated_identifier_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "unassigned_row_count": { + "name": "unassigned_row_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "warnings_json": { + "name": "warnings_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "generated_identifiers_json": { + "name": "generated_identifiers_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "duplicate_groups_json": { + "name": "duplicate_groups_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at_iso": { + "name": "created_at_iso", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "legacy_consumer_migration_reports_list_unique": { + "name": "legacy_consumer_migration_reports_list_unique", + "columns": [ + "circuit_list_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "legacy_consumer_migration_reports_circuit_list_id_circuit_lists_id_fk": { + "name": "legacy_consumer_migration_reports_circuit_list_id_circuit_lists_id_fk", + "tableFrom": "legacy_consumer_migration_reports", + "tableTo": "circuit_lists", + "columnsFrom": [ + "circuit_list_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "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 + }, + "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 580b1ce..a3e4e22 100644 --- a/src/db/migrations/meta/_journal.json +++ b/src/db/migrations/meta/_journal.json @@ -120,6 +120,20 @@ "when": 1785254079435, "tag": "0016_dashing_darkstar", "breakpoints": true + }, + { + "idx": 17, + "version": "6", + "when": 1785307189539, + "tag": "0017_vengeful_romulus", + "breakpoints": true + }, + { + "idx": 18, + "version": "6", + "when": 1785308458789, + "tag": "0018_fancy_argent", + "breakpoints": true } ] } \ No newline at end of file diff --git a/src/db/repositories/distribution-board-structure-project-command.repository.ts b/src/db/repositories/distribution-board-structure-project-command.repository.ts index 76b3ee0..8ef61df 100644 --- a/src/db/repositories/distribution-board-structure-project-command.repository.ts +++ b/src/db/repositories/distribution-board-structure-project-command.repository.ts @@ -6,9 +6,19 @@ import { createDistributionBoardInsertProjectCommand, distributionBoardDeleteCommandType, distributionBoardInsertCommandType, + normalizeDistributionBoardStructureProjectCommand, type DistributionBoardStructureProjectCommand, type DistributionBoardStructureSnapshot, + legacyDistributionBoardStructureCommandSchemaVersion, } from "../../domain/models/distribution-board-structure-project-command.model.js"; +import { + assertDistributionBoardUpdateProjectCommand, + createDistributionBoardUpdateProjectCommand, + type DistributionBoardUpdateField, + type DistributionBoardUpdatePatch, + type DistributionBoardUpdateProjectCommand, + type DistributionBoardUpdateValues, +} from "../../domain/models/distribution-board-project-command.model.js"; import type { DistributionBoardStructureProjectCommandStore, ExecuteDistributionBoardStructureCommandInput, @@ -18,6 +28,7 @@ import { circuitLists } from "../schema/circuit-lists.js"; import { circuitSections } from "../schema/circuit-sections.js"; import { circuits } from "../schema/circuits.js"; import { distributionBoards } from "../schema/distribution-boards.js"; +import { floors } from "../schema/floors.js"; import { projects } from "../schema/projects.js"; import { executeProjectCommandTransaction } from "./project-command-transaction.persistence.js"; @@ -35,26 +46,52 @@ export class DistributionBoardStructureProjectCommandRepository ); } + executeUpdate( + input: Omit< + ExecuteDistributionBoardStructureCommandInput, + "command" + > & { + command: DistributionBoardUpdateProjectCommand; + } + ) { + assertDistributionBoardUpdateProjectCommand(input.command); + return executeProjectCommandTransaction( + this.database, + input, + (tx) => this.update(tx, input) + ); + } + private applyCommand( database: AppDatabase, projectId: string, command: DistributionBoardStructureProjectCommand ): DistributionBoardStructureProjectCommand { - if (command.type === distributionBoardInsertCommandType) { - assertDistributionBoardInsertProjectCommand(command); - return this.insert( + const normalized = + normalizeDistributionBoardStructureProjectCommand(command); + if (normalized.type === distributionBoardInsertCommandType) { + assertDistributionBoardInsertProjectCommand(normalized); + const inverse = this.insert( database, projectId, - command.payload.structure + normalized.payload.structure ); + return command.schemaVersion === + legacyDistributionBoardStructureCommandSchemaVersion + ? toLegacyStructureCommand(inverse) + : inverse; } - if (command.type === distributionBoardDeleteCommandType) { - assertDistributionBoardDeleteProjectCommand(command); - return this.delete( + if (normalized.type === distributionBoardDeleteCommandType) { + assertDistributionBoardDeleteProjectCommand(normalized); + const inverse = this.delete( database, projectId, - command.payload.structure + normalized.payload.structure ); + return command.schemaVersion === + legacyDistributionBoardStructureCommandSchemaVersion + ? toLegacyStructureCommand(inverse) + : inverse; } throw new Error("Unsupported distribution-board structure command."); } @@ -73,13 +110,33 @@ export class DistributionBoardStructureProjectCommandRepository ); } const project = database - .select({ id: projects.id }) + .select({ + id: projects.id, + enabledSupplyTypes: + projects.enabledDistributionBoardSupplyTypes, + }) .from(projects) .where(eq(projects.id, projectId)) .get(); if (!project) { throw new Error("Project not found."); } + assertSupplyTypeEnabled( + project.enabledSupplyTypes, + structure.distributionBoard.supplyType + ); + if (structure.distributionBoard.floorId !== null) { + const floor = database + .select({ projectId: floors.projectId }) + .from(floors) + .where(eq(floors.id, structure.distributionBoard.floorId)) + .get(); + if (!floor || floor.projectId !== projectId) { + throw new Error( + "Distribution-board floor does not belong to project." + ); + } + } const existingBoard = database .select({ id: distributionBoards.id }) .from(distributionBoards) @@ -179,6 +236,139 @@ export class DistributionBoardStructureProjectCommandRepository } return createDistributionBoardInsertProjectCommand(structure); } + + private update( + database: AppDatabase, + input: Omit< + ExecuteDistributionBoardStructureCommandInput, + "command" + > & { + command: DistributionBoardUpdateProjectCommand; + } + ) { + const current = database + .select() + .from(distributionBoards) + .where( + and( + eq( + distributionBoards.id, + input.command.payload.distributionBoardId + ), + eq(distributionBoards.projectId, input.projectId) + ) + ) + .get(); + if (!current) { + throw new Error("Distribution board does not belong to project."); + } + const project = database + .select({ + enabledSupplyTypes: + projects.enabledDistributionBoardSupplyTypes, + }) + .from(projects) + .where(eq(projects.id, input.projectId)) + .get(); + if (!project) { + throw new Error("Project not found."); + } + const patch = Object.fromEntries( + input.command.payload.changes.map((change) => [ + change.field, + change.value, + ]) + ) as DistributionBoardUpdatePatch; + if (patch.floorId !== undefined && patch.floorId !== null) { + const floor = database + .select({ projectId: floors.projectId }) + .from(floors) + .where(eq(floors.id, patch.floorId)) + .get(); + if (!floor || floor.projectId !== input.projectId) { + throw new Error( + "Distribution-board floor does not belong to project." + ); + } + } + if (patch.supplyType !== undefined) { + assertSupplyTypeEnabled( + project.enabledSupplyTypes, + patch.supplyType + ); + } + const inversePatch = Object.fromEntries( + input.command.payload.changes.map((change) => [ + change.field, + getDistributionBoardFieldValue(current, change.field), + ]) + ) as DistributionBoardUpdatePatch; + const inverse = createDistributionBoardUpdateProjectCommand( + current.id, + inversePatch + ); + const updated = database + .update(distributionBoards) + .set(patch) + .where( + and( + eq(distributionBoards.id, current.id), + eq(distributionBoards.projectId, input.projectId) + ) + ) + .run(); + if (updated.changes !== 1) { + throw new Error( + "Distribution board changed before command execution." + ); + } + return inverse; + } +} + +function assertSupplyTypeEnabled( + enabledSupplyTypes: (typeof projects.$inferSelect)["enabledDistributionBoardSupplyTypes"], + supplyType: (typeof distributionBoards.$inferSelect)["supplyType"] +) { + if ( + supplyType !== null && + !enabledSupplyTypes.includes(supplyType) + ) { + throw new Error( + `Die Netzart ${supplyType} ist in den Projekteinstellungen nicht aktiviert.` + ); + } +} + +function toLegacyStructureCommand( + command: + | ReturnType + | ReturnType +): DistributionBoardStructureProjectCommand { + const { + floorId: _floorId, + supplyType: _supplyType, + ...distributionBoard + } = command.payload.structure.distributionBoard; + return { + schemaVersion: legacyDistributionBoardStructureCommandSchemaVersion, + type: command.type, + payload: { + structure: { + ...command.payload.structure, + distributionBoard, + }, + }, + } as DistributionBoardStructureProjectCommand; +} + +function getDistributionBoardFieldValue< + TField extends DistributionBoardUpdateField, +>( + distributionBoard: typeof distributionBoards.$inferSelect, + field: TField +): DistributionBoardUpdateValues[TField] { + return distributionBoard[field] as DistributionBoardUpdateValues[TField]; } function structureMatches( diff --git a/src/db/repositories/project-location-structure-project-command.repository.ts b/src/db/repositories/project-location-structure-project-command.repository.ts index e5226a9..2368def 100644 --- a/src/db/repositories/project-location-structure-project-command.repository.ts +++ b/src/db/repositories/project-location-structure-project-command.repository.ts @@ -24,6 +24,7 @@ import type { AppDatabase } from "../database-context.js"; import { circuitDeviceRows } from "../schema/circuit-device-rows.js"; import { consumers } from "../schema/consumers.js"; import { floors } from "../schema/floors.js"; +import { distributionBoards } from "../schema/distribution-boards.js"; import { projects } from "../schema/projects.js"; import { rooms } from "../schema/rooms.js"; import { executeProjectCommandTransaction } from "./project-command-transaction.persistence.js"; @@ -129,6 +130,17 @@ export class ProjectLocationStructureProjectCommandRepository "A project floor with assigned rooms cannot be removed by history." ); } + const referencedDistributionBoard = database + .select({ id: distributionBoards.id }) + .from(distributionBoards) + .where(eq(distributionBoards.floorId, expected.id)) + .limit(1) + .get(); + if (referencedDistributionBoard) { + throw new Error( + "A project floor assigned to a distribution board cannot be removed by history." + ); + } const deleted = database .delete(floors) .where( diff --git a/src/db/repositories/project-settings-project-command.repository.ts b/src/db/repositories/project-settings-project-command.repository.ts index 10485ff..8e0a44a 100644 --- a/src/db/repositories/project-settings-project-command.repository.ts +++ b/src/db/repositories/project-settings-project-command.repository.ts @@ -1,8 +1,10 @@ -import { eq } from "drizzle-orm"; +import { and, eq, isNotNull } from "drizzle-orm"; import { assertProjectSettingsUpdateProjectCommand, createProjectSettingsUpdateProjectCommand, legacyProjectSettingsUpdateCommandSchemaVersion, + normalizeProjectSettingsValues, + previousProjectSettingsUpdateCommandSchemaVersion, type ProjectSettingsValues, } from "../../domain/models/project-settings-project-command.model.js"; import type { @@ -11,6 +13,7 @@ import type { } from "../../domain/ports/project-settings-project-command.store.js"; import type { AppDatabase } from "../database-context.js"; import { projects } from "../schema/projects.js"; +import { distributionBoards } from "../schema/distribution-boards.js"; import { executeProjectCommandTransaction } from "./project-command-transaction.persistence.js"; export class ProjectSettingsProjectCommandRepository @@ -39,18 +42,7 @@ export class ProjectSettingsProjectCommandRepository if (!current) { throw new Error("Project not found."); } - const target: ProjectSettingsValues = - input.command.schemaVersion === - legacyProjectSettingsUpdateCommandSchemaVersion - ? { - name: current.name, - internalProjectNumber: current.internalProjectNumber, - externalProjectNumber: current.externalProjectNumber, - buildingOwner: current.buildingOwner, - description: current.description, - ...input.command.payload, - } - : input.command.payload; + const target = normalizeProjectSettingsValues(input.command, current); if (projectSettingsEqual(current, target)) { throw new Error("Project settings did not change."); } @@ -66,7 +58,23 @@ export class ProjectSettingsProjectCommandRepository threePhaseVoltageV: current.threePhaseVoltageV, }, } - : createProjectSettingsUpdateProjectCommand({ + : input.command.schemaVersion === + previousProjectSettingsUpdateCommandSchemaVersion + ? { + schemaVersion: + previousProjectSettingsUpdateCommandSchemaVersion, + type: input.command.type, + payload: { + name: current.name, + internalProjectNumber: current.internalProjectNumber, + externalProjectNumber: current.externalProjectNumber, + buildingOwner: current.buildingOwner, + description: current.description, + singlePhaseVoltageV: current.singlePhaseVoltageV, + threePhaseVoltageV: current.threePhaseVoltageV, + }, + } + : createProjectSettingsUpdateProjectCommand({ name: current.name, internalProjectNumber: current.internalProjectNumber, externalProjectNumber: current.externalProjectNumber, @@ -74,7 +82,10 @@ export class ProjectSettingsProjectCommandRepository description: current.description, singlePhaseVoltageV: current.singlePhaseVoltageV, threePhaseVoltageV: current.threePhaseVoltageV, + enabledDistributionBoardSupplyTypes: + current.enabledDistributionBoardSupplyTypes, }); + assertDisabledSupplyTypesAreUnused(tx, input.projectId, target); const updated = tx .update(projects) .set(target) @@ -100,5 +111,54 @@ function projectSettingsEqual( current.description === target.description && current.singlePhaseVoltageV === target.singlePhaseVoltageV && current.threePhaseVoltageV === target.threePhaseVoltageV + && sameSupplyTypes( + current.enabledDistributionBoardSupplyTypes, + target.enabledDistributionBoardSupplyTypes + ) ); } + +function sameSupplyTypes( + left: ProjectSettingsValues["enabledDistributionBoardSupplyTypes"], + right: ProjectSettingsValues["enabledDistributionBoardSupplyTypes"] +) { + return ( + left.length === right.length && + left.every((value, index) => value === right[index]) + ); +} + +function assertDisabledSupplyTypesAreUnused( + database: AppDatabase, + projectId: string, + target: ProjectSettingsValues +) { + const used = database + .select({ supplyType: distributionBoards.supplyType }) + .from(distributionBoards) + .where( + and( + eq(distributionBoards.projectId, projectId), + isNotNull(distributionBoards.supplyType) + ) + ) + .all(); + const disabledUsedTypes = [ + ...new Set( + used + .map((entry) => entry.supplyType) + .filter( + (supplyType) => + supplyType !== null && + !target.enabledDistributionBoardSupplyTypes.some( + (enabled) => enabled === supplyType + ) + ) + ), + ]; + if (disabledUsedTypes.length > 0) { + throw new Error( + `Verwendete Netzarten können nicht deaktiviert werden: ${disabledUsedTypes.join(", ")}.` + ); + } +} diff --git a/src/db/repositories/project-snapshot.repository.ts b/src/db/repositories/project-snapshot.repository.ts index 8fee255..21d5ecf 100644 --- a/src/db/repositories/project-snapshot.repository.ts +++ b/src/db/repositories/project-snapshot.repository.ts @@ -4,8 +4,10 @@ import { ProjectRevisionConflictError } from "../../domain/errors/project-revisi import { ProjectSnapshotNameConflictError } from "../../domain/errors/project-snapshot-name-conflict.error.js"; import { createProjectStateRestoreCommand } from "../../domain/models/project-state-restore-command.model.js"; import { + distributionBoardProjectStateSnapshotSchemaVersion, deserializeProjectStateSnapshot, legacyProjectStateSnapshotSchemaVersion, + previousProjectStateSnapshotSchemaVersion, projectStateSnapshotSchemaVersion, } from "../../domain/models/project-state-snapshot.model.js"; import type { @@ -141,6 +143,9 @@ export class ProjectSnapshotRepository implements ProjectSnapshotStore { } if ( stored.schemaVersion !== legacyProjectStateSnapshotSchemaVersion && + stored.schemaVersion !== previousProjectStateSnapshotSchemaVersion && + stored.schemaVersion !== + distributionBoardProjectStateSnapshotSchemaVersion && stored.schemaVersion !== projectStateSnapshotSchemaVersion ) { throw new Error("Project snapshot schema version is not supported."); diff --git a/src/db/repositories/project-state-restore-command.repository.ts b/src/db/repositories/project-state-restore-command.repository.ts index a3b2cd4..1c27a97 100644 --- a/src/db/repositories/project-state-restore-command.repository.ts +++ b/src/db/repositories/project-state-restore-command.repository.ts @@ -194,6 +194,8 @@ function replaceProjectState( description: state.project.description, singlePhaseVoltageV: state.project.singlePhaseVoltageV, threePhaseVoltageV: state.project.threePhaseVoltageV, + enabledDistributionBoardSupplyTypes: + state.project.enabledDistributionBoardSupplyTypes, }) .where(eq(projects.id, state.project.id)) .run(); diff --git a/src/db/repositories/project-state-snapshot.persistence.ts b/src/db/repositories/project-state-snapshot.persistence.ts index d399dad..9c6dbc6 100644 --- a/src/db/repositories/project-state-snapshot.persistence.ts +++ b/src/db/repositories/project-state-snapshot.persistence.ts @@ -38,6 +38,8 @@ export function readProjectStateSnapshot( description: projects.description, singlePhaseVoltageV: projects.singlePhaseVoltageV, threePhaseVoltageV: projects.threePhaseVoltageV, + enabledDistributionBoardSupplyTypes: + projects.enabledDistributionBoardSupplyTypes, currentRevision: projects.currentRevision, }) .from(projects) @@ -182,6 +184,8 @@ export function readProjectStateSnapshot( description: project.description, singlePhaseVoltageV: project.singlePhaseVoltageV, threePhaseVoltageV: project.threePhaseVoltageV, + enabledDistributionBoardSupplyTypes: + project.enabledDistributionBoardSupplyTypes, }, distributionBoards: boardRows, circuitLists: listRows, diff --git a/src/db/repositories/project-transfer.repository.ts b/src/db/repositories/project-transfer.repository.ts index 8974bd4..a6361ff 100644 --- a/src/db/repositories/project-transfer.repository.ts +++ b/src/db/repositories/project-transfer.repository.ts @@ -74,12 +74,18 @@ export class ProjectTransferRepository implements ProjectTransferStore { } private parseVerified(value: unknown) { + const rawPayloadSha256 = + isPlainObject(value) && + isPlainObject(value.projectState) + ? hashProjectStatePayload(JSON.stringify(value.projectState)) + : null; const transfer = parseProjectTransferEnvelope(value); const payloadJson = serializeProjectStateSnapshot( transfer.projectState ); if ( - hashProjectStatePayload(payloadJson) !== transfer.payloadSha256 + hashProjectStatePayload(payloadJson) !== transfer.payloadSha256 && + rawPayloadSha256 !== transfer.payloadSha256 ) { throw new Error("Project transfer checksum verification failed."); } @@ -87,6 +93,10 @@ export class ProjectTransferRepository implements ProjectTransferStore { } } +function isPlainObject(value: unknown): value is Record { + return value !== null && typeof value === "object" && !Array.isArray(value); +} + function insertProjectState( database: AppDatabase, state: ReturnType diff --git a/src/db/repositories/project.repository.ts b/src/db/repositories/project.repository.ts index d729422..bc96bee 100644 --- a/src/db/repositories/project.repository.ts +++ b/src/db/repositories/project.repository.ts @@ -5,6 +5,7 @@ import { projects } from "../schema/projects.js"; import type { CreateProjectInput, } from "../../shared/validation/project-structure.schemas.js"; +import { defaultDistributionBoardSupplyTypes } from "../../shared/constants/distribution-board.js"; export class ProjectRepository { constructor(private readonly database: AppDatabase) {} @@ -26,6 +27,9 @@ export class ProjectRepository { description: input.description ?? null, singlePhaseVoltageV: input.singlePhaseVoltageV ?? 230, threePhaseVoltageV: input.threePhaseVoltageV ?? 400, + enabledDistributionBoardSupplyTypes: [ + ...defaultDistributionBoardSupplyTypes, + ], currentRevision: 0, }; await db.insert(projects).values(project); diff --git a/src/db/schema/distribution-boards.ts b/src/db/schema/distribution-boards.ts index febbda0..f4895c4 100644 --- a/src/db/schema/distribution-boards.ts +++ b/src/db/schema/distribution-boards.ts @@ -1,5 +1,7 @@ import { sqliteTable, text } from "drizzle-orm/sqlite-core"; +import { floors } from "./floors.js"; import { projects } from "./projects.js"; +import type { DistributionBoardSupplyType } from "../../shared/constants/distribution-board.js"; export const distributionBoards = sqliteTable("distribution_boards", { id: text("id").primaryKey(), @@ -7,5 +9,9 @@ export const distributionBoards = sqliteTable("distribution_boards", { .notNull() .references(() => projects.id, { onDelete: "cascade" }), name: text("name").notNull(), + floorId: text("floor_id").references(() => floors.id, { + onDelete: "set null", + }), + supplyType: text("supply_type").$type(), }); diff --git a/src/db/schema/projects.ts b/src/db/schema/projects.ts index 05d6324..04671d1 100644 --- a/src/db/schema/projects.ts +++ b/src/db/schema/projects.ts @@ -1,4 +1,6 @@ +import { sql } from "drizzle-orm"; import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core"; +import type { DistributionBoardSupplyType } from "../../shared/constants/distribution-board.js"; export const projects = sqliteTable("projects", { id: text("id").primaryKey(), @@ -9,5 +11,12 @@ export const projects = sqliteTable("projects", { description: text("description"), singlePhaseVoltageV: integer("single_phase_voltage_v").notNull().default(230), threePhaseVoltageV: integer("three_phase_voltage_v").notNull().default(400), + enabledDistributionBoardSupplyTypes: text( + "enabled_distribution_board_supply_types", + { mode: "json" } + ) + .$type() + .notNull() + .default(sql`'["AV","SV","EV","USV","MSR","SiBe"]'`), currentRevision: integer("current_revision").notNull().default(0), }); diff --git a/src/domain/models/distribution-board-project-command.model.ts b/src/domain/models/distribution-board-project-command.model.ts new file mode 100644 index 0000000..a591488 --- /dev/null +++ b/src/domain/models/distribution-board-project-command.model.ts @@ -0,0 +1,112 @@ +import { + distributionBoardSupplyTypes, + type DistributionBoardSupplyType, +} from "../../shared/constants/distribution-board.js"; +import type { SerializedProjectCommand } from "./project-command.model.js"; + +export const distributionBoardUpdateCommandType = + "distribution-board.update" as const; +export const distributionBoardUpdateCommandSchemaVersion = 1 as const; + +export interface DistributionBoardUpdateValues { + floorId: string | null; + supplyType: DistributionBoardSupplyType | null; +} + +export type DistributionBoardUpdateField = + keyof DistributionBoardUpdateValues; +export type DistributionBoardUpdatePatch = + Partial; + +export interface DistributionBoardUpdateFieldChange< + TField extends DistributionBoardUpdateField = + DistributionBoardUpdateField, +> { + field: TField; + value: DistributionBoardUpdateValues[TField]; +} + +export interface DistributionBoardUpdateCommandPayload { + distributionBoardId: string; + changes: DistributionBoardUpdateFieldChange[]; +} + +export interface DistributionBoardUpdateProjectCommand + extends SerializedProjectCommand { + schemaVersion: typeof distributionBoardUpdateCommandSchemaVersion; + type: typeof distributionBoardUpdateCommandType; +} + +export function createDistributionBoardUpdateProjectCommand( + distributionBoardId: string, + patch: DistributionBoardUpdatePatch +): DistributionBoardUpdateProjectCommand { + const command: DistributionBoardUpdateProjectCommand = { + schemaVersion: distributionBoardUpdateCommandSchemaVersion, + type: distributionBoardUpdateCommandType, + payload: { + distributionBoardId, + changes: Object.entries(patch).map(([field, value]) => ({ + field: field as DistributionBoardUpdateField, + value, + })) as DistributionBoardUpdateFieldChange[], + }, + }; + assertDistributionBoardUpdateProjectCommand(command); + return command; +} + +export function assertDistributionBoardUpdateProjectCommand( + command: SerializedProjectCommand +): asserts command is DistributionBoardUpdateProjectCommand { + if ( + command.schemaVersion !== distributionBoardUpdateCommandSchemaVersion || + command.type !== distributionBoardUpdateCommandType || + !isPlainObject(command.payload) + ) { + throw new Error("Unsupported distribution-board update command."); + } + const { distributionBoardId, changes } = command.payload; + if ( + typeof distributionBoardId !== "string" || + !distributionBoardId.trim() || + !Array.isArray(changes) || + changes.length === 0 + ) { + throw new Error( + "Distribution-board update command requires an id and changes." + ); + } + const seen = new Set(); + for (const change of changes) { + if ( + !isPlainObject(change) || + (change.field !== "floorId" && change.field !== "supplyType") || + seen.has(change.field) + ) { + throw new Error( + "Distribution-board update contains an invalid or duplicate field." + ); + } + if (change.field === "floorId") { + if ( + change.value !== null && + (typeof change.value !== "string" || !change.value.trim()) + ) { + throw new Error("floorId must be a non-empty string or null."); + } + } else if ( + change.value !== null && + !distributionBoardSupplyTypes.includes( + change.value as DistributionBoardSupplyType + ) + ) { + throw new Error("supplyType is invalid."); + } + seen.add(change.field); + } +} + +function isPlainObject(value: unknown): value is Record { + return value !== null && typeof value === "object" && !Array.isArray(value); +} diff --git a/src/domain/models/distribution-board-structure-project-command.model.ts b/src/domain/models/distribution-board-structure-project-command.model.ts index 715003c..1ac0896 100644 --- a/src/domain/models/distribution-board-structure-project-command.model.ts +++ b/src/domain/models/distribution-board-structure-project-command.model.ts @@ -1,11 +1,16 @@ import crypto from "node:crypto"; +import { + distributionBoardSupplyTypes, + type DistributionBoardSupplyType, +} from "../../shared/constants/distribution-board.js"; import type { SerializedProjectCommand } from "./project-command.model.js"; export const distributionBoardInsertCommandType = "distribution-board.insert" as const; export const distributionBoardDeleteCommandType = "distribution-board.delete" as const; -export const distributionBoardStructureCommandSchemaVersion = 1 as const; +export const legacyDistributionBoardStructureCommandSchemaVersion = 1 as const; +export const distributionBoardStructureCommandSchemaVersion = 2 as const; export const defaultCircuitSectionDefinitions = [ { @@ -34,11 +39,23 @@ export const defaultCircuitSectionDefinitions = [ }, ] as const; +interface LegacyDistributionBoardStructureSnapshot { + distributionBoard: { + id: string; + projectId: string; + name: string; + }; + circuitList: DistributionBoardStructureSnapshot["circuitList"]; + sections: DistributionBoardStructureSnapshot["sections"]; +} + export interface DistributionBoardStructureSnapshot { distributionBoard: { id: string; projectId: string; name: string; + floorId: string | null; + supplyType: DistributionBoardSupplyType | null; }; circuitList: { id: string; @@ -56,29 +73,52 @@ export interface DistributionBoardStructureSnapshot { }>; } -interface DistributionBoardStructureCommandPayload { - structure: DistributionBoardStructureSnapshot; +interface DistributionBoardStructureCommandPayload< + TStructure = DistributionBoardStructureSnapshot, +> { + structure: TStructure; +} + +interface CurrentDistributionBoardStructureProjectCommand + extends SerializedProjectCommand { + schemaVersion: typeof distributionBoardStructureCommandSchemaVersion; + type: + | typeof distributionBoardInsertCommandType + | typeof distributionBoardDeleteCommandType; +} + +interface LegacyDistributionBoardStructureProjectCommand + extends SerializedProjectCommand< + DistributionBoardStructureCommandPayload + > { + schemaVersion: typeof legacyDistributionBoardStructureCommandSchemaVersion; + type: + | typeof distributionBoardInsertCommandType + | typeof distributionBoardDeleteCommandType; } export interface DistributionBoardInsertProjectCommand - extends SerializedProjectCommand { - schemaVersion: typeof distributionBoardStructureCommandSchemaVersion; + extends CurrentDistributionBoardStructureProjectCommand { type: typeof distributionBoardInsertCommandType; } export interface DistributionBoardDeleteProjectCommand - extends SerializedProjectCommand { - schemaVersion: typeof distributionBoardStructureCommandSchemaVersion; + extends CurrentDistributionBoardStructureProjectCommand { type: typeof distributionBoardDeleteCommandType; } export type DistributionBoardStructureProjectCommand = | DistributionBoardInsertProjectCommand - | DistributionBoardDeleteProjectCommand; + | DistributionBoardDeleteProjectCommand + | LegacyDistributionBoardStructureProjectCommand; export function createDistributionBoardStructureSnapshot( projectId: string, - name: string + name: string, + input: { + floorId?: string | null; + supplyType?: DistributionBoardSupplyType | null; + } = {} ): DistributionBoardStructureSnapshot { const normalizedName = name.trim(); assertNonEmptyString(projectId, "projectId"); @@ -89,6 +129,8 @@ export function createDistributionBoardStructureSnapshot( id: structureId, projectId, name: normalizedName, + floorId: input.floorId ?? null, + supplyType: input.supplyType ?? null, }, circuitList: { id: structureId, @@ -132,7 +174,7 @@ export function createDistributionBoardDeleteProjectCommand( export function assertDistributionBoardInsertProjectCommand( command: SerializedProjectCommand -): asserts command is DistributionBoardInsertProjectCommand { +): asserts command is DistributionBoardStructureProjectCommand { assertDistributionBoardStructureProjectCommand( command, distributionBoardInsertCommandType @@ -141,23 +183,88 @@ export function assertDistributionBoardInsertProjectCommand( export function assertDistributionBoardDeleteProjectCommand( command: SerializedProjectCommand -): asserts command is DistributionBoardDeleteProjectCommand { +): asserts command is DistributionBoardStructureProjectCommand { assertDistributionBoardStructureProjectCommand( command, distributionBoardDeleteCommandType ); } +export function normalizeDistributionBoardStructureProjectCommand( + command: DistributionBoardStructureProjectCommand +): DistributionBoardInsertProjectCommand | DistributionBoardDeleteProjectCommand { + if ( + command.schemaVersion === + legacyDistributionBoardStructureCommandSchemaVersion + ) { + const structure: DistributionBoardStructureSnapshot = { + ...command.payload.structure, + distributionBoard: { + ...command.payload.structure.distributionBoard, + floorId: null, + supplyType: null, + }, + }; + return command.type === distributionBoardInsertCommandType + ? createDistributionBoardInsertProjectCommand(structure) + : createDistributionBoardDeleteProjectCommand(structure); + } + return command as + | DistributionBoardInsertProjectCommand + | DistributionBoardDeleteProjectCommand; +} + export function assertDistributionBoardStructureSnapshot( structure: unknown ): asserts structure is DistributionBoardStructureSnapshot { + assertDistributionBoardStructureSnapshotVersion(structure, false); +} + +function assertDistributionBoardStructureProjectCommand( + command: SerializedProjectCommand, + expectedType: + | typeof distributionBoardInsertCommandType + | typeof distributionBoardDeleteCommandType +) { + if ( + command.type !== expectedType || + !isPlainObject(command.payload) || + Object.keys(command.payload).length !== 1 + ) { + throw new Error("Unsupported distribution-board structure command."); + } + if ( + command.schemaVersion === + legacyDistributionBoardStructureCommandSchemaVersion + ) { + assertDistributionBoardStructureSnapshotVersion( + command.payload.structure, + true + ); + return; + } + if ( + command.schemaVersion !== distributionBoardStructureCommandSchemaVersion + ) { + throw new Error("Unsupported distribution-board structure command."); + } + assertDistributionBoardStructureSnapshotVersion( + command.payload.structure, + false + ); +} + +function assertDistributionBoardStructureSnapshotVersion( + structure: unknown, + legacy: boolean +) { if (!isPlainObject(structure) || Object.keys(structure).length !== 3) { throw new Error("Distribution-board structure is invalid."); } const { distributionBoard, circuitList, sections } = structure; if ( !isPlainObject(distributionBoard) || - Object.keys(distributionBoard).length !== 3 || + Object.keys(distributionBoard).length !== (legacy ? 3 : 5) || !isPlainObject(circuitList) || Object.keys(circuitList).length !== 4 || !Array.isArray(sections) || @@ -165,8 +272,15 @@ export function assertDistributionBoardStructureSnapshot( ) { throw new Error("Distribution-board structure is incomplete."); } - for (const [field, value] of Object.entries(distributionBoard)) { - assertNonEmptyString(value, `distributionBoard.${field}`); + for (const field of ["id", "projectId", "name"] as const) { + assertNonEmptyString( + distributionBoard[field], + `distributionBoard.${field}` + ); + } + if (!legacy) { + assertNullableId(distributionBoard.floorId, "distributionBoard.floorId"); + assertNullableSupplyType(distributionBoard.supplyType); } for (const [field, value] of Object.entries(circuitList)) { assertNonEmptyString(value, `circuitList.${field}`); @@ -174,22 +288,16 @@ export function assertDistributionBoardStructureSnapshot( if ( circuitList.projectId !== distributionBoard.projectId || circuitList.distributionBoardId !== distributionBoard.id || - circuitList.name !== - `${distributionBoard.name} Stromkreisliste` + circuitList.name !== `${distributionBoard.name} Stromkreisliste` ) { - throw new Error( - "Distribution board and circuit list are inconsistent." - ); + throw new Error("Distribution board and circuit list are inconsistent."); } const sectionIds = new Set(); for (let index = 0; index < sections.length; index += 1) { const section = sections[index]; const expected = defaultCircuitSectionDefinitions[index]; - if ( - !isPlainObject(section) || - Object.keys(section).length !== 6 - ) { + if (!isPlainObject(section) || Object.keys(section).length !== 6) { throw new Error("Default circuit section is invalid."); } assertNonEmptyString(section.id, "section.id"); @@ -211,22 +319,23 @@ export function assertDistributionBoardStructureSnapshot( } } -function assertDistributionBoardStructureProjectCommand( - command: SerializedProjectCommand, - expectedType: - | typeof distributionBoardInsertCommandType - | typeof distributionBoardDeleteCommandType -) { - if ( - command.schemaVersion !== - distributionBoardStructureCommandSchemaVersion || - command.type !== expectedType || - !isPlainObject(command.payload) || - Object.keys(command.payload).length !== 1 - ) { - throw new Error("Unsupported distribution-board structure command."); +function assertNullableId(value: unknown, field: string) { + if (value !== null) { + assertNonEmptyString(value, field); + } +} + +function assertNullableSupplyType( + value: unknown +): asserts value is DistributionBoardSupplyType | null { + if ( + value !== null && + !distributionBoardSupplyTypes.includes( + value as DistributionBoardSupplyType + ) + ) { + throw new Error("distributionBoard.supplyType is invalid."); } - assertDistributionBoardStructureSnapshot(command.payload.structure); } function assertNonEmptyString( diff --git a/src/domain/models/project-settings-project-command.model.ts b/src/domain/models/project-settings-project-command.model.ts index df943ef..c14a3cd 100644 --- a/src/domain/models/project-settings-project-command.model.ts +++ b/src/domain/models/project-settings-project-command.model.ts @@ -1,16 +1,21 @@ import type { SerializedProjectCommand } from "./project-command.model.js"; +import { + distributionBoardSupplyTypes, + type DistributionBoardSupplyType, +} from "../../shared/constants/distribution-board.js"; export const projectSettingsUpdateCommandType = "project.update-settings" as const; export const legacyProjectSettingsUpdateCommandSchemaVersion = 1 as const; -export const projectSettingsUpdateCommandSchemaVersion = 2 as const; +export const previousProjectSettingsUpdateCommandSchemaVersion = 2 as const; +export const projectSettingsUpdateCommandSchemaVersion = 3 as const; export interface LegacyProjectSettingsValues { singlePhaseVoltageV: number; threePhaseVoltageV: number; } -export interface ProjectSettingsValues extends LegacyProjectSettingsValues { +export interface PreviousProjectSettingsValues extends LegacyProjectSettingsValues { name: string; internalProjectNumber: string | null; externalProjectNumber: string | null; @@ -18,6 +23,10 @@ export interface ProjectSettingsValues extends LegacyProjectSettingsValues { description: string | null; } +export interface ProjectSettingsValues extends PreviousProjectSettingsValues { + enabledDistributionBoardSupplyTypes: DistributionBoardSupplyType[]; +} + export interface LegacyProjectSettingsUpdateProjectCommand extends SerializedProjectCommand { schemaVersion: typeof legacyProjectSettingsUpdateCommandSchemaVersion; @@ -30,8 +39,15 @@ export interface CurrentProjectSettingsUpdateProjectCommand type: typeof projectSettingsUpdateCommandType; } +export interface PreviousProjectSettingsUpdateProjectCommand + extends SerializedProjectCommand { + schemaVersion: typeof previousProjectSettingsUpdateCommandSchemaVersion; + type: typeof projectSettingsUpdateCommandType; +} + export type ProjectSettingsUpdateProjectCommand = | LegacyProjectSettingsUpdateProjectCommand + | PreviousProjectSettingsUpdateProjectCommand | CurrentProjectSettingsUpdateProjectCommand; export function createProjectSettingsUpdateProjectCommand( @@ -53,6 +69,8 @@ export function assertProjectSettingsUpdateProjectCommand( command.type !== projectSettingsUpdateCommandType || (command.schemaVersion !== legacyProjectSettingsUpdateCommandSchemaVersion && + command.schemaVersion !== + previousProjectSettingsUpdateCommandSchemaVersion && command.schemaVersion !== projectSettingsUpdateCommandSchemaVersion) ) { throw new Error("Unsupported project settings update command."); @@ -66,6 +84,10 @@ export function assertProjectSettingsUpdateProjectCommand( assertLegacyValues(command.payload); return; } + const expectedKeyCount = + command.schemaVersion === previousProjectSettingsUpdateCommandSchemaVersion + ? 7 + : 8; if ( !isTrimmedString(command.payload.name, 1, 200) || !isNullableTrimmedString(command.payload.internalProjectNumber, 100) || @@ -74,12 +96,61 @@ export function assertProjectSettingsUpdateProjectCommand( !isNullableTrimmedString(command.payload.description, 2000) || !isPositiveFiniteNumber(command.payload.singlePhaseVoltageV) || !isPositiveFiniteNumber(command.payload.threePhaseVoltageV) || - Object.keys(command.payload).length !== 7 + Object.keys(command.payload).length !== expectedKeyCount ) { throw new Error( "Project settings update command contains invalid values." ); } + if ( + command.schemaVersion === projectSettingsUpdateCommandSchemaVersion && + !isValidSupplyTypes( + command.payload.enabledDistributionBoardSupplyTypes + ) + ) { + throw new Error( + "Project settings update command contains invalid supply types." + ); + } +} + +export function normalizeProjectSettingsValues( + command: ProjectSettingsUpdateProjectCommand, + current: ProjectSettingsValues +): ProjectSettingsValues { + if (command.schemaVersion === legacyProjectSettingsUpdateCommandSchemaVersion) { + return { + ...current, + ...command.payload, + }; + } + if ( + command.schemaVersion === previousProjectSettingsUpdateCommandSchemaVersion + ) { + return { + ...command.payload, + enabledDistributionBoardSupplyTypes: + current.enabledDistributionBoardSupplyTypes, + }; + } + return command.payload; +} + +function isValidSupplyTypes( + value: unknown +): value is DistributionBoardSupplyType[] { + return ( + Array.isArray(value) && + value.length > 0 && + new Set(value).size === value.length && + value.every( + (entry) => + typeof entry === "string" && + distributionBoardSupplyTypes.includes( + entry as DistributionBoardSupplyType + ) + ) + ); } function assertLegacyValues( diff --git a/src/domain/models/project-state-snapshot.model.ts b/src/domain/models/project-state-snapshot.model.ts index cd74685..c1a7c91 100644 --- a/src/domain/models/project-state-snapshot.model.ts +++ b/src/domain/models/project-state-snapshot.model.ts @@ -1,7 +1,13 @@ import { z } from "zod"; +import { + defaultDistributionBoardSupplyTypes, + distributionBoardSupplyTypes, +} from "../../shared/constants/distribution-board.js"; export const legacyProjectStateSnapshotSchemaVersion = 1 as const; -export const projectStateSnapshotSchemaVersion = 2 as const; +export const previousProjectStateSnapshotSchemaVersion = 2 as const; +export const distributionBoardProjectStateSnapshotSchemaVersion = 3 as const; +export const projectStateSnapshotSchemaVersion = 4 as const; const idSchema = z.string().trim().min(1); const nullableStringSchema = z.string().nullable(); @@ -23,7 +29,14 @@ const projectSchema = legacyProjectSchema.extend({ description: z.string().trim().min(1).max(2000).nullable(), }); -const distributionBoardSchema = z +const currentProjectSchema = projectSchema.extend({ + enabledDistributionBoardSupplyTypes: z + .array(z.enum(distributionBoardSupplyTypes)) + .min(1) + .refine((values) => new Set(values).size === values.length), +}); + +const legacyDistributionBoardSchema = z .object({ id: idSchema, projectId: idSchema, @@ -31,6 +44,13 @@ const distributionBoardSchema = z }) .strict(); +const distributionBoardSchema = legacyDistributionBoardSchema + .extend({ + floorId: idSchema.nullable(), + supplyType: z.enum(distributionBoardSupplyTypes).nullable(), + }) + .strict(); + const circuitListSchema = z .object({ id: idSchema, @@ -140,8 +160,7 @@ const roomSchema = z }) .strict(); -const projectStateSnapshotContents = { - distributionBoards: z.array(distributionBoardSchema), +const commonProjectStateSnapshotContents = { circuitLists: z.array(circuitListSchema), circuitSections: z.array(circuitSectionSchema), circuits: z.array(circuitSchema), @@ -154,15 +173,37 @@ const legacyProjectStateSnapshotSchema = z .object({ schemaVersion: z.literal(legacyProjectStateSnapshotSchemaVersion), project: legacyProjectSchema, - ...projectStateSnapshotContents, + distributionBoards: z.array(legacyDistributionBoardSchema), + ...commonProjectStateSnapshotContents, + }) + .strict(); + +const previousProjectStateSnapshotSchema = z + .object({ + schemaVersion: z.literal(previousProjectStateSnapshotSchemaVersion), + project: projectSchema, + distributionBoards: z.array(legacyDistributionBoardSchema), + ...commonProjectStateSnapshotContents, + }) + .strict(); + +const distributionBoardProjectStateSnapshotSchema = z + .object({ + schemaVersion: z.literal( + distributionBoardProjectStateSnapshotSchemaVersion + ), + project: projectSchema, + distributionBoards: z.array(distributionBoardSchema), + ...commonProjectStateSnapshotContents, }) .strict(); export const projectStateSnapshotSchema = z .object({ schemaVersion: z.literal(projectStateSnapshotSchemaVersion), - project: projectSchema, - ...projectStateSnapshotContents, + project: currentProjectSchema, + distributionBoards: z.array(distributionBoardSchema), + ...commonProjectStateSnapshotContents, }) .strict(); @@ -176,20 +217,34 @@ export function parseProjectStateSnapshot( const version = isPlainObject(value) ? value.schemaVersion : undefined; const snapshot = version === legacyProjectStateSnapshotSchemaVersion - ? upgradeLegacyProjectStateSnapshot( - legacyProjectStateSnapshotSchema.parse(value) + ? upgradeDistributionBoardProjectStateSnapshot( + upgradePreviousProjectStateSnapshot( + upgradeLegacyProjectStateSnapshot( + legacyProjectStateSnapshotSchema.parse(value) + ) + ) ) - : projectStateSnapshotSchema.parse(value); + : version === previousProjectStateSnapshotSchemaVersion + ? upgradeDistributionBoardProjectStateSnapshot( + upgradePreviousProjectStateSnapshot( + previousProjectStateSnapshotSchema.parse(value) + ) + ) + : version === distributionBoardProjectStateSnapshotSchemaVersion + ? upgradeDistributionBoardProjectStateSnapshot( + distributionBoardProjectStateSnapshotSchema.parse(value) + ) + : projectStateSnapshotSchema.parse(value); assertProjectStateSnapshotRelations(snapshot); return snapshot; } function upgradeLegacyProjectStateSnapshot( snapshot: z.infer -): ProjectStateSnapshot { +): z.infer { return { ...snapshot, - schemaVersion: projectStateSnapshotSchemaVersion, + schemaVersion: previousProjectStateSnapshotSchemaVersion, project: { ...snapshot.project, internalProjectNumber: null, @@ -200,6 +255,35 @@ function upgradeLegacyProjectStateSnapshot( }; } +function upgradePreviousProjectStateSnapshot( + snapshot: z.infer +): z.infer { + return { + ...snapshot, + schemaVersion: distributionBoardProjectStateSnapshotSchemaVersion, + distributionBoards: snapshot.distributionBoards.map((board) => ({ + ...board, + floorId: null, + supplyType: null, + })), + }; +} + +function upgradeDistributionBoardProjectStateSnapshot( + snapshot: z.infer +): ProjectStateSnapshot { + return { + ...snapshot, + schemaVersion: projectStateSnapshotSchemaVersion, + project: { + ...snapshot.project, + enabledDistributionBoardSupplyTypes: [ + ...defaultDistributionBoardSupplyTypes, + ], + }, + }; +} + function isPlainObject(value: unknown): value is Record { return value !== null && typeof value === "object" && !Array.isArray(value); } @@ -245,6 +329,23 @@ function assertProjectStateSnapshotRelations( for (const board of snapshot.distributionBoards) { assertProjectOwnership(board.projectId, projectId, "distribution board"); + if (board.floorId !== null) { + assertReference( + floorIds, + board.floorId, + "distribution board floor" + ); + } + if ( + board.supplyType !== null && + !snapshot.project.enabledDistributionBoardSupplyTypes.includes( + board.supplyType + ) + ) { + throw new Error( + "Snapshot distribution board uses a disabled supply type." + ); + } } for (const list of snapshot.circuitLists) { assertProjectOwnership(list.projectId, projectId, "circuit list"); diff --git a/src/domain/models/project-transfer.model.ts b/src/domain/models/project-transfer.model.ts index f65433f..baa20a0 100644 --- a/src/domain/models/project-transfer.model.ts +++ b/src/domain/models/project-transfer.model.ts @@ -94,6 +94,10 @@ export function remapProjectState( ...board, id: requiredId(boardIds, board.id), projectId: targetProjectId, + floorId: + board.floorId === null + ? null + : requiredId(floorIds, board.floorId), })), circuitLists: source.circuitLists.map((list) => ({ ...list, diff --git a/src/domain/ports/distribution-board-structure-project-command.store.ts b/src/domain/ports/distribution-board-structure-project-command.store.ts index 8c81dff..f0cb5c9 100644 --- a/src/domain/ports/distribution-board-structure-project-command.store.ts +++ b/src/domain/ports/distribution-board-structure-project-command.store.ts @@ -1,4 +1,5 @@ import type { DistributionBoardStructureProjectCommand } from "../models/distribution-board-structure-project-command.model.js"; +import type { DistributionBoardUpdateProjectCommand } from "../models/distribution-board-project-command.model.js"; import type { AppendedProjectRevision, ProjectRevisionSource, @@ -23,4 +24,15 @@ export interface DistributionBoardStructureProjectCommandStore { execute( input: ExecuteDistributionBoardStructureCommandInput ): ExecutedDistributionBoardStructureCommand; + executeUpdate( + input: Omit< + ExecuteDistributionBoardStructureCommandInput, + "command" + > & { + command: DistributionBoardUpdateProjectCommand; + } + ): { + revision: AppendedProjectRevision; + inverse: DistributionBoardUpdateProjectCommand; + }; } diff --git a/src/domain/services/project-command.service.ts b/src/domain/services/project-command.service.ts index 0e7bcdc..2d5f8b9 100644 --- a/src/domain/services/project-command.service.ts +++ b/src/domain/services/project-command.service.ts @@ -37,6 +37,10 @@ import { circuitDeleteCommandType, circuitInsertCommandType, } from "../models/circuit-structure-project-command.model.js"; +import { + assertDistributionBoardUpdateProjectCommand, + distributionBoardUpdateCommandType, +} from "../models/distribution-board-project-command.model.js"; import { assertDistributionBoardDeleteProjectCommand, assertDistributionBoardInsertProjectCommand, @@ -285,6 +289,13 @@ export class ProjectCommandService implements ProjectCommandExecutor { command: input.command, }).revision; } + case distributionBoardUpdateCommandType: { + assertDistributionBoardUpdateProjectCommand(input.command); + return this.distributionBoardStructureStore.executeUpdate({ + ...input, + command: input.command, + }).revision; + } case projectFloorInsertCommandType: { assertProjectFloorInsertProjectCommand(input.command); return this.projectLocationStructureStore.execute({ diff --git a/src/frontend/components/project-settings-modal.tsx b/src/frontend/components/project-settings-modal.tsx index 5073129..228f600 100644 --- a/src/frontend/components/project-settings-modal.tsx +++ b/src/frontend/components/project-settings-modal.tsx @@ -1,7 +1,12 @@ "use client"; -import { FormEvent, useState } from "react"; +import React, { FormEvent, useState } from "react"; import type { ProjectDto } from "../types"; +import { + distributionBoardSupplyTypeLabels, + distributionBoardSupplyTypes, + type DistributionBoardSupplyType, +} from "../../shared/constants/distribution-board"; export interface ProjectSettingsInput { name: string; @@ -11,6 +16,7 @@ export interface ProjectSettingsInput { description: string | null; singlePhaseVoltageV: number; threePhaseVoltageV: number; + enabledDistributionBoardSupplyTypes: DistributionBoardSupplyType[]; } interface ProjectSettingsModalProps { @@ -23,6 +29,7 @@ interface ProjectSettingsModalProps { ) => Promise; onSave: (input: ProjectSettingsInput) => Promise; project: ProjectDto; + usedDistributionBoardSupplyTypes: DistributionBoardSupplyType[]; } export function ProjectSettingsModal({ @@ -32,6 +39,7 @@ export function ProjectSettingsModal({ onImport, onSave, project, + usedDistributionBoardSupplyTypes, }: ProjectSettingsModalProps) { const [name, setName] = useState(project.name); const [internalProjectNumber, setInternalProjectNumber] = useState( @@ -52,6 +60,12 @@ export function ProjectSettingsModal({ const [threePhaseVoltageV, setThreePhaseVoltageV] = useState( String(project.threePhaseVoltageV) ); + const [ + enabledDistributionBoardSupplyTypes, + setEnabledDistributionBoardSupplyTypes, + ] = useState( + project.enabledDistributionBoardSupplyTypes + ); const [transfer, setTransfer] = useState(null); const [transferFilename, setTransferFilename] = useState(""); const [importMode, setImportMode] = useState<"replace" | "duplicate">( @@ -70,13 +84,31 @@ export function ProjectSettingsModal({ description: toNullableString(description), singlePhaseVoltageV: Number(singlePhaseVoltageV), threePhaseVoltageV: Number(threePhaseVoltageV), + enabledDistributionBoardSupplyTypes, }); } const isValid = name.trim().length > 0 && Number(singlePhaseVoltageV) > 0 && - Number(threePhaseVoltageV) > 0; + Number(threePhaseVoltageV) > 0 && + enabledDistributionBoardSupplyTypes.length > 0; + + function toggleSupplyType(supplyType: DistributionBoardSupplyType) { + if ( + enabledDistributionBoardSupplyTypes.includes(supplyType) && + usedDistributionBoardSupplyTypes.includes(supplyType) + ) { + return; + } + setEnabledDistributionBoardSupplyTypes((current) => + current.includes(supplyType) + ? current.filter((entry) => entry !== supplyType) + : distributionBoardSupplyTypes.filter( + (entry) => entry === supplyType || current.includes(entry) + ) + ); + } async function handleTransferFile(file: File | undefined) { setTransfer(null); @@ -165,6 +197,53 @@ export function ProjectSettingsModal({ 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} +
+