Add component persistence foundation
This commit is contained in:
@@ -335,6 +335,15 @@ Kopieren in ein Projekt erzeugt ein eigenständiges Projektgerät.
|
||||
- Die vollständige Verteilungs-Testfixture liegt unter
|
||||
`tests/support/distribution-board-fixture.ts` und ist kein exportierter
|
||||
Produktions-Schreibweg.
|
||||
- Migration `0024` bildet die additive relationale Grundlage für geschützte
|
||||
Stromkreisgruppen und Verteilerkomponenten. Die drei bestehenden
|
||||
Stromkreisabschnitte erhalten Kategorie und Gruppennummer 1. Separate
|
||||
1:1-Tabellen halten künftig Stromkreis- und Komponenten-Schutzgeräte; die
|
||||
bisherigen flachen Schutzfelder bleiben in dieser Übergangsphase unverändert.
|
||||
Ein triggergeführtes Register erzwingt bereits eine normalisierte,
|
||||
stromkreislistenweite BMK-Eindeutigkeit über Stromkreise und
|
||||
Verteilerkomponenten. Snapshot-, Transfer-, Command- und UI-Integration
|
||||
folgen in den nächsten abgegrenzten Arbeitspaketen.
|
||||
|
||||
PostgreSQL ist bewusst nicht implementiert. Die Domainregeln und
|
||||
Transaktionsgrenzen sollen portabel bleiben; Schema und Betriebsmodell benötigen
|
||||
|
||||
@@ -14,7 +14,10 @@ requirements and intended sequencing, not proof of implementation.
|
||||
component model in phased work packages.
|
||||
- [x] Phase A: central protection catalog, validation, defaults and pure grouped
|
||||
BMK rules.
|
||||
- [ ] Phase B: persistence and snapshot/transfer compatibility model.
|
||||
- [x] Phase B1: additive relational schema, group backfill, component/protection
|
||||
tables and shared BMK uniqueness.
|
||||
- [ ] Phase B2: snapshot/transfer schema upgrade and deterministic compatibility
|
||||
mapping.
|
||||
- [ ] Phase C: persistent commands and new-board defaults.
|
||||
- [ ] Phase D: group numbering, moves and destructive operations.
|
||||
- [ ] Phase E: editor projection and editing.
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
## Status
|
||||
|
||||
This document defines the agreed target behavior for distribution-board
|
||||
components, protected circuit groups and circuit protection devices. It is a
|
||||
future specification, not proof of implementation.
|
||||
components, protected circuit groups and circuit protection devices. Phase A
|
||||
and the relational Phase-B1 foundation are implemented; later sections remain
|
||||
requirements and are not proof of runtime or editor support.
|
||||
|
||||
Full rule-based protection and cable sizing is deliberately deferred. This
|
||||
phase establishes the structure and manually selected technical values that a
|
||||
@@ -544,6 +545,9 @@ Acceptance:
|
||||
|
||||
### B. Persistence and Compatibility Model
|
||||
|
||||
Status: In progress. Relational foundation B1 is complete; logical
|
||||
snapshot/transfer integration B2 is pending.
|
||||
|
||||
- add group category and group number
|
||||
- add distribution-board component persistence
|
||||
- add one-to-one circuit protection persistence
|
||||
@@ -552,6 +556,20 @@ Acceptance:
|
||||
- add snapshot/transfer schema upgrade
|
||||
- preserve existing identifiers and protection values
|
||||
|
||||
Implemented in B1:
|
||||
|
||||
- additive migration `0024` adds group identity to circuit sections and maps
|
||||
the three established sections to group 1 without changing UUIDs
|
||||
- separate component and circuit/component protection tables preserve the
|
||||
one-to-one ownership boundaries
|
||||
- a trigger-maintained circuit-list registry rejects normalized BMK collisions
|
||||
across circuits and distribution-board components
|
||||
- the existing flat circuit protection fields remain untouched until their
|
||||
deterministic snapshot/runtime transition is implemented
|
||||
- clean and populated pre-migration databases are covered by migration tests
|
||||
- newly created legacy-compatible board structures receive the same initial
|
||||
group identity while their persisted command format remains unchanged
|
||||
|
||||
Acceptance:
|
||||
|
||||
- a current database migrates without data loss
|
||||
|
||||
+2
-2
@@ -15,8 +15,8 @@
|
||||
"build:web": "next build",
|
||||
"typecheck:scripts": "tsc --noEmit -p tsconfig.scripts.json",
|
||||
"start": "node dist/server/index.js",
|
||||
"test": "tsx --test tests/protection-device.test.ts tests/distribution-board-component.test.ts tests/circuit-group-numbering.test.ts tests/project-device-schema.test.ts tests/project-device-schema-migration.test.ts tests/project-device-placement.service.test.ts tests/project-device-sync.service.test.ts tests/legacy-consumer-migration-planner.test.ts tests/legacy-consumer-migration.service.test.ts tests/legacy-consumer-migration.repository.test.ts tests/circuit-numbering.service.test.ts tests/circuit-power-calculation.test.ts tests/circuit-tree.controller.test.ts tests/circuit-editor-history.test.ts tests/circuit-grid-insertion.test.ts tests/circuit-grid-safety.test.ts tests/circuit-grid-model.test.ts tests/circuit-structure-command.test.ts tests/circuit-device-row-move-command.test.ts tests/circuit-section-reorder-command.test.ts tests/circuit-section-renumber-command.test.ts tests/circuit-grid-projection.test.ts tests/distribution-board-structure-project-command.repository.test.ts tests/project-location-structure-project-command.repository.test.ts tests/project-device-project-command.repository.test.ts tests/project-device-structure-project-command.repository.test.ts tests/project-device-row-sync-project-command.repository.test.ts tests/circuit-section-reorder-project-command.repository.test.ts tests/circuit-section-renumber-project-command.repository.test.ts tests/project-command.model.test.ts tests/project-revision.persistence.test.ts tests/circuit-project-command.repository.test.ts tests/circuit-structure-project-command.repository.test.ts tests/circuit-device-row-project-command.repository.test.ts tests/circuit-device-row-structure-project-command.repository.test.ts tests/circuit-device-row-move-project-command.repository.test.ts tests/project-history.repository.test.ts tests/project-history-timeline.test.ts tests/project-version-history.test.ts tests/project-overview-import.test.ts tests/project-settings-project-command.repository.test.ts tests/project-state-snapshot.test.ts tests/project-snapshot-policy.test.ts tests/project-snapshot.repository.test.ts tests/project-state-restore-command.repository.test.ts tests/project-command.service.test.ts tests/database-backup.test.ts",
|
||||
"test:watch": "tsx --watch --test tests/protection-device.test.ts tests/distribution-board-component.test.ts tests/circuit-group-numbering.test.ts tests/project-device-schema.test.ts tests/project-device-schema-migration.test.ts tests/project-device-placement.service.test.ts tests/project-device-sync.service.test.ts tests/legacy-consumer-migration-planner.test.ts tests/legacy-consumer-migration.service.test.ts tests/legacy-consumer-migration.repository.test.ts tests/circuit-numbering.service.test.ts tests/circuit-power-calculation.test.ts tests/circuit-tree.controller.test.ts tests/circuit-editor-history.test.ts tests/circuit-grid-insertion.test.ts tests/circuit-grid-safety.test.ts tests/circuit-grid-model.test.ts tests/circuit-structure-command.test.ts tests/circuit-device-row-move-command.test.ts tests/circuit-section-reorder-command.test.ts tests/circuit-section-renumber-command.test.ts tests/circuit-grid-projection.test.ts tests/distribution-board-structure-project-command.repository.test.ts tests/project-location-structure-project-command.repository.test.ts tests/project-device-project-command.repository.test.ts tests/project-device-structure-project-command.repository.test.ts tests/project-device-row-sync-project-command.repository.test.ts tests/circuit-section-reorder-project-command.repository.test.ts tests/circuit-section-renumber-project-command.repository.test.ts tests/project-command.model.test.ts tests/project-revision.persistence.test.ts tests/circuit-project-command.repository.test.ts tests/circuit-structure-project-command.repository.test.ts tests/circuit-device-row-project-command.repository.test.ts tests/circuit-device-row-structure-project-command.repository.test.ts tests/circuit-device-row-move-project-command.repository.test.ts tests/project-history.repository.test.ts tests/project-history-timeline.test.ts tests/project-version-history.test.ts tests/project-overview-import.test.ts tests/project-settings-project-command.repository.test.ts tests/project-state-snapshot.test.ts tests/project-snapshot-policy.test.ts tests/project-snapshot.repository.test.ts tests/project-state-restore-command.repository.test.ts tests/project-command.service.test.ts tests/database-backup.test.ts",
|
||||
"test": "tsx --test tests/protection-device.test.ts tests/distribution-board-component.test.ts tests/distribution-board-component-schema-migration.test.ts tests/circuit-group-numbering.test.ts tests/project-device-schema.test.ts tests/project-device-schema-migration.test.ts tests/project-device-placement.service.test.ts tests/project-device-sync.service.test.ts tests/legacy-consumer-migration-planner.test.ts tests/legacy-consumer-migration.service.test.ts tests/legacy-consumer-migration.repository.test.ts tests/circuit-numbering.service.test.ts tests/circuit-power-calculation.test.ts tests/circuit-tree.controller.test.ts tests/circuit-editor-history.test.ts tests/circuit-grid-insertion.test.ts tests/circuit-grid-safety.test.ts tests/circuit-grid-model.test.ts tests/circuit-structure-command.test.ts tests/circuit-device-row-move-command.test.ts tests/circuit-section-reorder-command.test.ts tests/circuit-section-renumber-command.test.ts tests/circuit-grid-projection.test.ts tests/distribution-board-structure-project-command.repository.test.ts tests/project-location-structure-project-command.repository.test.ts tests/project-device-project-command.repository.test.ts tests/project-device-structure-project-command.repository.test.ts tests/project-device-row-sync-project-command.repository.test.ts tests/circuit-section-reorder-project-command.repository.test.ts tests/circuit-section-renumber-project-command.repository.test.ts tests/project-command.model.test.ts tests/project-revision.persistence.test.ts tests/circuit-project-command.repository.test.ts tests/circuit-structure-project-command.repository.test.ts tests/circuit-device-row-project-command.repository.test.ts tests/circuit-device-row-structure-project-command.repository.test.ts tests/circuit-device-row-move-project-command.repository.test.ts tests/project-history.repository.test.ts tests/project-history-timeline.test.ts tests/project-version-history.test.ts tests/project-overview-import.test.ts tests/project-settings-project-command.repository.test.ts tests/project-state-snapshot.test.ts tests/project-snapshot-policy.test.ts tests/project-snapshot.repository.test.ts tests/project-state-restore-command.repository.test.ts tests/project-command.service.test.ts tests/database-backup.test.ts",
|
||||
"test:watch": "tsx --watch --test tests/protection-device.test.ts tests/distribution-board-component.test.ts tests/distribution-board-component-schema-migration.test.ts tests/circuit-group-numbering.test.ts tests/project-device-schema.test.ts tests/project-device-schema-migration.test.ts tests/project-device-placement.service.test.ts tests/project-device-sync.service.test.ts tests/legacy-consumer-migration-planner.test.ts tests/legacy-consumer-migration.service.test.ts tests/legacy-consumer-migration.repository.test.ts tests/circuit-numbering.service.test.ts tests/circuit-power-calculation.test.ts tests/circuit-tree.controller.test.ts tests/circuit-editor-history.test.ts tests/circuit-grid-insertion.test.ts tests/circuit-grid-safety.test.ts tests/circuit-grid-model.test.ts tests/circuit-structure-command.test.ts tests/circuit-device-row-move-command.test.ts tests/circuit-section-reorder-command.test.ts tests/circuit-section-renumber-command.test.ts tests/circuit-grid-projection.test.ts tests/distribution-board-structure-project-command.repository.test.ts tests/project-location-structure-project-command.repository.test.ts tests/project-device-project-command.repository.test.ts tests/project-device-structure-project-command.repository.test.ts tests/project-device-row-sync-project-command.repository.test.ts tests/circuit-section-reorder-project-command.repository.test.ts tests/circuit-section-renumber-project-command.repository.test.ts tests/project-command.model.test.ts tests/project-revision.persistence.test.ts tests/circuit-project-command.repository.test.ts tests/circuit-structure-project-command.repository.test.ts tests/circuit-device-row-project-command.repository.test.ts tests/circuit-device-row-structure-project-command.repository.test.ts tests/circuit-device-row-move-project-command.repository.test.ts tests/project-history.repository.test.ts tests/project-history-timeline.test.ts tests/project-version-history.test.ts tests/project-overview-import.test.ts tests/project-settings-project-command.repository.test.ts tests/project-state-snapshot.test.ts tests/project-snapshot-policy.test.ts tests/project-snapshot.repository.test.ts tests/project-state-restore-command.repository.test.ts tests/project-command.service.test.ts tests/database-backup.test.ts",
|
||||
"db:generate": "drizzle-kit generate",
|
||||
"db:migrate": "drizzle-kit migrate",
|
||||
"db:backup": "tsx scripts/db-backup.ts",
|
||||
|
||||
@@ -8,12 +8,21 @@ const requiredTables = [
|
||||
"circuit_sections",
|
||||
"circuits",
|
||||
"circuit_device_rows",
|
||||
"circuit_list_equipment_identifiers",
|
||||
"circuit_protection_devices",
|
||||
"distribution_board_components",
|
||||
"distribution_board_component_protection_devices",
|
||||
"legacy_consumer_circuit_migrations",
|
||||
"legacy_consumer_migration_reports",
|
||||
];
|
||||
|
||||
const tablePlaceholders = requiredTables.map(() => "?").join(", ");
|
||||
const rows = db
|
||||
.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name IN (?, ?, ?, ?, ?)")
|
||||
.prepare(
|
||||
`SELECT name
|
||||
FROM sqlite_master
|
||||
WHERE type = 'table' AND name IN (${tablePlaceholders})`
|
||||
)
|
||||
.all(...requiredTables);
|
||||
|
||||
const existing = new Set(rows.map((row) => row.name));
|
||||
@@ -48,6 +57,16 @@ const circuitColumns = new Set(
|
||||
db.prepare("PRAGMA table_info(circuits)").all().map((column) => column.name)
|
||||
);
|
||||
const missingCircuitColumns = requiredCircuitColumns.filter((name) => !circuitColumns.has(name));
|
||||
const requiredCircuitSectionColumns = ["category", "group_number"];
|
||||
const circuitSectionColumns = new Set(
|
||||
db
|
||||
.prepare("PRAGMA table_info(circuit_sections)")
|
||||
.all()
|
||||
.map((column) => column.name)
|
||||
);
|
||||
const missingCircuitSectionColumns = requiredCircuitSectionColumns.filter(
|
||||
(name) => !circuitSectionColumns.has(name)
|
||||
);
|
||||
const requiredProjectColumns = [
|
||||
"internal_project_number",
|
||||
"external_project_number",
|
||||
@@ -61,7 +80,11 @@ const projectColumns = new Set(
|
||||
const missingProjectColumns = requiredProjectColumns.filter(
|
||||
(name) => !projectColumns.has(name)
|
||||
);
|
||||
const requiredDistributionBoardColumns = ["floor_id", "supply_type"];
|
||||
const requiredDistributionBoardColumns = [
|
||||
"floor_id",
|
||||
"supply_type",
|
||||
"simultaneity_factor",
|
||||
];
|
||||
const distributionBoardColumns = new Set(
|
||||
db
|
||||
.prepare("PRAGMA table_info(distribution_boards)")
|
||||
@@ -72,6 +95,28 @@ const missingDistributionBoardColumns =
|
||||
requiredDistributionBoardColumns.filter(
|
||||
(name) => !distributionBoardColumns.has(name)
|
||||
);
|
||||
const requiredEquipmentIdentifierTriggers = [
|
||||
"circuits_equipment_identifier_insert",
|
||||
"circuits_equipment_identifier_update",
|
||||
"circuits_equipment_identifier_delete",
|
||||
"distribution_board_components_equipment_identifier_insert",
|
||||
"distribution_board_components_equipment_identifier_update",
|
||||
"distribution_board_components_equipment_identifier_delete",
|
||||
];
|
||||
const equipmentIdentifierTriggers = new Set(
|
||||
db
|
||||
.prepare(
|
||||
`SELECT name
|
||||
FROM sqlite_master
|
||||
WHERE type = 'trigger'`
|
||||
)
|
||||
.all()
|
||||
.map((trigger) => trigger.name)
|
||||
);
|
||||
const missingEquipmentIdentifierTriggers =
|
||||
requiredEquipmentIdentifierTriggers.filter(
|
||||
(name) => !equipmentIdentifierTriggers.has(name)
|
||||
);
|
||||
const integrityCheck = db.pragma("integrity_check", { simple: true });
|
||||
const foreignKeyViolations = db.pragma("foreign_key_check");
|
||||
|
||||
@@ -80,11 +125,19 @@ console.log("Required tables:", requiredTables.join(", "));
|
||||
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 circuit-section columns:",
|
||||
requiredCircuitSectionColumns.join(", ")
|
||||
);
|
||||
console.log("Required project columns:", requiredProjectColumns.join(", "));
|
||||
console.log(
|
||||
"Required distribution-board columns:",
|
||||
requiredDistributionBoardColumns.join(", ")
|
||||
);
|
||||
console.log(
|
||||
"Required BMK synchronization triggers:",
|
||||
requiredEquipmentIdentifierTriggers.join(", ")
|
||||
);
|
||||
console.log("Integrity:", integrityCheck);
|
||||
console.log("Foreign-key violations:", foreignKeyViolations.length);
|
||||
|
||||
@@ -111,6 +164,14 @@ if (missingCircuitColumns.length > 0) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (missingCircuitSectionColumns.length > 0) {
|
||||
console.error(
|
||||
"Missing circuit-section columns:",
|
||||
missingCircuitSectionColumns.join(", ")
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (missingProjectColumns.length > 0) {
|
||||
console.error("Missing project columns:", missingProjectColumns.join(", "));
|
||||
process.exit(1);
|
||||
@@ -124,9 +185,17 @@ if (missingDistributionBoardColumns.length > 0) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (missingEquipmentIdentifierTriggers.length > 0) {
|
||||
console.error(
|
||||
"Missing BMK synchronization triggers:",
|
||||
missingEquipmentIdentifierTriggers.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.");
|
||||
console.log("Circuit-first and distribution-component schema verification passed.");
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
CREATE TABLE `circuit_list_equipment_identifiers` (
|
||||
`owner_type` text NOT NULL,
|
||||
`owner_id` text NOT NULL,
|
||||
`circuit_list_id` text NOT NULL,
|
||||
`equipment_identifier` text NOT NULL,
|
||||
FOREIGN KEY (`circuit_list_id`) REFERENCES `circuit_lists`(`id`) ON UPDATE no action ON DELETE cascade
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX `circuit_list_equipment_identifiers_owner_unique` ON `circuit_list_equipment_identifiers` (`owner_type`,`owner_id`);--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX `circuit_list_equipment_identifiers_list_identifier_unique` ON `circuit_list_equipment_identifiers` (`circuit_list_id`,`equipment_identifier`);--> statement-breakpoint
|
||||
CREATE TABLE `circuit_protection_devices` (
|
||||
`circuit_id` text PRIMARY KEY NOT NULL,
|
||||
`type` text NOT NULL,
|
||||
`rated_current_a` real NOT NULL,
|
||||
`fuse_utilization_category` text,
|
||||
`trip_characteristic` text,
|
||||
`rcd_type` text,
|
||||
`rated_residual_current_ma` real,
|
||||
FOREIGN KEY (`circuit_id`) REFERENCES `circuits`(`id`) ON UPDATE no action ON DELETE cascade
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE `distribution_board_component_protection_devices` (
|
||||
`component_id` text PRIMARY KEY NOT NULL,
|
||||
`type` text NOT NULL,
|
||||
`rated_current_a` real NOT NULL,
|
||||
`fuse_utilization_category` text,
|
||||
`trip_characteristic` text,
|
||||
`rcd_type` text,
|
||||
`rated_residual_current_ma` real,
|
||||
FOREIGN KEY (`component_id`) REFERENCES `distribution_board_components`(`id`) ON UPDATE no action ON DELETE cascade
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE `distribution_board_components` (
|
||||
`id` text PRIMARY KEY NOT NULL,
|
||||
`circuit_list_id` text NOT NULL,
|
||||
`section_id` text,
|
||||
`equipment_identifier` text NOT NULL,
|
||||
`name` text NOT NULL,
|
||||
`role` text NOT NULL,
|
||||
`placement` text NOT NULL,
|
||||
`sort_order` integer DEFAULT 0 NOT NULL,
|
||||
FOREIGN KEY (`circuit_list_id`) REFERENCES `circuit_lists`(`id`) ON UPDATE no action ON DELETE cascade,
|
||||
FOREIGN KEY (`section_id`) REFERENCES `circuit_sections`(`id`) ON UPDATE no action ON DELETE cascade
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX `distribution_board_components_list_identifier_unique` ON `distribution_board_components` (`circuit_list_id`,`equipment_identifier`);--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX `distribution_board_components_section_role_unique` ON `distribution_board_components` (`section_id`,`role`);--> statement-breakpoint
|
||||
ALTER TABLE `circuit_sections` ADD `category` text;--> statement-breakpoint
|
||||
ALTER TABLE `circuit_sections` ADD `group_number` integer;--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX `circuit_sections_list_category_group_unique` ON `circuit_sections` (`circuit_list_id`,`category`,`group_number`);--> statement-breakpoint
|
||||
UPDATE `circuit_sections`
|
||||
SET
|
||||
`category` = CASE `key`
|
||||
WHEN 'lighting' THEN 'lighting'
|
||||
WHEN 'single_phase' THEN 'single_phase'
|
||||
WHEN 'three_phase' THEN 'three_phase'
|
||||
END,
|
||||
`group_number` = 1
|
||||
WHERE `key` IN ('lighting', 'single_phase', 'three_phase');--> statement-breakpoint
|
||||
INSERT INTO `circuit_list_equipment_identifiers` (
|
||||
`owner_type`,
|
||||
`owner_id`,
|
||||
`circuit_list_id`,
|
||||
`equipment_identifier`
|
||||
)
|
||||
SELECT
|
||||
'circuit',
|
||||
`id`,
|
||||
`circuit_list_id`,
|
||||
`equipment_identifier`
|
||||
FROM `circuits`;--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX `circuit_list_equipment_identifiers_normalized_unique`
|
||||
ON `circuit_list_equipment_identifiers` (
|
||||
`circuit_list_id`,
|
||||
lower(trim(`equipment_identifier`))
|
||||
);--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX `distribution_board_components_fixed_role_unique`
|
||||
ON `distribution_board_components` (`circuit_list_id`, `role`)
|
||||
WHERE `role` IN ('main_switch', 'surge_protective_device');--> statement-breakpoint
|
||||
CREATE TRIGGER `circuits_equipment_identifier_insert`
|
||||
AFTER INSERT ON `circuits`
|
||||
BEGIN
|
||||
INSERT INTO `circuit_list_equipment_identifiers` (
|
||||
`owner_type`,
|
||||
`owner_id`,
|
||||
`circuit_list_id`,
|
||||
`equipment_identifier`
|
||||
)
|
||||
VALUES (
|
||||
'circuit',
|
||||
NEW.`id`,
|
||||
NEW.`circuit_list_id`,
|
||||
NEW.`equipment_identifier`
|
||||
);
|
||||
END;--> statement-breakpoint
|
||||
CREATE TRIGGER `circuits_equipment_identifier_update`
|
||||
AFTER UPDATE OF `circuit_list_id`, `equipment_identifier` ON `circuits`
|
||||
BEGIN
|
||||
UPDATE `circuit_list_equipment_identifiers`
|
||||
SET
|
||||
`circuit_list_id` = NEW.`circuit_list_id`,
|
||||
`equipment_identifier` = NEW.`equipment_identifier`
|
||||
WHERE
|
||||
`owner_type` = 'circuit'
|
||||
AND `owner_id` = OLD.`id`;
|
||||
END;--> statement-breakpoint
|
||||
CREATE TRIGGER `circuits_equipment_identifier_delete`
|
||||
AFTER DELETE ON `circuits`
|
||||
BEGIN
|
||||
DELETE FROM `circuit_list_equipment_identifiers`
|
||||
WHERE
|
||||
`owner_type` = 'circuit'
|
||||
AND `owner_id` = OLD.`id`;
|
||||
END;--> statement-breakpoint
|
||||
CREATE TRIGGER `distribution_board_components_equipment_identifier_insert`
|
||||
AFTER INSERT ON `distribution_board_components`
|
||||
BEGIN
|
||||
INSERT INTO `circuit_list_equipment_identifiers` (
|
||||
`owner_type`,
|
||||
`owner_id`,
|
||||
`circuit_list_id`,
|
||||
`equipment_identifier`
|
||||
)
|
||||
VALUES (
|
||||
'distribution_board_component',
|
||||
NEW.`id`,
|
||||
NEW.`circuit_list_id`,
|
||||
NEW.`equipment_identifier`
|
||||
);
|
||||
END;--> statement-breakpoint
|
||||
CREATE TRIGGER `distribution_board_components_equipment_identifier_update`
|
||||
AFTER UPDATE OF `circuit_list_id`, `equipment_identifier`
|
||||
ON `distribution_board_components`
|
||||
BEGIN
|
||||
UPDATE `circuit_list_equipment_identifiers`
|
||||
SET
|
||||
`circuit_list_id` = NEW.`circuit_list_id`,
|
||||
`equipment_identifier` = NEW.`equipment_identifier`
|
||||
WHERE
|
||||
`owner_type` = 'distribution_board_component'
|
||||
AND `owner_id` = OLD.`id`;
|
||||
END;--> statement-breakpoint
|
||||
CREATE TRIGGER `distribution_board_components_equipment_identifier_delete`
|
||||
AFTER DELETE ON `distribution_board_components`
|
||||
BEGIN
|
||||
DELETE FROM `circuit_list_equipment_identifiers`
|
||||
WHERE
|
||||
`owner_type` = 'distribution_board_component'
|
||||
AND `owner_id` = OLD.`id`;
|
||||
END;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -169,6 +169,13 @@
|
||||
"when": 1785343645615,
|
||||
"tag": "0023_cheerful_night_thrasher",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 24,
|
||||
"version": "6",
|
||||
"when": 1785431250141,
|
||||
"tag": "0024_damp_mister_sinister",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -24,6 +24,7 @@ import type {
|
||||
DistributionBoardStructureProjectCommandStore,
|
||||
ExecuteDistributionBoardStructureCommandInput,
|
||||
} from "../../domain/ports/distribution-board-structure-project-command.store.js";
|
||||
import type { CircuitGroupCategory } from "../../shared/constants/circuit-group.js";
|
||||
import type { AppDatabase } from "../database-context.js";
|
||||
import { circuitLists } from "../schema/circuit-lists.js";
|
||||
import { circuitSections } from "../schema/circuit-sections.js";
|
||||
@@ -168,7 +169,10 @@ export class DistributionBoardStructureProjectCommandRepository
|
||||
.values(structure.distributionBoard)
|
||||
.run();
|
||||
database.insert(circuitLists).values(structure.circuitList).run();
|
||||
database.insert(circuitSections).values(structure.sections).run();
|
||||
database
|
||||
.insert(circuitSections)
|
||||
.values(structure.sections.map(withInitialGroupIdentity))
|
||||
.run();
|
||||
return createDistributionBoardDeleteProjectCommand(structure);
|
||||
}
|
||||
|
||||
@@ -409,9 +413,48 @@ function structureMatches(
|
||||
left.sortOrder - right.sortOrder ||
|
||||
left.id.localeCompare(right.id)
|
||||
);
|
||||
return expectedSections.every((section, index) =>
|
||||
sameRecord(section, actual.sections[index])
|
||||
);
|
||||
return expectedSections.every((section, index) => {
|
||||
const actualSection = actual.sections[index];
|
||||
if (
|
||||
actualSection.category !== initialCategoryBySectionKey(section.key) ||
|
||||
actualSection.groupNumber !== initialGroupNumberBySectionKey(section.key)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
const {
|
||||
category: _category,
|
||||
groupNumber: _groupNumber,
|
||||
...comparableActualSection
|
||||
} = actualSection;
|
||||
return sameRecord(section, comparableActualSection);
|
||||
});
|
||||
}
|
||||
|
||||
function withInitialGroupIdentity(
|
||||
section: DistributionBoardStructureSnapshot["sections"][number]
|
||||
): typeof circuitSections.$inferInsert {
|
||||
return {
|
||||
...section,
|
||||
category: initialCategoryBySectionKey(section.key),
|
||||
groupNumber: initialGroupNumberBySectionKey(section.key),
|
||||
};
|
||||
}
|
||||
|
||||
function initialCategoryBySectionKey(
|
||||
key: string
|
||||
): CircuitGroupCategory | null {
|
||||
if (
|
||||
key === "lighting" ||
|
||||
key === "single_phase" ||
|
||||
key === "three_phase"
|
||||
) {
|
||||
return key;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function initialGroupNumberBySectionKey(key: string): number | null {
|
||||
return initialCategoryBySectionKey(key) === null ? null : 1;
|
||||
}
|
||||
|
||||
function sameRecord(
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { sqliteTable, text, unique } from "drizzle-orm/sqlite-core";
|
||||
import { circuitLists } from "./circuit-lists.js";
|
||||
|
||||
export const circuitListEquipmentIdentifiers = sqliteTable(
|
||||
"circuit_list_equipment_identifiers",
|
||||
{
|
||||
ownerType: text("owner_type")
|
||||
.$type<"circuit" | "distribution_board_component">()
|
||||
.notNull(),
|
||||
ownerId: text("owner_id").notNull(),
|
||||
circuitListId: text("circuit_list_id")
|
||||
.notNull()
|
||||
.references(() => circuitLists.id, { onDelete: "cascade" }),
|
||||
equipmentIdentifier: text("equipment_identifier").notNull(),
|
||||
},
|
||||
(table) => [
|
||||
unique("circuit_list_equipment_identifiers_owner_unique").on(
|
||||
table.ownerType,
|
||||
table.ownerId
|
||||
),
|
||||
unique("circuit_list_equipment_identifiers_list_identifier_unique").on(
|
||||
table.circuitListId,
|
||||
table.equipmentIdentifier
|
||||
),
|
||||
]
|
||||
);
|
||||
@@ -0,0 +1,27 @@
|
||||
import { real, sqliteTable, text } from "drizzle-orm/sqlite-core";
|
||||
import type {
|
||||
BreakerTripCharacteristic,
|
||||
FuseUtilizationCategory,
|
||||
ProtectionDeviceType,
|
||||
RcdType,
|
||||
} from "../../shared/constants/protection-device.js";
|
||||
import { circuits } from "./circuits.js";
|
||||
|
||||
export const circuitProtectionDevices = sqliteTable(
|
||||
"circuit_protection_devices",
|
||||
{
|
||||
circuitId: text("circuit_id")
|
||||
.primaryKey()
|
||||
.references(() => circuits.id, { onDelete: "cascade" }),
|
||||
type: text("type").$type<ProtectionDeviceType>().notNull(),
|
||||
ratedCurrentA: real("rated_current_a").notNull(),
|
||||
fuseUtilizationCategory: text(
|
||||
"fuse_utilization_category"
|
||||
).$type<FuseUtilizationCategory>(),
|
||||
tripCharacteristic: text(
|
||||
"trip_characteristic"
|
||||
).$type<BreakerTripCharacteristic>(),
|
||||
rcdType: text("rcd_type").$type<RcdType>(),
|
||||
ratedResidualCurrentMa: real("rated_residual_current_ma"),
|
||||
}
|
||||
);
|
||||
@@ -1,5 +1,6 @@
|
||||
import { integer, sqliteTable, text, unique } from "drizzle-orm/sqlite-core";
|
||||
import { circuitLists } from "./circuit-lists.js";
|
||||
import type { CircuitGroupCategory } from "../../shared/constants/circuit-group.js";
|
||||
|
||||
export const circuitSections = sqliteTable(
|
||||
"circuit_sections",
|
||||
@@ -12,10 +13,17 @@ export const circuitSections = sqliteTable(
|
||||
displayName: text("display_name").notNull(),
|
||||
prefix: text("prefix").notNull(),
|
||||
sortOrder: integer("sort_order").notNull().default(0),
|
||||
category: text("category").$type<CircuitGroupCategory>(),
|
||||
groupNumber: integer("group_number"),
|
||||
},
|
||||
(table) => [
|
||||
unique("circuit_sections_list_key_unique").on(table.circuitListId, table.key),
|
||||
unique("circuit_sections_list_prefix_unique").on(table.circuitListId, table.prefix),
|
||||
unique("circuit_sections_list_category_group_unique").on(
|
||||
table.circuitListId,
|
||||
table.category,
|
||||
table.groupNumber
|
||||
),
|
||||
]
|
||||
);
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { real, sqliteTable, text } from "drizzle-orm/sqlite-core";
|
||||
import type {
|
||||
BreakerTripCharacteristic,
|
||||
FuseUtilizationCategory,
|
||||
ProtectionDeviceType,
|
||||
RcdType,
|
||||
} from "../../shared/constants/protection-device.js";
|
||||
import { distributionBoardComponents } from "./distribution-board-components.js";
|
||||
|
||||
export const distributionBoardComponentProtectionDevices = sqliteTable(
|
||||
"distribution_board_component_protection_devices",
|
||||
{
|
||||
componentId: text("component_id")
|
||||
.primaryKey()
|
||||
.references(() => distributionBoardComponents.id, {
|
||||
onDelete: "cascade",
|
||||
}),
|
||||
type: text("type").$type<ProtectionDeviceType>().notNull(),
|
||||
ratedCurrentA: real("rated_current_a").notNull(),
|
||||
fuseUtilizationCategory: text(
|
||||
"fuse_utilization_category"
|
||||
).$type<FuseUtilizationCategory>(),
|
||||
tripCharacteristic: text(
|
||||
"trip_characteristic"
|
||||
).$type<BreakerTripCharacteristic>(),
|
||||
rcdType: text("rcd_type").$type<RcdType>(),
|
||||
ratedResidualCurrentMa: real("rated_residual_current_ma"),
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,37 @@
|
||||
import { integer, sqliteTable, text, unique } from "drizzle-orm/sqlite-core";
|
||||
import type {
|
||||
DistributionBoardComponentPlacement,
|
||||
DistributionBoardComponentRole,
|
||||
} from "../../shared/constants/distribution-board-component.js";
|
||||
import { circuitLists } from "./circuit-lists.js";
|
||||
import { circuitSections } from "./circuit-sections.js";
|
||||
|
||||
export const distributionBoardComponents = sqliteTable(
|
||||
"distribution_board_components",
|
||||
{
|
||||
id: text("id").primaryKey(),
|
||||
circuitListId: text("circuit_list_id")
|
||||
.notNull()
|
||||
.references(() => circuitLists.id, { onDelete: "cascade" }),
|
||||
sectionId: text("section_id").references(() => circuitSections.id, {
|
||||
onDelete: "cascade",
|
||||
}),
|
||||
equipmentIdentifier: text("equipment_identifier").notNull(),
|
||||
name: text("name").notNull(),
|
||||
role: text("role").$type<DistributionBoardComponentRole>().notNull(),
|
||||
placement: text("placement")
|
||||
.$type<DistributionBoardComponentPlacement>()
|
||||
.notNull(),
|
||||
sortOrder: integer("sort_order").notNull().default(0),
|
||||
},
|
||||
(table) => [
|
||||
unique("distribution_board_components_list_identifier_unique").on(
|
||||
table.circuitListId,
|
||||
table.equipmentIdentifier
|
||||
),
|
||||
unique("distribution_board_components_section_role_unique").on(
|
||||
table.sectionId,
|
||||
table.role
|
||||
),
|
||||
]
|
||||
);
|
||||
@@ -0,0 +1,466 @@
|
||||
import assert from "node:assert/strict";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { describe, it } from "node:test";
|
||||
import Database from "better-sqlite3";
|
||||
import { migrate } from "drizzle-orm/better-sqlite3/migrator";
|
||||
import { createDatabaseContext } from "../src/db/database-context.js";
|
||||
|
||||
const migrationPath = path.resolve(
|
||||
"src",
|
||||
"db",
|
||||
"migrations",
|
||||
"0024_damp_mister_sinister.sql"
|
||||
);
|
||||
|
||||
function applyComponentSchemaMigration(sqlite: Database.Database): void {
|
||||
const migrationSql = fs.readFileSync(migrationPath, "utf8");
|
||||
for (const statement of migrationSql.split("--> statement-breakpoint")) {
|
||||
if (statement.trim()) {
|
||||
sqlite.exec(statement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createLegacySchema(sqlite: Database.Database): void {
|
||||
sqlite.pragma("foreign_keys = ON");
|
||||
sqlite.exec(`
|
||||
CREATE TABLE circuit_lists (
|
||||
id text PRIMARY KEY NOT NULL
|
||||
);
|
||||
CREATE TABLE circuit_sections (
|
||||
id text PRIMARY KEY NOT NULL,
|
||||
circuit_list_id text NOT NULL REFERENCES circuit_lists(id) ON DELETE CASCADE,
|
||||
key text NOT NULL,
|
||||
display_name text NOT NULL,
|
||||
prefix text NOT NULL,
|
||||
sort_order integer DEFAULT 0 NOT NULL
|
||||
);
|
||||
CREATE TABLE circuits (
|
||||
id text PRIMARY KEY NOT NULL,
|
||||
circuit_list_id text NOT NULL REFERENCES circuit_lists(id) ON DELETE CASCADE,
|
||||
equipment_identifier text NOT NULL,
|
||||
protection_type text,
|
||||
protection_rated_current real,
|
||||
protection_characteristic text
|
||||
);
|
||||
`);
|
||||
}
|
||||
|
||||
describe("distribution board component schema migration", () => {
|
||||
it("creates the additive schema on an empty database", () => {
|
||||
const context = createDatabaseContext(":memory:");
|
||||
try {
|
||||
migrate(context.db, {
|
||||
migrationsFolder: path.resolve("src", "db", "migrations"),
|
||||
});
|
||||
|
||||
const tables = (
|
||||
context.sqlite
|
||||
.prepare(
|
||||
`SELECT name
|
||||
FROM sqlite_master
|
||||
WHERE type = 'table'
|
||||
ORDER BY name`
|
||||
)
|
||||
.all() as Array<{ name: string }>
|
||||
).map((table) => table.name);
|
||||
|
||||
assert.equal(tables.includes("distribution_board_components"), true);
|
||||
assert.equal(tables.includes("circuit_protection_devices"), true);
|
||||
assert.deepEqual(
|
||||
tables.includes("distribution_board_component_protection_devices"),
|
||||
true
|
||||
);
|
||||
assert.deepEqual(
|
||||
tables.includes("circuit_list_equipment_identifiers"),
|
||||
true
|
||||
);
|
||||
|
||||
const sectionColumns = (
|
||||
context.sqlite
|
||||
.prepare("PRAGMA table_info(circuit_sections)")
|
||||
.all() as Array<{ name: string }>
|
||||
).map((column) => column.name);
|
||||
assert.equal(sectionColumns.includes("category"), true);
|
||||
assert.equal(sectionColumns.includes("group_number"), true);
|
||||
} finally {
|
||||
context.close();
|
||||
}
|
||||
});
|
||||
|
||||
it("backfills groups and the BMK registry without rewriting existing planning data", () => {
|
||||
const sqlite = new Database(":memory:");
|
||||
try {
|
||||
createLegacySchema(sqlite);
|
||||
sqlite.exec(`
|
||||
INSERT INTO circuit_lists (id) VALUES ('list-1');
|
||||
INSERT INTO circuit_sections (
|
||||
id,
|
||||
circuit_list_id,
|
||||
key,
|
||||
display_name,
|
||||
prefix,
|
||||
sort_order
|
||||
) VALUES
|
||||
('lighting', 'list-1', 'lighting', 'Beleuchtung', '-1F', 10),
|
||||
('single', 'list-1', 'single_phase', '1-phasig', '-2F', 20),
|
||||
('three', 'list-1', 'three_phase', '3-phasig', '-3F', 30),
|
||||
('unassigned', 'list-1', 'unassigned', 'Nicht zugeordnet', '-9F', 40);
|
||||
INSERT INTO circuits (
|
||||
id,
|
||||
circuit_list_id,
|
||||
equipment_identifier,
|
||||
protection_type,
|
||||
protection_rated_current,
|
||||
protection_characteristic
|
||||
) VALUES
|
||||
('circuit-1', 'list-1', '-1F1', 'legacy fuse', 13, 'legacy'),
|
||||
('circuit-2', 'list-1', '-2F4', NULL, NULL, NULL);
|
||||
`);
|
||||
|
||||
applyComponentSchemaMigration(sqlite);
|
||||
|
||||
assert.deepEqual(
|
||||
sqlite
|
||||
.prepare(
|
||||
`SELECT
|
||||
id,
|
||||
category,
|
||||
group_number AS groupNumber
|
||||
FROM circuit_sections
|
||||
ORDER BY sort_order`
|
||||
)
|
||||
.all(),
|
||||
[
|
||||
{ id: "lighting", category: "lighting", groupNumber: 1 },
|
||||
{ id: "single", category: "single_phase", groupNumber: 1 },
|
||||
{ id: "three", category: "three_phase", groupNumber: 1 },
|
||||
{ id: "unassigned", category: null, groupNumber: null },
|
||||
]
|
||||
);
|
||||
assert.deepEqual(
|
||||
sqlite
|
||||
.prepare(
|
||||
`SELECT
|
||||
id,
|
||||
equipment_identifier AS equipmentIdentifier,
|
||||
protection_type AS protectionType,
|
||||
protection_rated_current AS protectionRatedCurrent,
|
||||
protection_characteristic AS protectionCharacteristic
|
||||
FROM circuits
|
||||
ORDER BY id`
|
||||
)
|
||||
.all(),
|
||||
[
|
||||
{
|
||||
id: "circuit-1",
|
||||
equipmentIdentifier: "-1F1",
|
||||
protectionType: "legacy fuse",
|
||||
protectionRatedCurrent: 13,
|
||||
protectionCharacteristic: "legacy",
|
||||
},
|
||||
{
|
||||
id: "circuit-2",
|
||||
equipmentIdentifier: "-2F4",
|
||||
protectionType: null,
|
||||
protectionRatedCurrent: null,
|
||||
protectionCharacteristic: null,
|
||||
},
|
||||
]
|
||||
);
|
||||
assert.deepEqual(
|
||||
sqlite
|
||||
.prepare(
|
||||
`SELECT
|
||||
owner_type AS ownerType,
|
||||
owner_id AS ownerId,
|
||||
equipment_identifier AS equipmentIdentifier
|
||||
FROM circuit_list_equipment_identifiers
|
||||
ORDER BY owner_id`
|
||||
)
|
||||
.all(),
|
||||
[
|
||||
{
|
||||
ownerType: "circuit",
|
||||
ownerId: "circuit-1",
|
||||
equipmentIdentifier: "-1F1",
|
||||
},
|
||||
{
|
||||
ownerType: "circuit",
|
||||
ownerId: "circuit-2",
|
||||
equipmentIdentifier: "-2F4",
|
||||
},
|
||||
]
|
||||
);
|
||||
assert.deepEqual(
|
||||
sqlite
|
||||
.prepare("SELECT count(*) AS count FROM circuit_protection_devices")
|
||||
.get(),
|
||||
{ count: 0 }
|
||||
);
|
||||
} finally {
|
||||
sqlite.close();
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps the shared BMK registry synchronized and rejects normalized collisions", () => {
|
||||
const sqlite = new Database(":memory:");
|
||||
try {
|
||||
createLegacySchema(sqlite);
|
||||
sqlite.exec(`
|
||||
INSERT INTO circuit_lists (id) VALUES ('list-1');
|
||||
INSERT INTO circuit_sections (
|
||||
id,
|
||||
circuit_list_id,
|
||||
key,
|
||||
display_name,
|
||||
prefix
|
||||
) VALUES ('lighting', 'list-1', 'lighting', 'Beleuchtung', '-1F');
|
||||
INSERT INTO circuits (
|
||||
id,
|
||||
circuit_list_id,
|
||||
equipment_identifier
|
||||
) VALUES ('circuit-1', 'list-1', '-1F1');
|
||||
`);
|
||||
applyComponentSchemaMigration(sqlite);
|
||||
|
||||
sqlite
|
||||
.prepare(
|
||||
`INSERT INTO distribution_board_components (
|
||||
id,
|
||||
circuit_list_id,
|
||||
equipment_identifier,
|
||||
name,
|
||||
role,
|
||||
placement,
|
||||
sort_order
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?)`
|
||||
)
|
||||
.run(
|
||||
"main-switch",
|
||||
"list-1",
|
||||
"-Q0",
|
||||
"Hauptschalter",
|
||||
"main_switch",
|
||||
"header",
|
||||
10
|
||||
);
|
||||
|
||||
assert.throws(() =>
|
||||
sqlite
|
||||
.prepare(
|
||||
`INSERT INTO distribution_board_components (
|
||||
id,
|
||||
circuit_list_id,
|
||||
equipment_identifier,
|
||||
name,
|
||||
role,
|
||||
placement
|
||||
) VALUES (?, ?, ?, ?, ?, ?)`
|
||||
)
|
||||
.run(
|
||||
"duplicate",
|
||||
"list-1",
|
||||
" -q0 ",
|
||||
"Duplikat",
|
||||
"auxiliary",
|
||||
"footer"
|
||||
)
|
||||
);
|
||||
assert.throws(() =>
|
||||
sqlite
|
||||
.prepare(
|
||||
`INSERT INTO distribution_board_components (
|
||||
id,
|
||||
circuit_list_id,
|
||||
equipment_identifier,
|
||||
name,
|
||||
role,
|
||||
placement
|
||||
) VALUES (?, ?, ?, ?, ?, ?)`
|
||||
)
|
||||
.run(
|
||||
"circuit-duplicate",
|
||||
"list-1",
|
||||
"-1f1",
|
||||
"Duplikat",
|
||||
"auxiliary",
|
||||
"footer"
|
||||
)
|
||||
);
|
||||
|
||||
sqlite
|
||||
.prepare(
|
||||
`INSERT INTO circuits (
|
||||
id,
|
||||
circuit_list_id,
|
||||
equipment_identifier
|
||||
) VALUES (?, ?, ?)`
|
||||
)
|
||||
.run("circuit-2", "list-1", "-1F2");
|
||||
sqlite
|
||||
.prepare(
|
||||
`UPDATE circuits
|
||||
SET equipment_identifier = ?
|
||||
WHERE id = ?`
|
||||
)
|
||||
.run("-1F3", "circuit-2");
|
||||
|
||||
assert.deepEqual(
|
||||
sqlite
|
||||
.prepare(
|
||||
`SELECT equipment_identifier AS equipmentIdentifier
|
||||
FROM circuit_list_equipment_identifiers
|
||||
WHERE owner_type = 'circuit' AND owner_id = 'circuit-2'`
|
||||
)
|
||||
.get(),
|
||||
{ equipmentIdentifier: "-1F3" }
|
||||
);
|
||||
|
||||
assert.throws(() =>
|
||||
sqlite
|
||||
.prepare(
|
||||
`UPDATE circuits
|
||||
SET equipment_identifier = ?
|
||||
WHERE id = ?`
|
||||
)
|
||||
.run("-q0", "circuit-2")
|
||||
);
|
||||
assert.deepEqual(
|
||||
sqlite
|
||||
.prepare(
|
||||
`SELECT equipment_identifier AS equipmentIdentifier
|
||||
FROM circuits
|
||||
WHERE id = 'circuit-2'`
|
||||
)
|
||||
.get(),
|
||||
{ equipmentIdentifier: "-1F3" }
|
||||
);
|
||||
|
||||
sqlite
|
||||
.prepare("DELETE FROM distribution_board_components WHERE id = ?")
|
||||
.run("main-switch");
|
||||
assert.deepEqual(
|
||||
sqlite
|
||||
.prepare(
|
||||
`SELECT count(*) AS count
|
||||
FROM circuit_list_equipment_identifiers
|
||||
WHERE owner_type = 'distribution_board_component'`
|
||||
)
|
||||
.get(),
|
||||
{ count: 0 }
|
||||
);
|
||||
} finally {
|
||||
sqlite.close();
|
||||
}
|
||||
});
|
||||
|
||||
it("stores independent one-to-one circuit and component protection state", () => {
|
||||
const sqlite = new Database(":memory:");
|
||||
try {
|
||||
createLegacySchema(sqlite);
|
||||
sqlite.exec(`
|
||||
INSERT INTO circuit_lists (id) VALUES ('list-1');
|
||||
INSERT INTO circuit_sections (
|
||||
id,
|
||||
circuit_list_id,
|
||||
key,
|
||||
display_name,
|
||||
prefix
|
||||
) VALUES ('lighting', 'list-1', 'lighting', 'Beleuchtung', '-1F');
|
||||
INSERT INTO circuits (
|
||||
id,
|
||||
circuit_list_id,
|
||||
equipment_identifier
|
||||
) VALUES ('circuit-1', 'list-1', '-1F1');
|
||||
`);
|
||||
applyComponentSchemaMigration(sqlite);
|
||||
sqlite.exec(`
|
||||
INSERT INTO circuit_protection_devices (
|
||||
circuit_id,
|
||||
type,
|
||||
rated_current_a,
|
||||
trip_characteristic
|
||||
) VALUES ('circuit-1', 'LS', 10, 'B');
|
||||
INSERT INTO distribution_board_components (
|
||||
id,
|
||||
circuit_list_id,
|
||||
section_id,
|
||||
equipment_identifier,
|
||||
name,
|
||||
role,
|
||||
placement
|
||||
) VALUES (
|
||||
'group-rcd',
|
||||
'list-1',
|
||||
'lighting',
|
||||
'-1Q1.0',
|
||||
'Gruppen-FI',
|
||||
'group_residual_current_protection',
|
||||
'group'
|
||||
);
|
||||
INSERT INTO distribution_board_component_protection_devices (
|
||||
component_id,
|
||||
type,
|
||||
rated_current_a,
|
||||
rcd_type,
|
||||
rated_residual_current_ma
|
||||
) VALUES ('group-rcd', 'FI', 40, 'A', 30);
|
||||
`);
|
||||
|
||||
assert.deepEqual(
|
||||
sqlite
|
||||
.prepare("SELECT * FROM circuit_protection_devices")
|
||||
.get(),
|
||||
{
|
||||
circuit_id: "circuit-1",
|
||||
type: "LS",
|
||||
rated_current_a: 10,
|
||||
fuse_utilization_category: null,
|
||||
trip_characteristic: "B",
|
||||
rcd_type: null,
|
||||
rated_residual_current_ma: null,
|
||||
}
|
||||
);
|
||||
assert.deepEqual(
|
||||
sqlite
|
||||
.prepare(
|
||||
"SELECT * FROM distribution_board_component_protection_devices"
|
||||
)
|
||||
.get(),
|
||||
{
|
||||
component_id: "group-rcd",
|
||||
type: "FI",
|
||||
rated_current_a: 40,
|
||||
fuse_utilization_category: null,
|
||||
trip_characteristic: null,
|
||||
rcd_type: "A",
|
||||
rated_residual_current_ma: 30,
|
||||
}
|
||||
);
|
||||
|
||||
sqlite.prepare("DELETE FROM circuits WHERE id = ?").run("circuit-1");
|
||||
sqlite
|
||||
.prepare("DELETE FROM distribution_board_components WHERE id = ?")
|
||||
.run("group-rcd");
|
||||
assert.deepEqual(
|
||||
sqlite
|
||||
.prepare("SELECT count(*) AS count FROM circuit_protection_devices")
|
||||
.get(),
|
||||
{ count: 0 }
|
||||
);
|
||||
assert.deepEqual(
|
||||
sqlite
|
||||
.prepare(
|
||||
`SELECT count(*) AS count
|
||||
FROM distribution_board_component_protection_devices`
|
||||
)
|
||||
.get(),
|
||||
{ count: 0 }
|
||||
);
|
||||
} finally {
|
||||
sqlite.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user