Create clean release database baseline
This commit is contained in:
@@ -33,17 +33,12 @@ It must support circuits, device rows, project devices, drag-and-drop restructur
|
|||||||
- General application repositories also require an explicit `AppDatabase`;
|
- General application repositories also require an explicit `AppDatabase`;
|
||||||
`src/server/composition/application-repositories.ts` owns their runtime
|
`src/server/composition/application-repositories.ts` owns their runtime
|
||||||
instances, and controllers never import the global SQLite client.
|
instances, and controllers never import the global SQLite client.
|
||||||
- The upgrade-only legacy migration service follows the same dependency rule;
|
|
||||||
its concrete repositories are instantiated only in
|
|
||||||
`scripts/db-migrate-legacy-consumers.ts`.
|
|
||||||
- General Circuit, CircuitDeviceRow, CircuitList and DistributionBoard
|
- General Circuit, CircuitDeviceRow, CircuitList and DistributionBoard
|
||||||
repositories expose only active reads. Runtime writes belong in typed command
|
repositories expose only active reads. Runtime writes belong in typed command
|
||||||
repositories; direct integration fixtures belong under `tests/support`.
|
repositories; direct integration fixtures belong under `tests/support`.
|
||||||
|
|
||||||
The supported runtime model is Circuit-First. The former Consumer UI and API are
|
The supported runtime model is Circuit-First. The former Consumer UI, API,
|
||||||
removed. Retained `consumers` rows, migration mappings and reports are upgrade-only
|
tables and upgrade tooling are removed; do not reintroduce them.
|
||||||
data accessed by `npm run db:migrate:legacy-consumers`; do not build application
|
|
||||||
features on them.
|
|
||||||
|
|
||||||
See `docs/current-architecture.md` for the complete module and request flow.
|
See `docs/current-architecture.md` for the complete module and request flow.
|
||||||
|
|
||||||
@@ -255,9 +250,9 @@ not change the project revision or undo/redo stacks. Restoring a server-stored
|
|||||||
snapshot verifies its checksum and the current-state hash, replaces supported
|
snapshot verifies its checksum and the current-state hash, replaces supported
|
||||||
project data atomically and records a new `restore` revision with a complete
|
project data atomically and records a new `restore` revision with a complete
|
||||||
inverse command. Restore can therefore be undone and redone after a restart.
|
inverse command. Restore can therefore be undone and redone after a restart.
|
||||||
Snapshot schema version 7 also contains circuit-group identity,
|
Baseline snapshot schema version 1 contains circuit-group identity,
|
||||||
distribution-board components and the separate circuit/component protection
|
distribution-board components and the separate circuit/component protection
|
||||||
records. Versions 1 through 6 remain readable. Portable duplicate imports
|
records. Pre-baseline snapshots are unsupported. Portable duplicate imports
|
||||||
remap component ids and protection-owner references together with the existing
|
remap component ids and protection-owner references together with the existing
|
||||||
project graph.
|
project graph.
|
||||||
The central revision boundary creates an automatic logical snapshot after each
|
The central revision boundary creates an automatic logical snapshot after each
|
||||||
@@ -294,13 +289,11 @@ change in one revision and Undo/Redo restores them together; the project PUT
|
|||||||
route requires `expectedRevision`. The system catalog is `AV`, `SV`, `EV`,
|
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
|
`USV`, `MSR`, `SiBe`; at least one must be enabled and a type used by a board
|
||||||
cannot be disabled.
|
cannot be disabled.
|
||||||
Distribution-board setup uses `distribution-board.insert` schema version 3
|
Distribution-board setup uses `distribution-board.insert` schema version 1
|
||||||
with a complete stable snapshot of the board, circuit list, three default
|
with a complete stable snapshot of the board, circuit list, three default
|
||||||
groups, main switch `-Q0` and surge protective device `-FA`. Its
|
groups, main switch `-Q0` and surge protective device `-FA`. Its
|
||||||
inverse removes only the same unchanged and still-empty structure; the POST
|
inverse removes only the same unchanged and still-empty structure; the POST
|
||||||
route requires `expectedRevision` and returns the updated history state.
|
route requires `expectedRevision` and returns the updated history state.
|
||||||
Stored schema-version 1 and 2 setup commands remain executable with their four
|
|
||||||
legacy sections and without invented components.
|
|
||||||
Complete populated distribution-board copying and deletion use
|
Complete populated distribution-board copying and deletion use
|
||||||
`distribution-board.insert-subtree` and `distribution-board.delete-subtree`.
|
`distribution-board.insert-subtree` and `distribution-board.delete-subtree`.
|
||||||
Their exact snapshots include the board, circuit list, groups, circuits,
|
Their exact snapshots include the board, circuit list, groups, circuits,
|
||||||
@@ -389,7 +382,10 @@ Users must be able to override sizing suggestions.
|
|||||||
## Persistence and Migration Rules
|
## Persistence and Migration Rules
|
||||||
|
|
||||||
- SQLite is the currently supported database.
|
- SQLite is the currently supported database.
|
||||||
- Never edit an already applied migration.
|
- Migration `0000` is the clean release baseline for empty databases.
|
||||||
|
- Pre-baseline development databases, snapshots and stored commands are not
|
||||||
|
supported.
|
||||||
|
- After the first release, never edit an already applied migration.
|
||||||
- Back up an existing database before applying a new migration.
|
- Back up an existing database before applying a new migration.
|
||||||
- Inspect generated SQL; it must contain only the intended schema change.
|
- Inspect generated SQL; it must contain only the intended schema change.
|
||||||
- Keep database backups separate from logical project snapshots.
|
- Keep database backups separate from logical project snapshots.
|
||||||
|
|||||||
@@ -108,11 +108,9 @@ npm run db:generate
|
|||||||
```
|
```
|
||||||
|
|
||||||
`db:backup` verwendet die SQLite-Online-Backup-API und prüft das Ergebnis auf
|
`db:backup` verwendet die SQLite-Online-Backup-API und prüft das Ergebnis auf
|
||||||
Integrität und Fremdschlüsselverletzungen. Vor jeder Migration einer bestehenden
|
Integrität und Fremdschlüsselverletzungen. Die Migration `0000` ist die
|
||||||
Datenbank ist ein Backup erforderlich.
|
Release-Baseline und richtet ausschließlich eine neue, leere Datenbank ein.
|
||||||
|
Datenbanken aus Entwicklungsständen vor dieser Baseline werden nicht unterstützt.
|
||||||
`db:migrate:legacy-consumers` und `db:backfill:sections` sind ausschließlich
|
|
||||||
Upgrade-Werkzeuge für ältere Datenbanken. Neue Installationen benötigen sie nicht.
|
|
||||||
|
|
||||||
## Dokumentation
|
## Dokumentation
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
@@ -20,9 +20,6 @@ ausdrücklich getrennt und dürfen nicht als bereits implementiert verstanden we
|
|||||||
|
|
||||||
- [Entwicklungs- und Contributor-Workflow](development-workflow.md)
|
- [Entwicklungs- und Contributor-Workflow](development-workflow.md)
|
||||||
- [Deployment und Betrieb](deployment.md)
|
- [Deployment und Betrieb](deployment.md)
|
||||||
- [Lokale Circuit-First-Datenbankmigration](local-db-circuit-first-migration.md)
|
|
||||||
- [Retained Legacy-Datenmigration](circuit-list-editor-migration.md) –
|
|
||||||
ausschließlich für Upgrades alter Datenbanken
|
|
||||||
|
|
||||||
## Zukunftsarchitektur
|
## Zukunftsarchitektur
|
||||||
|
|
||||||
|
|||||||
@@ -225,8 +225,6 @@ returns HTTP `409` with `PROJECT_HISTORY_OPERATION_UNAVAILABLE`.
|
|||||||
`{ "distributionBoard": { ... }, "revision": { ... }, "history": { ... } }`
|
`{ "distributionBoard": { ... }, "revision": { ... }, "history": { ... } }`
|
||||||
- persistent Undo removes only the unchanged and still-empty generated
|
- persistent Undo removes only the unchanged and still-empty generated
|
||||||
structure; Redo restores the same ids
|
structure; Redo restores the same ids
|
||||||
- stored command schema versions 1 and 2 remain executable with their four
|
|
||||||
legacy sections and without generated components
|
|
||||||
- stale revisions return `409 PROJECT_REVISION_CONFLICT`
|
- stale revisions return `409 PROJECT_REVISION_CONFLICT`
|
||||||
- `PUT /projects/:projectId/distribution-boards/:distributionBoardId`
|
- `PUT /projects/:projectId/distribution-boards/:distributionBoardId`
|
||||||
- body:
|
- body:
|
||||||
@@ -358,12 +356,6 @@ available only as the corresponding versioned commands through
|
|||||||
structure POST, move, reorder, renumber, identifier-restore, Circuit DELETE or
|
structure POST, move, reorder, renumber, identifier-restore, Circuit DELETE or
|
||||||
CircuitDeviceRow DELETE routes.
|
CircuitDeviceRow DELETE routes.
|
||||||
|
|
||||||
## Removed Legacy Endpoints
|
|
||||||
|
|
||||||
The former `/consumers` read/write endpoints were removed after every retained
|
|
||||||
consumer had a verified Circuit-First migration mapping. Database upgrade tooling
|
|
||||||
reads retained legacy rows directly; application features must use the Circuit-First
|
|
||||||
endpoints above.
|
|
||||||
|
|
||||||
## Linked Project Device Review
|
## Linked Project Device Review
|
||||||
|
|
||||||
|
|||||||
@@ -49,11 +49,6 @@ The pure grid modules have no React state and are covered by focused unit tests.
|
|||||||
- Represents fixed header components, optional group protection and manually
|
- Represents fixed header components, optional group protection and manually
|
||||||
named auxiliary footer devices with their own unique BMK.
|
named auxiliary footer devices with their own unique BMK.
|
||||||
- Group protection may own a separate one-to-one protection configuration.
|
- Group protection may own a separate one-to-one protection configuration.
|
||||||
- Retained legacy migration source
|
|
||||||
- Old `consumers` rows, mappings and reports are available only to explicit
|
|
||||||
database upgrade tooling.
|
|
||||||
- They are not application-domain entities and have no UI or API.
|
|
||||||
|
|
||||||
## Why A Circuit Is Not One Row
|
## Why A Circuit Is Not One Row
|
||||||
|
|
||||||
A circuit can contain zero, one, or many device rows. Treating a circuit as a single row breaks:
|
A circuit can contain zero, one, or many device rows. Treating a circuit as a single row breaks:
|
||||||
|
|||||||
@@ -23,4 +23,3 @@
|
|||||||
- Protection and cable values are planner-owned manual selections. The later
|
- Protection and cable values are planner-owned manual selections. The later
|
||||||
sizing/warning engine does not yet validate them against load, length or
|
sizing/warning engine does not yet validate them against load, length or
|
||||||
voltage-drop rules.
|
voltage-drop rules.
|
||||||
- Legacy consumer UI and server endpoints are removed; retained source rows and migration mappings remain available only for upgrade traceability.
|
|
||||||
|
|||||||
@@ -1,90 +0,0 @@
|
|||||||
# Circuit List Editor Migration
|
|
||||||
|
|
||||||
## Status
|
|
||||||
|
|
||||||
Upgrade-only. New installations already use the Circuit-First schema and do not
|
|
||||||
run this workflow.
|
|
||||||
|
|
||||||
## Goal
|
|
||||||
|
|
||||||
Migrate legacy row-first consumers into the circuit-first model without deleting legacy data.
|
|
||||||
|
|
||||||
## Legacy Mapping
|
|
||||||
|
|
||||||
Legacy `Consumer` rows map into:
|
|
||||||
|
|
||||||
- `Circuit` for shared circuit identity and circuit-level technical fields
|
|
||||||
- `CircuitDeviceRow` for per-device load rows
|
|
||||||
|
|
||||||
Multiple legacy consumers can map into one circuit when they share normalized circuit identity.
|
|
||||||
|
|
||||||
For one circuit list, all new circuits, device rows, trace mappings and the migration report are committed in one SQLite transaction. A failed run therefore leaves none of those prepared migration writes behind.
|
|
||||||
|
|
||||||
## Grouping Strategy (`circuitNumber`)
|
|
||||||
|
|
||||||
Migration groups legacy rows by normalized `circuitNumber`:
|
|
||||||
|
|
||||||
- valid/normalizable values become one target circuit per normalized value
|
|
||||||
- duplicates are grouped under that circuit (multiple `CircuitDeviceRow`s)
|
|
||||||
- missing/invalid values trigger generated identifiers and may fall back to `unassigned` section
|
|
||||||
|
|
||||||
## Default Section Backfill
|
|
||||||
|
|
||||||
Before migration, default sections are created/backfilled per circuit list.
|
|
||||||
This guarantees a valid target section space, including `unassigned` when no section can be inferred.
|
|
||||||
|
|
||||||
## Migration Commands
|
|
||||||
|
|
||||||
Run in this order for local database workflows:
|
|
||||||
|
|
||||||
1. Backup:
|
|
||||||
- `npm run db:backup`
|
|
||||||
2. Migrate schema:
|
|
||||||
- `npm run db:migrate`
|
|
||||||
3. Verify circuit schema:
|
|
||||||
- `npm run db:verify:circuit-schema`
|
|
||||||
4. Backfill missing sections:
|
|
||||||
- `npm run db:backfill:sections`
|
|
||||||
5. Migrate legacy consumers:
|
|
||||||
- `npm run db:migrate:legacy-consumers`
|
|
||||||
|
|
||||||
The migration command finishes with a cutover verification across the complete
|
|
||||||
database. It exits with an error while any legacy consumer lacks a migration
|
|
||||||
mapping, including consumers that cannot be migrated because they have no circuit
|
|
||||||
list assignment. The legacy multi-list UI was removed only after this check passed;
|
|
||||||
old `/projects/:projectId/circuit-lists` bookmarks redirect to the project page.
|
|
||||||
|
|
||||||
## Validation Checks
|
|
||||||
|
|
||||||
After migration, verify:
|
|
||||||
|
|
||||||
- tree endpoint returns sections/circuits/rows
|
|
||||||
- grouped duplicate circuit numbers are reported
|
|
||||||
- generated identifiers are reported where expected
|
|
||||||
- migrated rows include `legacyConsumerId` traceability
|
|
||||||
- no duplicate BMKs exist inside one circuit list
|
|
||||||
|
|
||||||
## If Tree Endpoint Returns Empty Sections
|
|
||||||
|
|
||||||
Likely causes:
|
|
||||||
|
|
||||||
- circuit-first tables missing (migration not run)
|
|
||||||
- sections not backfilled yet
|
|
||||||
- migrated dataset genuinely empty for selected list
|
|
||||||
|
|
||||||
Actions:
|
|
||||||
|
|
||||||
1. Run schema migration and verification commands.
|
|
||||||
2. Run section backfill command.
|
|
||||||
3. Run legacy-consumer migration command.
|
|
||||||
4. Retry tree endpoint.
|
|
||||||
|
|
||||||
## Legacy Data Retention
|
|
||||||
|
|
||||||
Do not delete legacy consumers yet.
|
|
||||||
|
|
||||||
- legacy read/write endpoints have been removed from the application server
|
|
||||||
- migration trace tables reference old/new mapping
|
|
||||||
- retained rows allow upgrade verification and audit of the completed mapping
|
|
||||||
- a future automatic cleanup migration must migrate and verify old databases
|
|
||||||
before dropping these source and trace tables
|
|
||||||
@@ -9,10 +9,8 @@ Der unterstützte Editor ist Circuit-First:
|
|||||||
Ein Stromkreis ist nicht dasselbe wie eine Gerätezeile. BMK, Schutz- und Kabeldaten
|
Ein Stromkreis ist nicht dasselbe wie eine Gerätezeile. BMK, Schutz- und Kabeldaten
|
||||||
gehören zum Stromkreis; Last-, Raum- und Kategoriedaten gehören zur Gerätezeile.
|
gehören zum Stromkreis; Last-, Raum- und Kategoriedaten gehören zur Gerätezeile.
|
||||||
|
|
||||||
Die frühere Consumer-Oberfläche und ihre API sind entfernt. Die Tabelle
|
Die frühere Consumer-Oberfläche, ihre API, Tabellen und Upgrade-Werkzeuge sind
|
||||||
`consumers` sowie Mappings und Reports bleiben ausschließlich erhalten, damit
|
entfernt. Neue Funktionen bauen ausschließlich auf dem Circuit-First-Modell auf.
|
||||||
ältere Datenbanken über den expliziten Upgrade-Befehl migriert und geprüft werden
|
|
||||||
können.
|
|
||||||
|
|
||||||
## Laufzeit
|
## Laufzeit
|
||||||
|
|
||||||
@@ -130,8 +128,8 @@ Datenbank-Backups gespeichert. `POST /api/projects/:projectId/snapshots`
|
|||||||
erzeugt bei passender erwarteter Revision transaktional einen vollständigen,
|
erzeugt bei passender erwarteter Revision transaktional einen vollständigen,
|
||||||
schema-versionierten Projektzustand mit SHA-256-Prüfwert. Enthalten sind
|
schema-versionierten Projektzustand mit SHA-256-Prüfwert. Enthalten sind
|
||||||
Projekteinstellungen, Verteiler, Stromkreislisten, Bereiche, Stromkreise und
|
Projekteinstellungen, Verteiler, Stromkreislisten, Bereiche, Stromkreise und
|
||||||
Gerätezeilen sowie Projektgeräte, Geschosse und Räume. Globale Geräte,
|
Gerätezeilen sowie Projektgeräte, Geschosse und Räume. Globale Geräte sind nicht
|
||||||
Legacy-Consumer und Migrationsberichte sind nicht Teil des Projekt-Snapshots.
|
Teil des Projekt-Snapshots.
|
||||||
Create/List verändern weder Projektrevision noch Undo-/Redo-Stapel.
|
Create/List verändern weder Projektrevision noch Undo-/Redo-Stapel.
|
||||||
`kind` unterscheidet benannte und automatische Stände. Die zentrale
|
`kind` unterscheidet benannte und automatische Stände. Die zentrale
|
||||||
Revisionspersistenz erzeugt nach jeweils 25 weiteren Projektänderungen
|
Revisionspersistenz erzeugt nach jeweils 25 weiteren Projektänderungen
|
||||||
@@ -144,9 +142,7 @@ Prüfsumme, erwartete Revision und den unmittelbar zuvor gelesenen
|
|||||||
Projektzustand. Der Restore ersetzt alle unterstützten Projektdaten in einer
|
Projektzustand. Der Restore ersetzt alle unterstützten Projektdaten in einer
|
||||||
Transaktion und schreibt dabei eine neue Revision mit Quelle `restore` sowie
|
Transaktion und schreibt dabei eine neue Revision mit Quelle `restore` sowie
|
||||||
ein vollständiges inverses Kommando. Undo und Redo können deshalb auch einen
|
ein vollständiges inverses Kommando. Undo und Redo können deshalb auch einen
|
||||||
Restore nach einem Neustart exakt zurücknehmen oder wiederholen. Kompatible
|
Restore nach einem Neustart exakt zurücknehmen oder wiederholen.
|
||||||
Upgrade-only-Verknüpfungen und Migrationsnachweise bleiben erhalten, obwohl sie
|
|
||||||
nicht Bestandteil des logischen Snapshots sind.
|
|
||||||
Die Projektseite bindet diese APIs in einem einklappbaren Bereich
|
Die Projektseite bindet diese APIs in einem einklappbaren Bereich
|
||||||
„Versionen und Sicherungspunkte“ ein. Dort können Benutzer Sicherungspunkte
|
„Versionen und Sicherungspunkte“ ein. Dort können Benutzer Sicherungspunkte
|
||||||
benennen, jeden aufgeführten benannten oder automatischen Stand nach
|
benennen, jeden aufgeführten benannten oder automatischen Stand nach
|
||||||
@@ -246,8 +242,7 @@ Store leitet das inverse Kommando aus dem gespeicherten Projekt ab und schreibt
|
|||||||
Werte, Revision und Historienstapel gemeinsam. `PUT /api/projects/:projectId`
|
Werte, Revision und Historienstapel gemeinsam. `PUT /api/projects/:projectId`
|
||||||
verlangt deshalb `expectedRevision`, liefert Projekt plus aktualisierten
|
verlangt deshalb `expectedRevision`, liefert Projekt plus aktualisierten
|
||||||
Historienstand und besitzt keinen separaten direkten Settings-Schreibweg mehr.
|
Historienstand und besitzt keinen separaten direkten Settings-Schreibweg mehr.
|
||||||
Kommando- und Snapshot-Versionen vor dieser Erweiterung bleiben les- und
|
Kommando- und Snapshot-Payloads müssen dem aktuellen Baseline-Schema entsprechen.
|
||||||
ausführbar; fehlende Metadaten werden dabei als `null` behandelt.
|
|
||||||
Die Projektseite zeigt Verteilungen, Etagen und Räume als kompakte
|
Die Projektseite zeigt Verteilungen, Etagen und Räume als kompakte
|
||||||
Bestandsübersichten; ihre versionierten Erstellwege öffnen beschriftete Modals
|
Bestandsübersichten; ihre versionierten Erstellwege öffnen beschriftete Modals
|
||||||
statt dauerhafter Eingabezeilen. Projektgeräte werden als durchsuchbare,
|
statt dauerhafter Eingabezeilen. Projektgeräte werden als durchsuchbare,
|
||||||
@@ -264,8 +259,8 @@ rückgängig machbare Projektrevision. Der Modus `duplicate` ordnet Projekt-,
|
|||||||
Struktur-, Gruppen-, Verteilerkomponenten-, Raum-, Stromkreis- und
|
Struktur-, Gruppen-, Verteilerkomponenten-, Raum-, Stromkreis- und
|
||||||
Gerätezeilen-UUIDs sowie Schutzgeräte-Referenzen vollständig neu zu und legt
|
Gerätezeilen-UUIDs sowie Schutzgeräte-Referenzen vollständig neu zu und legt
|
||||||
die Kopie mit Revision `0` in einer Transaktion an. Upgrade-only-
|
die Kopie mit Revision `0` in einer Transaktion an. Upgrade-only-
|
||||||
Consumer-Verweise werden nicht in die Kopie übernommen; fachliche Verknüpfungen
|
Fachliche Verknüpfungen innerhalb des unterstützten Laufzeitmodells bleiben
|
||||||
innerhalb des unterstützten Laufzeitmodells bleiben erhalten. Die
|
erhalten. Die
|
||||||
Projektübersicht verwendet dafür den separaten Collection-Endpunkt
|
Projektübersicht verwendet dafür den separaten Collection-Endpunkt
|
||||||
`POST /api/projects/import`, der ausschließlich eine neue Kopie anlegt und
|
`POST /api/projects/import`, der ausschließlich eine neue Kopie anlegt und
|
||||||
deshalb weder eine bestehende Projekt-ID noch `expectedRevision` annimmt. Das
|
deshalb weder eine bestehende Projekt-ID noch `expectedRevision` annimmt. Das
|
||||||
@@ -283,8 +278,6 @@ Controller-Schreibweg ist entfernt. Verteilungen besitzen eine optionale
|
|||||||
Etagenreferenz sowie eine Netzart aus dem Projektkatalog. Anlage und
|
Etagenreferenz sowie eine Netzart aus dem Projektkatalog. Anlage und
|
||||||
nachträgliche Bearbeitung prüfen die Projektzugehörigkeit der Etage und die
|
nachträgliche Bearbeitung prüfen die Projektzugehörigkeit der Etage und die
|
||||||
Freigabe der Netzart in den Projekteinstellungen.
|
Freigabe der Netzart in den Projekteinstellungen.
|
||||||
Gespeicherte Anlage-Commands der Schemas 1 und 2 bleiben mit ihren vier
|
|
||||||
Legacy-Abschnitten und ohne nachträglich erfundene Komponenten ausführbar.
|
|
||||||
Bereits befüllte Verteilungen werden über
|
Bereits befüllte Verteilungen werden über
|
||||||
`distribution-board.insert-subtree` und `distribution-board.delete-subtree`
|
`distribution-board.insert-subtree` und `distribution-board.delete-subtree`
|
||||||
als vollständiger Unterbaum kopiert beziehungsweise gelöscht. Der Snapshot
|
als vollständiger Unterbaum kopiert beziehungsweise gelöscht. Der Snapshot
|
||||||
@@ -462,32 +455,30 @@ Kopieren in ein Projekt erzeugt ein eigenständiges Projektgerät.
|
|||||||
|
|
||||||
- SQLite ist die aktuell unterstützte Datenbank.
|
- SQLite ist die aktuell unterstützte Datenbank.
|
||||||
- Fremdschlüssel werden für jeden Datenbankkontext aktiviert.
|
- Fremdschlüssel werden für jeden Datenbankkontext aktiviert.
|
||||||
- `npm run db:migrate` wendet Drizzle-Migrationen an.
|
- `npm run db:migrate` richtet mit der einzelnen Migration `0000` das komplette
|
||||||
|
aktuelle Schema einer leeren Datenbank ein.
|
||||||
- `npm run db:verify:circuit-schema` prüft erforderliche und entfernte Spalten.
|
- `npm run db:verify:circuit-schema` prüft erforderliche und entfernte Spalten.
|
||||||
- `npm run db:backup` erzeugt ein konsistentes und verifiziertes Online-Backup.
|
- `npm run db:backup` erzeugt ein konsistentes und verifiziertes Online-Backup.
|
||||||
- `npm run typecheck:scripts` prüft alle TypeScript-Wartungs- und
|
- `npm run typecheck:scripts` prüft die TypeScript-Wartungsskripte, ohne Code zu
|
||||||
Upgrade-Skripte mit ihren Anwendungspfaden, ohne Code zu erzeugen.
|
erzeugen.
|
||||||
- Angewendete Migrationen werden niemals nachträglich verändert.
|
- Die Baseline ersetzt alle vorigen Entwicklungsmigrationen. Datenbanken,
|
||||||
- `db:migrate:legacy-consumers` ist Upgrade-Werkzeug, kein Anwendungspfad.
|
Snapshots und persistierte Commands aus Vor-Baseline-Ständen werden bewusst
|
||||||
Der fachliche Migrationsdienst kennt nur schmale Reader-/Store-Ports unter
|
nicht unterstützt. Ab der ersten veröffentlichten Version sind angewendete
|
||||||
`src/domain/ports`; konkrete SQLite-Repositories werden ausschließlich im
|
Migrationen unveränderlich und Änderungen erfolgen additiv.
|
||||||
CLI-Skript zusammengesetzt.
|
|
||||||
- Allgemeine Circuit-, Gerätezeilen-, CircuitList- und DistributionBoard-
|
- Allgemeine Circuit-, Gerätezeilen-, CircuitList- und DistributionBoard-
|
||||||
Repositories stellen im Anwendungspfad nur noch benötigte Leseabfragen bereit.
|
Repositories stellen im Anwendungspfad nur noch benötigte Leseabfragen bereit.
|
||||||
Fachliche Schreibvorgänge liegen in den typisierten Command-Repositories;
|
Fachliche Schreibvorgänge liegen in den typisierten Command-Repositories.
|
||||||
Upgrade-Schreibvorgänge bleiben in expliziten Migrationsadaptern.
|
|
||||||
- Die vollständige Verteilungs-Testfixture liegt unter
|
- Die vollständige Verteilungs-Testfixture liegt unter
|
||||||
`tests/support/distribution-board-fixture.ts` und ist kein exportierter
|
`tests/support/distribution-board-fixture.ts` und ist kein exportierter
|
||||||
Produktions-Schreibweg.
|
Produktions-Schreibweg.
|
||||||
- Migration `0024` bildet die additive relationale Grundlage für geschützte
|
- Die Baseline bildet die relationale Grundlage für geschützte Stromkreisgruppen
|
||||||
Stromkreisgruppen und Verteilerkomponenten. Die drei bestehenden
|
und Verteilerkomponenten. Separate
|
||||||
Stromkreisabschnitte erhalten Kategorie und Gruppennummer 1. Separate
|
1:1-Tabellen halten Stromkreis- und Komponenten-Schutzgeräte. Die früheren
|
||||||
1:1-Tabellen halten künftig Stromkreis- und Komponenten-Schutzgeräte; die
|
flachen Stromkreis-Schutzfelder sind aus der Baseline entfernt.
|
||||||
bisherigen flachen Schutzfelder bleiben in dieser Übergangsphase unverändert.
|
|
||||||
Ein triggergeführtes Register erzwingt bereits eine normalisierte,
|
Ein triggergeführtes Register erzwingt bereits eine normalisierte,
|
||||||
stromkreislistenweite BMK-Eindeutigkeit über Stromkreise und
|
stromkreislistenweite BMK-Eindeutigkeit über Stromkreise und
|
||||||
Verteilerkomponenten. Snapshot- und Transfer-Integration verwenden
|
Verteilerkomponenten. Snapshot- und Transfer-Integration verwenden
|
||||||
Snapshot-Schema 7. Persistente Insert/Delete/Update-Commands für
|
Snapshot-Schema 1 der Baseline. Persistente Insert/Delete/Update-Commands für
|
||||||
veränderliche Verteilerkomponenten, Gruppen einschließlich befüllter
|
veränderliche Verteilerkomponenten, Gruppen einschließlich befüllter
|
||||||
Unterbäume sowie vollständige Gruppensortierung sind integriert. Der Editor
|
Unterbäume sowie vollständige Gruppensortierung sind integriert. Der Editor
|
||||||
zeigt die geschützte Struktur an und bearbeitet veränderliche Gruppen- und
|
zeigt die geschützte Struktur an und bearbeitet veränderliche Gruppen- und
|
||||||
|
|||||||
@@ -96,18 +96,19 @@ erneut direkte Schreibmethoden erhalten.
|
|||||||
|
|
||||||
## Migrationen
|
## Migrationen
|
||||||
|
|
||||||
1. Bestehende lokale Datenbank sichern: `npm run db:backup`.
|
1. Schema unter `src/db/schema/` ändern.
|
||||||
2. Schema unter `src/db/schema/` ändern.
|
2. `npm run db:generate` ausführen.
|
||||||
3. `npm run db:generate` ausführen.
|
3. Das generierte SQL vollständig prüfen; es darf nur die beabsichtigte Änderung
|
||||||
4. Das generierte SQL vollständig prüfen; es darf nur die beabsichtigte Änderung
|
|
||||||
enthalten.
|
enthalten.
|
||||||
5. Upgrade-/Erhaltungstest ergänzen, wenn Spalten oder Beziehungen geändert
|
4. Upgrade-/Erhaltungstest ergänzen, wenn Spalten oder Beziehungen geändert
|
||||||
werden.
|
werden.
|
||||||
6. `npm test`, `npm run db:migrate` und
|
5. `npm test`, `npm run db:migrate` und
|
||||||
`npm run db:verify:circuit-schema` ausführen.
|
`npm run db:verify:circuit-schema` ausführen.
|
||||||
|
|
||||||
Bereits angewendete SQL-Migrationen dürfen nicht geändert oder neu sortiert
|
`0000` ist die saubere Baseline für leere Neuinstallationen. Vor-Baseline-
|
||||||
werden. Korrekturen erfolgen immer über eine neue Migration.
|
Entwicklungsdatenbanken werden nicht aktualisiert. Nach Veröffentlichung der
|
||||||
|
ersten Version dürfen angewendete SQL-Migrationen nicht geändert oder neu
|
||||||
|
sortiert werden; Korrekturen erfolgen dann immer über eine neue Migration.
|
||||||
|
|
||||||
## Pull-Request-Handoff
|
## Pull-Request-Handoff
|
||||||
|
|
||||||
|
|||||||
@@ -1,137 +0,0 @@
|
|||||||
# Local DB Migration: Circuit-First Schema
|
|
||||||
|
|
||||||
This project uses SQLite at `data/leistungsbilanz.db` and Drizzle migrations in `src/db/migrations`.
|
|
||||||
|
|
||||||
This document is an upgrade runbook for databases created before the Circuit-First
|
|
||||||
cutover. A clean installation only needs the normal `db:migrate` and schema
|
|
||||||
verification steps documented in the main README.
|
|
||||||
|
|
||||||
## Safe command order
|
|
||||||
|
|
||||||
1. Backup local DB (required before schema/data migration)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run db:backup
|
|
||||||
```
|
|
||||||
|
|
||||||
The command uses SQLite's online backup API, so committed WAL changes are included
|
|
||||||
even while the application is running. It opens the resulting standalone database
|
|
||||||
and requires both `PRAGMA integrity_check` and `PRAGMA foreign_key_check` to pass.
|
|
||||||
Database backups are operational recovery files and remain separate from the future
|
|
||||||
user-visible project version history.
|
|
||||||
|
|
||||||
2. Apply pending schema migrations (includes `0008_circuit_first_model`, the additive
|
|
||||||
`0009_project_device_circuit_fields` transition and the post-cutover
|
|
||||||
`0011_project_device_canonical_fields` cleanup)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run db:migrate
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Verify circuit-first tables exist
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run db:verify:circuit-schema
|
|
||||||
```
|
|
||||||
|
|
||||||
4. Backfill default sections for existing `circuit_lists`
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run db:backfill:sections
|
|
||||||
```
|
|
||||||
|
|
||||||
5. Run legacy consumer -> circuit/device-row migration explicitly
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run db:migrate:legacy-consumers
|
|
||||||
```
|
|
||||||
|
|
||||||
## Verification SQL
|
|
||||||
|
|
||||||
Run these against `data/leistungsbilanz.db`:
|
|
||||||
|
|
||||||
```sql
|
|
||||||
SELECT name
|
|
||||||
FROM sqlite_master
|
|
||||||
WHERE type = 'table'
|
|
||||||
AND name IN (
|
|
||||||
'circuit_sections',
|
|
||||||
'circuits',
|
|
||||||
'circuit_device_rows',
|
|
||||||
'legacy_consumer_circuit_migrations',
|
|
||||||
'legacy_consumer_migration_reports'
|
|
||||||
)
|
|
||||||
ORDER BY name;
|
|
||||||
```
|
|
||||||
|
|
||||||
The verification command also checks the circuit-first project-device columns added by migration `0009`.
|
|
||||||
|
|
||||||
```sql
|
|
||||||
SELECT circuit_list_id, key, prefix, sort_order
|
|
||||||
FROM circuit_sections
|
|
||||||
ORDER BY circuit_list_id, sort_order;
|
|
||||||
```
|
|
||||||
|
|
||||||
```sql
|
|
||||||
SELECT circuit_list_id, COUNT(*) AS circuits
|
|
||||||
FROM circuits
|
|
||||||
GROUP BY circuit_list_id;
|
|
||||||
```
|
|
||||||
|
|
||||||
```sql
|
|
||||||
SELECT c.circuit_list_id, COUNT(r.id) AS device_rows
|
|
||||||
FROM circuits c
|
|
||||||
LEFT JOIN circuit_device_rows r ON r.circuit_id = c.id
|
|
||||||
GROUP BY c.circuit_list_id;
|
|
||||||
```
|
|
||||||
|
|
||||||
## API verification
|
|
||||||
|
|
||||||
After the steps above:
|
|
||||||
|
|
||||||
`GET /api/projects/:projectId/circuit-lists/:circuitListId/tree`
|
|
||||||
|
|
||||||
Expected response shape:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"circuitListId": "string",
|
|
||||||
"sections": [
|
|
||||||
{
|
|
||||||
"id": "string",
|
|
||||||
"key": "lighting|single_phase|three_phase|unassigned|...",
|
|
||||||
"displayName": "string",
|
|
||||||
"prefix": "-1F|-2F|-3F|-UF|...",
|
|
||||||
"sortOrder": 10,
|
|
||||||
"circuits": [
|
|
||||||
{
|
|
||||||
"id": "string",
|
|
||||||
"equipmentIdentifier": "-2F1",
|
|
||||||
"displayName": "string",
|
|
||||||
"sortOrder": 10,
|
|
||||||
"isReserve": false,
|
|
||||||
"circuitTotalPower": 1.23,
|
|
||||||
"deviceRows": [
|
|
||||||
{
|
|
||||||
"id": "string",
|
|
||||||
"displayName": "string",
|
|
||||||
"quantity": 1,
|
|
||||||
"powerPerUnit": 0.3,
|
|
||||||
"simultaneityFactor": 1,
|
|
||||||
"rowTotalPower": 0.3
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
If migrations were not applied, endpoint may return an empty fallback with a warning.
|
|
||||||
|
|
||||||
## Visual verification
|
|
||||||
|
|
||||||
Manual device rows and multi-device circuits can be created directly in the
|
|
||||||
circuit-list editor. The former development scripts for direct database writes
|
|
||||||
were removed because they bypassed project revisions and persistent undo/redo.
|
|
||||||
@@ -93,8 +93,6 @@ Implemented named-snapshot foundation:
|
|||||||
- restore records a new `restore` revision containing both the selected target
|
- restore records a new `restore` revision containing both the selected target
|
||||||
state and the complete pre-restore inverse; undo/redo therefore remains
|
state and the complete pre-restore inverse; undo/redo therefore remains
|
||||||
available after application restarts
|
available after application restarts
|
||||||
- compatible upgrade-only Consumer links, mappings and reports are preserved
|
|
||||||
separately and are never imported into the logical snapshot payload
|
|
||||||
- arbitrary restore payloads are rejected by the generic user-command
|
- arbitrary restore payloads are rejected by the generic user-command
|
||||||
endpoint; only server-stored snapshots can initiate a forward restore
|
endpoint; only server-stored snapshots can initiate a forward restore
|
||||||
- the project page provides an initially collapsed snapshot/timeline panel,
|
- the project page provides an initially collapsed snapshot/timeline panel,
|
||||||
|
|||||||
@@ -342,9 +342,8 @@ Implemented foundation:
|
|||||||
- circuit-device-row transaction tests cover both successful commits and forced SQLite rollbacks
|
- circuit-device-row transaction tests cover both successful commits and forced SQLite rollbacks
|
||||||
- project-device row synchronization has real multi-row SQLite commit and rollback coverage
|
- project-device row synchronization has real multi-row SQLite commit and rollback coverage
|
||||||
- BMK swaps and section reorder have real SQLite commit and rollback coverage
|
- BMK swaps and section reorder have real SQLite commit and rollback coverage
|
||||||
- the legacy consumer migration service depends only on narrow domain ports;
|
- the former Consumer migration service, mappings and reports were removed with
|
||||||
all concrete repositories are composed at the script entry point
|
the pre-release database cutover
|
||||||
- legacy circuit, row, mapping and report writes have real late-failure rollback coverage
|
|
||||||
- circuit and device-row persistence value mapping is separated from the general repositories
|
- circuit and device-row persistence value mapping is separated from the general repositories
|
||||||
- obsolete direct Circuit, CircuitDeviceRow, CircuitList, DistributionBoard and
|
- obsolete direct Circuit, CircuitDeviceRow, CircuitList, DistributionBoard and
|
||||||
Project repository writes are removed; integration fixtures live under
|
Project repository writes are removed; integration fixtures live under
|
||||||
@@ -362,8 +361,9 @@ Implemented foundation:
|
|||||||
direct global SQLite access
|
direct global SQLite access
|
||||||
- TypeScript maintenance and upgrade scripts have a dedicated no-emit
|
- TypeScript maintenance and upgrade scripts have a dedicated no-emit
|
||||||
typecheck, including all referenced application modules
|
typecheck, including all referenced application modules
|
||||||
- the legacy consumer UI and application read/write endpoints are removed after verified data cutover
|
- the former Consumer UI, API, tables and upgrade tooling are removed
|
||||||
- retained legacy rows are accessible only through explicit database upgrade tooling
|
- migration `0000` is the clean baseline for new installations; pre-baseline
|
||||||
|
development databases are intentionally unsupported
|
||||||
- project devices no longer persist duplicate legacy power, phase, cosPhi or remark fields
|
- project devices no longer persist duplicate legacy power, phase, cosPhi or remark fields
|
||||||
|
|
||||||
## Phase 12: Project Revisions and Persistent Undo / Redo
|
## Phase 12: Project Revisions and Persistent Undo / Redo
|
||||||
@@ -618,7 +618,8 @@ Implemented handoff:
|
|||||||
|
|
||||||
- README is a concise setup and project entry point without prototype status lists
|
- README is a concise setup and project entry point without prototype status lists
|
||||||
- `docs/README.md` separates current, operational, future, specification and archived material
|
- `docs/README.md` separates current, operational, future, specification and archived material
|
||||||
- current runtime, module boundaries and upgrade-only legacy data are documented explicitly
|
- current runtime and module boundaries are documented without unsupported
|
||||||
|
upgrade-only data paths
|
||||||
- contributor workflow covers clean databases, safe demo data, migrations, tests and commits
|
- contributor workflow covers clean databases, safe demo data, migrations, tests and commits
|
||||||
- development Compose is clearly separated from the currently unsupported production deployment
|
- development Compose is clearly separated from the currently unsupported production deployment
|
||||||
- `AGENTS.md` points LLM-assisted work to supported code paths and deferred architecture
|
- `AGENTS.md` points LLM-assisted work to supported code paths and deferred architecture
|
||||||
|
|||||||
+3
-5
@@ -15,14 +15,12 @@
|
|||||||
"build:web": "next build",
|
"build:web": "next build",
|
||||||
"typecheck:scripts": "tsc --noEmit -p tsconfig.scripts.json",
|
"typecheck:scripts": "tsc --noEmit -p tsconfig.scripts.json",
|
||||||
"start": "node dist/server/index.js",
|
"start": "node dist/server/index.js",
|
||||||
"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": "node scripts/run-tests.mjs",
|
||||||
"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",
|
"test:watch": "node scripts/run-tests.mjs --watch",
|
||||||
"db:generate": "drizzle-kit generate",
|
"db:generate": "drizzle-kit generate",
|
||||||
"db:migrate": "drizzle-kit migrate",
|
"db:migrate": "drizzle-kit migrate",
|
||||||
"db:backup": "tsx scripts/db-backup.ts",
|
"db:backup": "tsx scripts/db-backup.ts",
|
||||||
"db:verify:circuit-schema": "node scripts/db-verify-circuit-schema.js",
|
"db:verify:circuit-schema": "node scripts/db-verify-circuit-schema.js"
|
||||||
"db:backfill:sections": "tsx scripts/db-backfill-sections.ts",
|
|
||||||
"db:migrate:legacy-consumers": "tsx scripts/db-migrate-legacy-consumers.ts"
|
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
import { db } from "../src/db/client.js";
|
|
||||||
import { CircuitListRepository } from "../src/db/repositories/circuit-list.repository.js";
|
|
||||||
import { CircuitSectionRepository } from "../src/db/repositories/circuit-section.repository.js";
|
|
||||||
import { ProjectRepository } from "../src/db/repositories/project.repository.js";
|
|
||||||
|
|
||||||
const projectRepository = new ProjectRepository(db);
|
|
||||||
const circuitListRepository = new CircuitListRepository(db);
|
|
||||||
const circuitSectionRepository = new CircuitSectionRepository(db);
|
|
||||||
|
|
||||||
async function run() {
|
|
||||||
const projects = await projectRepository.list();
|
|
||||||
let totalLists = 0;
|
|
||||||
for (const project of projects) {
|
|
||||||
const lists = await circuitListRepository.listByProject(project.id);
|
|
||||||
for (const list of lists) {
|
|
||||||
await circuitSectionRepository.createDefaults(list.id);
|
|
||||||
totalLists += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
console.log(`Section backfill done for ${totalLists} circuit list(s).`);
|
|
||||||
}
|
|
||||||
|
|
||||||
run().catch((error) => {
|
|
||||||
console.error("Section backfill failed:", error);
|
|
||||||
process.exit(1);
|
|
||||||
});
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
import { db } from "../src/db/client.js";
|
|
||||||
import { CircuitRepository } from "../src/db/repositories/circuit.repository.js";
|
|
||||||
import { CircuitListRepository } from "../src/db/repositories/circuit-list.repository.js";
|
|
||||||
import { CircuitSectionRepository } from "../src/db/repositories/circuit-section.repository.js";
|
|
||||||
import { LegacyConsumerMigrationRepository } from "../src/db/repositories/legacy-consumer-migration.repository.js";
|
|
||||||
import { ProjectRepository } from "../src/db/repositories/project.repository.js";
|
|
||||||
import { RoomRepository } from "../src/db/repositories/room.repository.js";
|
|
||||||
import { LegacyConsumerMigrationService } from "../src/domain/services/legacy-consumer-migration.service.js";
|
|
||||||
|
|
||||||
const projectRepository = new ProjectRepository(db);
|
|
||||||
const circuitListRepository = new CircuitListRepository(db);
|
|
||||||
const migrationRepository = new LegacyConsumerMigrationRepository(db);
|
|
||||||
const migrationService = new LegacyConsumerMigrationService({
|
|
||||||
circuitListReader: circuitListRepository,
|
|
||||||
sectionStore: new CircuitSectionRepository(db),
|
|
||||||
circuitReader: new CircuitRepository(db),
|
|
||||||
roomReader: new RoomRepository(db),
|
|
||||||
migrationStore: migrationRepository,
|
|
||||||
});
|
|
||||||
|
|
||||||
async function run() {
|
|
||||||
const projects = await projectRepository.list();
|
|
||||||
const reports = [];
|
|
||||||
|
|
||||||
for (const project of projects) {
|
|
||||||
const lists = await circuitListRepository.listByProject(project.id);
|
|
||||||
for (const list of lists) {
|
|
||||||
const report = await migrationService.migrateCircuitList(project.id, list.id);
|
|
||||||
reports.push({
|
|
||||||
projectId: project.id,
|
|
||||||
circuitListId: list.id,
|
|
||||||
legacyConsumerCount: report.legacyConsumerCount,
|
|
||||||
createdCircuitCount: report.createdCircuitCount,
|
|
||||||
createdDeviceRowCount: report.createdDeviceRowCount,
|
|
||||||
generatedIdentifiers: report.generatedIdentifiers.length,
|
|
||||||
unassignedRows: report.unassignedRows.length,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const unmigratedConsumers =
|
|
||||||
await migrationRepository.listUnmigratedConsumers();
|
|
||||||
if (unmigratedConsumers.length > 0) {
|
|
||||||
const withoutCircuitList = unmigratedConsumers.filter(
|
|
||||||
(consumer) => !consumer.circuitListId
|
|
||||||
).length;
|
|
||||||
throw new Error(
|
|
||||||
`Cutover verification failed: ${unmigratedConsumers.length} legacy consumer(s) remain unmigrated` +
|
|
||||||
` (${withoutCircuitList} without a circuit list).`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("Legacy consumer migration summary:");
|
|
||||||
console.table(reports);
|
|
||||||
console.log("Cutover verification passed: all legacy consumers are mapped.");
|
|
||||||
}
|
|
||||||
|
|
||||||
run().catch((error) => {
|
|
||||||
console.error("Legacy consumer migration failed:", error);
|
|
||||||
process.exit(1);
|
|
||||||
});
|
|
||||||
@@ -12,8 +12,6 @@ const requiredTables = [
|
|||||||
"circuit_protection_devices",
|
"circuit_protection_devices",
|
||||||
"distribution_board_components",
|
"distribution_board_components",
|
||||||
"distribution_board_component_protection_devices",
|
"distribution_board_component_protection_devices",
|
||||||
"legacy_consumer_circuit_migrations",
|
|
||||||
"legacy_consumer_migration_reports",
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const tablePlaceholders = requiredTables.map(() => "?").join(", ");
|
const tablePlaceholders = requiredTables.map(() => "?").join(", ");
|
||||||
@@ -57,6 +55,14 @@ const circuitColumns = new Set(
|
|||||||
db.prepare("PRAGMA table_info(circuits)").all().map((column) => column.name)
|
db.prepare("PRAGMA table_info(circuits)").all().map((column) => column.name)
|
||||||
);
|
);
|
||||||
const missingCircuitColumns = requiredCircuitColumns.filter((name) => !circuitColumns.has(name));
|
const missingCircuitColumns = requiredCircuitColumns.filter((name) => !circuitColumns.has(name));
|
||||||
|
const removedCircuitColumns = [
|
||||||
|
"protection_type",
|
||||||
|
"protection_rated_current",
|
||||||
|
"protection_characteristic",
|
||||||
|
];
|
||||||
|
const remainingRemovedCircuitColumns = removedCircuitColumns.filter((name) =>
|
||||||
|
circuitColumns.has(name)
|
||||||
|
);
|
||||||
const requiredCircuitSectionColumns = ["category", "group_number"];
|
const requiredCircuitSectionColumns = ["category", "group_number"];
|
||||||
const circuitSectionColumns = new Set(
|
const circuitSectionColumns = new Set(
|
||||||
db
|
db
|
||||||
@@ -164,6 +170,14 @@ if (missingCircuitColumns.length > 0) {
|
|||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (remainingRemovedCircuitColumns.length > 0) {
|
||||||
|
console.error(
|
||||||
|
"Transitional circuit protection columns still present:",
|
||||||
|
remainingRemovedCircuitColumns.join(", ")
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
if (missingCircuitSectionColumns.length > 0) {
|
if (missingCircuitSectionColumns.length > 0) {
|
||||||
console.error(
|
console.error(
|
||||||
"Missing circuit-section columns:",
|
"Missing circuit-section columns:",
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { readdirSync } from "node:fs";
|
||||||
|
import { spawnSync } from "node:child_process";
|
||||||
|
import path from "node:path";
|
||||||
|
|
||||||
|
const testFiles = readdirSync(path.resolve("tests"), {
|
||||||
|
withFileTypes: true,
|
||||||
|
})
|
||||||
|
.filter((entry) => entry.isFile() && entry.name.endsWith(".test.ts"))
|
||||||
|
.map((entry) => path.join("tests", entry.name))
|
||||||
|
.sort();
|
||||||
|
|
||||||
|
if (testFiles.length === 0) {
|
||||||
|
throw new Error("No TypeScript test files found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const watch = process.argv.includes("--watch");
|
||||||
|
const result = spawnSync(
|
||||||
|
process.execPath,
|
||||||
|
[
|
||||||
|
"--import",
|
||||||
|
"tsx",
|
||||||
|
"--test",
|
||||||
|
"--test-concurrency=1",
|
||||||
|
...(watch ? ["--watch"] : []),
|
||||||
|
...testFiles,
|
||||||
|
],
|
||||||
|
{ stdio: "inherit" }
|
||||||
|
);
|
||||||
|
|
||||||
|
process.exit(result.status ?? 1);
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
CREATE TABLE `consumers` (
|
|
||||||
`id` text PRIMARY KEY NOT NULL,
|
|
||||||
`project_id` text NOT NULL,
|
|
||||||
`distribution_board_id` text,
|
|
||||||
`name` text NOT NULL,
|
|
||||||
`category` text,
|
|
||||||
`quantity` integer NOT NULL,
|
|
||||||
`installed_power_per_unit_kw` real NOT NULL,
|
|
||||||
`demand_factor` real NOT NULL,
|
|
||||||
`voltage_v` real,
|
|
||||||
`phase_count` integer,
|
|
||||||
`power_factor` real,
|
|
||||||
`note` text,
|
|
||||||
FOREIGN KEY (`project_id`) REFERENCES `projects`(`id`) ON UPDATE no action ON DELETE cascade,
|
|
||||||
FOREIGN KEY (`distribution_board_id`) REFERENCES `distribution_boards`(`id`) ON UPDATE no action ON DELETE set null
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
CREATE TABLE `distribution_boards` (
|
|
||||||
`id` text PRIMARY KEY NOT NULL,
|
|
||||||
`project_id` text NOT NULL,
|
|
||||||
`name` text NOT NULL,
|
|
||||||
FOREIGN KEY (`project_id`) REFERENCES `projects`(`id`) ON UPDATE no action ON DELETE cascade
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
CREATE TABLE `projects` (
|
|
||||||
`id` text PRIMARY KEY NOT NULL,
|
|
||||||
`name` text NOT NULL
|
|
||||||
);
|
|
||||||
@@ -0,0 +1,300 @@
|
|||||||
|
CREATE TABLE `circuit_device_rows` (
|
||||||
|
`id` text PRIMARY KEY NOT NULL,
|
||||||
|
`circuit_id` text NOT NULL,
|
||||||
|
`linked_project_device_id` text,
|
||||||
|
`sort_order` integer DEFAULT 0 NOT NULL,
|
||||||
|
`name` text NOT NULL,
|
||||||
|
`display_name` text NOT NULL,
|
||||||
|
`phase_type` text,
|
||||||
|
`connection_kind` text,
|
||||||
|
`cost_group` text,
|
||||||
|
`category` text,
|
||||||
|
`level` text,
|
||||||
|
`room_id` text,
|
||||||
|
`room_number_snapshot` text,
|
||||||
|
`room_name_snapshot` text,
|
||||||
|
`quantity` integer NOT NULL,
|
||||||
|
`power_per_unit` real NOT NULL,
|
||||||
|
`simultaneity_factor` real NOT NULL,
|
||||||
|
`cos_phi` real,
|
||||||
|
`remark` text,
|
||||||
|
`overridden_fields` text,
|
||||||
|
FOREIGN KEY (`circuit_id`) REFERENCES `circuits`(`id`) ON UPDATE no action ON DELETE cascade,
|
||||||
|
FOREIGN KEY (`linked_project_device_id`) REFERENCES `project_devices`(`id`) ON UPDATE no action ON DELETE set null,
|
||||||
|
FOREIGN KEY (`room_id`) REFERENCES `rooms`(`id`) ON UPDATE no action ON DELETE set null
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
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_lists` (
|
||||||
|
`id` text PRIMARY KEY NOT NULL,
|
||||||
|
`project_id` text NOT NULL,
|
||||||
|
`distribution_board_id` text NOT NULL,
|
||||||
|
`name` text NOT NULL,
|
||||||
|
FOREIGN KEY (`project_id`) REFERENCES `projects`(`id`) ON UPDATE no action ON DELETE cascade,
|
||||||
|
FOREIGN KEY (`distribution_board_id`) REFERENCES `distribution_boards`(`id`) ON UPDATE no action ON DELETE cascade
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX `circuit_lists_distribution_board_id_unique` ON `circuit_lists` (`distribution_board_id`);--> 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 `circuit_sections` (
|
||||||
|
`id` text PRIMARY KEY NOT NULL,
|
||||||
|
`circuit_list_id` text NOT NULL,
|
||||||
|
`key` text NOT NULL,
|
||||||
|
`display_name` text NOT NULL,
|
||||||
|
`prefix` text NOT NULL,
|
||||||
|
`sort_order` integer DEFAULT 0 NOT NULL,
|
||||||
|
`category` text,
|
||||||
|
`group_number` integer,
|
||||||
|
FOREIGN KEY (`circuit_list_id`) REFERENCES `circuit_lists`(`id`) ON UPDATE no action ON DELETE cascade
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX `circuit_sections_list_key_unique` ON `circuit_sections` (`circuit_list_id`,`key`);--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX `circuit_sections_list_prefix_unique` ON `circuit_sections` (`circuit_list_id`,`prefix`);--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX `circuit_sections_list_category_group_unique` ON `circuit_sections` (`circuit_list_id`,`category`,`group_number`);--> statement-breakpoint
|
||||||
|
CREATE TABLE `circuits` (
|
||||||
|
`id` text PRIMARY KEY NOT NULL,
|
||||||
|
`circuit_list_id` text NOT NULL,
|
||||||
|
`section_id` text NOT NULL,
|
||||||
|
`equipment_identifier` text NOT NULL,
|
||||||
|
`display_name` text,
|
||||||
|
`sort_order` integer DEFAULT 0 NOT NULL,
|
||||||
|
`cable_type` text,
|
||||||
|
`cable_cross_section` text,
|
||||||
|
`cable_length` real,
|
||||||
|
`rcd_assignment` text,
|
||||||
|
`terminal_designation` text,
|
||||||
|
`voltage` real,
|
||||||
|
`control_requirement` text,
|
||||||
|
`status` text,
|
||||||
|
`is_reserve` integer DEFAULT 0 NOT NULL,
|
||||||
|
`remark` text,
|
||||||
|
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 `circuits_list_equipment_identifier_unique` ON `circuits` (`circuit_list_id`,`equipment_identifier`);--> 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
|
||||||
|
CREATE TABLE `distribution_boards` (
|
||||||
|
`id` text PRIMARY KEY NOT NULL,
|
||||||
|
`project_id` text NOT NULL,
|
||||||
|
`name` text NOT NULL,
|
||||||
|
`floor_id` text,
|
||||||
|
`supply_type` text,
|
||||||
|
`simultaneity_factor` real DEFAULT 1 NOT NULL,
|
||||||
|
FOREIGN KEY (`project_id`) REFERENCES `projects`(`id`) ON UPDATE no action ON DELETE cascade,
|
||||||
|
FOREIGN KEY (`floor_id`) REFERENCES `floors`(`id`) ON UPDATE no action ON DELETE set null
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE `floors` (
|
||||||
|
`id` text PRIMARY KEY NOT NULL,
|
||||||
|
`project_id` text NOT NULL,
|
||||||
|
`name` text NOT NULL,
|
||||||
|
`sort_order` integer DEFAULT 0 NOT NULL,
|
||||||
|
FOREIGN KEY (`project_id`) REFERENCES `projects`(`id`) ON UPDATE no action ON DELETE cascade
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE `global_devices` (
|
||||||
|
`id` text PRIMARY KEY NOT NULL,
|
||||||
|
`name` text NOT NULL,
|
||||||
|
`display_name` text NOT NULL,
|
||||||
|
`category` text,
|
||||||
|
`quantity` integer NOT NULL,
|
||||||
|
`installed_power_per_unit_kw` real NOT NULL,
|
||||||
|
`demand_factor` real NOT NULL,
|
||||||
|
`voltage_v` real,
|
||||||
|
`phase_count` integer,
|
||||||
|
`power_factor` real,
|
||||||
|
`note` text
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE `project_change_sets` (
|
||||||
|
`id` text PRIMARY KEY NOT NULL,
|
||||||
|
`project_revision_id` text NOT NULL,
|
||||||
|
`command_type` text NOT NULL,
|
||||||
|
`payload_schema_version` integer NOT NULL,
|
||||||
|
`forward_payload_json` text NOT NULL,
|
||||||
|
`inverse_payload_json` text NOT NULL,
|
||||||
|
FOREIGN KEY (`project_revision_id`) REFERENCES `project_revisions`(`id`) ON UPDATE no action ON DELETE cascade
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX `project_change_sets_revision_unique` ON `project_change_sets` (`project_revision_id`);--> statement-breakpoint
|
||||||
|
CREATE TABLE `project_devices` (
|
||||||
|
`id` text PRIMARY KEY NOT NULL,
|
||||||
|
`project_id` text NOT NULL,
|
||||||
|
`name` text NOT NULL,
|
||||||
|
`display_name` text NOT NULL,
|
||||||
|
`phase_type` text DEFAULT 'single_phase' NOT NULL,
|
||||||
|
`connection_kind` text,
|
||||||
|
`cost_group` text,
|
||||||
|
`category` text,
|
||||||
|
`quantity` integer NOT NULL,
|
||||||
|
`power_per_unit` real DEFAULT 0 NOT NULL,
|
||||||
|
`simultaneity_factor` real DEFAULT 1 NOT NULL,
|
||||||
|
`cos_phi` real,
|
||||||
|
`remark` text,
|
||||||
|
`voltage_v` real,
|
||||||
|
FOREIGN KEY (`project_id`) REFERENCES `projects`(`id`) ON UPDATE no action ON DELETE cascade
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE `project_history_stack_entries` (
|
||||||
|
`change_set_id` text PRIMARY KEY NOT NULL,
|
||||||
|
`project_id` text NOT NULL,
|
||||||
|
`stack` text NOT NULL,
|
||||||
|
`position` integer NOT NULL,
|
||||||
|
FOREIGN KEY (`change_set_id`) REFERENCES `project_change_sets`(`id`) ON UPDATE no action ON DELETE cascade,
|
||||||
|
FOREIGN KEY (`project_id`) REFERENCES `projects`(`id`) ON UPDATE no action ON DELETE cascade,
|
||||||
|
CONSTRAINT "project_history_stack_entries_stack_check" CHECK("project_history_stack_entries"."stack" in ('undo', 'redo'))
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX `project_history_stack_entries_position_unique` ON `project_history_stack_entries` (`project_id`,`stack`,`position`);--> statement-breakpoint
|
||||||
|
CREATE TABLE `project_revisions` (
|
||||||
|
`id` text PRIMARY KEY NOT NULL,
|
||||||
|
`project_id` text NOT NULL,
|
||||||
|
`revision_number` integer NOT NULL,
|
||||||
|
`created_at_iso` text NOT NULL,
|
||||||
|
`actor_id` text,
|
||||||
|
`source` text NOT NULL,
|
||||||
|
`description` text,
|
||||||
|
FOREIGN KEY (`project_id`) REFERENCES `projects`(`id`) ON UPDATE no action ON DELETE cascade
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX `project_revisions_project_number_unique` ON `project_revisions` (`project_id`,`revision_number`);--> statement-breakpoint
|
||||||
|
CREATE TABLE `project_snapshots` (
|
||||||
|
`id` text PRIMARY KEY NOT NULL,
|
||||||
|
`project_id` text NOT NULL,
|
||||||
|
`source_revision` integer NOT NULL,
|
||||||
|
`schema_version` integer NOT NULL,
|
||||||
|
`kind` text DEFAULT 'named' NOT NULL,
|
||||||
|
`name` text NOT NULL,
|
||||||
|
`description` text,
|
||||||
|
`payload_json` text NOT NULL,
|
||||||
|
`payload_sha256` text NOT NULL,
|
||||||
|
`created_at_iso` text NOT NULL,
|
||||||
|
`created_by_actor_id` text,
|
||||||
|
FOREIGN KEY (`project_id`) REFERENCES `projects`(`id`) ON UPDATE no action ON DELETE cascade
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE INDEX `project_snapshots_project_created_idx` ON `project_snapshots` (`project_id`,`created_at_iso`);--> statement-breakpoint
|
||||||
|
CREATE INDEX `project_snapshots_project_kind_revision_idx` ON `project_snapshots` (`project_id`,`kind`,`source_revision`);--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX `project_snapshots_project_name_unique` ON `project_snapshots` (`project_id`,`name`);--> statement-breakpoint
|
||||||
|
CREATE TABLE `projects` (
|
||||||
|
`id` text PRIMARY KEY NOT NULL,
|
||||||
|
`name` text NOT NULL,
|
||||||
|
`internal_project_number` text,
|
||||||
|
`external_project_number` text,
|
||||||
|
`building_owner` text,
|
||||||
|
`description` text,
|
||||||
|
`single_phase_voltage_v` integer DEFAULT 230 NOT NULL,
|
||||||
|
`three_phase_voltage_v` integer DEFAULT 400 NOT NULL,
|
||||||
|
`enabled_distribution_board_supply_types` text DEFAULT '["AV","SV","EV","USV","MSR","SiBe"]' NOT NULL,
|
||||||
|
`current_revision` integer DEFAULT 0 NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE `rooms` (
|
||||||
|
`id` text PRIMARY KEY NOT NULL,
|
||||||
|
`project_id` text NOT NULL,
|
||||||
|
`floor_id` text,
|
||||||
|
`room_number` text NOT NULL,
|
||||||
|
`room_name` text NOT NULL,
|
||||||
|
FOREIGN KEY (`project_id`) REFERENCES `projects`(`id`) ON UPDATE no action ON DELETE cascade,
|
||||||
|
FOREIGN KEY (`floor_id`) REFERENCES `floors`(`id`) ON UPDATE no action ON DELETE set null
|
||||||
|
);
|
||||||
|
--> 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;
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
CREATE TABLE `global_devices` (
|
|
||||||
`id` text PRIMARY KEY NOT NULL,
|
|
||||||
`name` text NOT NULL,
|
|
||||||
`category` text,
|
|
||||||
`quantity` integer NOT NULL,
|
|
||||||
`installed_power_per_unit_kw` real NOT NULL,
|
|
||||||
`demand_factor` real NOT NULL,
|
|
||||||
`voltage_v` real,
|
|
||||||
`phase_count` integer,
|
|
||||||
`power_factor` real,
|
|
||||||
`note` text
|
|
||||||
);
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
ALTER TABLE `projects` ADD COLUMN `single_phase_voltage_v` integer NOT NULL DEFAULT 230;
|
|
||||||
--> statement-breakpoint
|
|
||||||
ALTER TABLE `projects` ADD COLUMN `three_phase_voltage_v` integer NOT NULL DEFAULT 400;
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
CREATE TABLE `floors` (
|
|
||||||
`id` text PRIMARY KEY NOT NULL,
|
|
||||||
`project_id` text NOT NULL,
|
|
||||||
`name` text NOT NULL,
|
|
||||||
`sort_order` integer NOT NULL DEFAULT 0,
|
|
||||||
FOREIGN KEY (`project_id`) REFERENCES `projects`(`id`) ON UPDATE no action ON DELETE cascade
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
CREATE TABLE `rooms` (
|
|
||||||
`id` text PRIMARY KEY NOT NULL,
|
|
||||||
`project_id` text NOT NULL,
|
|
||||||
`floor_id` text,
|
|
||||||
`room_number` text NOT NULL,
|
|
||||||
`room_name` text NOT NULL,
|
|
||||||
FOREIGN KEY (`project_id`) REFERENCES `projects`(`id`) ON UPDATE no action ON DELETE cascade,
|
|
||||||
FOREIGN KEY (`floor_id`) REFERENCES `floors`(`id`) ON UPDATE no action ON DELETE set null
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
ALTER TABLE `consumers` ADD COLUMN `room_id` text REFERENCES `rooms`(`id`) ON UPDATE no action ON DELETE set null;
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
CREATE TABLE `circuit_lists` (
|
|
||||||
`id` text PRIMARY KEY NOT NULL,
|
|
||||||
`project_id` text NOT NULL,
|
|
||||||
`distribution_board_id` text NOT NULL,
|
|
||||||
`name` text NOT NULL,
|
|
||||||
FOREIGN KEY (`project_id`) REFERENCES `projects`(`id`) ON UPDATE no action ON DELETE cascade,
|
|
||||||
FOREIGN KEY (`distribution_board_id`) REFERENCES `distribution_boards`(`id`) ON UPDATE no action ON DELETE cascade
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
CREATE UNIQUE INDEX `circuit_lists_distribution_board_id_unique` ON `circuit_lists` (`distribution_board_id`);
|
|
||||||
--> statement-breakpoint
|
|
||||||
INSERT INTO `circuit_lists` (`id`, `project_id`, `distribution_board_id`, `name`)
|
|
||||||
SELECT `id`, `project_id`, `id`, `name` || ' Stromkreisliste'
|
|
||||||
FROM `distribution_boards`;
|
|
||||||
--> statement-breakpoint
|
|
||||||
ALTER TABLE `consumers` ADD COLUMN `circuit_list_id` text REFERENCES `circuit_lists`(`id`) ON UPDATE no action ON DELETE set null;
|
|
||||||
--> statement-breakpoint
|
|
||||||
UPDATE `consumers` SET `circuit_list_id` = `distribution_board_id` WHERE `distribution_board_id` IS NOT NULL;
|
|
||||||
--> statement-breakpoint
|
|
||||||
ALTER TABLE `consumers` ADD COLUMN `circuit_number` text;
|
|
||||||
--> statement-breakpoint
|
|
||||||
ALTER TABLE `consumers` ADD COLUMN `description` text;
|
|
||||||
--> statement-breakpoint
|
|
||||||
UPDATE `consumers` SET `description` = `name` WHERE `description` IS NULL;
|
|
||||||
--> statement-breakpoint
|
|
||||||
ALTER TABLE `consumers` ADD COLUMN `device_type` text;
|
|
||||||
--> statement-breakpoint
|
|
||||||
ALTER TABLE `consumers` ADD COLUMN `phase_type` text;
|
|
||||||
--> statement-breakpoint
|
|
||||||
ALTER TABLE `consumers` ADD COLUMN `trade_or_cost_group` text;
|
|
||||||
--> statement-breakpoint
|
|
||||||
ALTER TABLE `consumers` ADD COLUMN `group_name` text;
|
|
||||||
--> statement-breakpoint
|
|
||||||
ALTER TABLE `consumers` ADD COLUMN `protection_type` text;
|
|
||||||
--> statement-breakpoint
|
|
||||||
ALTER TABLE `consumers` ADD COLUMN `protection_rated_current` real;
|
|
||||||
--> statement-breakpoint
|
|
||||||
ALTER TABLE `consumers` ADD COLUMN `protection_characteristic` text;
|
|
||||||
--> statement-breakpoint
|
|
||||||
ALTER TABLE `consumers` ADD COLUMN `cable_type` text;
|
|
||||||
--> statement-breakpoint
|
|
||||||
ALTER TABLE `consumers` ADD COLUMN `cable_cross_section` text;
|
|
||||||
--> statement-breakpoint
|
|
||||||
ALTER TABLE `consumers` ADD COLUMN `comment` text;
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
CREATE TABLE `project_devices` (
|
|
||||||
`id` text PRIMARY KEY NOT NULL,
|
|
||||||
`project_id` text NOT NULL,
|
|
||||||
`name` text NOT NULL,
|
|
||||||
`category` text,
|
|
||||||
`quantity` integer NOT NULL,
|
|
||||||
`installed_power_per_unit_kw` real NOT NULL,
|
|
||||||
`demand_factor` real NOT NULL,
|
|
||||||
`voltage_v` real,
|
|
||||||
`phase_count` integer,
|
|
||||||
`power_factor` real,
|
|
||||||
`note` text,
|
|
||||||
FOREIGN KEY (`project_id`) REFERENCES `projects`(`id`) ON UPDATE no action ON DELETE cascade
|
|
||||||
);
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
ALTER TABLE `global_devices` ADD COLUMN `display_name` text;
|
|
||||||
--> statement-breakpoint
|
|
||||||
UPDATE `global_devices` SET `display_name` = `name` WHERE `display_name` IS NULL;
|
|
||||||
--> statement-breakpoint
|
|
||||||
ALTER TABLE `project_devices` ADD COLUMN `display_name` text;
|
|
||||||
--> statement-breakpoint
|
|
||||||
UPDATE `project_devices` SET `display_name` = `name` WHERE `display_name` IS NULL;
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
ALTER TABLE `consumers` ADD COLUMN `project_device_id` text REFERENCES `project_devices`(`id`) ON UPDATE no action ON DELETE set null;
|
|
||||||
--> statement-breakpoint
|
|
||||||
ALTER TABLE `consumers` ADD COLUMN `is_linked_to_device` integer NOT NULL DEFAULT 0;
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
CREATE TABLE `circuit_sections` (
|
|
||||||
`id` text PRIMARY KEY NOT NULL,
|
|
||||||
`circuit_list_id` text NOT NULL,
|
|
||||||
`key` text NOT NULL,
|
|
||||||
`display_name` text NOT NULL,
|
|
||||||
`prefix` text NOT NULL,
|
|
||||||
`sort_order` integer NOT NULL DEFAULT 0,
|
|
||||||
FOREIGN KEY (`circuit_list_id`) REFERENCES `circuit_lists`(`id`) ON UPDATE no action ON DELETE cascade
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
CREATE UNIQUE INDEX `circuit_sections_list_key_unique` ON `circuit_sections` (`circuit_list_id`,`key`);
|
|
||||||
--> statement-breakpoint
|
|
||||||
CREATE UNIQUE INDEX `circuit_sections_list_prefix_unique` ON `circuit_sections` (`circuit_list_id`,`prefix`);
|
|
||||||
--> statement-breakpoint
|
|
||||||
|
|
||||||
CREATE TABLE `circuits` (
|
|
||||||
`id` text PRIMARY KEY NOT NULL,
|
|
||||||
`circuit_list_id` text NOT NULL,
|
|
||||||
`section_id` text NOT NULL,
|
|
||||||
`equipment_identifier` text NOT NULL,
|
|
||||||
`display_name` text,
|
|
||||||
`sort_order` integer NOT NULL DEFAULT 0,
|
|
||||||
`protection_type` text,
|
|
||||||
`protection_rated_current` real,
|
|
||||||
`protection_characteristic` text,
|
|
||||||
`cable_type` text,
|
|
||||||
`cable_cross_section` text,
|
|
||||||
`cable_length` real,
|
|
||||||
`rcd_assignment` text,
|
|
||||||
`terminal_designation` text,
|
|
||||||
`voltage` real,
|
|
||||||
`status` text,
|
|
||||||
`is_reserve` integer NOT NULL DEFAULT 0,
|
|
||||||
`remark` text,
|
|
||||||
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 `circuits_list_equipment_identifier_unique` ON `circuits` (`circuit_list_id`,`equipment_identifier`);
|
|
||||||
--> statement-breakpoint
|
|
||||||
|
|
||||||
CREATE TABLE `circuit_device_rows` (
|
|
||||||
`id` text PRIMARY KEY NOT NULL,
|
|
||||||
`circuit_id` text NOT NULL,
|
|
||||||
`linked_project_device_id` text,
|
|
||||||
`legacy_consumer_id` text,
|
|
||||||
`sort_order` integer NOT NULL DEFAULT 0,
|
|
||||||
`name` text NOT NULL,
|
|
||||||
`display_name` text NOT NULL,
|
|
||||||
`phase_type` text,
|
|
||||||
`connection_kind` text,
|
|
||||||
`cost_group` text,
|
|
||||||
`category` text,
|
|
||||||
`level` text,
|
|
||||||
`room_id` text,
|
|
||||||
`room_number_snapshot` text,
|
|
||||||
`room_name_snapshot` text,
|
|
||||||
`quantity` integer NOT NULL,
|
|
||||||
`power_per_unit` real NOT NULL,
|
|
||||||
`simultaneity_factor` real NOT NULL,
|
|
||||||
`cos_phi` real,
|
|
||||||
`remark` text,
|
|
||||||
`overridden_fields` text,
|
|
||||||
FOREIGN KEY (`circuit_id`) REFERENCES `circuits`(`id`) ON UPDATE no action ON DELETE cascade,
|
|
||||||
FOREIGN KEY (`linked_project_device_id`) REFERENCES `project_devices`(`id`) ON UPDATE no action ON DELETE set null,
|
|
||||||
FOREIGN KEY (`room_id`) REFERENCES `rooms`(`id`) ON UPDATE no action ON DELETE set null
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
|
|
||||||
CREATE TABLE `legacy_consumer_circuit_migrations` (
|
|
||||||
`consumer_id` text PRIMARY KEY NOT NULL,
|
|
||||||
`circuit_id` text NOT NULL,
|
|
||||||
`circuit_device_row_id` text NOT NULL,
|
|
||||||
`circuit_list_id` text NOT NULL,
|
|
||||||
`created_at_iso` text NOT NULL,
|
|
||||||
FOREIGN KEY (`circuit_id`) REFERENCES `circuits`(`id`) ON UPDATE no action ON DELETE cascade,
|
|
||||||
FOREIGN KEY (`circuit_device_row_id`) REFERENCES `circuit_device_rows`(`id`) ON UPDATE no action ON DELETE cascade,
|
|
||||||
FOREIGN KEY (`circuit_list_id`) REFERENCES `circuit_lists`(`id`) ON UPDATE no action ON DELETE cascade
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
|
|
||||||
CREATE TABLE `legacy_consumer_migration_reports` (
|
|
||||||
`id` text PRIMARY KEY NOT NULL,
|
|
||||||
`circuit_list_id` text NOT NULL,
|
|
||||||
`legacy_consumer_count` integer NOT NULL,
|
|
||||||
`created_circuit_count` integer NOT NULL,
|
|
||||||
`created_device_row_count` integer NOT NULL,
|
|
||||||
`duplicate_grouped_count` integer NOT NULL,
|
|
||||||
`generated_identifier_count` integer NOT NULL,
|
|
||||||
`unassigned_row_count` integer NOT NULL,
|
|
||||||
`warnings_json` text NOT NULL,
|
|
||||||
`generated_identifiers_json` text NOT NULL,
|
|
||||||
`duplicate_groups_json` text NOT NULL,
|
|
||||||
`created_at_iso` text NOT NULL,
|
|
||||||
FOREIGN KEY (`circuit_list_id`) REFERENCES `circuit_lists`(`id`) ON UPDATE no action ON DELETE cascade
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
CREATE UNIQUE INDEX `legacy_consumer_migration_reports_list_unique` ON `legacy_consumer_migration_reports` (`circuit_list_id`);
|
|
||||||
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
ALTER TABLE `project_devices` ADD COLUMN `phase_type` text NOT NULL DEFAULT 'single_phase';
|
|
||||||
--> statement-breakpoint
|
|
||||||
UPDATE `project_devices`
|
|
||||||
SET `phase_type` = CASE WHEN `phase_count` = 3 THEN 'three_phase' ELSE 'single_phase' END;
|
|
||||||
--> statement-breakpoint
|
|
||||||
ALTER TABLE `project_devices` ADD COLUMN `connection_kind` text;
|
|
||||||
--> statement-breakpoint
|
|
||||||
ALTER TABLE `project_devices` ADD COLUMN `cost_group` text;
|
|
||||||
--> statement-breakpoint
|
|
||||||
ALTER TABLE `project_devices` ADD COLUMN `power_per_unit` real NOT NULL DEFAULT 0;
|
|
||||||
--> statement-breakpoint
|
|
||||||
UPDATE `project_devices` SET `power_per_unit` = `installed_power_per_unit_kw`;
|
|
||||||
--> statement-breakpoint
|
|
||||||
ALTER TABLE `project_devices` ADD COLUMN `simultaneity_factor` real NOT NULL DEFAULT 1;
|
|
||||||
--> statement-breakpoint
|
|
||||||
UPDATE `project_devices` SET `simultaneity_factor` = `demand_factor`;
|
|
||||||
--> statement-breakpoint
|
|
||||||
ALTER TABLE `project_devices` ADD COLUMN `cos_phi` real;
|
|
||||||
--> statement-breakpoint
|
|
||||||
UPDATE `project_devices` SET `cos_phi` = `power_factor`;
|
|
||||||
--> statement-breakpoint
|
|
||||||
ALTER TABLE `project_devices` ADD COLUMN `remark` text;
|
|
||||||
--> statement-breakpoint
|
|
||||||
UPDATE `project_devices` SET `remark` = `note`;
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
ALTER TABLE `circuits` ADD COLUMN `control_requirement` text;
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
ALTER TABLE `project_devices` DROP COLUMN `installed_power_per_unit_kw`;
|
|
||||||
--> statement-breakpoint
|
|
||||||
ALTER TABLE `project_devices` DROP COLUMN `demand_factor`;
|
|
||||||
--> statement-breakpoint
|
|
||||||
ALTER TABLE `project_devices` DROP COLUMN `phase_count`;
|
|
||||||
--> statement-breakpoint
|
|
||||||
ALTER TABLE `project_devices` DROP COLUMN `power_factor`;
|
|
||||||
--> statement-breakpoint
|
|
||||||
ALTER TABLE `project_devices` DROP COLUMN `note`;
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
CREATE TABLE `project_change_sets` (
|
|
||||||
`id` text PRIMARY KEY NOT NULL,
|
|
||||||
`project_revision_id` text NOT NULL,
|
|
||||||
`command_type` text NOT NULL,
|
|
||||||
`payload_schema_version` integer NOT NULL,
|
|
||||||
`forward_payload_json` text NOT NULL,
|
|
||||||
`inverse_payload_json` text NOT NULL,
|
|
||||||
FOREIGN KEY (`project_revision_id`) REFERENCES `project_revisions`(`id`) ON UPDATE no action ON DELETE cascade
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
CREATE UNIQUE INDEX `project_change_sets_revision_unique` ON `project_change_sets` (`project_revision_id`);--> statement-breakpoint
|
|
||||||
CREATE TABLE `project_revisions` (
|
|
||||||
`id` text PRIMARY KEY NOT NULL,
|
|
||||||
`project_id` text NOT NULL,
|
|
||||||
`revision_number` integer NOT NULL,
|
|
||||||
`created_at_iso` text NOT NULL,
|
|
||||||
`actor_id` text,
|
|
||||||
`source` text NOT NULL,
|
|
||||||
`description` text,
|
|
||||||
FOREIGN KEY (`project_id`) REFERENCES `projects`(`id`) ON UPDATE no action ON DELETE cascade
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
CREATE UNIQUE INDEX `project_revisions_project_number_unique` ON `project_revisions` (`project_id`,`revision_number`);--> statement-breakpoint
|
|
||||||
ALTER TABLE `projects` ADD `current_revision` integer DEFAULT 0 NOT NULL;
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
CREATE TABLE `project_history_stack_entries` (
|
|
||||||
`change_set_id` text PRIMARY KEY NOT NULL,
|
|
||||||
`project_id` text NOT NULL,
|
|
||||||
`stack` text NOT NULL,
|
|
||||||
`position` integer NOT NULL,
|
|
||||||
FOREIGN KEY (`change_set_id`) REFERENCES `project_change_sets`(`id`) ON UPDATE no action ON DELETE cascade,
|
|
||||||
FOREIGN KEY (`project_id`) REFERENCES `projects`(`id`) ON UPDATE no action ON DELETE cascade,
|
|
||||||
CONSTRAINT "project_history_stack_entries_stack_check" CHECK("project_history_stack_entries"."stack" in ('undo', 'redo'))
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
CREATE UNIQUE INDEX `project_history_stack_entries_position_unique` ON `project_history_stack_entries` (`project_id`,`stack`,`position`);
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
CREATE TABLE `project_snapshots` (
|
|
||||||
`id` text PRIMARY KEY NOT NULL,
|
|
||||||
`project_id` text NOT NULL,
|
|
||||||
`source_revision` integer NOT NULL,
|
|
||||||
`schema_version` integer NOT NULL,
|
|
||||||
`name` text NOT NULL,
|
|
||||||
`description` text,
|
|
||||||
`payload_json` text NOT NULL,
|
|
||||||
`payload_sha256` text NOT NULL,
|
|
||||||
`created_at_iso` text NOT NULL,
|
|
||||||
`created_by_actor_id` text,
|
|
||||||
FOREIGN KEY (`project_id`) REFERENCES `projects`(`id`) ON UPDATE no action ON DELETE cascade
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
CREATE INDEX `project_snapshots_project_created_idx` ON `project_snapshots` (`project_id`,`created_at_iso`);--> statement-breakpoint
|
|
||||||
CREATE UNIQUE INDEX `project_snapshots_project_name_unique` ON `project_snapshots` (`project_id`,`name`);
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
ALTER TABLE `project_snapshots` ADD `kind` text DEFAULT 'named' NOT NULL;--> statement-breakpoint
|
|
||||||
CREATE INDEX `project_snapshots_project_kind_revision_idx` ON `project_snapshots` (`project_id`,`kind`,`source_revision`);
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
ALTER TABLE `projects` ADD `internal_project_number` text;--> statement-breakpoint
|
|
||||||
ALTER TABLE `projects` ADD `external_project_number` text;--> statement-breakpoint
|
|
||||||
ALTER TABLE `projects` ADD `building_owner` text;--> statement-breakpoint
|
|
||||||
ALTER TABLE `projects` ADD `description` text;
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
ALTER TABLE `distribution_boards` ADD `floor_id` text REFERENCES floors(id);--> statement-breakpoint
|
|
||||||
ALTER TABLE `distribution_boards` ADD `supply_type` text;
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
ALTER TABLE `projects` ADD `enabled_distribution_board_supply_types` text DEFAULT '["AV","SV","EV","USV","MSR","SiBe"]' NOT NULL;
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
UPDATE `global_devices`
|
|
||||||
SET `voltage_v` = NULL;
|
|
||||||
--> statement-breakpoint
|
|
||||||
UPDATE `project_devices` AS `pd`
|
|
||||||
SET `voltage_v` = CASE
|
|
||||||
WHEN `pd`.`phase_type` = 'three_phase' THEN `p`.`three_phase_voltage_v`
|
|
||||||
ELSE `p`.`single_phase_voltage_v`
|
|
||||||
END
|
|
||||||
FROM `projects` AS `p`
|
|
||||||
WHERE `p`.`id` = `pd`.`project_id`;
|
|
||||||
--> statement-breakpoint
|
|
||||||
WITH `derived_circuit_voltages` AS (
|
|
||||||
SELECT
|
|
||||||
`c`.`id` AS `circuit_id`,
|
|
||||||
CASE
|
|
||||||
WHEN `cs`.`key` = 'three_phase' THEN `p`.`three_phase_voltage_v`
|
|
||||||
WHEN `cs`.`key` IN ('lighting', 'single_phase') THEN `p`.`single_phase_voltage_v`
|
|
||||||
WHEN EXISTS (
|
|
||||||
SELECT 1
|
|
||||||
FROM `circuit_device_rows` AS `three_phase_row`
|
|
||||||
WHERE `three_phase_row`.`circuit_id` = `c`.`id`
|
|
||||||
AND `three_phase_row`.`phase_type` = 'three_phase'
|
|
||||||
) AND NOT EXISTS (
|
|
||||||
SELECT 1
|
|
||||||
FROM `circuit_device_rows` AS `other_row`
|
|
||||||
WHERE `other_row`.`circuit_id` = `c`.`id`
|
|
||||||
AND `other_row`.`phase_type` = 'single_phase'
|
|
||||||
) THEN `p`.`three_phase_voltage_v`
|
|
||||||
ELSE `p`.`single_phase_voltage_v`
|
|
||||||
END AS `derived_voltage`
|
|
||||||
FROM `circuits` AS `c`
|
|
||||||
INNER JOIN `circuit_lists` AS `cl`
|
|
||||||
ON `cl`.`id` = `c`.`circuit_list_id`
|
|
||||||
INNER JOIN `projects` AS `p`
|
|
||||||
ON `p`.`id` = `cl`.`project_id`
|
|
||||||
INNER JOIN `circuit_sections` AS `cs`
|
|
||||||
ON `cs`.`id` = `c`.`section_id`
|
|
||||||
)
|
|
||||||
UPDATE `circuits`
|
|
||||||
SET `voltage` = (
|
|
||||||
SELECT `derived_voltage`
|
|
||||||
FROM `derived_circuit_voltages`
|
|
||||||
WHERE `circuit_id` = `circuits`.`id`
|
|
||||||
)
|
|
||||||
WHERE `id` IN (
|
|
||||||
SELECT `circuit_id`
|
|
||||||
FROM `derived_circuit_voltages`
|
|
||||||
);
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
UPDATE `project_devices`
|
|
||||||
SET `phase_type` = CASE
|
|
||||||
WHEN LOWER(REPLACE(REPLACE(TRIM(`phase_type`), '-', ''), '_', ''))
|
|
||||||
IN ('1ph', '1phase', '1phasig', 'einphasig', 'singlephase')
|
|
||||||
THEN 'single_phase'
|
|
||||||
WHEN LOWER(REPLACE(REPLACE(TRIM(`phase_type`), '-', ''), '_', ''))
|
|
||||||
IN ('3ph', '3phase', '3phasig', 'dreiphasig', 'threephase')
|
|
||||||
THEN 'three_phase'
|
|
||||||
ELSE `phase_type`
|
|
||||||
END
|
|
||||||
WHERE LOWER(REPLACE(REPLACE(TRIM(`phase_type`), '-', ''), '_', ''))
|
|
||||||
IN (
|
|
||||||
'1ph',
|
|
||||||
'1phase',
|
|
||||||
'1phasig',
|
|
||||||
'einphasig',
|
|
||||||
'singlephase',
|
|
||||||
'3ph',
|
|
||||||
'3phase',
|
|
||||||
'3phasig',
|
|
||||||
'dreiphasig',
|
|
||||||
'threephase'
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
UPDATE `circuit_device_rows`
|
|
||||||
SET `phase_type` = CASE
|
|
||||||
WHEN LOWER(REPLACE(REPLACE(TRIM(`phase_type`), '-', ''), '_', ''))
|
|
||||||
IN ('1ph', '1phase', '1phasig', 'einphasig', 'singlephase')
|
|
||||||
THEN 'single_phase'
|
|
||||||
WHEN LOWER(REPLACE(REPLACE(TRIM(`phase_type`), '-', ''), '_', ''))
|
|
||||||
IN ('3ph', '3phase', '3phasig', 'dreiphasig', 'threephase')
|
|
||||||
THEN 'three_phase'
|
|
||||||
ELSE `phase_type`
|
|
||||||
END
|
|
||||||
WHERE LOWER(REPLACE(REPLACE(TRIM(`phase_type`), '-', ''), '_', ''))
|
|
||||||
IN (
|
|
||||||
'1ph',
|
|
||||||
'1phase',
|
|
||||||
'1phasig',
|
|
||||||
'einphasig',
|
|
||||||
'singlephase',
|
|
||||||
'3ph',
|
|
||||||
'3phase',
|
|
||||||
'3phasig',
|
|
||||||
'dreiphasig',
|
|
||||||
'threephase'
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
WITH `derived_circuit_voltages` AS (
|
|
||||||
SELECT
|
|
||||||
`c`.`id` AS `circuit_id`,
|
|
||||||
CASE
|
|
||||||
WHEN `cs`.`key` = 'three_phase' THEN `p`.`three_phase_voltage_v`
|
|
||||||
WHEN `cs`.`key` IN ('lighting', 'single_phase') THEN `p`.`single_phase_voltage_v`
|
|
||||||
WHEN EXISTS (
|
|
||||||
SELECT 1
|
|
||||||
FROM `circuit_device_rows` AS `three_phase_row`
|
|
||||||
WHERE `three_phase_row`.`circuit_id` = `c`.`id`
|
|
||||||
AND `three_phase_row`.`phase_type` = 'three_phase'
|
|
||||||
) AND NOT EXISTS (
|
|
||||||
SELECT 1
|
|
||||||
FROM `circuit_device_rows` AS `single_phase_row`
|
|
||||||
WHERE `single_phase_row`.`circuit_id` = `c`.`id`
|
|
||||||
AND `single_phase_row`.`phase_type` = 'single_phase'
|
|
||||||
) THEN `p`.`three_phase_voltage_v`
|
|
||||||
ELSE `p`.`single_phase_voltage_v`
|
|
||||||
END AS `derived_voltage`
|
|
||||||
FROM `circuits` AS `c`
|
|
||||||
INNER JOIN `circuit_lists` AS `cl`
|
|
||||||
ON `cl`.`id` = `c`.`circuit_list_id`
|
|
||||||
INNER JOIN `projects` AS `p`
|
|
||||||
ON `p`.`id` = `cl`.`project_id`
|
|
||||||
INNER JOIN `circuit_sections` AS `cs`
|
|
||||||
ON `cs`.`id` = `c`.`section_id`
|
|
||||||
)
|
|
||||||
UPDATE `circuits`
|
|
||||||
SET `voltage` = (
|
|
||||||
SELECT `derived_voltage`
|
|
||||||
FROM `derived_circuit_voltages`
|
|
||||||
WHERE `circuit_id` = `circuits`.`id`
|
|
||||||
)
|
|
||||||
WHERE `id` IN (
|
|
||||||
SELECT `circuit_id`
|
|
||||||
FROM `derived_circuit_voltages`
|
|
||||||
);
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
UPDATE `circuit_device_rows`
|
|
||||||
SET `phase_type` = COALESCE(
|
|
||||||
(
|
|
||||||
SELECT `pd`.`phase_type`
|
|
||||||
FROM `project_devices` AS `pd`
|
|
||||||
WHERE `pd`.`id` = `circuit_device_rows`.`linked_project_device_id`
|
|
||||||
AND `pd`.`phase_type` IN ('single_phase', 'three_phase')
|
|
||||||
),
|
|
||||||
(
|
|
||||||
SELECT CASE
|
|
||||||
WHEN `cs`.`key` = 'three_phase' THEN 'three_phase'
|
|
||||||
WHEN `cs`.`key` IN ('lighting', 'single_phase') THEN 'single_phase'
|
|
||||||
ELSE NULL
|
|
||||||
END
|
|
||||||
FROM `circuits` AS `c`
|
|
||||||
INNER JOIN `circuit_sections` AS `cs`
|
|
||||||
ON `cs`.`id` = `c`.`section_id`
|
|
||||||
WHERE `c`.`id` = `circuit_device_rows`.`circuit_id`
|
|
||||||
)
|
|
||||||
)
|
|
||||||
WHERE `phase_type` IS NULL
|
|
||||||
AND (
|
|
||||||
EXISTS (
|
|
||||||
SELECT 1
|
|
||||||
FROM `project_devices` AS `pd`
|
|
||||||
WHERE `pd`.`id` = `circuit_device_rows`.`linked_project_device_id`
|
|
||||||
AND `pd`.`phase_type` IN ('single_phase', 'three_phase')
|
|
||||||
)
|
|
||||||
OR EXISTS (
|
|
||||||
SELECT 1
|
|
||||||
FROM `circuits` AS `c`
|
|
||||||
INNER JOIN `circuit_sections` AS `cs`
|
|
||||||
ON `cs`.`id` = `c`.`section_id`
|
|
||||||
WHERE `c`.`id` = `circuit_device_rows`.`circuit_id`
|
|
||||||
AND `cs`.`key` IN ('lighting', 'single_phase', 'three_phase')
|
|
||||||
)
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
WITH `derived_circuit_voltages` AS (
|
|
||||||
SELECT
|
|
||||||
`c`.`id` AS `circuit_id`,
|
|
||||||
CASE
|
|
||||||
WHEN `cs`.`key` = 'three_phase' THEN `p`.`three_phase_voltage_v`
|
|
||||||
WHEN `cs`.`key` IN ('lighting', 'single_phase') THEN `p`.`single_phase_voltage_v`
|
|
||||||
WHEN EXISTS (
|
|
||||||
SELECT 1
|
|
||||||
FROM `circuit_device_rows` AS `three_phase_row`
|
|
||||||
WHERE `three_phase_row`.`circuit_id` = `c`.`id`
|
|
||||||
AND `three_phase_row`.`phase_type` = 'three_phase'
|
|
||||||
) AND NOT EXISTS (
|
|
||||||
SELECT 1
|
|
||||||
FROM `circuit_device_rows` AS `single_phase_row`
|
|
||||||
WHERE `single_phase_row`.`circuit_id` = `c`.`id`
|
|
||||||
AND `single_phase_row`.`phase_type` = 'single_phase'
|
|
||||||
) THEN `p`.`three_phase_voltage_v`
|
|
||||||
ELSE `p`.`single_phase_voltage_v`
|
|
||||||
END AS `derived_voltage`
|
|
||||||
FROM `circuits` AS `c`
|
|
||||||
INNER JOIN `circuit_lists` AS `cl`
|
|
||||||
ON `cl`.`id` = `c`.`circuit_list_id`
|
|
||||||
INNER JOIN `projects` AS `p`
|
|
||||||
ON `p`.`id` = `cl`.`project_id`
|
|
||||||
INNER JOIN `circuit_sections` AS `cs`
|
|
||||||
ON `cs`.`id` = `c`.`section_id`
|
|
||||||
)
|
|
||||||
UPDATE `circuits`
|
|
||||||
SET `voltage` = (
|
|
||||||
SELECT `derived_voltage`
|
|
||||||
FROM `derived_circuit_voltages`
|
|
||||||
WHERE `circuit_id` = `circuits`.`id`
|
|
||||||
)
|
|
||||||
WHERE `id` IN (
|
|
||||||
SELECT `circuit_id`
|
|
||||||
FROM `derived_circuit_voltages`
|
|
||||||
);
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
UPDATE `circuit_device_rows`
|
|
||||||
SET `phase_type` = 'single_phase'
|
|
||||||
WHERE `phase_type` IS NULL;
|
|
||||||
--> statement-breakpoint
|
|
||||||
UPDATE `global_devices`
|
|
||||||
SET `phase_count` = 1
|
|
||||||
WHERE `phase_count` IS NULL OR `phase_count` NOT IN (1, 3);
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
ALTER TABLE `distribution_boards` ADD `simultaneity_factor` real DEFAULT 1 NOT NULL;
|
|
||||||
@@ -1,150 +0,0 @@
|
|||||||
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
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -5,176 +5,8 @@
|
|||||||
{
|
{
|
||||||
"idx": 0,
|
"idx": 0,
|
||||||
"version": "6",
|
"version": "6",
|
||||||
"when": 1777565414148,
|
"when": 1785499119431,
|
||||||
"tag": "0000_bizarre_colossus",
|
"tag": "0000_whole_surge",
|
||||||
"breakpoints": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idx": 1,
|
|
||||||
"version": "6",
|
|
||||||
"when": 1777577000000,
|
|
||||||
"tag": "0001_global_devices",
|
|
||||||
"breakpoints": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idx": 2,
|
|
||||||
"version": "6",
|
|
||||||
"when": 1777580000000,
|
|
||||||
"tag": "0002_project_voltage_defaults",
|
|
||||||
"breakpoints": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idx": 3,
|
|
||||||
"version": "6",
|
|
||||||
"when": 1777589000000,
|
|
||||||
"tag": "0003_project_floors_rooms",
|
|
||||||
"breakpoints": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idx": 4,
|
|
||||||
"version": "6",
|
|
||||||
"when": 1777594000000,
|
|
||||||
"tag": "0004_circuit_lists_and_entry_fields",
|
|
||||||
"breakpoints": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idx": 5,
|
|
||||||
"version": "6",
|
|
||||||
"when": 1777597000000,
|
|
||||||
"tag": "0005_project_devices",
|
|
||||||
"breakpoints": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idx": 6,
|
|
||||||
"version": "6",
|
|
||||||
"when": 1777652000000,
|
|
||||||
"tag": "0006_device_display_name",
|
|
||||||
"breakpoints": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idx": 7,
|
|
||||||
"version": "6",
|
|
||||||
"when": 1777680000000,
|
|
||||||
"tag": "0007_consumer_device_link",
|
|
||||||
"breakpoints": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idx": 8,
|
|
||||||
"version": "6",
|
|
||||||
"when": 1777800000000,
|
|
||||||
"tag": "0008_circuit_first_model",
|
|
||||||
"breakpoints": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idx": 9,
|
|
||||||
"version": "6",
|
|
||||||
"when": 1784743200000,
|
|
||||||
"tag": "0009_project_device_circuit_fields",
|
|
||||||
"breakpoints": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idx": 10,
|
|
||||||
"version": "6",
|
|
||||||
"when": 1784746800000,
|
|
||||||
"tag": "0010_circuit_control_requirement",
|
|
||||||
"breakpoints": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idx": 11,
|
|
||||||
"version": "6",
|
|
||||||
"when": 1784832541202,
|
|
||||||
"tag": "0011_project_device_canonical_fields",
|
|
||||||
"breakpoints": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idx": 12,
|
|
||||||
"version": "6",
|
|
||||||
"when": 1784833957929,
|
|
||||||
"tag": "0012_project_revision_foundation",
|
|
||||||
"breakpoints": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idx": 13,
|
|
||||||
"version": "6",
|
|
||||||
"when": 1784835703230,
|
|
||||||
"tag": "0013_project_history_stacks",
|
|
||||||
"breakpoints": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idx": 14,
|
|
||||||
"version": "6",
|
|
||||||
"when": 1785009799979,
|
|
||||||
"tag": "0014_project_snapshots",
|
|
||||||
"breakpoints": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idx": 15,
|
|
||||||
"version": "6",
|
|
||||||
"when": 1785014383032,
|
|
||||||
"tag": "0015_modern_madame_masque",
|
|
||||||
"breakpoints": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idx": 16,
|
|
||||||
"version": "6",
|
|
||||||
"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
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idx": 19,
|
|
||||||
"version": "6",
|
|
||||||
"when": 1785310907349,
|
|
||||||
"tag": "0019_normalize_project_voltages",
|
|
||||||
"breakpoints": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idx": 20,
|
|
||||||
"version": "6",
|
|
||||||
"when": 1785314248007,
|
|
||||||
"tag": "0020_normalize_phase_types",
|
|
||||||
"breakpoints": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idx": 21,
|
|
||||||
"version": "6",
|
|
||||||
"when": 1785314558555,
|
|
||||||
"tag": "0021_fill_missing_phase_types",
|
|
||||||
"breakpoints": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idx": 22,
|
|
||||||
"version": "6",
|
|
||||||
"when": 1785314626476,
|
|
||||||
"tag": "0022_default_remaining_phase_types",
|
|
||||||
"breakpoints": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idx": 23,
|
|
||||||
"version": "6",
|
|
||||||
"when": 1785343645615,
|
|
||||||
"tag": "0023_cheerful_night_thrasher",
|
|
||||||
"breakpoints": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idx": 24,
|
|
||||||
"version": "6",
|
|
||||||
"when": 1785431250141,
|
|
||||||
"tag": "0024_damp_mister_sinister",
|
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -382,9 +382,6 @@ export class CircuitDeviceRowMoveProjectCommandRepository
|
|||||||
equipmentIdentifier: snapshot.equipmentIdentifier,
|
equipmentIdentifier: snapshot.equipmentIdentifier,
|
||||||
displayName: snapshot.displayName,
|
displayName: snapshot.displayName,
|
||||||
sortOrder: snapshot.sortOrder,
|
sortOrder: snapshot.sortOrder,
|
||||||
protectionType: snapshot.protectionType,
|
|
||||||
protectionRatedCurrent: snapshot.protectionRatedCurrent,
|
|
||||||
protectionCharacteristic: snapshot.protectionCharacteristic,
|
|
||||||
cableType: snapshot.cableType,
|
cableType: snapshot.cableType,
|
||||||
cableCrossSection: snapshot.cableCrossSection,
|
cableCrossSection: snapshot.cableCrossSection,
|
||||||
cableLength: snapshot.cableLength,
|
cableLength: snapshot.cableLength,
|
||||||
@@ -423,11 +420,6 @@ export class CircuitDeviceRowMoveProjectCommandRepository
|
|||||||
snapshot.equipmentIdentifier ||
|
snapshot.equipmentIdentifier ||
|
||||||
current.displayName !== snapshot.displayName ||
|
current.displayName !== snapshot.displayName ||
|
||||||
current.sortOrder !== snapshot.sortOrder ||
|
current.sortOrder !== snapshot.sortOrder ||
|
||||||
current.protectionType !== snapshot.protectionType ||
|
|
||||||
current.protectionRatedCurrent !==
|
|
||||||
snapshot.protectionRatedCurrent ||
|
|
||||||
current.protectionCharacteristic !==
|
|
||||||
snapshot.protectionCharacteristic ||
|
|
||||||
current.cableType !== snapshot.cableType ||
|
current.cableType !== snapshot.cableType ||
|
||||||
current.cableCrossSection !== snapshot.cableCrossSection ||
|
current.cableCrossSection !== snapshot.cableCrossSection ||
|
||||||
current.cableLength !== snapshot.cableLength ||
|
current.cableLength !== snapshot.cableLength ||
|
||||||
|
|||||||
@@ -77,11 +77,6 @@ export class CircuitDeviceRowStructureProjectCommandRepository
|
|||||||
source: ExecuteCircuitDeviceRowStructureCommandInput["source"],
|
source: ExecuteCircuitDeviceRowStructureCommandInput["source"],
|
||||||
row: CircuitDeviceRowSnapshot
|
row: CircuitDeviceRowSnapshot
|
||||||
) {
|
) {
|
||||||
if (source === "user" && row.legacyConsumerId !== null) {
|
|
||||||
throw new Error(
|
|
||||||
"User commands cannot assign legacy consumer ids."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (source === "user" && !isElectricalPhaseType(row.phaseType)) {
|
if (source === "user" && !isElectricalPhaseType(row.phaseType)) {
|
||||||
throw new Error("Device-row phase type is invalid.");
|
throw new Error("Device-row phase type is invalid.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,7 +49,6 @@ export function toCircuitDeviceRowSnapshot(
|
|||||||
id: row.id,
|
id: row.id,
|
||||||
circuitId: row.circuitId,
|
circuitId: row.circuitId,
|
||||||
linkedProjectDeviceId: row.linkedProjectDeviceId,
|
linkedProjectDeviceId: row.linkedProjectDeviceId,
|
||||||
legacyConsumerId: row.legacyConsumerId,
|
|
||||||
sortOrder: row.sortOrder,
|
sortOrder: row.sortOrder,
|
||||||
name: row.name,
|
name: row.name,
|
||||||
displayName: row.displayName,
|
displayName: row.displayName,
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ export interface CircuitDeviceRowPatchInput {
|
|||||||
export interface CircuitDeviceRowCreateInput extends CircuitDeviceRowUpdateInput {
|
export interface CircuitDeviceRowCreateInput extends CircuitDeviceRowUpdateInput {
|
||||||
circuitId: string;
|
circuitId: string;
|
||||||
linkedProjectDeviceId?: string;
|
linkedProjectDeviceId?: string;
|
||||||
legacyConsumerId?: string;
|
|
||||||
sortOrder: number;
|
sortOrder: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,7 +109,6 @@ export function toCircuitDeviceRowCreateValues(
|
|||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
circuitId: input.circuitId,
|
circuitId: input.circuitId,
|
||||||
legacyConsumerId: input.legacyConsumerId ?? null,
|
|
||||||
sortOrder: input.sortOrder,
|
sortOrder: input.sortOrder,
|
||||||
...toCircuitDeviceRowUpdateValues(input),
|
...toCircuitDeviceRowUpdateValues(input),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ export class CircuitDeviceRowRepository {
|
|||||||
id: circuitDeviceRows.id,
|
id: circuitDeviceRows.id,
|
||||||
circuitId: circuitDeviceRows.circuitId,
|
circuitId: circuitDeviceRows.circuitId,
|
||||||
linkedProjectDeviceId: circuitDeviceRows.linkedProjectDeviceId,
|
linkedProjectDeviceId: circuitDeviceRows.linkedProjectDeviceId,
|
||||||
legacyConsumerId: circuitDeviceRows.legacyConsumerId,
|
|
||||||
sortOrder: circuitDeviceRows.sortOrder,
|
sortOrder: circuitDeviceRows.sortOrder,
|
||||||
name: circuitDeviceRows.name,
|
name: circuitDeviceRows.name,
|
||||||
displayName: circuitDeviceRows.displayName,
|
displayName: circuitDeviceRows.displayName,
|
||||||
|
|||||||
@@ -133,11 +133,6 @@ export class CircuitStructureProjectCommandRepository
|
|||||||
throw new Error("Circuit device-row id already exists.");
|
throw new Error("Circuit device-row id already exists.");
|
||||||
}
|
}
|
||||||
for (const row of snapshot.deviceRows) {
|
for (const row of snapshot.deviceRows) {
|
||||||
if (source === "user" && row.legacyConsumerId !== null) {
|
|
||||||
throw new Error(
|
|
||||||
"User commands cannot assign legacy consumer ids."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (source === "user" && !isElectricalPhaseType(row.phaseType)) {
|
if (source === "user" && !isElectricalPhaseType(row.phaseType)) {
|
||||||
throw new Error("Device-row phase type is invalid.");
|
throw new Error("Device-row phase type is invalid.");
|
||||||
}
|
}
|
||||||
@@ -158,9 +153,6 @@ export class CircuitStructureProjectCommandRepository
|
|||||||
equipmentIdentifier: snapshot.equipmentIdentifier,
|
equipmentIdentifier: snapshot.equipmentIdentifier,
|
||||||
displayName: snapshot.displayName,
|
displayName: snapshot.displayName,
|
||||||
sortOrder: snapshot.sortOrder,
|
sortOrder: snapshot.sortOrder,
|
||||||
protectionType: snapshot.protectionType,
|
|
||||||
protectionRatedCurrent: snapshot.protectionRatedCurrent,
|
|
||||||
protectionCharacteristic: snapshot.protectionCharacteristic,
|
|
||||||
cableType: snapshot.cableType,
|
cableType: snapshot.cableType,
|
||||||
cableCrossSection: snapshot.cableCrossSection,
|
cableCrossSection: snapshot.cableCrossSection,
|
||||||
cableLength: snapshot.cableLength,
|
cableLength: snapshot.cableLength,
|
||||||
@@ -238,9 +230,6 @@ export class CircuitStructureProjectCommandRepository
|
|||||||
equipmentIdentifier: circuit.equipmentIdentifier,
|
equipmentIdentifier: circuit.equipmentIdentifier,
|
||||||
displayName: circuit.displayName,
|
displayName: circuit.displayName,
|
||||||
sortOrder: circuit.sortOrder,
|
sortOrder: circuit.sortOrder,
|
||||||
protectionType: circuit.protectionType,
|
|
||||||
protectionRatedCurrent: circuit.protectionRatedCurrent,
|
|
||||||
protectionCharacteristic: circuit.protectionCharacteristic,
|
|
||||||
cableType: circuit.cableType,
|
cableType: circuit.cableType,
|
||||||
cableCrossSection: circuit.cableCrossSection,
|
cableCrossSection: circuit.cableCrossSection,
|
||||||
cableLength: circuit.cableLength,
|
cableLength: circuit.cableLength,
|
||||||
|
|||||||
@@ -4,9 +4,6 @@ export interface CircuitCreatePersistenceInput {
|
|||||||
equipmentIdentifier: string;
|
equipmentIdentifier: string;
|
||||||
displayName?: string;
|
displayName?: string;
|
||||||
sortOrder: number;
|
sortOrder: number;
|
||||||
protectionType?: string;
|
|
||||||
protectionRatedCurrent?: number;
|
|
||||||
protectionCharacteristic?: string;
|
|
||||||
cableType?: string;
|
cableType?: string;
|
||||||
cableCrossSection?: string;
|
cableCrossSection?: string;
|
||||||
cableLength?: number;
|
cableLength?: number;
|
||||||
@@ -24,9 +21,6 @@ export interface CircuitPatchPersistenceInput {
|
|||||||
equipmentIdentifier?: string;
|
equipmentIdentifier?: string;
|
||||||
displayName?: string | null;
|
displayName?: string | null;
|
||||||
sortOrder?: number;
|
sortOrder?: number;
|
||||||
protectionType?: string | null;
|
|
||||||
protectionRatedCurrent?: number | null;
|
|
||||||
protectionCharacteristic?: string | null;
|
|
||||||
cableType?: string | null;
|
cableType?: string | null;
|
||||||
cableCrossSection?: string | null;
|
cableCrossSection?: string | null;
|
||||||
cableLength?: number | null;
|
cableLength?: number | null;
|
||||||
@@ -47,9 +41,6 @@ export function toCircuitCreateValues(id: string, input: CircuitCreatePersistenc
|
|||||||
equipmentIdentifier: input.equipmentIdentifier,
|
equipmentIdentifier: input.equipmentIdentifier,
|
||||||
displayName: input.displayName ?? null,
|
displayName: input.displayName ?? null,
|
||||||
sortOrder: input.sortOrder,
|
sortOrder: input.sortOrder,
|
||||||
protectionType: input.protectionType ?? null,
|
|
||||||
protectionRatedCurrent: input.protectionRatedCurrent ?? null,
|
|
||||||
protectionCharacteristic: input.protectionCharacteristic ?? null,
|
|
||||||
cableType: input.cableType ?? null,
|
cableType: input.cableType ?? null,
|
||||||
cableCrossSection: input.cableCrossSection ?? null,
|
cableCrossSection: input.cableCrossSection ?? null,
|
||||||
cableLength: input.cableLength ?? null,
|
cableLength: input.cableLength ?? null,
|
||||||
@@ -74,13 +65,6 @@ export function toCircuitPatchValues(input: CircuitPatchPersistenceInput) {
|
|||||||
}
|
}
|
||||||
if (has("displayName")) values.displayName = input.displayName ?? null;
|
if (has("displayName")) values.displayName = input.displayName ?? null;
|
||||||
if (input.sortOrder !== undefined) values.sortOrder = input.sortOrder;
|
if (input.sortOrder !== undefined) values.sortOrder = input.sortOrder;
|
||||||
if (has("protectionType")) values.protectionType = input.protectionType ?? null;
|
|
||||||
if (has("protectionRatedCurrent")) {
|
|
||||||
values.protectionRatedCurrent = input.protectionRatedCurrent ?? null;
|
|
||||||
}
|
|
||||||
if (has("protectionCharacteristic")) {
|
|
||||||
values.protectionCharacteristic = input.protectionCharacteristic ?? null;
|
|
||||||
}
|
|
||||||
if (has("cableType")) values.cableType = input.cableType ?? null;
|
if (has("cableType")) values.cableType = input.cableType ?? null;
|
||||||
if (has("cableCrossSection")) {
|
if (has("cableCrossSection")) {
|
||||||
values.cableCrossSection = input.cableCrossSection ?? null;
|
values.cableCrossSection = input.cableCrossSection ?? null;
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import {
|
|||||||
import {
|
import {
|
||||||
assertDistributionBoardUpdateProjectCommand,
|
assertDistributionBoardUpdateProjectCommand,
|
||||||
createDistributionBoardUpdateProjectCommand,
|
createDistributionBoardUpdateProjectCommand,
|
||||||
legacyDistributionBoardUpdateCommandSchemaVersion,
|
|
||||||
type DistributionBoardUpdateField,
|
type DistributionBoardUpdateField,
|
||||||
type DistributionBoardUpdatePatch,
|
type DistributionBoardUpdatePatch,
|
||||||
type DistributionBoardUpdateProjectCommand,
|
type DistributionBoardUpdateProjectCommand,
|
||||||
@@ -332,19 +331,10 @@ export class DistributionBoardStructureProjectCommandRepository
|
|||||||
getDistributionBoardFieldValue(current, change.field),
|
getDistributionBoardFieldValue(current, change.field),
|
||||||
])
|
])
|
||||||
) as DistributionBoardUpdatePatch;
|
) as DistributionBoardUpdatePatch;
|
||||||
const currentInverse = createDistributionBoardUpdateProjectCommand(
|
const inverse = createDistributionBoardUpdateProjectCommand(
|
||||||
current.id,
|
current.id,
|
||||||
inversePatch
|
inversePatch
|
||||||
);
|
);
|
||||||
const inverse =
|
|
||||||
input.command.schemaVersion ===
|
|
||||||
legacyDistributionBoardUpdateCommandSchemaVersion
|
|
||||||
? ({
|
|
||||||
...currentInverse,
|
|
||||||
schemaVersion:
|
|
||||||
legacyDistributionBoardUpdateCommandSchemaVersion,
|
|
||||||
} as DistributionBoardUpdateProjectCommand)
|
|
||||||
: currentInverse;
|
|
||||||
const updated = database
|
const updated = database
|
||||||
.update(distributionBoards)
|
.update(distributionBoards)
|
||||||
.set(patch)
|
.set(patch)
|
||||||
|
|||||||
@@ -1,142 +0,0 @@
|
|||||||
import crypto from "node:crypto";
|
|
||||||
import { eq, inArray, isNull } from "drizzle-orm";
|
|
||||||
import type {
|
|
||||||
LegacyMigrationCircuitInput,
|
|
||||||
LegacyMigrationReportInput,
|
|
||||||
} from "../../domain/ports/legacy-consumer-migration.store.js";
|
|
||||||
import type { AppDatabase } from "../database-context.js";
|
|
||||||
import { circuitDeviceRows } from "../schema/circuit-device-rows.js";
|
|
||||||
import { circuits } from "../schema/circuits.js";
|
|
||||||
import { consumers } from "../schema/consumers.js";
|
|
||||||
import { legacyConsumerCircuitMigrations } from "../schema/legacy-consumer-circuit-migrations.js";
|
|
||||||
import { legacyConsumerMigrationReports } from "../schema/legacy-consumer-migration-report.js";
|
|
||||||
import { toCircuitDeviceRowCreateValues } from "./circuit-device-row.persistence.js";
|
|
||||||
import { toCircuitCreateValues } from "./circuit.persistence.js";
|
|
||||||
|
|
||||||
export class LegacyConsumerMigrationRepository {
|
|
||||||
constructor(private readonly database: AppDatabase) {}
|
|
||||||
|
|
||||||
async listSourceConsumersByCircuitList(circuitListId: string) {
|
|
||||||
return this.database
|
|
||||||
.select()
|
|
||||||
.from(consumers)
|
|
||||||
.where(eq(consumers.circuitListId, circuitListId));
|
|
||||||
}
|
|
||||||
|
|
||||||
async listUnmigratedConsumers() {
|
|
||||||
return this.database
|
|
||||||
.select({
|
|
||||||
id: consumers.id,
|
|
||||||
projectId: consumers.projectId,
|
|
||||||
circuitListId: consumers.circuitListId,
|
|
||||||
})
|
|
||||||
.from(consumers)
|
|
||||||
.leftJoin(
|
|
||||||
legacyConsumerCircuitMigrations,
|
|
||||||
eq(legacyConsumerCircuitMigrations.consumerId, consumers.id)
|
|
||||||
)
|
|
||||||
.where(isNull(legacyConsumerCircuitMigrations.consumerId));
|
|
||||||
}
|
|
||||||
|
|
||||||
async listMigratedConsumerIds(circuitListId: string) {
|
|
||||||
const rows = await this.database
|
|
||||||
.select({ consumerId: legacyConsumerCircuitMigrations.consumerId })
|
|
||||||
.from(legacyConsumerCircuitMigrations)
|
|
||||||
.where(eq(legacyConsumerCircuitMigrations.circuitListId, circuitListId));
|
|
||||||
return rows.map((row) => row.consumerId);
|
|
||||||
}
|
|
||||||
|
|
||||||
persistCircuitListMigration(input: {
|
|
||||||
circuitListId: string;
|
|
||||||
circuits: LegacyMigrationCircuitInput[];
|
|
||||||
report: LegacyMigrationReportInput;
|
|
||||||
}) {
|
|
||||||
if (input.circuits.some((entry) => entry.circuit.circuitListId !== input.circuitListId)) {
|
|
||||||
throw new Error("All migrated circuits must belong to the target circuit list.");
|
|
||||||
}
|
|
||||||
|
|
||||||
const consumerIds = input.circuits.flatMap((entry) =>
|
|
||||||
entry.deviceRows.map((row) => row.legacyConsumerId)
|
|
||||||
);
|
|
||||||
if (new Set(consumerIds).size !== consumerIds.length) {
|
|
||||||
throw new Error("A legacy consumer may only be migrated once per operation.");
|
|
||||||
}
|
|
||||||
|
|
||||||
const preparedCircuits = input.circuits.map((entry) => {
|
|
||||||
const circuitId = crypto.randomUUID();
|
|
||||||
return {
|
|
||||||
circuitId,
|
|
||||||
circuitValues: toCircuitCreateValues(circuitId, entry.circuit),
|
|
||||||
rows: entry.deviceRows.map((row) => {
|
|
||||||
const rowId = crypto.randomUUID();
|
|
||||||
return {
|
|
||||||
consumerId: row.legacyConsumerId,
|
|
||||||
rowId,
|
|
||||||
rowValues: toCircuitDeviceRowCreateValues(rowId, {
|
|
||||||
...row,
|
|
||||||
circuitId,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
const createdAtIso = new Date().toISOString();
|
|
||||||
|
|
||||||
this.database.transaction((tx) => {
|
|
||||||
if (consumerIds.length > 0) {
|
|
||||||
const existingMappings = tx
|
|
||||||
.select({ consumerId: legacyConsumerCircuitMigrations.consumerId })
|
|
||||||
.from(legacyConsumerCircuitMigrations)
|
|
||||||
.where(inArray(legacyConsumerCircuitMigrations.consumerId, consumerIds))
|
|
||||||
.all();
|
|
||||||
if (existingMappings.length > 0) {
|
|
||||||
throw new Error("A legacy consumer was migrated before this operation completed.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const preparedCircuit of preparedCircuits) {
|
|
||||||
tx.insert(circuits).values(preparedCircuit.circuitValues).run();
|
|
||||||
for (const row of preparedCircuit.rows) {
|
|
||||||
tx.insert(circuitDeviceRows).values(row.rowValues).run();
|
|
||||||
tx
|
|
||||||
.insert(legacyConsumerCircuitMigrations)
|
|
||||||
.values({
|
|
||||||
consumerId: row.consumerId,
|
|
||||||
circuitId: preparedCircuit.circuitId,
|
|
||||||
circuitDeviceRowId: row.rowId,
|
|
||||||
circuitListId: input.circuitListId,
|
|
||||||
createdAtIso,
|
|
||||||
})
|
|
||||||
.run();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const reportValues = {
|
|
||||||
...input.report,
|
|
||||||
createdAtIso,
|
|
||||||
};
|
|
||||||
const existingReport = tx
|
|
||||||
.select({ id: legacyConsumerMigrationReports.id })
|
|
||||||
.from(legacyConsumerMigrationReports)
|
|
||||||
.where(eq(legacyConsumerMigrationReports.circuitListId, input.circuitListId))
|
|
||||||
.limit(1)
|
|
||||||
.all();
|
|
||||||
if (existingReport.length > 0) {
|
|
||||||
tx
|
|
||||||
.update(legacyConsumerMigrationReports)
|
|
||||||
.set(reportValues)
|
|
||||||
.where(eq(legacyConsumerMigrationReports.id, existingReport[0].id))
|
|
||||||
.run();
|
|
||||||
} else {
|
|
||||||
tx
|
|
||||||
.insert(legacyConsumerMigrationReports)
|
|
||||||
.values({
|
|
||||||
id: crypto.randomUUID(),
|
|
||||||
circuitListId: input.circuitListId,
|
|
||||||
...reportValues,
|
|
||||||
})
|
|
||||||
.run();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -34,7 +34,6 @@ const circuitDeviceRowSnapshotFields = [
|
|||||||
"id",
|
"id",
|
||||||
"circuitId",
|
"circuitId",
|
||||||
"linkedProjectDeviceId",
|
"linkedProjectDeviceId",
|
||||||
"legacyConsumerId",
|
|
||||||
"sortOrder",
|
"sortOrder",
|
||||||
"name",
|
"name",
|
||||||
"displayName",
|
"displayName",
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ import type {
|
|||||||
} from "../../domain/ports/project-location-structure-project-command.store.js";
|
} from "../../domain/ports/project-location-structure-project-command.store.js";
|
||||||
import type { AppDatabase } from "../database-context.js";
|
import type { AppDatabase } from "../database-context.js";
|
||||||
import { circuitDeviceRows } from "../schema/circuit-device-rows.js";
|
import { circuitDeviceRows } from "../schema/circuit-device-rows.js";
|
||||||
import { consumers } from "../schema/consumers.js";
|
|
||||||
import { floors } from "../schema/floors.js";
|
import { floors } from "../schema/floors.js";
|
||||||
import { distributionBoards } from "../schema/distribution-boards.js";
|
import { distributionBoards } from "../schema/distribution-boards.js";
|
||||||
import { projects } from "../schema/projects.js";
|
import { projects } from "../schema/projects.js";
|
||||||
@@ -216,13 +215,7 @@ export class ProjectLocationStructureProjectCommandRepository
|
|||||||
.where(eq(circuitDeviceRows.roomId, expected.id))
|
.where(eq(circuitDeviceRows.roomId, expected.id))
|
||||||
.limit(1)
|
.limit(1)
|
||||||
.get();
|
.get();
|
||||||
const referencedLegacyConsumer = database
|
if (referencedDeviceRow) {
|
||||||
.select({ id: consumers.id })
|
|
||||||
.from(consumers)
|
|
||||||
.where(eq(consumers.roomId, expected.id))
|
|
||||||
.limit(1)
|
|
||||||
.get();
|
|
||||||
if (referencedDeviceRow || referencedLegacyConsumer) {
|
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"A referenced project room cannot be removed by history."
|
"A referenced project room cannot be removed by history."
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,9 +2,6 @@ import { and, eq, isNotNull } from "drizzle-orm";
|
|||||||
import {
|
import {
|
||||||
assertProjectSettingsUpdateProjectCommand,
|
assertProjectSettingsUpdateProjectCommand,
|
||||||
createProjectSettingsUpdateProjectCommand,
|
createProjectSettingsUpdateProjectCommand,
|
||||||
legacyProjectSettingsUpdateCommandSchemaVersion,
|
|
||||||
normalizeProjectSettingsValues,
|
|
||||||
previousProjectSettingsUpdateCommandSchemaVersion,
|
|
||||||
type ProjectSettingsValues,
|
type ProjectSettingsValues,
|
||||||
} from "../../domain/models/project-settings-project-command.model.js";
|
} from "../../domain/models/project-settings-project-command.model.js";
|
||||||
import type {
|
import type {
|
||||||
@@ -43,49 +40,22 @@ export class ProjectSettingsProjectCommandRepository
|
|||||||
if (!current) {
|
if (!current) {
|
||||||
throw new Error("Project not found.");
|
throw new Error("Project not found.");
|
||||||
}
|
}
|
||||||
const target = normalizeProjectSettingsValues(input.command, current);
|
const target = input.command.payload;
|
||||||
if (projectSettingsEqual(current, target)) {
|
if (projectSettingsEqual(current, target)) {
|
||||||
throw new Error("Project settings did not change.");
|
throw new Error("Project settings did not change.");
|
||||||
}
|
}
|
||||||
|
|
||||||
const inverse =
|
const inverse = createProjectSettingsUpdateProjectCommand({
|
||||||
input.command.schemaVersion ===
|
name: current.name,
|
||||||
legacyProjectSettingsUpdateCommandSchemaVersion
|
internalProjectNumber: current.internalProjectNumber,
|
||||||
? {
|
externalProjectNumber: current.externalProjectNumber,
|
||||||
schemaVersion: legacyProjectSettingsUpdateCommandSchemaVersion,
|
buildingOwner: current.buildingOwner,
|
||||||
type: input.command.type,
|
description: current.description,
|
||||||
payload: {
|
singlePhaseVoltageV: current.singlePhaseVoltageV,
|
||||||
singlePhaseVoltageV: current.singlePhaseVoltageV,
|
threePhaseVoltageV: current.threePhaseVoltageV,
|
||||||
threePhaseVoltageV: current.threePhaseVoltageV,
|
enabledDistributionBoardSupplyTypes:
|
||||||
},
|
current.enabledDistributionBoardSupplyTypes,
|
||||||
}
|
});
|
||||||
: 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,
|
|
||||||
buildingOwner: current.buildingOwner,
|
|
||||||
description: current.description,
|
|
||||||
singlePhaseVoltageV: current.singlePhaseVoltageV,
|
|
||||||
threePhaseVoltageV: current.threePhaseVoltageV,
|
|
||||||
enabledDistributionBoardSupplyTypes:
|
|
||||||
current.enabledDistributionBoardSupplyTypes,
|
|
||||||
});
|
|
||||||
assertDisabledSupplyTypesAreUnused(tx, input.projectId, target);
|
assertDisabledSupplyTypesAreUnused(tx, input.projectId, target);
|
||||||
const updated = tx
|
const updated = tx
|
||||||
.update(projects)
|
.update(projects)
|
||||||
|
|||||||
@@ -4,14 +4,8 @@ import { ProjectRevisionConflictError } from "../../domain/errors/project-revisi
|
|||||||
import { ProjectSnapshotNameConflictError } from "../../domain/errors/project-snapshot-name-conflict.error.js";
|
import { ProjectSnapshotNameConflictError } from "../../domain/errors/project-snapshot-name-conflict.error.js";
|
||||||
import { createProjectStateRestoreCommand } from "../../domain/models/project-state-restore-command.model.js";
|
import { createProjectStateRestoreCommand } from "../../domain/models/project-state-restore-command.model.js";
|
||||||
import {
|
import {
|
||||||
distributionBoardProjectStateSnapshotSchemaVersion,
|
|
||||||
deserializeProjectStateSnapshot,
|
deserializeProjectStateSnapshot,
|
||||||
legacyProjectStateSnapshotSchemaVersion,
|
|
||||||
previousProjectStateSnapshotSchemaVersion,
|
|
||||||
projectStateSnapshotSchemaVersion,
|
projectStateSnapshotSchemaVersion,
|
||||||
simultaneityFactorProjectStateSnapshotSchemaVersion,
|
|
||||||
supplyTypesProjectStateSnapshotSchemaVersion,
|
|
||||||
voltageProjectStateSnapshotSchemaVersion,
|
|
||||||
} from "../../domain/models/project-state-snapshot.model.js";
|
} from "../../domain/models/project-state-snapshot.model.js";
|
||||||
import type {
|
import type {
|
||||||
CreateNamedProjectSnapshotInput,
|
CreateNamedProjectSnapshotInput,
|
||||||
@@ -144,19 +138,7 @@ export class ProjectSnapshotRepository implements ProjectSnapshotStore {
|
|||||||
if (actualChecksum !== stored.payloadSha256) {
|
if (actualChecksum !== stored.payloadSha256) {
|
||||||
throw new Error("Project snapshot checksum verification failed.");
|
throw new Error("Project snapshot checksum verification failed.");
|
||||||
}
|
}
|
||||||
if (
|
if (stored.schemaVersion !== projectStateSnapshotSchemaVersion) {
|
||||||
stored.schemaVersion !== legacyProjectStateSnapshotSchemaVersion &&
|
|
||||||
stored.schemaVersion !== previousProjectStateSnapshotSchemaVersion &&
|
|
||||||
stored.schemaVersion !==
|
|
||||||
distributionBoardProjectStateSnapshotSchemaVersion &&
|
|
||||||
stored.schemaVersion !==
|
|
||||||
supplyTypesProjectStateSnapshotSchemaVersion &&
|
|
||||||
stored.schemaVersion !==
|
|
||||||
voltageProjectStateSnapshotSchemaVersion &&
|
|
||||||
stored.schemaVersion !==
|
|
||||||
simultaneityFactorProjectStateSnapshotSchemaVersion &&
|
|
||||||
stored.schemaVersion !== projectStateSnapshotSchemaVersion
|
|
||||||
) {
|
|
||||||
throw new Error("Project snapshot schema version is not supported.");
|
throw new Error("Project snapshot schema version is not supported.");
|
||||||
}
|
}
|
||||||
const targetState = deserializeProjectStateSnapshot(stored.payloadJson);
|
const targetState = deserializeProjectStateSnapshot(stored.payloadJson);
|
||||||
|
|||||||
@@ -18,13 +18,10 @@ import { circuitLists } from "../schema/circuit-lists.js";
|
|||||||
import { circuitProtectionDevices } from "../schema/circuit-protection-devices.js";
|
import { circuitProtectionDevices } from "../schema/circuit-protection-devices.js";
|
||||||
import { circuitSections } from "../schema/circuit-sections.js";
|
import { circuitSections } from "../schema/circuit-sections.js";
|
||||||
import { circuits } from "../schema/circuits.js";
|
import { circuits } from "../schema/circuits.js";
|
||||||
import { consumers } from "../schema/consumers.js";
|
|
||||||
import { distributionBoardComponentProtectionDevices } from "../schema/distribution-board-component-protection-devices.js";
|
import { distributionBoardComponentProtectionDevices } from "../schema/distribution-board-component-protection-devices.js";
|
||||||
import { distributionBoardComponents } from "../schema/distribution-board-components.js";
|
import { distributionBoardComponents } from "../schema/distribution-board-components.js";
|
||||||
import { distributionBoards } from "../schema/distribution-boards.js";
|
import { distributionBoards } from "../schema/distribution-boards.js";
|
||||||
import { floors } from "../schema/floors.js";
|
import { floors } from "../schema/floors.js";
|
||||||
import { legacyConsumerCircuitMigrations } from "../schema/legacy-consumer-circuit-migrations.js";
|
|
||||||
import { legacyConsumerMigrationReports } from "../schema/legacy-consumer-migration-report.js";
|
|
||||||
import { projectDevices } from "../schema/project-devices.js";
|
import { projectDevices } from "../schema/project-devices.js";
|
||||||
import { projectChangeSets } from "../schema/project-change-sets.js";
|
import { projectChangeSets } from "../schema/project-change-sets.js";
|
||||||
import { projects } from "../schema/projects.js";
|
import { projects } from "../schema/projects.js";
|
||||||
@@ -186,10 +183,6 @@ function replaceProjectState(
|
|||||||
database: AppDatabase,
|
database: AppDatabase,
|
||||||
state: ProjectStateSnapshot
|
state: ProjectStateSnapshot
|
||||||
) {
|
) {
|
||||||
const upgradeOnlyData = captureUpgradeOnlyData(
|
|
||||||
database,
|
|
||||||
state.project.id
|
|
||||||
);
|
|
||||||
database
|
database
|
||||||
.delete(distributionBoards)
|
.delete(distributionBoards)
|
||||||
.where(eq(distributionBoards.projectId, state.project.id))
|
.where(eq(distributionBoards.projectId, state.project.id))
|
||||||
@@ -257,136 +250,6 @@ function replaceProjectState(
|
|||||||
circuitDeviceRows,
|
circuitDeviceRows,
|
||||||
state.circuits.flatMap((circuit) => circuit.deviceRows)
|
state.circuits.flatMap((circuit) => circuit.deviceRows)
|
||||||
);
|
);
|
||||||
restoreCompatibleUpgradeOnlyData(database, state, upgradeOnlyData);
|
|
||||||
}
|
|
||||||
|
|
||||||
function captureUpgradeOnlyData(
|
|
||||||
database: AppDatabase,
|
|
||||||
projectId: string
|
|
||||||
) {
|
|
||||||
const consumerLinks = database
|
|
||||||
.select({
|
|
||||||
id: consumers.id,
|
|
||||||
distributionBoardId: consumers.distributionBoardId,
|
|
||||||
circuitListId: consumers.circuitListId,
|
|
||||||
projectDeviceId: consumers.projectDeviceId,
|
|
||||||
roomId: consumers.roomId,
|
|
||||||
})
|
|
||||||
.from(consumers)
|
|
||||||
.where(eq(consumers.projectId, projectId))
|
|
||||||
.all();
|
|
||||||
const migrationMappings = database
|
|
||||||
.select({
|
|
||||||
consumerId: legacyConsumerCircuitMigrations.consumerId,
|
|
||||||
circuitId: legacyConsumerCircuitMigrations.circuitId,
|
|
||||||
circuitDeviceRowId:
|
|
||||||
legacyConsumerCircuitMigrations.circuitDeviceRowId,
|
|
||||||
circuitListId: legacyConsumerCircuitMigrations.circuitListId,
|
|
||||||
createdAtIso: legacyConsumerCircuitMigrations.createdAtIso,
|
|
||||||
})
|
|
||||||
.from(legacyConsumerCircuitMigrations)
|
|
||||||
.innerJoin(
|
|
||||||
circuitLists,
|
|
||||||
eq(
|
|
||||||
circuitLists.id,
|
|
||||||
legacyConsumerCircuitMigrations.circuitListId
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.where(eq(circuitLists.projectId, projectId))
|
|
||||||
.all();
|
|
||||||
const migrationReports = database
|
|
||||||
.select({
|
|
||||||
id: legacyConsumerMigrationReports.id,
|
|
||||||
circuitListId: legacyConsumerMigrationReports.circuitListId,
|
|
||||||
legacyConsumerCount:
|
|
||||||
legacyConsumerMigrationReports.legacyConsumerCount,
|
|
||||||
createdCircuitCount:
|
|
||||||
legacyConsumerMigrationReports.createdCircuitCount,
|
|
||||||
createdDeviceRowCount:
|
|
||||||
legacyConsumerMigrationReports.createdDeviceRowCount,
|
|
||||||
duplicateGroupedCount:
|
|
||||||
legacyConsumerMigrationReports.duplicateGroupedCount,
|
|
||||||
generatedIdentifierCount:
|
|
||||||
legacyConsumerMigrationReports.generatedIdentifierCount,
|
|
||||||
unassignedRowCount:
|
|
||||||
legacyConsumerMigrationReports.unassignedRowCount,
|
|
||||||
warningsJson: legacyConsumerMigrationReports.warningsJson,
|
|
||||||
generatedIdentifiersJson:
|
|
||||||
legacyConsumerMigrationReports.generatedIdentifiersJson,
|
|
||||||
duplicateGroupsJson:
|
|
||||||
legacyConsumerMigrationReports.duplicateGroupsJson,
|
|
||||||
createdAtIso: legacyConsumerMigrationReports.createdAtIso,
|
|
||||||
})
|
|
||||||
.from(legacyConsumerMigrationReports)
|
|
||||||
.innerJoin(
|
|
||||||
circuitLists,
|
|
||||||
eq(circuitLists.id, legacyConsumerMigrationReports.circuitListId)
|
|
||||||
)
|
|
||||||
.where(eq(circuitLists.projectId, projectId))
|
|
||||||
.all();
|
|
||||||
return { consumerLinks, migrationMappings, migrationReports };
|
|
||||||
}
|
|
||||||
|
|
||||||
function restoreCompatibleUpgradeOnlyData(
|
|
||||||
database: AppDatabase,
|
|
||||||
state: ProjectStateSnapshot,
|
|
||||||
data: ReturnType<typeof captureUpgradeOnlyData>
|
|
||||||
) {
|
|
||||||
const boardIds = new Set(
|
|
||||||
state.distributionBoards.map((entry) => entry.id)
|
|
||||||
);
|
|
||||||
const listIds = new Set(state.circuitLists.map((entry) => entry.id));
|
|
||||||
const deviceIds = new Set(
|
|
||||||
state.projectDevices.map((entry) => entry.id)
|
|
||||||
);
|
|
||||||
const roomIds = new Set(state.rooms.map((entry) => entry.id));
|
|
||||||
const circuitIds = new Set(state.circuits.map((entry) => entry.id));
|
|
||||||
const rowIds = new Set(
|
|
||||||
state.circuits.flatMap((circuit) =>
|
|
||||||
circuit.deviceRows.map((entry) => entry.id)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
for (const link of data.consumerLinks) {
|
|
||||||
database
|
|
||||||
.update(consumers)
|
|
||||||
.set({
|
|
||||||
distributionBoardId:
|
|
||||||
link.distributionBoardId &&
|
|
||||||
boardIds.has(link.distributionBoardId)
|
|
||||||
? link.distributionBoardId
|
|
||||||
: null,
|
|
||||||
circuitListId:
|
|
||||||
link.circuitListId && listIds.has(link.circuitListId)
|
|
||||||
? link.circuitListId
|
|
||||||
: null,
|
|
||||||
projectDeviceId:
|
|
||||||
link.projectDeviceId && deviceIds.has(link.projectDeviceId)
|
|
||||||
? link.projectDeviceId
|
|
||||||
: null,
|
|
||||||
roomId:
|
|
||||||
link.roomId && roomIds.has(link.roomId) ? link.roomId : null,
|
|
||||||
})
|
|
||||||
.where(eq(consumers.id, link.id))
|
|
||||||
.run();
|
|
||||||
}
|
|
||||||
insertMany(
|
|
||||||
database,
|
|
||||||
legacyConsumerCircuitMigrations,
|
|
||||||
data.migrationMappings.filter(
|
|
||||||
(entry) =>
|
|
||||||
listIds.has(entry.circuitListId) &&
|
|
||||||
circuitIds.has(entry.circuitId) &&
|
|
||||||
rowIds.has(entry.circuitDeviceRowId)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
insertMany(
|
|
||||||
database,
|
|
||||||
legacyConsumerMigrationReports,
|
|
||||||
data.migrationReports.filter((entry) =>
|
|
||||||
listIds.has(entry.circuitListId)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function insertMany<TTable extends Parameters<AppDatabase["insert"]>[0]>(
|
function insertMany<TTable extends Parameters<AppDatabase["insert"]>[0]>(
|
||||||
|
|||||||
@@ -145,9 +145,6 @@ export function readProjectStateSnapshot(
|
|||||||
equipmentIdentifier: circuits.equipmentIdentifier,
|
equipmentIdentifier: circuits.equipmentIdentifier,
|
||||||
displayName: circuits.displayName,
|
displayName: circuits.displayName,
|
||||||
sortOrder: circuits.sortOrder,
|
sortOrder: circuits.sortOrder,
|
||||||
protectionType: circuits.protectionType,
|
|
||||||
protectionRatedCurrent: circuits.protectionRatedCurrent,
|
|
||||||
protectionCharacteristic: circuits.protectionCharacteristic,
|
|
||||||
cableType: circuits.cableType,
|
cableType: circuits.cableType,
|
||||||
cableCrossSection: circuits.cableCrossSection,
|
cableCrossSection: circuits.cableCrossSection,
|
||||||
cableLength: circuits.cableLength,
|
cableLength: circuits.cableLength,
|
||||||
@@ -173,7 +170,6 @@ export function readProjectStateSnapshot(
|
|||||||
id: circuitDeviceRows.id,
|
id: circuitDeviceRows.id,
|
||||||
circuitId: circuitDeviceRows.circuitId,
|
circuitId: circuitDeviceRows.circuitId,
|
||||||
linkedProjectDeviceId: circuitDeviceRows.linkedProjectDeviceId,
|
linkedProjectDeviceId: circuitDeviceRows.linkedProjectDeviceId,
|
||||||
legacyConsumerId: circuitDeviceRows.legacyConsumerId,
|
|
||||||
sortOrder: circuitDeviceRows.sortOrder,
|
sortOrder: circuitDeviceRows.sortOrder,
|
||||||
name: circuitDeviceRows.name,
|
name: circuitDeviceRows.name,
|
||||||
displayName: circuitDeviceRows.displayName,
|
displayName: circuitDeviceRows.displayName,
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ export const circuitDeviceRows = sqliteTable("circuit_device_rows", {
|
|||||||
linkedProjectDeviceId: text("linked_project_device_id").references(() => projectDevices.id, {
|
linkedProjectDeviceId: text("linked_project_device_id").references(() => projectDevices.id, {
|
||||||
onDelete: "set null",
|
onDelete: "set null",
|
||||||
}),
|
}),
|
||||||
legacyConsumerId: text("legacy_consumer_id"),
|
|
||||||
sortOrder: integer("sort_order").notNull().default(0),
|
sortOrder: integer("sort_order").notNull().default(0),
|
||||||
name: text("name").notNull(),
|
name: text("name").notNull(),
|
||||||
displayName: text("display_name").notNull(),
|
displayName: text("display_name").notNull(),
|
||||||
|
|||||||
@@ -15,9 +15,6 @@ export const circuits = sqliteTable(
|
|||||||
equipmentIdentifier: text("equipment_identifier").notNull(),
|
equipmentIdentifier: text("equipment_identifier").notNull(),
|
||||||
displayName: text("display_name"),
|
displayName: text("display_name"),
|
||||||
sortOrder: integer("sort_order").notNull().default(0),
|
sortOrder: integer("sort_order").notNull().default(0),
|
||||||
protectionType: text("protection_type"),
|
|
||||||
protectionRatedCurrent: real("protection_rated_current"),
|
|
||||||
protectionCharacteristic: text("protection_characteristic"),
|
|
||||||
cableType: text("cable_type"),
|
cableType: text("cable_type"),
|
||||||
cableCrossSection: text("cable_cross_section"),
|
cableCrossSection: text("cable_cross_section"),
|
||||||
cableLength: real("cable_length"),
|
cableLength: real("cable_length"),
|
||||||
|
|||||||
@@ -1,47 +0,0 @@
|
|||||||
import { integer, real, sqliteTable, text } from "drizzle-orm/sqlite-core";
|
|
||||||
import { circuitLists } from "./circuit-lists.js";
|
|
||||||
import { distributionBoards } from "./distribution-boards.js";
|
|
||||||
import { projectDevices } from "./project-devices.js";
|
|
||||||
import { projects } from "./projects.js";
|
|
||||||
import { rooms } from "./rooms.js";
|
|
||||||
|
|
||||||
export const consumers = sqliteTable("consumers", {
|
|
||||||
id: text("id").primaryKey(),
|
|
||||||
projectId: text("project_id")
|
|
||||||
.notNull()
|
|
||||||
.references(() => projects.id, { onDelete: "cascade" }),
|
|
||||||
distributionBoardId: text("distribution_board_id").references(() => distributionBoards.id, {
|
|
||||||
onDelete: "set null",
|
|
||||||
}),
|
|
||||||
circuitListId: text("circuit_list_id").references(() => circuitLists.id, {
|
|
||||||
onDelete: "set null",
|
|
||||||
}),
|
|
||||||
projectDeviceId: text("project_device_id").references(() => projectDevices.id, {
|
|
||||||
onDelete: "set null",
|
|
||||||
}),
|
|
||||||
isLinkedToDevice: integer("is_linked_to_device").notNull().default(0),
|
|
||||||
roomId: text("room_id").references(() => rooms.id, {
|
|
||||||
onDelete: "set null",
|
|
||||||
}),
|
|
||||||
circuitNumber: text("circuit_number"),
|
|
||||||
description: text("description"),
|
|
||||||
name: text("name").notNull(),
|
|
||||||
category: text("category"),
|
|
||||||
deviceType: text("device_type"),
|
|
||||||
phaseType: text("phase_type"),
|
|
||||||
tradeOrCostGroup: text("trade_or_cost_group"),
|
|
||||||
group: text("group_name"),
|
|
||||||
protectionType: text("protection_type"),
|
|
||||||
protectionRatedCurrent: real("protection_rated_current"),
|
|
||||||
protectionCharacteristic: text("protection_characteristic"),
|
|
||||||
cableType: text("cable_type"),
|
|
||||||
cableCrossSection: text("cable_cross_section"),
|
|
||||||
comment: text("comment"),
|
|
||||||
quantity: integer("quantity").notNull(),
|
|
||||||
installedPowerPerUnitKw: real("installed_power_per_unit_kw").notNull(),
|
|
||||||
demandFactor: real("demand_factor").notNull(),
|
|
||||||
voltageV: real("voltage_v"),
|
|
||||||
phaseCount: integer("phase_count"),
|
|
||||||
powerFactor: real("power_factor"),
|
|
||||||
note: text("note"),
|
|
||||||
});
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
import { sqliteTable, text } from "drizzle-orm/sqlite-core";
|
|
||||||
import { circuitDeviceRows } from "./circuit-device-rows.js";
|
|
||||||
import { circuitLists } from "./circuit-lists.js";
|
|
||||||
import { circuits } from "./circuits.js";
|
|
||||||
|
|
||||||
export const legacyConsumerCircuitMigrations = sqliteTable("legacy_consumer_circuit_migrations", {
|
|
||||||
consumerId: text("consumer_id").primaryKey(),
|
|
||||||
circuitId: text("circuit_id")
|
|
||||||
.notNull()
|
|
||||||
.references(() => circuits.id, { onDelete: "cascade" }),
|
|
||||||
circuitDeviceRowId: text("circuit_device_row_id")
|
|
||||||
.notNull()
|
|
||||||
.references(() => circuitDeviceRows.id, { onDelete: "cascade" }),
|
|
||||||
circuitListId: text("circuit_list_id")
|
|
||||||
.notNull()
|
|
||||||
.references(() => circuitLists.id, { onDelete: "cascade" }),
|
|
||||||
createdAtIso: text("created_at_iso").notNull(),
|
|
||||||
});
|
|
||||||
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
import { integer, sqliteTable, text, unique } from "drizzle-orm/sqlite-core";
|
|
||||||
import { circuitLists } from "./circuit-lists.js";
|
|
||||||
|
|
||||||
export const legacyConsumerMigrationReports = sqliteTable(
|
|
||||||
"legacy_consumer_migration_reports",
|
|
||||||
{
|
|
||||||
id: text("id").primaryKey(),
|
|
||||||
circuitListId: text("circuit_list_id")
|
|
||||||
.notNull()
|
|
||||||
.references(() => circuitLists.id, { onDelete: "cascade" }),
|
|
||||||
legacyConsumerCount: integer("legacy_consumer_count").notNull(),
|
|
||||||
createdCircuitCount: integer("created_circuit_count").notNull(),
|
|
||||||
createdDeviceRowCount: integer("created_device_row_count").notNull(),
|
|
||||||
duplicateGroupedCount: integer("duplicate_grouped_count").notNull(),
|
|
||||||
generatedIdentifierCount: integer("generated_identifier_count").notNull(),
|
|
||||||
unassignedRowCount: integer("unassigned_row_count").notNull(),
|
|
||||||
warningsJson: text("warnings_json").notNull(),
|
|
||||||
generatedIdentifiersJson: text("generated_identifiers_json").notNull(),
|
|
||||||
duplicateGroupsJson: text("duplicate_groups_json").notNull(),
|
|
||||||
createdAtIso: text("created_at_iso").notNull(),
|
|
||||||
},
|
|
||||||
(table) => [unique("legacy_consumer_migration_reports_list_unique").on(table.circuitListId)]
|
|
||||||
);
|
|
||||||
|
|
||||||
@@ -10,7 +10,6 @@ export interface CircuitDeviceRowSnapshot {
|
|||||||
id: string;
|
id: string;
|
||||||
circuitId: string;
|
circuitId: string;
|
||||||
linkedProjectDeviceId: string | null;
|
linkedProjectDeviceId: string | null;
|
||||||
legacyConsumerId: string | null;
|
|
||||||
sortOrder: number;
|
sortOrder: number;
|
||||||
name: string;
|
name: string;
|
||||||
displayName: string;
|
displayName: string;
|
||||||
@@ -101,7 +100,6 @@ export function assertCircuitDeviceRowInsertProjectCommand(
|
|||||||
assertNonEmptyString(row.id, "row.id");
|
assertNonEmptyString(row.id, "row.id");
|
||||||
assertNonEmptyString(row.circuitId, "row.circuitId");
|
assertNonEmptyString(row.circuitId, "row.circuitId");
|
||||||
assertNullableString(row.linkedProjectDeviceId, "row.linkedProjectDeviceId");
|
assertNullableString(row.linkedProjectDeviceId, "row.linkedProjectDeviceId");
|
||||||
assertNullableString(row.legacyConsumerId, "row.legacyConsumerId");
|
|
||||||
assertFiniteNumber(row.sortOrder, "row.sortOrder");
|
assertFiniteNumber(row.sortOrder, "row.sortOrder");
|
||||||
assertNonEmptyString(row.name, "row.name");
|
assertNonEmptyString(row.name, "row.name");
|
||||||
assertNonEmptyString(row.displayName, "row.displayName");
|
assertNonEmptyString(row.displayName, "row.displayName");
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ export interface CircuitDeviceRow {
|
|||||||
id: string;
|
id: string;
|
||||||
circuitId: string;
|
circuitId: string;
|
||||||
linkedProjectDeviceId?: string;
|
linkedProjectDeviceId?: string;
|
||||||
legacyConsumerId?: string;
|
|
||||||
sortOrder: number;
|
sortOrder: number;
|
||||||
name: string;
|
name: string;
|
||||||
displayName: string;
|
displayName: string;
|
||||||
|
|||||||
@@ -8,9 +8,6 @@ export interface CircuitUpdateValues {
|
|||||||
equipmentIdentifier: string;
|
equipmentIdentifier: string;
|
||||||
displayName: string | null;
|
displayName: string | null;
|
||||||
sortOrder: number;
|
sortOrder: number;
|
||||||
protectionType: string | null;
|
|
||||||
protectionRatedCurrent: number | null;
|
|
||||||
protectionCharacteristic: string | null;
|
|
||||||
cableType: string | null;
|
cableType: string | null;
|
||||||
cableCrossSection: string | null;
|
cableCrossSection: string | null;
|
||||||
cableLength: number | null;
|
cableLength: number | null;
|
||||||
@@ -118,7 +115,6 @@ function assertCircuitUpdateFieldValue(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
field === "protectionRatedCurrent" ||
|
|
||||||
field === "cableLength" ||
|
field === "cableLength" ||
|
||||||
field === "voltage"
|
field === "voltage"
|
||||||
) {
|
) {
|
||||||
@@ -147,9 +143,6 @@ function isCircuitUpdateField(value: string): value is CircuitUpdateField {
|
|||||||
"equipmentIdentifier",
|
"equipmentIdentifier",
|
||||||
"displayName",
|
"displayName",
|
||||||
"sortOrder",
|
"sortOrder",
|
||||||
"protectionType",
|
|
||||||
"protectionRatedCurrent",
|
|
||||||
"protectionCharacteristic",
|
|
||||||
"cableType",
|
"cableType",
|
||||||
"cableCrossSection",
|
"cableCrossSection",
|
||||||
"cableLength",
|
"cableLength",
|
||||||
|
|||||||
@@ -21,9 +21,6 @@ export interface CircuitSnapshot {
|
|||||||
equipmentIdentifier: string;
|
equipmentIdentifier: string;
|
||||||
displayName: string | null;
|
displayName: string | null;
|
||||||
sortOrder: number;
|
sortOrder: number;
|
||||||
protectionType: string | null;
|
|
||||||
protectionRatedCurrent: number | null;
|
|
||||||
protectionCharacteristic: string | null;
|
|
||||||
cableType: string | null;
|
cableType: string | null;
|
||||||
cableCrossSection: string | null;
|
cableCrossSection: string | null;
|
||||||
cableLength: number | null;
|
cableLength: number | null;
|
||||||
@@ -115,8 +112,6 @@ export function assertCircuitInsertProjectCommand(
|
|||||||
assertNullableString(circuit.displayName, "circuit.displayName");
|
assertNullableString(circuit.displayName, "circuit.displayName");
|
||||||
assertFiniteNumber(circuit.sortOrder, "circuit.sortOrder");
|
assertFiniteNumber(circuit.sortOrder, "circuit.sortOrder");
|
||||||
for (const field of [
|
for (const field of [
|
||||||
"protectionType",
|
|
||||||
"protectionCharacteristic",
|
|
||||||
"cableType",
|
"cableType",
|
||||||
"cableCrossSection",
|
"cableCrossSection",
|
||||||
"rcdAssignment",
|
"rcdAssignment",
|
||||||
@@ -127,10 +122,6 @@ export function assertCircuitInsertProjectCommand(
|
|||||||
] as const) {
|
] as const) {
|
||||||
assertNullableString(circuit[field], `circuit.${field}`);
|
assertNullableString(circuit[field], `circuit.${field}`);
|
||||||
}
|
}
|
||||||
assertNullableNonNegativeNumber(
|
|
||||||
circuit.protectionRatedCurrent,
|
|
||||||
"circuit.protectionRatedCurrent"
|
|
||||||
);
|
|
||||||
assertNullableNonNegativeNumber(
|
assertNullableNonNegativeNumber(
|
||||||
circuit.cableLength,
|
circuit.cableLength,
|
||||||
"circuit.cableLength"
|
"circuit.cableLength"
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
export interface CircuitTreeDeviceRow {
|
export interface CircuitTreeDeviceRow {
|
||||||
id: string;
|
id: string;
|
||||||
linkedProjectDeviceId?: string;
|
linkedProjectDeviceId?: string;
|
||||||
legacyConsumerId?: string;
|
|
||||||
sortOrder: number;
|
sortOrder: number;
|
||||||
name: string;
|
name: string;
|
||||||
displayName: string;
|
displayName: string;
|
||||||
@@ -29,9 +28,6 @@ export interface CircuitTreeCircuit {
|
|||||||
equipmentIdentifier: string;
|
equipmentIdentifier: string;
|
||||||
displayName?: string;
|
displayName?: string;
|
||||||
sortOrder: number;
|
sortOrder: number;
|
||||||
protectionType?: string;
|
|
||||||
protectionRatedCurrent?: number;
|
|
||||||
protectionCharacteristic?: string;
|
|
||||||
cableType?: string;
|
cableType?: string;
|
||||||
cableCrossSection?: string;
|
cableCrossSection?: string;
|
||||||
cableLength?: number;
|
cableLength?: number;
|
||||||
@@ -73,17 +69,6 @@ export interface CircuitTreeResponse {
|
|||||||
footerComponents: CircuitTreeComponent[];
|
footerComponents: CircuitTreeComponent[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LegacyMigrationReport {
|
|
||||||
circuitListId: string;
|
|
||||||
legacyConsumerCount: number;
|
|
||||||
createdCircuitCount: number;
|
|
||||||
createdDeviceRowCount: number;
|
|
||||||
groupedDuplicateCircuitNumbers: Array<{ normalizedCircuitNumber: string; count: number }>;
|
|
||||||
generatedIdentifiers: string[];
|
|
||||||
unassignedRows: Array<{ consumerId: string; reason: string }>;
|
|
||||||
warnings: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
import type { CircuitGroupCategory } from "../../shared/constants/circuit-group.js";
|
import type { CircuitGroupCategory } from "../../shared/constants/circuit-group.js";
|
||||||
import type {
|
import type {
|
||||||
DistributionBoardComponentPlacement,
|
DistributionBoardComponentPlacement,
|
||||||
|
|||||||
@@ -5,9 +5,6 @@ export interface Circuit {
|
|||||||
equipmentIdentifier: string;
|
equipmentIdentifier: string;
|
||||||
displayName?: string;
|
displayName?: string;
|
||||||
sortOrder: number;
|
sortOrder: number;
|
||||||
protectionType?: string;
|
|
||||||
protectionRatedCurrent?: number;
|
|
||||||
protectionCharacteristic?: string;
|
|
||||||
cableType?: string;
|
cableType?: string;
|
||||||
cableCrossSection?: string;
|
cableCrossSection?: string;
|
||||||
cableLength?: number;
|
cableLength?: number;
|
||||||
|
|||||||
@@ -6,8 +6,7 @@ import type { SerializedProjectCommand } from "./project-command.model.js";
|
|||||||
|
|
||||||
export const distributionBoardUpdateCommandType =
|
export const distributionBoardUpdateCommandType =
|
||||||
"distribution-board.update" as const;
|
"distribution-board.update" as const;
|
||||||
export const legacyDistributionBoardUpdateCommandSchemaVersion = 1 as const;
|
export const distributionBoardUpdateCommandSchemaVersion = 1 as const;
|
||||||
export const distributionBoardUpdateCommandSchemaVersion = 2 as const;
|
|
||||||
|
|
||||||
export interface DistributionBoardUpdateValues {
|
export interface DistributionBoardUpdateValues {
|
||||||
floorId: string | null;
|
floorId: string | null;
|
||||||
@@ -33,27 +32,17 @@ export interface DistributionBoardUpdateCommandPayload {
|
|||||||
changes: DistributionBoardUpdateFieldChange[];
|
changes: DistributionBoardUpdateFieldChange[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CurrentDistributionBoardUpdateProjectCommand
|
export interface DistributionBoardUpdateProjectCommand
|
||||||
extends SerializedProjectCommand<DistributionBoardUpdateCommandPayload> {
|
extends SerializedProjectCommand<DistributionBoardUpdateCommandPayload> {
|
||||||
schemaVersion: typeof distributionBoardUpdateCommandSchemaVersion;
|
schemaVersion: typeof distributionBoardUpdateCommandSchemaVersion;
|
||||||
type: typeof distributionBoardUpdateCommandType;
|
type: typeof distributionBoardUpdateCommandType;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface LegacyDistributionBoardUpdateProjectCommand
|
|
||||||
extends SerializedProjectCommand<DistributionBoardUpdateCommandPayload> {
|
|
||||||
schemaVersion: typeof legacyDistributionBoardUpdateCommandSchemaVersion;
|
|
||||||
type: typeof distributionBoardUpdateCommandType;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type DistributionBoardUpdateProjectCommand =
|
|
||||||
| CurrentDistributionBoardUpdateProjectCommand
|
|
||||||
| LegacyDistributionBoardUpdateProjectCommand;
|
|
||||||
|
|
||||||
export function createDistributionBoardUpdateProjectCommand(
|
export function createDistributionBoardUpdateProjectCommand(
|
||||||
distributionBoardId: string,
|
distributionBoardId: string,
|
||||||
patch: DistributionBoardUpdatePatch
|
patch: DistributionBoardUpdatePatch
|
||||||
): CurrentDistributionBoardUpdateProjectCommand {
|
): DistributionBoardUpdateProjectCommand {
|
||||||
const command: CurrentDistributionBoardUpdateProjectCommand = {
|
const command: DistributionBoardUpdateProjectCommand = {
|
||||||
schemaVersion: distributionBoardUpdateCommandSchemaVersion,
|
schemaVersion: distributionBoardUpdateCommandSchemaVersion,
|
||||||
type: distributionBoardUpdateCommandType,
|
type: distributionBoardUpdateCommandType,
|
||||||
payload: {
|
payload: {
|
||||||
@@ -72,9 +61,7 @@ export function assertDistributionBoardUpdateProjectCommand(
|
|||||||
command: SerializedProjectCommand<unknown>
|
command: SerializedProjectCommand<unknown>
|
||||||
): asserts command is DistributionBoardUpdateProjectCommand {
|
): asserts command is DistributionBoardUpdateProjectCommand {
|
||||||
if (
|
if (
|
||||||
(command.schemaVersion !==
|
command.schemaVersion !== distributionBoardUpdateCommandSchemaVersion ||
|
||||||
legacyDistributionBoardUpdateCommandSchemaVersion &&
|
|
||||||
command.schemaVersion !== distributionBoardUpdateCommandSchemaVersion) ||
|
|
||||||
command.type !== distributionBoardUpdateCommandType ||
|
command.type !== distributionBoardUpdateCommandType ||
|
||||||
!isPlainObject(command.payload)
|
!isPlainObject(command.payload)
|
||||||
) {
|
) {
|
||||||
@@ -97,9 +84,7 @@ export function assertDistributionBoardUpdateProjectCommand(
|
|||||||
!isPlainObject(change) ||
|
!isPlainObject(change) ||
|
||||||
(change.field !== "floorId" &&
|
(change.field !== "floorId" &&
|
||||||
change.field !== "supplyType" &&
|
change.field !== "supplyType" &&
|
||||||
(command.schemaVersion ===
|
change.field !== "simultaneityFactor") ||
|
||||||
legacyDistributionBoardUpdateCommandSchemaVersion ||
|
|
||||||
change.field !== "simultaneityFactor")) ||
|
|
||||||
seen.has(change.field)
|
seen.has(change.field)
|
||||||
) {
|
) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
|||||||
@@ -18,36 +18,7 @@ export const distributionBoardInsertCommandType =
|
|||||||
"distribution-board.insert" as const;
|
"distribution-board.insert" as const;
|
||||||
export const distributionBoardDeleteCommandType =
|
export const distributionBoardDeleteCommandType =
|
||||||
"distribution-board.delete" as const;
|
"distribution-board.delete" as const;
|
||||||
export const legacyDistributionBoardStructureCommandSchemaVersion = 1 as const;
|
export const distributionBoardStructureCommandSchemaVersion = 1 as const;
|
||||||
export const previousDistributionBoardStructureCommandSchemaVersion = 2 as const;
|
|
||||||
export const distributionBoardStructureCommandSchemaVersion = 3 as const;
|
|
||||||
|
|
||||||
const legacyDefaultCircuitSectionDefinitions = [
|
|
||||||
{
|
|
||||||
key: "lighting",
|
|
||||||
displayName: "Lighting",
|
|
||||||
prefix: "-1F",
|
|
||||||
sortOrder: 10,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "single_phase",
|
|
||||||
displayName: "Single-phase circuits",
|
|
||||||
prefix: "-2F",
|
|
||||||
sortOrder: 20,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "three_phase",
|
|
||||||
displayName: "Three-phase circuits",
|
|
||||||
prefix: "-3F",
|
|
||||||
sortOrder: 30,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "unassigned",
|
|
||||||
displayName: "Unassigned",
|
|
||||||
prefix: "-UF",
|
|
||||||
sortOrder: 90,
|
|
||||||
},
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
export const defaultCircuitSectionDefinitions = [
|
export const defaultCircuitSectionDefinitions = [
|
||||||
{
|
{
|
||||||
@@ -76,31 +47,6 @@ export const defaultCircuitSectionDefinitions = [
|
|||||||
},
|
},
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
interface LegacyDistributionBoardStructureSnapshot {
|
|
||||||
distributionBoard: {
|
|
||||||
id: string;
|
|
||||||
projectId: string;
|
|
||||||
name: string;
|
|
||||||
};
|
|
||||||
circuitList: DistributionBoardStructureSnapshot["circuitList"];
|
|
||||||
sections: LegacyCircuitSectionSnapshot[];
|
|
||||||
}
|
|
||||||
|
|
||||||
interface PreviousDistributionBoardStructureSnapshot {
|
|
||||||
distributionBoard: DistributionBoardStructureSnapshot["distributionBoard"];
|
|
||||||
circuitList: DistributionBoardStructureSnapshot["circuitList"];
|
|
||||||
sections: LegacyCircuitSectionSnapshot[];
|
|
||||||
}
|
|
||||||
|
|
||||||
interface LegacyCircuitSectionSnapshot {
|
|
||||||
id: string;
|
|
||||||
circuitListId: string;
|
|
||||||
key: string;
|
|
||||||
displayName: string;
|
|
||||||
prefix: string;
|
|
||||||
sortOrder: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DistributionBoardStructureSnapshot {
|
export interface DistributionBoardStructureSnapshot {
|
||||||
distributionBoard: {
|
distributionBoard: {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -140,22 +86,15 @@ export interface DistributionBoardStructureSnapshot {
|
|||||||
export interface NormalizedDistributionBoardStructureSnapshot {
|
export interface NormalizedDistributionBoardStructureSnapshot {
|
||||||
distributionBoard: DistributionBoardStructureSnapshot["distributionBoard"];
|
distributionBoard: DistributionBoardStructureSnapshot["distributionBoard"];
|
||||||
circuitList: DistributionBoardStructureSnapshot["circuitList"];
|
circuitList: DistributionBoardStructureSnapshot["circuitList"];
|
||||||
sections: Array<
|
sections: DistributionBoardStructureSnapshot["sections"];
|
||||||
LegacyCircuitSectionSnapshot & {
|
|
||||||
category: CircuitGroupCategory | null;
|
|
||||||
groupNumber: number | null;
|
|
||||||
}
|
|
||||||
>;
|
|
||||||
components: DistributionBoardStructureSnapshot["components"];
|
components: DistributionBoardStructureSnapshot["components"];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DistributionBoardStructureCommandPayload<
|
interface DistributionBoardStructureCommandPayload {
|
||||||
TStructure = DistributionBoardStructureSnapshot,
|
structure: DistributionBoardStructureSnapshot;
|
||||||
> {
|
|
||||||
structure: TStructure;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CurrentDistributionBoardStructureProjectCommand
|
interface DistributionBoardStructureProjectCommandBase
|
||||||
extends SerializedProjectCommand<DistributionBoardStructureCommandPayload> {
|
extends SerializedProjectCommand<DistributionBoardStructureCommandPayload> {
|
||||||
schemaVersion: typeof distributionBoardStructureCommandSchemaVersion;
|
schemaVersion: typeof distributionBoardStructureCommandSchemaVersion;
|
||||||
type:
|
type:
|
||||||
@@ -163,41 +102,19 @@ interface CurrentDistributionBoardStructureProjectCommand
|
|||||||
| typeof distributionBoardDeleteCommandType;
|
| typeof distributionBoardDeleteCommandType;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface LegacyDistributionBoardStructureProjectCommand
|
|
||||||
extends SerializedProjectCommand<
|
|
||||||
DistributionBoardStructureCommandPayload<LegacyDistributionBoardStructureSnapshot>
|
|
||||||
> {
|
|
||||||
schemaVersion: typeof legacyDistributionBoardStructureCommandSchemaVersion;
|
|
||||||
type:
|
|
||||||
| typeof distributionBoardInsertCommandType
|
|
||||||
| typeof distributionBoardDeleteCommandType;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface PreviousDistributionBoardStructureProjectCommand
|
|
||||||
extends SerializedProjectCommand<
|
|
||||||
DistributionBoardStructureCommandPayload<PreviousDistributionBoardStructureSnapshot>
|
|
||||||
> {
|
|
||||||
schemaVersion: typeof previousDistributionBoardStructureCommandSchemaVersion;
|
|
||||||
type:
|
|
||||||
| typeof distributionBoardInsertCommandType
|
|
||||||
| typeof distributionBoardDeleteCommandType;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DistributionBoardInsertProjectCommand
|
export interface DistributionBoardInsertProjectCommand
|
||||||
extends CurrentDistributionBoardStructureProjectCommand {
|
extends DistributionBoardStructureProjectCommandBase {
|
||||||
type: typeof distributionBoardInsertCommandType;
|
type: typeof distributionBoardInsertCommandType;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DistributionBoardDeleteProjectCommand
|
export interface DistributionBoardDeleteProjectCommand
|
||||||
extends CurrentDistributionBoardStructureProjectCommand {
|
extends DistributionBoardStructureProjectCommandBase {
|
||||||
type: typeof distributionBoardDeleteCommandType;
|
type: typeof distributionBoardDeleteCommandType;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DistributionBoardStructureProjectCommand =
|
export type DistributionBoardStructureProjectCommand =
|
||||||
| DistributionBoardInsertProjectCommand
|
| DistributionBoardInsertProjectCommand
|
||||||
| DistributionBoardDeleteProjectCommand
|
| DistributionBoardDeleteProjectCommand;
|
||||||
| PreviousDistributionBoardStructureProjectCommand
|
|
||||||
| LegacyDistributionBoardStructureProjectCommand;
|
|
||||||
|
|
||||||
export function createDistributionBoardStructureSnapshot(
|
export function createDistributionBoardStructureSnapshot(
|
||||||
projectId: string,
|
projectId: string,
|
||||||
@@ -296,35 +213,6 @@ export function assertDistributionBoardDeleteProjectCommand(
|
|||||||
export function normalizeDistributionBoardStructureProjectCommand(
|
export function normalizeDistributionBoardStructureProjectCommand(
|
||||||
command: DistributionBoardStructureProjectCommand
|
command: DistributionBoardStructureProjectCommand
|
||||||
): NormalizedDistributionBoardStructureSnapshot {
|
): NormalizedDistributionBoardStructureSnapshot {
|
||||||
if (
|
|
||||||
command.schemaVersion ===
|
|
||||||
legacyDistributionBoardStructureCommandSchemaVersion
|
|
||||||
) {
|
|
||||||
return {
|
|
||||||
distributionBoard: {
|
|
||||||
...command.payload.structure.distributionBoard,
|
|
||||||
floorId: null,
|
|
||||||
supplyType: null,
|
|
||||||
},
|
|
||||||
circuitList: command.payload.structure.circuitList,
|
|
||||||
sections: command.payload.structure.sections.map(
|
|
||||||
normalizeLegacySection
|
|
||||||
),
|
|
||||||
components: [],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
command.schemaVersion ===
|
|
||||||
previousDistributionBoardStructureCommandSchemaVersion
|
|
||||||
) {
|
|
||||||
return {
|
|
||||||
...command.payload.structure,
|
|
||||||
sections: command.payload.structure.sections.map(
|
|
||||||
normalizeLegacySection
|
|
||||||
),
|
|
||||||
components: [],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return command.payload.structure;
|
return command.payload.structure;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -340,26 +228,10 @@ export function invertDistributionBoardStructureProjectCommand(
|
|||||||
} as DistributionBoardStructureProjectCommand;
|
} as DistributionBoardStructureProjectCommand;
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeLegacySection(
|
|
||||||
section: LegacyCircuitSectionSnapshot
|
|
||||||
): NormalizedDistributionBoardStructureSnapshot["sections"][number] {
|
|
||||||
const category =
|
|
||||||
section.key === "lighting" ||
|
|
||||||
section.key === "single_phase" ||
|
|
||||||
section.key === "three_phase"
|
|
||||||
? section.key
|
|
||||||
: null;
|
|
||||||
return {
|
|
||||||
...section,
|
|
||||||
category,
|
|
||||||
groupNumber: category === null ? null : 1,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function assertDistributionBoardStructureSnapshot(
|
export function assertDistributionBoardStructureSnapshot(
|
||||||
structure: unknown
|
structure: unknown
|
||||||
): asserts structure is DistributionBoardStructureSnapshot {
|
): asserts structure is DistributionBoardStructureSnapshot {
|
||||||
assertDistributionBoardStructureSnapshotVersion(structure, "current");
|
assertDistributionBoardStructureSnapshotVersion(structure);
|
||||||
}
|
}
|
||||||
|
|
||||||
function assertDistributionBoardStructureProjectCommand(
|
function assertDistributionBoardStructureProjectCommand(
|
||||||
@@ -375,63 +247,36 @@ function assertDistributionBoardStructureProjectCommand(
|
|||||||
) {
|
) {
|
||||||
throw new Error("Unsupported distribution-board structure command.");
|
throw new Error("Unsupported distribution-board structure command.");
|
||||||
}
|
}
|
||||||
if (
|
|
||||||
command.schemaVersion ===
|
|
||||||
legacyDistributionBoardStructureCommandSchemaVersion
|
|
||||||
) {
|
|
||||||
assertDistributionBoardStructureSnapshotVersion(
|
|
||||||
command.payload.structure,
|
|
||||||
"legacy"
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
command.schemaVersion ===
|
|
||||||
previousDistributionBoardStructureCommandSchemaVersion
|
|
||||||
) {
|
|
||||||
assertDistributionBoardStructureSnapshotVersion(
|
|
||||||
command.payload.structure,
|
|
||||||
"previous"
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (
|
if (
|
||||||
command.schemaVersion !==
|
command.schemaVersion !==
|
||||||
distributionBoardStructureCommandSchemaVersion
|
distributionBoardStructureCommandSchemaVersion
|
||||||
) {
|
) {
|
||||||
throw new Error("Unsupported distribution-board structure command.");
|
throw new Error("Unsupported distribution-board structure command.");
|
||||||
}
|
}
|
||||||
assertDistributionBoardStructureSnapshotVersion(
|
assertDistributionBoardStructureSnapshotVersion(command.payload.structure);
|
||||||
command.payload.structure,
|
|
||||||
"current"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function assertDistributionBoardStructureSnapshotVersion(
|
function assertDistributionBoardStructureSnapshotVersion(
|
||||||
structure: unknown,
|
structure: unknown
|
||||||
version: "legacy" | "previous" | "current"
|
|
||||||
) {
|
) {
|
||||||
const current = version === "current";
|
|
||||||
if (
|
if (
|
||||||
!isPlainObject(structure) ||
|
!isPlainObject(structure) ||
|
||||||
Object.keys(structure).length !== (current ? 4 : 3)
|
Object.keys(structure).length !== 4
|
||||||
) {
|
) {
|
||||||
throw new Error("Distribution-board structure is invalid.");
|
throw new Error("Distribution-board structure is invalid.");
|
||||||
}
|
}
|
||||||
const { distributionBoard, circuitList, sections, components } =
|
const { distributionBoard, circuitList, sections, components } =
|
||||||
structure;
|
structure;
|
||||||
const expectedSections = current
|
const expectedSections = defaultCircuitSectionDefinitions;
|
||||||
? defaultCircuitSectionDefinitions
|
|
||||||
: legacyDefaultCircuitSectionDefinitions;
|
|
||||||
if (
|
if (
|
||||||
!isPlainObject(distributionBoard) ||
|
!isPlainObject(distributionBoard) ||
|
||||||
Object.keys(distributionBoard).length !==
|
Object.keys(distributionBoard).length !== 5 ||
|
||||||
(version === "legacy" ? 3 : 5) ||
|
|
||||||
!isPlainObject(circuitList) ||
|
!isPlainObject(circuitList) ||
|
||||||
Object.keys(circuitList).length !== 4 ||
|
Object.keys(circuitList).length !== 4 ||
|
||||||
!Array.isArray(sections) ||
|
!Array.isArray(sections) ||
|
||||||
sections.length !== expectedSections.length ||
|
sections.length !== expectedSections.length ||
|
||||||
(current && (!Array.isArray(components) || components.length !== 2))
|
!Array.isArray(components) ||
|
||||||
|
components.length !== 2
|
||||||
) {
|
) {
|
||||||
throw new Error("Distribution-board structure is incomplete.");
|
throw new Error("Distribution-board structure is incomplete.");
|
||||||
}
|
}
|
||||||
@@ -441,10 +286,8 @@ function assertDistributionBoardStructureSnapshotVersion(
|
|||||||
`distributionBoard.${field}`
|
`distributionBoard.${field}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (version !== "legacy") {
|
assertNullableId(distributionBoard.floorId, "distributionBoard.floorId");
|
||||||
assertNullableId(distributionBoard.floorId, "distributionBoard.floorId");
|
assertNullableSupplyType(distributionBoard.supplyType);
|
||||||
assertNullableSupplyType(distributionBoard.supplyType);
|
|
||||||
}
|
|
||||||
for (const [field, value] of Object.entries(circuitList)) {
|
for (const [field, value] of Object.entries(circuitList)) {
|
||||||
assertNonEmptyString(value, `circuitList.${field}`);
|
assertNonEmptyString(value, `circuitList.${field}`);
|
||||||
}
|
}
|
||||||
@@ -462,7 +305,7 @@ function assertDistributionBoardStructureSnapshotVersion(
|
|||||||
const expected = expectedSections[index];
|
const expected = expectedSections[index];
|
||||||
if (
|
if (
|
||||||
!isPlainObject(section) ||
|
!isPlainObject(section) ||
|
||||||
Object.keys(section).length !== (current ? 8 : 6)
|
Object.keys(section).length !== 8
|
||||||
) {
|
) {
|
||||||
throw new Error("Default circuit section is invalid.");
|
throw new Error("Default circuit section is invalid.");
|
||||||
}
|
}
|
||||||
@@ -477,10 +320,8 @@ function assertDistributionBoardStructureSnapshotVersion(
|
|||||||
section.displayName !== expected.displayName ||
|
section.displayName !== expected.displayName ||
|
||||||
section.prefix !== expected.prefix ||
|
section.prefix !== expected.prefix ||
|
||||||
section.sortOrder !== expected.sortOrder ||
|
section.sortOrder !== expected.sortOrder ||
|
||||||
(current &&
|
section.category !== expected.category ||
|
||||||
(!("category" in expected) ||
|
section.groupNumber !== expected.groupNumber
|
||||||
section.category !== expected.category ||
|
|
||||||
section.groupNumber !== expected.groupNumber))
|
|
||||||
) {
|
) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Distribution-board structure has invalid default sections."
|
"Distribution-board structure has invalid default sections."
|
||||||
@@ -488,9 +329,7 @@ function assertDistributionBoardStructureSnapshotVersion(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (current) {
|
assertDefaultComponents(components, circuitList.id);
|
||||||
assertDefaultComponents(components, circuitList.id);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function assertDefaultComponents(
|
function assertDefaultComponents(
|
||||||
|
|||||||
@@ -156,7 +156,6 @@ export function cloneDistributionBoardSubtree(
|
|||||||
...row,
|
...row,
|
||||||
id: createId(),
|
id: createId(),
|
||||||
circuitId,
|
circuitId,
|
||||||
legacyConsumerId: null,
|
|
||||||
})),
|
})),
|
||||||
protectionDevice:
|
protectionDevice:
|
||||||
circuit.protectionDevice === null ||
|
circuit.protectionDevice === null ||
|
||||||
|
|||||||
@@ -6,54 +6,29 @@ import {
|
|||||||
|
|
||||||
export const projectSettingsUpdateCommandType =
|
export const projectSettingsUpdateCommandType =
|
||||||
"project.update-settings" as const;
|
"project.update-settings" as const;
|
||||||
export const legacyProjectSettingsUpdateCommandSchemaVersion = 1 as const;
|
export const projectSettingsUpdateCommandSchemaVersion = 1 as const;
|
||||||
export const previousProjectSettingsUpdateCommandSchemaVersion = 2 as const;
|
|
||||||
export const projectSettingsUpdateCommandSchemaVersion = 3 as const;
|
|
||||||
|
|
||||||
export interface LegacyProjectSettingsValues {
|
export interface ProjectSettingsValues {
|
||||||
singlePhaseVoltageV: number;
|
|
||||||
threePhaseVoltageV: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PreviousProjectSettingsValues extends LegacyProjectSettingsValues {
|
|
||||||
name: string;
|
name: string;
|
||||||
internalProjectNumber: string | null;
|
internalProjectNumber: string | null;
|
||||||
externalProjectNumber: string | null;
|
externalProjectNumber: string | null;
|
||||||
buildingOwner: string | null;
|
buildingOwner: string | null;
|
||||||
description: string | null;
|
description: string | null;
|
||||||
}
|
singlePhaseVoltageV: number;
|
||||||
|
threePhaseVoltageV: number;
|
||||||
export interface ProjectSettingsValues extends PreviousProjectSettingsValues {
|
|
||||||
enabledDistributionBoardSupplyTypes: DistributionBoardSupplyType[];
|
enabledDistributionBoardSupplyTypes: DistributionBoardSupplyType[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LegacyProjectSettingsUpdateProjectCommand
|
export interface ProjectSettingsUpdateProjectCommand
|
||||||
extends SerializedProjectCommand<LegacyProjectSettingsValues> {
|
|
||||||
schemaVersion: typeof legacyProjectSettingsUpdateCommandSchemaVersion;
|
|
||||||
type: typeof projectSettingsUpdateCommandType;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CurrentProjectSettingsUpdateProjectCommand
|
|
||||||
extends SerializedProjectCommand<ProjectSettingsValues> {
|
extends SerializedProjectCommand<ProjectSettingsValues> {
|
||||||
schemaVersion: typeof projectSettingsUpdateCommandSchemaVersion;
|
schemaVersion: typeof projectSettingsUpdateCommandSchemaVersion;
|
||||||
type: typeof projectSettingsUpdateCommandType;
|
type: typeof projectSettingsUpdateCommandType;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PreviousProjectSettingsUpdateProjectCommand
|
|
||||||
extends SerializedProjectCommand<PreviousProjectSettingsValues> {
|
|
||||||
schemaVersion: typeof previousProjectSettingsUpdateCommandSchemaVersion;
|
|
||||||
type: typeof projectSettingsUpdateCommandType;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ProjectSettingsUpdateProjectCommand =
|
|
||||||
| LegacyProjectSettingsUpdateProjectCommand
|
|
||||||
| PreviousProjectSettingsUpdateProjectCommand
|
|
||||||
| CurrentProjectSettingsUpdateProjectCommand;
|
|
||||||
|
|
||||||
export function createProjectSettingsUpdateProjectCommand(
|
export function createProjectSettingsUpdateProjectCommand(
|
||||||
values: ProjectSettingsValues
|
values: ProjectSettingsValues
|
||||||
): CurrentProjectSettingsUpdateProjectCommand {
|
): ProjectSettingsUpdateProjectCommand {
|
||||||
const command: CurrentProjectSettingsUpdateProjectCommand = {
|
const command: ProjectSettingsUpdateProjectCommand = {
|
||||||
schemaVersion: projectSettingsUpdateCommandSchemaVersion,
|
schemaVersion: projectSettingsUpdateCommandSchemaVersion,
|
||||||
type: projectSettingsUpdateCommandType,
|
type: projectSettingsUpdateCommandType,
|
||||||
payload: { ...values },
|
payload: { ...values },
|
||||||
@@ -67,11 +42,7 @@ export function assertProjectSettingsUpdateProjectCommand(
|
|||||||
): asserts command is ProjectSettingsUpdateProjectCommand {
|
): asserts command is ProjectSettingsUpdateProjectCommand {
|
||||||
if (
|
if (
|
||||||
command.type !== projectSettingsUpdateCommandType ||
|
command.type !== projectSettingsUpdateCommandType ||
|
||||||
(command.schemaVersion !==
|
command.schemaVersion !== projectSettingsUpdateCommandSchemaVersion
|
||||||
legacyProjectSettingsUpdateCommandSchemaVersion &&
|
|
||||||
command.schemaVersion !==
|
|
||||||
previousProjectSettingsUpdateCommandSchemaVersion &&
|
|
||||||
command.schemaVersion !== projectSettingsUpdateCommandSchemaVersion)
|
|
||||||
) {
|
) {
|
||||||
throw new Error("Unsupported project settings update command.");
|
throw new Error("Unsupported project settings update command.");
|
||||||
}
|
}
|
||||||
@@ -80,14 +51,6 @@ export function assertProjectSettingsUpdateProjectCommand(
|
|||||||
"Project settings update command contains invalid values."
|
"Project settings update command contains invalid values."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (command.schemaVersion === legacyProjectSettingsUpdateCommandSchemaVersion) {
|
|
||||||
assertLegacyValues(command.payload);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const expectedKeyCount =
|
|
||||||
command.schemaVersion === previousProjectSettingsUpdateCommandSchemaVersion
|
|
||||||
? 7
|
|
||||||
: 8;
|
|
||||||
if (
|
if (
|
||||||
!isTrimmedString(command.payload.name, 1, 200) ||
|
!isTrimmedString(command.payload.name, 1, 200) ||
|
||||||
!isNullableTrimmedString(command.payload.internalProjectNumber, 100) ||
|
!isNullableTrimmedString(command.payload.internalProjectNumber, 100) ||
|
||||||
@@ -96,14 +59,13 @@ export function assertProjectSettingsUpdateProjectCommand(
|
|||||||
!isNullableTrimmedString(command.payload.description, 2000) ||
|
!isNullableTrimmedString(command.payload.description, 2000) ||
|
||||||
!isPositiveFiniteNumber(command.payload.singlePhaseVoltageV) ||
|
!isPositiveFiniteNumber(command.payload.singlePhaseVoltageV) ||
|
||||||
!isPositiveFiniteNumber(command.payload.threePhaseVoltageV) ||
|
!isPositiveFiniteNumber(command.payload.threePhaseVoltageV) ||
|
||||||
Object.keys(command.payload).length !== expectedKeyCount
|
Object.keys(command.payload).length !== 8
|
||||||
) {
|
) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Project settings update command contains invalid values."
|
"Project settings update command contains invalid values."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
command.schemaVersion === projectSettingsUpdateCommandSchemaVersion &&
|
|
||||||
!isValidSupplyTypes(
|
!isValidSupplyTypes(
|
||||||
command.payload.enabledDistributionBoardSupplyTypes
|
command.payload.enabledDistributionBoardSupplyTypes
|
||||||
)
|
)
|
||||||
@@ -114,28 +76,6 @@ export function assertProjectSettingsUpdateProjectCommand(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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(
|
function isValidSupplyTypes(
|
||||||
value: unknown
|
value: unknown
|
||||||
): value is DistributionBoardSupplyType[] {
|
): value is DistributionBoardSupplyType[] {
|
||||||
@@ -153,20 +93,6 @@ function isValidSupplyTypes(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function assertLegacyValues(
|
|
||||||
payload: Record<string, unknown>
|
|
||||||
): asserts payload is Record<string, unknown> & LegacyProjectSettingsValues {
|
|
||||||
if (
|
|
||||||
!isPositiveFiniteNumber(payload.singlePhaseVoltageV) ||
|
|
||||||
!isPositiveFiniteNumber(payload.threePhaseVoltageV) ||
|
|
||||||
Object.keys(payload).length !== 2
|
|
||||||
) {
|
|
||||||
throw new Error(
|
|
||||||
"Project settings update command contains invalid voltages."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function isPositiveFiniteNumber(value: unknown): value is number {
|
function isPositiveFiniteNumber(value: unknown): value is number {
|
||||||
return (
|
return (
|
||||||
typeof value === "number" &&
|
typeof value === "number" &&
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import {
|
import { distributionBoardSupplyTypes } from "../../shared/constants/distribution-board.js";
|
||||||
defaultDistributionBoardSupplyTypes,
|
|
||||||
distributionBoardSupplyTypes,
|
|
||||||
} from "../../shared/constants/distribution-board.js";
|
|
||||||
import {
|
import {
|
||||||
circuitGroupCategories,
|
circuitGroupCategories,
|
||||||
type CircuitGroupCategory,
|
|
||||||
} from "../../shared/constants/circuit-group.js";
|
} from "../../shared/constants/circuit-group.js";
|
||||||
import {
|
import {
|
||||||
distributionBoardComponentPlacements,
|
distributionBoardComponentPlacements,
|
||||||
@@ -21,61 +17,38 @@ import {
|
|||||||
import {
|
import {
|
||||||
resolveCircuitPhaseType,
|
resolveCircuitPhaseType,
|
||||||
resolveProjectVoltage,
|
resolveProjectVoltage,
|
||||||
normalizeKnownElectricalPhaseType,
|
|
||||||
} from "../services/project-voltage.service.js";
|
} from "../services/project-voltage.service.js";
|
||||||
|
|
||||||
export const legacyProjectStateSnapshotSchemaVersion = 1 as const;
|
export const projectStateSnapshotSchemaVersion = 1 as const;
|
||||||
export const previousProjectStateSnapshotSchemaVersion = 2 as const;
|
|
||||||
export const distributionBoardProjectStateSnapshotSchemaVersion = 3 as const;
|
|
||||||
export const supplyTypesProjectStateSnapshotSchemaVersion = 4 as const;
|
|
||||||
export const voltageProjectStateSnapshotSchemaVersion = 5 as const;
|
|
||||||
export const simultaneityFactorProjectStateSnapshotSchemaVersion = 6 as const;
|
|
||||||
export const projectStateSnapshotSchemaVersion = 7 as const;
|
|
||||||
|
|
||||||
const idSchema = z.string().trim().min(1);
|
const idSchema = z.string().trim().min(1);
|
||||||
const nullableStringSchema = z.string().nullable();
|
const nullableStringSchema = z.string().nullable();
|
||||||
const finiteNumberSchema = z.number().finite();
|
const finiteNumberSchema = z.number().finite();
|
||||||
|
|
||||||
const legacyProjectSchema = z
|
const projectSchema = z
|
||||||
.object({
|
.object({
|
||||||
id: idSchema,
|
id: idSchema,
|
||||||
name: z.string().trim().min(1),
|
name: z.string().trim().min(1),
|
||||||
|
internalProjectNumber: z.string().trim().min(1).max(100).nullable(),
|
||||||
|
externalProjectNumber: z.string().trim().min(1).max(100).nullable(),
|
||||||
|
buildingOwner: z.string().trim().min(1).max(200).nullable(),
|
||||||
|
description: z.string().trim().min(1).max(2000).nullable(),
|
||||||
singlePhaseVoltageV: finiteNumberSchema.positive(),
|
singlePhaseVoltageV: finiteNumberSchema.positive(),
|
||||||
threePhaseVoltageV: finiteNumberSchema.positive(),
|
threePhaseVoltageV: finiteNumberSchema.positive(),
|
||||||
|
enabledDistributionBoardSupplyTypes: z
|
||||||
|
.array(z.enum(distributionBoardSupplyTypes))
|
||||||
|
.min(1)
|
||||||
|
.refine((values) => new Set(values).size === values.length),
|
||||||
})
|
})
|
||||||
.strict();
|
.strict();
|
||||||
|
|
||||||
const projectSchema = legacyProjectSchema.extend({
|
const distributionBoardSchema = z
|
||||||
internalProjectNumber: z.string().trim().min(1).max(100).nullable(),
|
|
||||||
externalProjectNumber: z.string().trim().min(1).max(100).nullable(),
|
|
||||||
buildingOwner: z.string().trim().min(1).max(200).nullable(),
|
|
||||||
description: z.string().trim().min(1).max(2000).nullable(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const currentProjectSchema = projectSchema.extend({
|
|
||||||
enabledDistributionBoardSupplyTypes: z
|
|
||||||
.array(z.enum(distributionBoardSupplyTypes))
|
|
||||||
.min(1)
|
|
||||||
.refine((values) => new Set(values).size === values.length),
|
|
||||||
});
|
|
||||||
|
|
||||||
const legacyDistributionBoardSchema = z
|
|
||||||
.object({
|
.object({
|
||||||
id: idSchema,
|
id: idSchema,
|
||||||
projectId: idSchema,
|
projectId: idSchema,
|
||||||
name: z.string().trim().min(1),
|
name: z.string().trim().min(1),
|
||||||
})
|
|
||||||
.strict();
|
|
||||||
|
|
||||||
const distributionBoardSchema = legacyDistributionBoardSchema
|
|
||||||
.extend({
|
|
||||||
floorId: idSchema.nullable(),
|
floorId: idSchema.nullable(),
|
||||||
supplyType: z.enum(distributionBoardSupplyTypes).nullable(),
|
supplyType: z.enum(distributionBoardSupplyTypes).nullable(),
|
||||||
})
|
|
||||||
.strict();
|
|
||||||
|
|
||||||
const currentDistributionBoardSchema = distributionBoardSchema
|
|
||||||
.extend({
|
|
||||||
simultaneityFactor: finiteNumberSchema.min(0).max(1),
|
simultaneityFactor: finiteNumberSchema.min(0).max(1),
|
||||||
})
|
})
|
||||||
.strict();
|
.strict();
|
||||||
@@ -89,7 +62,7 @@ const circuitListSchema = z
|
|||||||
})
|
})
|
||||||
.strict();
|
.strict();
|
||||||
|
|
||||||
const legacyCircuitSectionSchema = z
|
const circuitSectionSchema = z
|
||||||
.object({
|
.object({
|
||||||
id: idSchema,
|
id: idSchema,
|
||||||
circuitListId: idSchema,
|
circuitListId: idSchema,
|
||||||
@@ -97,11 +70,6 @@ const legacyCircuitSectionSchema = z
|
|||||||
displayName: z.string().trim().min(1),
|
displayName: z.string().trim().min(1),
|
||||||
prefix: z.string().trim().min(1),
|
prefix: z.string().trim().min(1),
|
||||||
sortOrder: finiteNumberSchema,
|
sortOrder: finiteNumberSchema,
|
||||||
})
|
|
||||||
.strict();
|
|
||||||
|
|
||||||
const circuitSectionSchema = legacyCircuitSectionSchema
|
|
||||||
.extend({
|
|
||||||
category: z.enum(circuitGroupCategories).nullable(),
|
category: z.enum(circuitGroupCategories).nullable(),
|
||||||
groupNumber: z.number().int().positive().nullable(),
|
groupNumber: z.number().int().positive().nullable(),
|
||||||
})
|
})
|
||||||
@@ -121,11 +89,10 @@ const circuitDeviceRowSchema = z
|
|||||||
id: idSchema,
|
id: idSchema,
|
||||||
circuitId: idSchema,
|
circuitId: idSchema,
|
||||||
linkedProjectDeviceId: idSchema.nullable(),
|
linkedProjectDeviceId: idSchema.nullable(),
|
||||||
legacyConsumerId: z.string().nullable(),
|
|
||||||
sortOrder: finiteNumberSchema,
|
sortOrder: finiteNumberSchema,
|
||||||
name: z.string().trim().min(1),
|
name: z.string().trim().min(1),
|
||||||
displayName: z.string().trim().min(1),
|
displayName: z.string().trim().min(1),
|
||||||
phaseType: nullableStringSchema,
|
phaseType: z.enum(["single_phase", "three_phase"]).nullable(),
|
||||||
connectionKind: nullableStringSchema,
|
connectionKind: nullableStringSchema,
|
||||||
costGroup: nullableStringSchema,
|
costGroup: nullableStringSchema,
|
||||||
category: nullableStringSchema,
|
category: nullableStringSchema,
|
||||||
@@ -150,9 +117,6 @@ const circuitSchema = z
|
|||||||
equipmentIdentifier: z.string().trim().min(1),
|
equipmentIdentifier: z.string().trim().min(1),
|
||||||
displayName: nullableStringSchema,
|
displayName: nullableStringSchema,
|
||||||
sortOrder: finiteNumberSchema,
|
sortOrder: finiteNumberSchema,
|
||||||
protectionType: nullableStringSchema,
|
|
||||||
protectionRatedCurrent: finiteNumberSchema.nonnegative().nullable(),
|
|
||||||
protectionCharacteristic: nullableStringSchema,
|
|
||||||
cableType: nullableStringSchema,
|
cableType: nullableStringSchema,
|
||||||
cableCrossSection: nullableStringSchema,
|
cableCrossSection: nullableStringSchema,
|
||||||
cableLength: finiteNumberSchema.nonnegative().nullable(),
|
cableLength: finiteNumberSchema.nonnegative().nullable(),
|
||||||
@@ -280,18 +244,13 @@ const componentProtectionDeviceSchema = z
|
|||||||
.strict()
|
.strict()
|
||||||
.superRefine(validatePersistedProtectionDevice);
|
.superRefine(validatePersistedProtectionDevice);
|
||||||
|
|
||||||
const legacyProjectStateSnapshotContents = {
|
const projectStateSnapshotContents = {
|
||||||
circuitLists: z.array(circuitListSchema),
|
circuitLists: z.array(circuitListSchema),
|
||||||
circuitSections: z.array(legacyCircuitSectionSchema),
|
circuitSections: z.array(circuitSectionSchema),
|
||||||
circuits: z.array(circuitSchema),
|
circuits: z.array(circuitSchema),
|
||||||
projectDevices: z.array(projectDeviceSchema),
|
projectDevices: z.array(projectDeviceSchema),
|
||||||
floors: z.array(floorSchema),
|
floors: z.array(floorSchema),
|
||||||
rooms: z.array(roomSchema),
|
rooms: z.array(roomSchema),
|
||||||
};
|
|
||||||
|
|
||||||
const currentProjectStateSnapshotContents = {
|
|
||||||
...legacyProjectStateSnapshotContents,
|
|
||||||
circuitSections: z.array(circuitSectionSchema),
|
|
||||||
distributionBoardComponents: z.array(distributionBoardComponentSchema),
|
distributionBoardComponents: z.array(distributionBoardComponentSchema),
|
||||||
circuitProtectionDevices: z.array(circuitProtectionDeviceSchema),
|
circuitProtectionDevices: z.array(circuitProtectionDeviceSchema),
|
||||||
distributionBoardComponentProtectionDevices: z.array(
|
distributionBoardComponentProtectionDevices: z.array(
|
||||||
@@ -299,64 +258,14 @@ const currentProjectStateSnapshotContents = {
|
|||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
const legacyProjectStateSnapshotSchema = z
|
export const projectStateSnapshotSchema = z
|
||||||
.object({
|
.object({
|
||||||
schemaVersion: z.literal(legacyProjectStateSnapshotSchemaVersion),
|
|
||||||
project: legacyProjectSchema,
|
|
||||||
distributionBoards: z.array(legacyDistributionBoardSchema),
|
|
||||||
...legacyProjectStateSnapshotContents,
|
|
||||||
})
|
|
||||||
.strict();
|
|
||||||
|
|
||||||
const previousProjectStateSnapshotSchema = z
|
|
||||||
.object({
|
|
||||||
schemaVersion: z.literal(previousProjectStateSnapshotSchemaVersion),
|
|
||||||
project: projectSchema,
|
|
||||||
distributionBoards: z.array(legacyDistributionBoardSchema),
|
|
||||||
...legacyProjectStateSnapshotContents,
|
|
||||||
})
|
|
||||||
.strict();
|
|
||||||
|
|
||||||
const distributionBoardProjectStateSnapshotSchema = z
|
|
||||||
.object({
|
|
||||||
schemaVersion: z.literal(
|
|
||||||
distributionBoardProjectStateSnapshotSchemaVersion
|
|
||||||
),
|
|
||||||
project: projectSchema,
|
|
||||||
distributionBoards: z.array(distributionBoardSchema),
|
|
||||||
...legacyProjectStateSnapshotContents,
|
|
||||||
})
|
|
||||||
.strict();
|
|
||||||
|
|
||||||
const supplyTypesProjectStateSnapshotSchema = z
|
|
||||||
.object({
|
|
||||||
schemaVersion: z.literal(supplyTypesProjectStateSnapshotSchemaVersion),
|
|
||||||
project: currentProjectSchema,
|
|
||||||
distributionBoards: z.array(distributionBoardSchema),
|
|
||||||
...legacyProjectStateSnapshotContents,
|
|
||||||
})
|
|
||||||
.strict();
|
|
||||||
|
|
||||||
const voltageProjectStateSnapshotSchema =
|
|
||||||
supplyTypesProjectStateSnapshotSchema.extend({
|
|
||||||
schemaVersion: z.literal(
|
|
||||||
voltageProjectStateSnapshotSchemaVersion
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
const simultaneityFactorProjectStateSnapshotSchema =
|
|
||||||
voltageProjectStateSnapshotSchema.extend({
|
|
||||||
schemaVersion: z.literal(
|
|
||||||
simultaneityFactorProjectStateSnapshotSchemaVersion
|
|
||||||
),
|
|
||||||
distributionBoards: z.array(currentDistributionBoardSchema),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const projectStateSnapshotSchema =
|
|
||||||
simultaneityFactorProjectStateSnapshotSchema.extend({
|
|
||||||
schemaVersion: z.literal(projectStateSnapshotSchemaVersion),
|
schemaVersion: z.literal(projectStateSnapshotSchemaVersion),
|
||||||
...currentProjectStateSnapshotContents,
|
project: projectSchema,
|
||||||
});
|
distributionBoards: z.array(distributionBoardSchema),
|
||||||
|
...projectStateSnapshotContents,
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
export type ProjectStateSnapshot = z.infer<
|
export type ProjectStateSnapshot = z.infer<
|
||||||
typeof projectStateSnapshotSchema
|
typeof projectStateSnapshotSchema
|
||||||
@@ -365,68 +274,7 @@ export type ProjectStateSnapshot = z.infer<
|
|||||||
export function parseProjectStateSnapshot(
|
export function parseProjectStateSnapshot(
|
||||||
value: unknown
|
value: unknown
|
||||||
): ProjectStateSnapshot {
|
): ProjectStateSnapshot {
|
||||||
const version = isPlainObject(value) ? value.schemaVersion : undefined;
|
const parsedSnapshot = projectStateSnapshotSchema.parse(value);
|
||||||
const parsedSnapshot =
|
|
||||||
version === legacyProjectStateSnapshotSchemaVersion
|
|
||||||
? upgradeSimultaneityFactorProjectStateSnapshot(
|
|
||||||
upgradeVoltageProjectStateSnapshot(
|
|
||||||
upgradeSupplyTypesProjectStateSnapshot(
|
|
||||||
upgradeDistributionBoardProjectStateSnapshot(
|
|
||||||
upgradePreviousProjectStateSnapshot(
|
|
||||||
upgradeLegacyProjectStateSnapshot(
|
|
||||||
legacyProjectStateSnapshotSchema.parse(value)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
: version === previousProjectStateSnapshotSchemaVersion
|
|
||||||
? upgradeSimultaneityFactorProjectStateSnapshot(
|
|
||||||
upgradeVoltageProjectStateSnapshot(
|
|
||||||
upgradeSupplyTypesProjectStateSnapshot(
|
|
||||||
upgradeDistributionBoardProjectStateSnapshot(
|
|
||||||
upgradePreviousProjectStateSnapshot(
|
|
||||||
previousProjectStateSnapshotSchema.parse(value)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
: version === distributionBoardProjectStateSnapshotSchemaVersion
|
|
||||||
? upgradeSimultaneityFactorProjectStateSnapshot(
|
|
||||||
upgradeVoltageProjectStateSnapshot(
|
|
||||||
upgradeSupplyTypesProjectStateSnapshot(
|
|
||||||
upgradeDistributionBoardProjectStateSnapshot(
|
|
||||||
distributionBoardProjectStateSnapshotSchema.parse(
|
|
||||||
value
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
: version === supplyTypesProjectStateSnapshotSchemaVersion
|
|
||||||
? upgradeSimultaneityFactorProjectStateSnapshot(
|
|
||||||
upgradeVoltageProjectStateSnapshot(
|
|
||||||
upgradeSupplyTypesProjectStateSnapshot(
|
|
||||||
supplyTypesProjectStateSnapshotSchema.parse(value)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
: version === voltageProjectStateSnapshotSchemaVersion
|
|
||||||
? upgradeSimultaneityFactorProjectStateSnapshot(
|
|
||||||
upgradeVoltageProjectStateSnapshot(
|
|
||||||
voltageProjectStateSnapshotSchema.parse(value)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
: version ===
|
|
||||||
simultaneityFactorProjectStateSnapshotSchemaVersion
|
|
||||||
? upgradeSimultaneityFactorProjectStateSnapshot(
|
|
||||||
simultaneityFactorProjectStateSnapshotSchema.parse(
|
|
||||||
value
|
|
||||||
)
|
|
||||||
)
|
|
||||||
: projectStateSnapshotSchema.parse(value);
|
|
||||||
const snapshot = normalizeSnapshotPhaseTypes(parsedSnapshot);
|
const snapshot = normalizeSnapshotPhaseTypes(parsedSnapshot);
|
||||||
assertProjectStateSnapshotRelations(snapshot);
|
assertProjectStateSnapshotRelations(snapshot);
|
||||||
return snapshot;
|
return snapshot;
|
||||||
@@ -446,15 +294,13 @@ function normalizeSnapshotPhaseTypes(
|
|||||||
circuits: snapshot.circuits.map((circuit) => {
|
circuits: snapshot.circuits.map((circuit) => {
|
||||||
const section = sectionById.get(circuit.sectionId);
|
const section = sectionById.get(circuit.sectionId);
|
||||||
const deviceRows = circuit.deviceRows.map((row) => {
|
const deviceRows = circuit.deviceRows.map((row) => {
|
||||||
const normalizedPhaseType =
|
|
||||||
normalizeKnownElectricalPhaseType(row.phaseType);
|
|
||||||
const linkedPhaseType = row.linkedProjectDeviceId
|
const linkedPhaseType = row.linkedProjectDeviceId
|
||||||
? projectDeviceById.get(row.linkedProjectDeviceId)?.phaseType
|
? projectDeviceById.get(row.linkedProjectDeviceId)?.phaseType
|
||||||
: undefined;
|
: undefined;
|
||||||
return {
|
return {
|
||||||
...row,
|
...row,
|
||||||
phaseType:
|
phaseType:
|
||||||
normalizedPhaseType ??
|
row.phaseType ??
|
||||||
linkedPhaseType ??
|
linkedPhaseType ??
|
||||||
(section?.key === "three_phase"
|
(section?.key === "three_phase"
|
||||||
? "three_phase"
|
? "three_phase"
|
||||||
@@ -478,132 +324,6 @@ function normalizeSnapshotPhaseTypes(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function upgradeLegacyProjectStateSnapshot(
|
|
||||||
snapshot: z.infer<typeof legacyProjectStateSnapshotSchema>
|
|
||||||
): z.infer<typeof previousProjectStateSnapshotSchema> {
|
|
||||||
return {
|
|
||||||
...snapshot,
|
|
||||||
schemaVersion: previousProjectStateSnapshotSchemaVersion,
|
|
||||||
project: {
|
|
||||||
...snapshot.project,
|
|
||||||
internalProjectNumber: null,
|
|
||||||
externalProjectNumber: null,
|
|
||||||
buildingOwner: null,
|
|
||||||
description: null,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function upgradePreviousProjectStateSnapshot(
|
|
||||||
snapshot: z.infer<typeof previousProjectStateSnapshotSchema>
|
|
||||||
): z.infer<typeof distributionBoardProjectStateSnapshotSchema> {
|
|
||||||
return {
|
|
||||||
...snapshot,
|
|
||||||
schemaVersion: distributionBoardProjectStateSnapshotSchemaVersion,
|
|
||||||
distributionBoards: snapshot.distributionBoards.map((board) => ({
|
|
||||||
...board,
|
|
||||||
floorId: null,
|
|
||||||
supplyType: null,
|
|
||||||
})),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function upgradeDistributionBoardProjectStateSnapshot(
|
|
||||||
snapshot: z.infer<typeof distributionBoardProjectStateSnapshotSchema>
|
|
||||||
): z.infer<typeof supplyTypesProjectStateSnapshotSchema> {
|
|
||||||
return {
|
|
||||||
...snapshot,
|
|
||||||
schemaVersion: supplyTypesProjectStateSnapshotSchemaVersion,
|
|
||||||
project: {
|
|
||||||
...snapshot.project,
|
|
||||||
enabledDistributionBoardSupplyTypes: [
|
|
||||||
...defaultDistributionBoardSupplyTypes,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function upgradeSupplyTypesProjectStateSnapshot(
|
|
||||||
snapshot: z.infer<typeof supplyTypesProjectStateSnapshotSchema>
|
|
||||||
): z.infer<typeof voltageProjectStateSnapshotSchema> {
|
|
||||||
const settings = snapshot.project;
|
|
||||||
const sectionById = new Map(
|
|
||||||
snapshot.circuitSections.map((section) => [section.id, section])
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
...snapshot,
|
|
||||||
schemaVersion: voltageProjectStateSnapshotSchemaVersion,
|
|
||||||
projectDevices: snapshot.projectDevices.map((device) => ({
|
|
||||||
...device,
|
|
||||||
voltageV: resolveProjectVoltage(device.phaseType, settings),
|
|
||||||
})),
|
|
||||||
circuits: snapshot.circuits.map((circuit) => {
|
|
||||||
const section = sectionById.get(circuit.sectionId);
|
|
||||||
if (!section) {
|
|
||||||
return circuit;
|
|
||||||
}
|
|
||||||
const phaseType = resolveCircuitPhaseType(
|
|
||||||
section.key,
|
|
||||||
circuit.deviceRows.map((row) => row.phaseType)
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
...circuit,
|
|
||||||
voltage: resolveProjectVoltage(phaseType, settings),
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function upgradeVoltageProjectStateSnapshot(
|
|
||||||
snapshot: z.infer<typeof voltageProjectStateSnapshotSchema>
|
|
||||||
): z.infer<typeof simultaneityFactorProjectStateSnapshotSchema> {
|
|
||||||
return {
|
|
||||||
...snapshot,
|
|
||||||
schemaVersion: simultaneityFactorProjectStateSnapshotSchemaVersion,
|
|
||||||
distributionBoards: snapshot.distributionBoards.map((board) => ({
|
|
||||||
...board,
|
|
||||||
simultaneityFactor: 1,
|
|
||||||
})),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function upgradeSimultaneityFactorProjectStateSnapshot(
|
|
||||||
snapshot: z.infer<typeof simultaneityFactorProjectStateSnapshotSchema>
|
|
||||||
): ProjectStateSnapshot {
|
|
||||||
return {
|
|
||||||
...snapshot,
|
|
||||||
schemaVersion: projectStateSnapshotSchemaVersion,
|
|
||||||
circuitSections: snapshot.circuitSections.map((section) => {
|
|
||||||
const category = initialCategoryBySectionKey(section.key);
|
|
||||||
return {
|
|
||||||
...section,
|
|
||||||
category,
|
|
||||||
groupNumber: category === null ? null : 1,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
distributionBoardComponents: [],
|
|
||||||
circuitProtectionDevices: [],
|
|
||||||
distributionBoardComponentProtectionDevices: [],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function initialCategoryBySectionKey(
|
|
||||||
key: string
|
|
||||||
): CircuitGroupCategory | null {
|
|
||||||
if (
|
|
||||||
key === "lighting" ||
|
|
||||||
key === "single_phase" ||
|
|
||||||
key === "three_phase"
|
|
||||||
) {
|
|
||||||
return key;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
||||||
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function serializeProjectStateSnapshot(
|
export function serializeProjectStateSnapshot(
|
||||||
snapshot: ProjectStateSnapshot
|
snapshot: ProjectStateSnapshot
|
||||||
): string {
|
): string {
|
||||||
|
|||||||
@@ -156,7 +156,6 @@ export function remapProjectState(
|
|||||||
row.linkedProjectDeviceId === null
|
row.linkedProjectDeviceId === null
|
||||||
? null
|
? null
|
||||||
: requiredId(deviceIds, row.linkedProjectDeviceId),
|
: requiredId(deviceIds, row.linkedProjectDeviceId),
|
||||||
legacyConsumerId: null,
|
|
||||||
roomId:
|
roomId:
|
||||||
row.roomId === null ? null : requiredId(roomIds, row.roomId),
|
row.roomId === null ? null : requiredId(roomIds, row.roomId),
|
||||||
})),
|
})),
|
||||||
|
|||||||
@@ -1,143 +0,0 @@
|
|||||||
export interface LegacyConsumerMigrationCircuitListReader {
|
|
||||||
findById(
|
|
||||||
projectId: string,
|
|
||||||
circuitListId: string
|
|
||||||
): Promise<{ id: string } | null>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LegacyConsumerMigrationSection {
|
|
||||||
id: string;
|
|
||||||
key: string;
|
|
||||||
prefix: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LegacyConsumerMigrationSectionStore {
|
|
||||||
createDefaults(circuitListId: string): Promise<void>;
|
|
||||||
listByCircuitList(
|
|
||||||
circuitListId: string
|
|
||||||
): Promise<LegacyConsumerMigrationSection[]>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LegacyConsumerMigrationCircuitReader {
|
|
||||||
listByCircuitList(circuitListId: string): Promise<
|
|
||||||
Array<{
|
|
||||||
sectionId: string;
|
|
||||||
equipmentIdentifier: string;
|
|
||||||
sortOrder: number;
|
|
||||||
}>
|
|
||||||
>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LegacyConsumerMigrationRoomReader {
|
|
||||||
listByProject(projectId: string): Promise<
|
|
||||||
Array<{
|
|
||||||
id: string;
|
|
||||||
roomNumber: string;
|
|
||||||
roomName: string;
|
|
||||||
}>
|
|
||||||
>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LegacyConsumerSource {
|
|
||||||
id: string;
|
|
||||||
projectDeviceId: string | null;
|
|
||||||
roomId: string | null;
|
|
||||||
circuitNumber: string | null;
|
|
||||||
description: string | null;
|
|
||||||
name: string;
|
|
||||||
category: string | null;
|
|
||||||
deviceType: string | null;
|
|
||||||
phaseType: string | null;
|
|
||||||
tradeOrCostGroup: string | null;
|
|
||||||
protectionType: string | null;
|
|
||||||
protectionRatedCurrent: number | null;
|
|
||||||
protectionCharacteristic: string | null;
|
|
||||||
cableType: string | null;
|
|
||||||
cableCrossSection: string | null;
|
|
||||||
comment: string | null;
|
|
||||||
quantity: number;
|
|
||||||
installedPowerPerUnitKw: number;
|
|
||||||
demandFactor: number;
|
|
||||||
voltageV: number | null;
|
|
||||||
phaseCount: number | null;
|
|
||||||
powerFactor: number | null;
|
|
||||||
note: string | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LegacyMigrationDeviceRowInput {
|
|
||||||
linkedProjectDeviceId?: string;
|
|
||||||
legacyConsumerId: string;
|
|
||||||
sortOrder: number;
|
|
||||||
name: string;
|
|
||||||
displayName: string;
|
|
||||||
phaseType?: string;
|
|
||||||
connectionKind?: string;
|
|
||||||
costGroup?: string;
|
|
||||||
category?: string;
|
|
||||||
level?: string;
|
|
||||||
roomId?: string;
|
|
||||||
roomNumberSnapshot?: string;
|
|
||||||
roomNameSnapshot?: string;
|
|
||||||
quantity: number;
|
|
||||||
powerPerUnit: number;
|
|
||||||
simultaneityFactor: number;
|
|
||||||
cosPhi?: number;
|
|
||||||
remark?: string;
|
|
||||||
overriddenFields?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LegacyMigrationCircuitInput {
|
|
||||||
circuit: {
|
|
||||||
circuitListId: string;
|
|
||||||
sectionId: string;
|
|
||||||
equipmentIdentifier: string;
|
|
||||||
displayName?: string;
|
|
||||||
sortOrder: number;
|
|
||||||
protectionType?: string;
|
|
||||||
protectionRatedCurrent?: number;
|
|
||||||
protectionCharacteristic?: string;
|
|
||||||
cableType?: string;
|
|
||||||
cableCrossSection?: string;
|
|
||||||
cableLength?: number;
|
|
||||||
voltage?: number;
|
|
||||||
controlRequirement?: string;
|
|
||||||
remark?: string;
|
|
||||||
rcdAssignment?: string;
|
|
||||||
terminalDesignation?: string;
|
|
||||||
status?: string;
|
|
||||||
isReserve?: boolean;
|
|
||||||
};
|
|
||||||
deviceRows: LegacyMigrationDeviceRowInput[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LegacyMigrationReportInput {
|
|
||||||
legacyConsumerCount: number;
|
|
||||||
createdCircuitCount: number;
|
|
||||||
createdDeviceRowCount: number;
|
|
||||||
duplicateGroupedCount: number;
|
|
||||||
generatedIdentifierCount: number;
|
|
||||||
unassignedRowCount: number;
|
|
||||||
warningsJson: string;
|
|
||||||
generatedIdentifiersJson: string;
|
|
||||||
duplicateGroupsJson: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LegacyConsumerMigrationStore {
|
|
||||||
listSourceConsumersByCircuitList(
|
|
||||||
circuitListId: string
|
|
||||||
): Promise<LegacyConsumerSource[]>;
|
|
||||||
listMigratedConsumerIds(circuitListId: string): Promise<string[]>;
|
|
||||||
persistCircuitListMigration(input: {
|
|
||||||
circuitListId: string;
|
|
||||||
circuits: LegacyMigrationCircuitInput[];
|
|
||||||
report: LegacyMigrationReportInput;
|
|
||||||
}): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LegacyConsumerMigrationDependencies {
|
|
||||||
circuitListReader: LegacyConsumerMigrationCircuitListReader;
|
|
||||||
sectionStore: LegacyConsumerMigrationSectionStore;
|
|
||||||
circuitReader: LegacyConsumerMigrationCircuitReader;
|
|
||||||
roomReader: LegacyConsumerMigrationRoomReader;
|
|
||||||
migrationStore: LegacyConsumerMigrationStore;
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
export interface LegacyConsumerForPlanning {
|
|
||||||
id: string;
|
|
||||||
circuitNumber: string | null;
|
|
||||||
category: string | null;
|
|
||||||
phaseType: string | null;
|
|
||||||
phaseCount: number | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Accepts only normalized BMK-like legacy circuit numbers used for grouping.
|
|
||||||
export function normalizeCircuitNumber(value: string | null): string | null {
|
|
||||||
if (!value) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const trimmed = value.trim().toUpperCase();
|
|
||||||
if (!trimmed) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (!/^-\d+F\d+$/.test(trimmed)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return trimmed;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Best-effort fallback when no valid circuit number exists. Keeps migration deterministic
|
|
||||||
// by preferring explicit category/phase cues over random assignment.
|
|
||||||
export function inferSectionKeyFromLegacyInput(consumer: LegacyConsumerForPlanning): string | null {
|
|
||||||
const category = (consumer.category ?? "").toLowerCase();
|
|
||||||
if (category.includes("light") || category.includes("beleuchtung")) {
|
|
||||||
return "lighting";
|
|
||||||
}
|
|
||||||
if (consumer.phaseCount === 3) {
|
|
||||||
return "three_phase";
|
|
||||||
}
|
|
||||||
if (consumer.phaseCount === 1) {
|
|
||||||
return "single_phase";
|
|
||||||
}
|
|
||||||
|
|
||||||
const phaseType = (consumer.phaseType ?? "").toLowerCase();
|
|
||||||
if (phaseType.includes("three") || phaseType.includes("3")) {
|
|
||||||
return "three_phase";
|
|
||||||
}
|
|
||||||
if (phaseType.includes("single") || phaseType.includes("1")) {
|
|
||||||
return "single_phase";
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prefix-based section inference for already normalized equipment identifiers.
|
|
||||||
export function inferSectionKeyFromEquipmentIdentifier(equipmentIdentifier: string): string | null {
|
|
||||||
if (equipmentIdentifier.startsWith("-1F")) {
|
|
||||||
return "lighting";
|
|
||||||
}
|
|
||||||
if (equipmentIdentifier.startsWith("-2F")) {
|
|
||||||
return "single_phase";
|
|
||||||
}
|
|
||||||
if (equipmentIdentifier.startsWith("-3F")) {
|
|
||||||
return "three_phase";
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
@@ -1,269 +0,0 @@
|
|||||||
import type { LegacyMigrationReport } from "../models/circuit-tree.model.js";
|
|
||||||
import type {
|
|
||||||
LegacyConsumerMigrationDependencies,
|
|
||||||
LegacyConsumerSource,
|
|
||||||
LegacyMigrationCircuitInput,
|
|
||||||
} from "../ports/legacy-consumer-migration.store.js";
|
|
||||||
import {
|
|
||||||
inferSectionKeyFromEquipmentIdentifier,
|
|
||||||
inferSectionKeyFromLegacyInput,
|
|
||||||
normalizeCircuitNumber,
|
|
||||||
} from "./legacy-consumer-migration-planner.js";
|
|
||||||
|
|
||||||
function parseEquipmentSequence(
|
|
||||||
equipmentIdentifier: string,
|
|
||||||
prefix: string
|
|
||||||
): number | null {
|
|
||||||
if (!equipmentIdentifier.startsWith(prefix)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const suffix = equipmentIdentifier.slice(prefix.length);
|
|
||||||
if (!/^\d+$/.test(suffix)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return Number(suffix);
|
|
||||||
}
|
|
||||||
|
|
||||||
export class LegacyConsumerMigrationService {
|
|
||||||
constructor(
|
|
||||||
private readonly dependencies: LegacyConsumerMigrationDependencies
|
|
||||||
) {}
|
|
||||||
|
|
||||||
async migrateCircuitList(
|
|
||||||
projectId: string,
|
|
||||||
circuitListId: string
|
|
||||||
): Promise<LegacyMigrationReport> {
|
|
||||||
// Migration is additive: legacy consumers are preserved, and circuit-first entities are created
|
|
||||||
// with mapping records so transition remains auditable and reversible.
|
|
||||||
const list = await this.dependencies.circuitListReader.findById(
|
|
||||||
projectId,
|
|
||||||
circuitListId
|
|
||||||
);
|
|
||||||
if (!list) {
|
|
||||||
throw new Error("Circuit list not found in project.");
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.dependencies.sectionStore.createDefaults(circuitListId);
|
|
||||||
const sections =
|
|
||||||
await this.dependencies.sectionStore.listByCircuitList(circuitListId);
|
|
||||||
const sectionByKey = new Map(sections.map((section) => [section.key, section]));
|
|
||||||
const unassignedSection = sectionByKey.get("unassigned");
|
|
||||||
if (!unassignedSection) {
|
|
||||||
throw new Error("Unassigned section is required.");
|
|
||||||
}
|
|
||||||
|
|
||||||
const existingCircuits =
|
|
||||||
await this.dependencies.circuitReader.listByCircuitList(circuitListId);
|
|
||||||
const usedEquipmentIdentifiers = new Set(
|
|
||||||
existingCircuits.map((circuit) => circuit.equipmentIdentifier.toUpperCase())
|
|
||||||
);
|
|
||||||
|
|
||||||
const legacyConsumers =
|
|
||||||
await this.dependencies.migrationStore.listSourceConsumersByCircuitList(
|
|
||||||
circuitListId
|
|
||||||
);
|
|
||||||
const rooms = await this.dependencies.roomReader.listByProject(projectId);
|
|
||||||
const roomById = new Map(rooms.map((room) => [room.id, room]));
|
|
||||||
|
|
||||||
const report: LegacyMigrationReport = {
|
|
||||||
circuitListId,
|
|
||||||
legacyConsumerCount: legacyConsumers.length,
|
|
||||||
createdCircuitCount: 0,
|
|
||||||
createdDeviceRowCount: 0,
|
|
||||||
groupedDuplicateCircuitNumbers: [],
|
|
||||||
generatedIdentifiers: [],
|
|
||||||
unassignedRows: [],
|
|
||||||
warnings: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
// Idempotency guard: skip consumers already mapped in previous migration run.
|
|
||||||
const migratedConsumerIds = new Set(
|
|
||||||
await this.dependencies.migrationStore.listMigratedConsumerIds(
|
|
||||||
circuitListId
|
|
||||||
)
|
|
||||||
);
|
|
||||||
const consumersToMigrate = legacyConsumers.filter((consumer) => !migratedConsumerIds.has(consumer.id));
|
|
||||||
|
|
||||||
// Legacy rows are grouped by normalized circuit number so duplicates become
|
|
||||||
// multiple device rows within one circuit instead of duplicate circuits.
|
|
||||||
const byNormalizedCircuitNumber = new Map<string, LegacyConsumerSource[]>();
|
|
||||||
const withoutNormalizedCircuitNumber: LegacyConsumerSource[] = [];
|
|
||||||
for (const consumer of consumersToMigrate) {
|
|
||||||
const normalized = normalizeCircuitNumber(consumer.circuitNumber ?? null);
|
|
||||||
if (!normalized) {
|
|
||||||
withoutNormalizedCircuitNumber.push(consumer);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!byNormalizedCircuitNumber.has(normalized)) {
|
|
||||||
byNormalizedCircuitNumber.set(normalized, []);
|
|
||||||
}
|
|
||||||
byNormalizedCircuitNumber.get(normalized)!.push(consumer);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Duplicate normalized circuit numbers are expected and represented as one circuit
|
|
||||||
// with multiple circuit_device_rows.
|
|
||||||
report.groupedDuplicateCircuitNumbers = [...byNormalizedCircuitNumber.entries()]
|
|
||||||
.filter(([, grouped]) => grouped.length > 1)
|
|
||||||
.map(([normalizedCircuitNumber, grouped]) => ({ normalizedCircuitNumber, count: grouped.length }));
|
|
||||||
|
|
||||||
const groups: Array<{
|
|
||||||
equipmentIdentifier: string | null;
|
|
||||||
consumers: LegacyConsumerSource[];
|
|
||||||
inferredSectionKey: string | null;
|
|
||||||
isGeneratedIdentifier: boolean;
|
|
||||||
}> = [];
|
|
||||||
|
|
||||||
// Stable normalized circuit numbers keep existing intent where available.
|
|
||||||
for (const [normalizedCircuitNumber, grouped] of byNormalizedCircuitNumber.entries()) {
|
|
||||||
groups.push({
|
|
||||||
equipmentIdentifier: normalizedCircuitNumber,
|
|
||||||
consumers: grouped,
|
|
||||||
inferredSectionKey: inferSectionKeyFromEquipmentIdentifier(normalizedCircuitNumber),
|
|
||||||
isGeneratedIdentifier: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Missing/invalid circuit numbers are migrated as single-row groups with
|
|
||||||
// generated identifiers and best-effort section inference.
|
|
||||||
for (const consumer of withoutNormalizedCircuitNumber) {
|
|
||||||
groups.push({
|
|
||||||
equipmentIdentifier: null,
|
|
||||||
consumers: [consumer],
|
|
||||||
inferredSectionKey: inferSectionKeyFromLegacyInput(consumer),
|
|
||||||
isGeneratedIdentifier: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let nextSortOrder = existingCircuits.length ? Math.max(...existingCircuits.map((circuit) => circuit.sortOrder)) + 10 : 10;
|
|
||||||
|
|
||||||
const maxBySectionPrefix = new Map<string, number>();
|
|
||||||
for (const circuit of existingCircuits) {
|
|
||||||
const section = sections.find((entry) => entry.id === circuit.sectionId);
|
|
||||||
if (!section) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const sequence = parseEquipmentSequence(circuit.equipmentIdentifier.toUpperCase(), section.prefix.toUpperCase());
|
|
||||||
if (sequence === null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const current = maxBySectionPrefix.get(section.prefix.toUpperCase()) ?? 0;
|
|
||||||
maxBySectionPrefix.set(section.prefix.toUpperCase(), Math.max(current, sequence));
|
|
||||||
}
|
|
||||||
|
|
||||||
const migrationCircuits: LegacyMigrationCircuitInput[] = [];
|
|
||||||
for (const group of groups) {
|
|
||||||
const representative = group.consumers[0];
|
|
||||||
let section = group.inferredSectionKey ? sectionByKey.get(group.inferredSectionKey) : null;
|
|
||||||
if (!section) {
|
|
||||||
section = unassignedSection;
|
|
||||||
}
|
|
||||||
|
|
||||||
let equipmentIdentifier = group.equipmentIdentifier;
|
|
||||||
if (!equipmentIdentifier || usedEquipmentIdentifiers.has(equipmentIdentifier.toUpperCase())) {
|
|
||||||
const prefix = section.prefix.toUpperCase();
|
|
||||||
const current = maxBySectionPrefix.get(prefix) ?? 0;
|
|
||||||
const generatedSequence = current + 1;
|
|
||||||
maxBySectionPrefix.set(prefix, generatedSequence);
|
|
||||||
equipmentIdentifier = `${section.prefix}${generatedSequence}`;
|
|
||||||
report.generatedIdentifiers.push(equipmentIdentifier);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!group.inferredSectionKey && group.isGeneratedIdentifier) {
|
|
||||||
for (const consumer of group.consumers) {
|
|
||||||
report.unassignedRows.push({
|
|
||||||
consumerId: consumer.id,
|
|
||||||
reason: "Missing or invalid circuit number and no section inferred from phase/category.",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
usedEquipmentIdentifiers.add(equipmentIdentifier.toUpperCase());
|
|
||||||
|
|
||||||
const deviceRows: LegacyMigrationCircuitInput["deviceRows"] = [];
|
|
||||||
const circuitSortOrder = nextSortOrder;
|
|
||||||
nextSortOrder += 10;
|
|
||||||
|
|
||||||
let rowSortOrder = 10;
|
|
||||||
for (const consumer of group.consumers) {
|
|
||||||
const room = consumer.roomId ? roomById.get(consumer.roomId) : undefined;
|
|
||||||
let noteRemark = consumer.note?.trim() || consumer.comment?.trim() || undefined;
|
|
||||||
if (consumer.deviceType && consumer.deviceType.trim()) {
|
|
||||||
const legacyDeviceTypeNote = `Legacy deviceType: ${consumer.deviceType.trim()}`;
|
|
||||||
noteRemark = noteRemark ? `${noteRemark} | ${legacyDeviceTypeNote}` : legacyDeviceTypeNote;
|
|
||||||
}
|
|
||||||
|
|
||||||
deviceRows.push({
|
|
||||||
linkedProjectDeviceId: consumer.projectDeviceId ?? undefined,
|
|
||||||
legacyConsumerId: consumer.id,
|
|
||||||
sortOrder: rowSortOrder,
|
|
||||||
name: consumer.name,
|
|
||||||
displayName: consumer.description ?? consumer.name,
|
|
||||||
phaseType: consumer.phaseType ?? undefined,
|
|
||||||
connectionKind: undefined,
|
|
||||||
costGroup: consumer.tradeOrCostGroup ?? undefined,
|
|
||||||
category: consumer.category ?? undefined,
|
|
||||||
roomId: consumer.roomId ?? undefined,
|
|
||||||
roomNumberSnapshot: room?.roomNumber,
|
|
||||||
roomNameSnapshot: room?.roomName,
|
|
||||||
quantity: consumer.quantity,
|
|
||||||
powerPerUnit: consumer.installedPowerPerUnitKw,
|
|
||||||
simultaneityFactor: consumer.demandFactor,
|
|
||||||
cosPhi: consumer.powerFactor ?? undefined,
|
|
||||||
remark: noteRemark,
|
|
||||||
});
|
|
||||||
rowSortOrder += 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
migrationCircuits.push({
|
|
||||||
circuit: {
|
|
||||||
circuitListId,
|
|
||||||
sectionId: section.id,
|
|
||||||
equipmentIdentifier,
|
|
||||||
displayName: representative.description ?? representative.name,
|
|
||||||
sortOrder: circuitSortOrder,
|
|
||||||
protectionType: representative.protectionType ?? undefined,
|
|
||||||
protectionRatedCurrent: representative.protectionRatedCurrent ?? undefined,
|
|
||||||
protectionCharacteristic: representative.protectionCharacteristic ?? undefined,
|
|
||||||
cableType: representative.cableType ?? undefined,
|
|
||||||
cableCrossSection: representative.cableCrossSection ?? undefined,
|
|
||||||
voltage: representative.voltageV ?? undefined,
|
|
||||||
remark: undefined,
|
|
||||||
},
|
|
||||||
deviceRows,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
report.createdCircuitCount = migrationCircuits.length;
|
|
||||||
report.createdDeviceRowCount = migrationCircuits.reduce(
|
|
||||||
(count, entry) => count + entry.deviceRows.length,
|
|
||||||
0
|
|
||||||
);
|
|
||||||
|
|
||||||
if (consumersToMigrate.some((consumer) => Boolean(consumer.comment?.trim()))) {
|
|
||||||
report.warnings.push(
|
|
||||||
"Legacy comment field was mapped to circuit_device_rows.remark because circuit-level vs row-level intent is ambiguous."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const row of report.unassignedRows) {
|
|
||||||
report.warnings.push(`Consumer ${row.consumerId} was migrated into unassigned section.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.dependencies.migrationStore.persistCircuitListMigration({
|
|
||||||
circuitListId,
|
|
||||||
circuits: migrationCircuits,
|
|
||||||
report: {
|
|
||||||
legacyConsumerCount: report.legacyConsumerCount,
|
|
||||||
createdCircuitCount: report.createdCircuitCount,
|
|
||||||
createdDeviceRowCount: report.createdDeviceRowCount,
|
|
||||||
duplicateGroupedCount: report.groupedDuplicateCircuitNumbers.length,
|
|
||||||
generatedIdentifierCount: report.generatedIdentifiers.length,
|
|
||||||
unassignedRowCount: report.unassignedRows.length,
|
|
||||||
warningsJson: JSON.stringify(report.warnings),
|
|
||||||
generatedIdentifiersJson: JSON.stringify(report.generatedIdentifiers),
|
|
||||||
duplicateGroupsJson: JSON.stringify(report.groupedDuplicateCircuitNumbers),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return report;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -6,34 +6,6 @@ export function isElectricalPhaseType(
|
|||||||
return value === "single_phase" || value === "three_phase";
|
return value === "single_phase" || value === "three_phase";
|
||||||
}
|
}
|
||||||
|
|
||||||
export function normalizeKnownElectricalPhaseType(
|
|
||||||
value: string | null
|
|
||||||
): ElectricalPhaseType | null {
|
|
||||||
if (value === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const normalized = value
|
|
||||||
.trim()
|
|
||||||
.toLocaleLowerCase("de-DE")
|
|
||||||
.replaceAll("-", "")
|
|
||||||
.replaceAll("_", "");
|
|
||||||
if (
|
|
||||||
["1ph", "1phase", "1phasig", "einphasig", "singlephase"].includes(
|
|
||||||
normalized
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return "single_phase";
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
["3ph", "3phase", "3phasig", "dreiphasig", "threephase"].includes(
|
|
||||||
normalized
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return "three_phase";
|
|
||||||
}
|
|
||||||
throw new Error(`Unknown electrical phase type: ${value}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ProjectVoltageSettings {
|
export interface ProjectVoltageSettings {
|
||||||
singlePhaseVoltageV: number;
|
singlePhaseVoltageV: number;
|
||||||
threePhaseVoltageV: number;
|
threePhaseVoltageV: number;
|
||||||
|
|||||||
@@ -30,10 +30,8 @@ import {
|
|||||||
formatPhaseTypeLabel,
|
formatPhaseTypeLabel,
|
||||||
formatValue,
|
formatValue,
|
||||||
getCircuitSectionLabel,
|
getCircuitSectionLabel,
|
||||||
getLegacyProjectColumnLayoutStorageKey,
|
|
||||||
getProjectColumnLayoutStorageKey,
|
getProjectColumnLayoutStorageKey,
|
||||||
isGridEditorControlTarget,
|
isGridEditorControlTarget,
|
||||||
migrateLegacyColumnLayout,
|
|
||||||
normalizeColumnOrder,
|
normalizeColumnOrder,
|
||||||
parseStoredColumnLayout,
|
parseStoredColumnLayout,
|
||||||
} from "../utils/circuit-grid-model";
|
} from "../utils/circuit-grid-model";
|
||||||
@@ -191,9 +189,6 @@ type CircuitReorderDropIntent =
|
|||||||
| { kind: "after-circuit"; sectionId: string; targetCircuitId: string; valid: boolean }
|
| { kind: "after-circuit"; sectionId: string; targetCircuitId: string; valid: boolean }
|
||||||
| { kind: "section-end"; sectionId: string; valid: boolean };
|
| { kind: "section-end"; sectionId: string; valid: boolean };
|
||||||
|
|
||||||
const LEGACY_COLUMN_LAYOUT_STORAGE_KEY =
|
|
||||||
"circuitTreeEditor.columnLayout.v1";
|
|
||||||
|
|
||||||
function normalizeUiError(err: unknown): string {
|
function normalizeUiError(err: unknown): string {
|
||||||
const message = err instanceof Error ? err.message : "Der Vorgang ist fehlgeschlagen.";
|
const message = err instanceof Error ? err.message : "Der Vorgang ist fehlgeschlagen.";
|
||||||
try {
|
try {
|
||||||
@@ -379,25 +374,11 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
|||||||
const currentLayout = parseStoredColumnLayout(
|
const currentLayout = parseStoredColumnLayout(
|
||||||
localStorage.getItem(getProjectColumnLayoutStorageKey(projectId))
|
localStorage.getItem(getProjectColumnLayoutStorageKey(projectId))
|
||||||
);
|
);
|
||||||
const legacyLayout =
|
if (!currentLayout) {
|
||||||
parseStoredColumnLayout(
|
|
||||||
localStorage.getItem(
|
|
||||||
getLegacyProjectColumnLayoutStorageKey(projectId)
|
|
||||||
)
|
|
||||||
) ??
|
|
||||||
parseStoredColumnLayout(
|
|
||||||
localStorage.getItem(LEGACY_COLUMN_LAYOUT_STORAGE_KEY)
|
|
||||||
);
|
|
||||||
const parsed =
|
|
||||||
currentLayout ??
|
|
||||||
(legacyLayout
|
|
||||||
? migrateLegacyColumnLayout(legacyLayout)
|
|
||||||
: null);
|
|
||||||
if (!parsed) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setColumnOrder(parsed.order);
|
setColumnOrder(currentLayout.order);
|
||||||
setVisibleColumnKeys(parsed.visible);
|
setVisibleColumnKeys(currentLayout.visible);
|
||||||
} catch {
|
} catch {
|
||||||
// ignore invalid local storage and use defaults
|
// ignore invalid local storage and use defaults
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -114,11 +114,11 @@ function SectionRows(props: { section: CircuitTreeResponseDto["sections"][number
|
|||||||
<td className="text-end">
|
<td className="text-end">
|
||||||
{formatValue(circuit.circuitTotalPower, "circuitTotalPower")}
|
{formatValue(circuit.circuitTotalPower, "circuitTotalPower")}
|
||||||
</td>
|
</td>
|
||||||
<td>{circuit.protectionType ?? "-"}</td>
|
<td>{circuit.protectionDevice?.type ?? "-"}</td>
|
||||||
<td className="text-end">
|
<td className="text-end">
|
||||||
{formatValue(circuit.protectionRatedCurrent, "protectionRatedCurrent")}
|
{formatValue(circuit.protectionDevice?.ratedCurrentA, "protectionRatedCurrent")}
|
||||||
</td>
|
</td>
|
||||||
<td>{circuit.protectionCharacteristic ?? "-"}</td>
|
<td>{circuit.protectionDevice?.tripCharacteristic ?? circuit.protectionDevice?.fuseUtilizationCategory ?? "-"}</td>
|
||||||
<td>{circuit.cableType ?? "-"}</td>
|
<td>{circuit.cableType ?? "-"}</td>
|
||||||
<td>{circuit.cableCrossSection ?? "-"}</td>
|
<td>{circuit.cableCrossSection ?? "-"}</td>
|
||||||
<td className="text-end">{formatValue(circuit.cableLength, "cableLength")}</td>
|
<td className="text-end">{formatValue(circuit.cableLength, "cableLength")}</td>
|
||||||
@@ -147,11 +147,11 @@ function SectionRows(props: { section: CircuitTreeResponseDto["sections"][number
|
|||||||
</td>
|
</td>
|
||||||
<td className="text-end">{formatValue(row.cosPhi, "cosPhi")}</td>
|
<td className="text-end">{formatValue(row.cosPhi, "cosPhi")}</td>
|
||||||
<td className="text-end">{formatValue(row.rowTotalPower, "rowTotalPower")}</td>
|
<td className="text-end">{formatValue(row.rowTotalPower, "rowTotalPower")}</td>
|
||||||
<td>{circuit.protectionType ?? "-"}</td>
|
<td>{circuit.protectionDevice?.type ?? "-"}</td>
|
||||||
<td className="text-end">
|
<td className="text-end">
|
||||||
{formatValue(circuit.protectionRatedCurrent, "protectionRatedCurrent")}
|
{formatValue(circuit.protectionDevice?.ratedCurrentA, "protectionRatedCurrent")}
|
||||||
</td>
|
</td>
|
||||||
<td>{circuit.protectionCharacteristic ?? "-"}</td>
|
<td>{circuit.protectionDevice?.tripCharacteristic ?? circuit.protectionDevice?.fuseUtilizationCategory ?? "-"}</td>
|
||||||
<td>{circuit.cableType ?? "-"}</td>
|
<td>{circuit.cableType ?? "-"}</td>
|
||||||
<td>{circuit.cableCrossSection ?? "-"}</td>
|
<td>{circuit.cableCrossSection ?? "-"}</td>
|
||||||
<td className="text-end">{formatValue(circuit.cableLength, "cableLength")}</td>
|
<td className="text-end">{formatValue(circuit.cableLength, "cableLength")}</td>
|
||||||
@@ -169,11 +169,11 @@ function SectionRows(props: { section: CircuitTreeResponseDto["sections"][number
|
|||||||
<td className="text-end">
|
<td className="text-end">
|
||||||
{formatValue(circuit.circuitTotalPower, "circuitTotalPower")}
|
{formatValue(circuit.circuitTotalPower, "circuitTotalPower")}
|
||||||
</td>
|
</td>
|
||||||
<td>{circuit.protectionType ?? "-"}</td>
|
<td>{circuit.protectionDevice?.type ?? "-"}</td>
|
||||||
<td className="text-end">
|
<td className="text-end">
|
||||||
{formatValue(circuit.protectionRatedCurrent, "protectionRatedCurrent")}
|
{formatValue(circuit.protectionDevice?.ratedCurrentA, "protectionRatedCurrent")}
|
||||||
</td>
|
</td>
|
||||||
<td>{circuit.protectionCharacteristic ?? "-"}</td>
|
<td>{circuit.protectionDevice?.tripCharacteristic ?? circuit.protectionDevice?.fuseUtilizationCategory ?? "-"}</td>
|
||||||
<td>{circuit.cableType ?? "-"}</td>
|
<td>{circuit.cableType ?? "-"}</td>
|
||||||
<td>{circuit.cableCrossSection ?? "-"}</td>
|
<td>{circuit.cableCrossSection ?? "-"}</td>
|
||||||
<td className="text-end">{formatValue(circuit.cableLength, "cableLength")}</td>
|
<td className="text-end">{formatValue(circuit.cableLength, "cableLength")}</td>
|
||||||
|
|||||||
@@ -267,7 +267,6 @@ export interface ProjectDeviceSyncCommandResultDto
|
|||||||
export interface CircuitTreeDeviceRowDto {
|
export interface CircuitTreeDeviceRowDto {
|
||||||
id: string;
|
id: string;
|
||||||
linkedProjectDeviceId?: string;
|
linkedProjectDeviceId?: string;
|
||||||
legacyConsumerId?: string;
|
|
||||||
sortOrder: number;
|
sortOrder: number;
|
||||||
name: string;
|
name: string;
|
||||||
displayName: string;
|
displayName: string;
|
||||||
@@ -316,9 +315,6 @@ export interface CircuitTreeCircuitDto {
|
|||||||
equipmentIdentifier: string;
|
equipmentIdentifier: string;
|
||||||
displayName?: string;
|
displayName?: string;
|
||||||
sortOrder: number;
|
sortOrder: number;
|
||||||
protectionType?: string;
|
|
||||||
protectionRatedCurrent?: number;
|
|
||||||
protectionCharacteristic?: string;
|
|
||||||
cableType?: string;
|
cableType?: string;
|
||||||
cableCrossSection?: string;
|
cableCrossSection?: string;
|
||||||
cableLength?: number;
|
cableLength?: number;
|
||||||
@@ -347,17 +343,6 @@ export interface CircuitTreeSectionDto {
|
|||||||
circuits: CircuitTreeCircuitDto[];
|
circuits: CircuitTreeCircuitDto[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CircuitTreeMigrationReportDto {
|
|
||||||
circuitListId: string;
|
|
||||||
legacyConsumerCount: number;
|
|
||||||
createdCircuitCount: number;
|
|
||||||
createdDeviceRowCount: number;
|
|
||||||
groupedDuplicateCircuitNumbers: Array<{ normalizedCircuitNumber: string; count: number }>;
|
|
||||||
generatedIdentifiers: string[];
|
|
||||||
unassignedRows: Array<{ consumerId: string; reason: string }>;
|
|
||||||
warnings: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CircuitTreeResponseDto {
|
export interface CircuitTreeResponseDto {
|
||||||
circuitListId: string;
|
circuitListId: string;
|
||||||
currentRevision: number;
|
currentRevision: number;
|
||||||
@@ -369,7 +354,6 @@ export interface CircuitTreeResponseDto {
|
|||||||
headerComponents: CircuitTreeComponentDto[];
|
headerComponents: CircuitTreeComponentDto[];
|
||||||
sections: CircuitTreeSectionDto[];
|
sections: CircuitTreeSectionDto[];
|
||||||
footerComponents: CircuitTreeComponentDto[];
|
footerComponents: CircuitTreeComponentDto[];
|
||||||
migrationReport?: CircuitTreeMigrationReportDto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CreateCircuitInputDto {
|
export interface CreateCircuitInputDto {
|
||||||
@@ -377,9 +361,6 @@ export interface CreateCircuitInputDto {
|
|||||||
equipmentIdentifier: string;
|
equipmentIdentifier: string;
|
||||||
displayName?: string;
|
displayName?: string;
|
||||||
sortOrder: number;
|
sortOrder: number;
|
||||||
protectionType?: string;
|
|
||||||
protectionRatedCurrent?: number;
|
|
||||||
protectionCharacteristic?: string;
|
|
||||||
cableType?: string;
|
cableType?: string;
|
||||||
cableCrossSection?: string;
|
cableCrossSection?: string;
|
||||||
cableLength?: number;
|
cableLength?: number;
|
||||||
|
|||||||
@@ -130,8 +130,6 @@ export const defaultVisibleColumnKeys = allColumns
|
|||||||
|
|
||||||
const projectColumnLayoutStoragePrefix =
|
const projectColumnLayoutStoragePrefix =
|
||||||
"circuitTreeEditor.columnLayout.v3";
|
"circuitTreeEditor.columnLayout.v3";
|
||||||
const legacyProjectColumnLayoutStoragePrefix =
|
|
||||||
"circuitTreeEditor.columnLayout.v2";
|
|
||||||
|
|
||||||
export interface CircuitColumnLayout {
|
export interface CircuitColumnLayout {
|
||||||
order: CellKey[];
|
order: CellKey[];
|
||||||
@@ -164,12 +162,6 @@ export function getProjectColumnLayoutStorageKey(
|
|||||||
return `${projectColumnLayoutStoragePrefix}.${projectId}`;
|
return `${projectColumnLayoutStoragePrefix}.${projectId}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getLegacyProjectColumnLayoutStorageKey(
|
|
||||||
projectId: string
|
|
||||||
): string {
|
|
||||||
return `${legacyProjectColumnLayoutStoragePrefix}.${projectId}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function normalizeColumnOrder(keys: CellKey[]): CellKey[] {
|
export function normalizeColumnOrder(keys: CellKey[]): CellKey[] {
|
||||||
const unique = [...new Set(keys)];
|
const unique = [...new Set(keys)];
|
||||||
const allKeys = allColumns.map((column) => column.key);
|
const allKeys = allColumns.map((column) => column.key);
|
||||||
@@ -233,28 +225,6 @@ export function parseStoredColumnLayout(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function migrateLegacyColumnLayout(
|
|
||||||
layout: CircuitColumnLayout
|
|
||||||
): CircuitColumnLayout {
|
|
||||||
const newlyVisibleKey: CellKey = "protectionSummary";
|
|
||||||
const order = layout.order.filter(
|
|
||||||
(key) => key !== newlyVisibleKey
|
|
||||||
) as CellKey[];
|
|
||||||
const totalPowerIndex = order.indexOf("rowTotalPower");
|
|
||||||
order.splice(
|
|
||||||
totalPowerIndex >= 0 ? totalPowerIndex + 1 : order.length,
|
|
||||||
0,
|
|
||||||
newlyVisibleKey
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
order,
|
|
||||||
visible: layout.visible.includes(newlyVisibleKey)
|
|
||||||
? layout.visible
|
|
||||||
: [...layout.visible, newlyVisibleKey],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const deviceOnlyColumns = new Set<CellKey>([
|
const deviceOnlyColumns = new Set<CellKey>([
|
||||||
"quantity",
|
"quantity",
|
||||||
"powerPerUnit",
|
"powerPerUnit",
|
||||||
@@ -422,11 +392,7 @@ export function buildCircuitEditPatch(
|
|||||||
if (key === "protectionSummary" || key === "cableSummary") {
|
if (key === "protectionSummary" || key === "cableSummary") {
|
||||||
return { remark: draft.trim() || null };
|
return { remark: draft.trim() || null };
|
||||||
}
|
}
|
||||||
if (
|
if (key === "cableLength" || key === "voltage") {
|
||||||
key === "protectionRatedCurrent" ||
|
|
||||||
key === "cableLength" ||
|
|
||||||
key === "voltage"
|
|
||||||
) {
|
|
||||||
return { [key]: parseNumeric(key, draft) ?? null };
|
return { [key]: parseNumeric(key, draft) ?? null };
|
||||||
}
|
}
|
||||||
if (key === "isReserve") {
|
if (key === "isReserve") {
|
||||||
@@ -530,23 +496,14 @@ export function getCircuitValue(circuit: CircuitTreeCircuitDto, key: CellKey): G
|
|||||||
case "protectionType":
|
case "protectionType":
|
||||||
return protection
|
return protection
|
||||||
? protectionDeviceTypeLabels[protection.type]
|
? protectionDeviceTypeLabels[protection.type]
|
||||||
: circuit.protectionType;
|
: undefined;
|
||||||
case "protectionRatedCurrent":
|
case "protectionRatedCurrent":
|
||||||
return protection?.ratedCurrentA ?? circuit.protectionRatedCurrent;
|
return protection?.ratedCurrentA;
|
||||||
case "protectionCharacteristic":
|
case "protectionCharacteristic":
|
||||||
return protectionCharacteristic || circuit.protectionCharacteristic;
|
return protectionCharacteristic || undefined;
|
||||||
case "protectionSummary": {
|
case "protectionSummary": {
|
||||||
if (!protection) {
|
if (!protection) {
|
||||||
const legacyCurrent =
|
return undefined;
|
||||||
circuit.protectionRatedCurrent !== undefined &&
|
|
||||||
circuit.protectionRatedCurrent !== null
|
|
||||||
? `${circuit.protectionRatedCurrent}A`
|
|
||||||
: "";
|
|
||||||
return [
|
|
||||||
circuit.protectionType,
|
|
||||||
legacyCurrent,
|
|
||||||
circuit.protectionCharacteristic,
|
|
||||||
].filter(Boolean).join(" ").trim() || undefined;
|
|
||||||
}
|
}
|
||||||
const currentValue =
|
const currentValue =
|
||||||
protection.ratedCurrentA;
|
protection.ratedCurrentA;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user