Create protected board defaults
This commit is contained in:
@@ -294,10 +294,13 @@ 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` with a complete
|
Distribution-board setup uses `distribution-board.insert` schema version 3
|
||||||
stable snapshot of the board, circuit list and four default sections. Its
|
with a complete stable snapshot of the board, circuit list, three default
|
||||||
|
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.
|
||||||
Distribution-board floor assignment and a project-enabled supply type use
|
Distribution-board floor assignment and a project-enabled supply type use
|
||||||
`distribution-board.update`; both values are snapshot/export fields and one
|
`distribution-board.update`; both values are snapshot/export fields and one
|
||||||
persistent undo step.
|
persistent undo step.
|
||||||
|
|||||||
@@ -194,11 +194,14 @@ returns HTTP `409` with `PROJECT_HISTORY_OPERATION_UNAVAILABLE`.
|
|||||||
- `supplyType` is one of `AV`, `SV`, `EV`, `USV`, `MSR` or `SiBe` and must
|
- `supplyType` is one of `AV`, `SV`, `EV`, `USV`, `MSR` or `SiBe` and must
|
||||||
be enabled in the project settings
|
be enabled in the project settings
|
||||||
- executes `distribution-board.insert` with stable ids for the distribution
|
- executes `distribution-board.insert` with stable ids for the distribution
|
||||||
board, its circuit list and all four default circuit sections
|
board, its circuit list, the three default circuit groups, main switch
|
||||||
|
`-Q0` and surge protective device `-FA`
|
||||||
- response:
|
- response:
|
||||||
`{ "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:
|
||||||
|
|||||||
@@ -271,8 +271,10 @@ Projektübersicht verwendet dafür den separaten Collection-Endpunkt
|
|||||||
deshalb weder eine bestehende Projekt-ID noch `expectedRevision` annimmt. Das
|
deshalb weder eine bestehende Projekt-ID noch `expectedRevision` annimmt. Das
|
||||||
Ersetzen eines Projekts bleibt auf dessen Einstellungsmodal und den
|
Ersetzen eines Projekts bleibt auf dessen Einstellungsmodal und den
|
||||||
projektgebundenen Endpunkt beschränkt.
|
projektgebundenen Endpunkt beschränkt.
|
||||||
`distribution-board.insert` versioniert die Anlage einer Verteilung als einen
|
`distribution-board.insert` versioniert die Anlage einer Verteilung mit
|
||||||
vollständigen Block aus Verteilung, Stromkreisliste und vier Standardbereichen.
|
Command-Schema 3 als vollständigen Block aus Verteilung, Stromkreisliste, den
|
||||||
|
drei Gruppen `Beleuchtung 1`, `1-phasig 1`, `3-phasig 1`, Hauptschalter `-Q0`
|
||||||
|
und Überspannungsableiter `-FA`.
|
||||||
Alle UUIDs entstehen vor dem Command und bleiben über Undo/Redo stabil.
|
Alle UUIDs entstehen vor dem Command und bleiben über Undo/Redo stabil.
|
||||||
`distribution-board.delete` ist die persistierte Inverse und entfernt nur den
|
`distribution-board.delete` ist die persistierte Inverse und entfernt nur den
|
||||||
vollständig unveränderten, weiterhin stromkreislosen Block. Controller und
|
vollständig unveränderten, weiterhin stromkreislosen Block. Controller und
|
||||||
@@ -281,6 +283,8 @@ 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.
|
||||||
`distribution-board.update` versioniert Etage, Netzart und den
|
`distribution-board.update` versioniert Etage, Netzart und den
|
||||||
verteilerweiten Gleichzeitigkeitsfaktor gemeinsam und stellt alle Werte über
|
verteilerweiten Gleichzeitigkeitsfaktor gemeinsam und stellt alle Werte über
|
||||||
dauerhaftes Undo/Redo wieder her. Der Faktor liegt zwischen `0` und `1` und
|
dauerhaftes Undo/Redo wieder her. Der Faktor liegt zwischen `0` und `1` und
|
||||||
|
|||||||
@@ -18,7 +18,9 @@ requirements and intended sequencing, not proof of implementation.
|
|||||||
tables and shared BMK uniqueness.
|
tables and shared BMK uniqueness.
|
||||||
- [x] Phase B2: snapshot/transfer schema upgrade and deterministic compatibility
|
- [x] Phase B2: snapshot/transfer schema upgrade and deterministic compatibility
|
||||||
mapping.
|
mapping.
|
||||||
- [ ] Phase C: persistent commands and new-board defaults.
|
- [x] Phase C1: versioned new-board command with three groups and fixed header
|
||||||
|
components.
|
||||||
|
- [ ] Phase C2: component and group CRUD/reorder commands.
|
||||||
- [ ] Phase D: group numbering, moves and destructive operations.
|
- [ ] Phase D: group numbering, moves and destructive operations.
|
||||||
- [ ] Phase E: editor projection and editing.
|
- [ ] Phase E: editor projection and editing.
|
||||||
- [ ] Phase F: documentation and full GUI verification.
|
- [ ] Phase F: documentation and full GUI verification.
|
||||||
|
|||||||
@@ -593,11 +593,25 @@ Acceptance:
|
|||||||
|
|
||||||
### C. Persistent Commands and Board Defaults
|
### C. Persistent Commands and Board Defaults
|
||||||
|
|
||||||
|
Status: In progress. New-board defaults C1 are complete; component and group
|
||||||
|
management commands C2 are pending.
|
||||||
|
|
||||||
- extend `distribution-board.insert` with fixed components and three groups
|
- extend `distribution-board.insert` with fixed components and three groups
|
||||||
- implement component and group CRUD commands
|
- implement component and group CRUD commands
|
||||||
- implement group and footer reordering
|
- implement group and footer reordering
|
||||||
- add complete inverse snapshots and rollback tests
|
- add complete inverse snapshots and rollback tests
|
||||||
|
|
||||||
|
Implemented in C1:
|
||||||
|
|
||||||
|
- `distribution-board.insert` schema version 3 carries stable UUIDs for three
|
||||||
|
initial groups, main switch `-Q0` and surge protective device `-FA`
|
||||||
|
- its inverse verifies and removes the unchanged complete structure; Redo
|
||||||
|
restores the same UUIDs
|
||||||
|
- a late revision/history failure rolls back board, list, groups and components
|
||||||
|
together
|
||||||
|
- stored schema versions 1 and 2 retain four legacy sections and receive no
|
||||||
|
invented components
|
||||||
|
|
||||||
Acceptance:
|
Acceptance:
|
||||||
|
|
||||||
- new boards contain the agreed fixed structure
|
- new boards contain the agreed fixed structure
|
||||||
|
|||||||
@@ -2,14 +2,12 @@ import { and, asc, eq, inArray } from "drizzle-orm";
|
|||||||
import {
|
import {
|
||||||
assertDistributionBoardDeleteProjectCommand,
|
assertDistributionBoardDeleteProjectCommand,
|
||||||
assertDistributionBoardInsertProjectCommand,
|
assertDistributionBoardInsertProjectCommand,
|
||||||
createDistributionBoardDeleteProjectCommand,
|
|
||||||
createDistributionBoardInsertProjectCommand,
|
|
||||||
distributionBoardDeleteCommandType,
|
distributionBoardDeleteCommandType,
|
||||||
distributionBoardInsertCommandType,
|
distributionBoardInsertCommandType,
|
||||||
|
invertDistributionBoardStructureProjectCommand,
|
||||||
normalizeDistributionBoardStructureProjectCommand,
|
normalizeDistributionBoardStructureProjectCommand,
|
||||||
type DistributionBoardStructureProjectCommand,
|
type DistributionBoardStructureProjectCommand,
|
||||||
type DistributionBoardStructureSnapshot,
|
type NormalizedDistributionBoardStructureSnapshot,
|
||||||
legacyDistributionBoardStructureCommandSchemaVersion,
|
|
||||||
} from "../../domain/models/distribution-board-structure-project-command.model.js";
|
} from "../../domain/models/distribution-board-structure-project-command.model.js";
|
||||||
import {
|
import {
|
||||||
assertDistributionBoardUpdateProjectCommand,
|
assertDistributionBoardUpdateProjectCommand,
|
||||||
@@ -24,11 +22,11 @@ import type {
|
|||||||
DistributionBoardStructureProjectCommandStore,
|
DistributionBoardStructureProjectCommandStore,
|
||||||
ExecuteDistributionBoardStructureCommandInput,
|
ExecuteDistributionBoardStructureCommandInput,
|
||||||
} from "../../domain/ports/distribution-board-structure-project-command.store.js";
|
} from "../../domain/ports/distribution-board-structure-project-command.store.js";
|
||||||
import type { CircuitGroupCategory } from "../../shared/constants/circuit-group.js";
|
|
||||||
import type { AppDatabase } from "../database-context.js";
|
import type { AppDatabase } from "../database-context.js";
|
||||||
import { circuitLists } from "../schema/circuit-lists.js";
|
import { circuitLists } from "../schema/circuit-lists.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 { 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 { projects } from "../schema/projects.js";
|
import { projects } from "../schema/projects.js";
|
||||||
@@ -69,31 +67,15 @@ export class DistributionBoardStructureProjectCommandRepository
|
|||||||
projectId: string,
|
projectId: string,
|
||||||
command: DistributionBoardStructureProjectCommand
|
command: DistributionBoardStructureProjectCommand
|
||||||
): DistributionBoardStructureProjectCommand {
|
): DistributionBoardStructureProjectCommand {
|
||||||
const normalized =
|
const structure =
|
||||||
normalizeDistributionBoardStructureProjectCommand(command);
|
normalizeDistributionBoardStructureProjectCommand(command);
|
||||||
if (normalized.type === distributionBoardInsertCommandType) {
|
if (command.type === distributionBoardInsertCommandType) {
|
||||||
assertDistributionBoardInsertProjectCommand(normalized);
|
this.insert(database, projectId, structure);
|
||||||
const inverse = this.insert(
|
return invertDistributionBoardStructureProjectCommand(command);
|
||||||
database,
|
|
||||||
projectId,
|
|
||||||
normalized.payload.structure
|
|
||||||
);
|
|
||||||
return command.schemaVersion ===
|
|
||||||
legacyDistributionBoardStructureCommandSchemaVersion
|
|
||||||
? toLegacyStructureCommand(inverse)
|
|
||||||
: inverse;
|
|
||||||
}
|
}
|
||||||
if (normalized.type === distributionBoardDeleteCommandType) {
|
if (command.type === distributionBoardDeleteCommandType) {
|
||||||
assertDistributionBoardDeleteProjectCommand(normalized);
|
this.delete(database, projectId, structure);
|
||||||
const inverse = this.delete(
|
return invertDistributionBoardStructureProjectCommand(command);
|
||||||
database,
|
|
||||||
projectId,
|
|
||||||
normalized.payload.structure
|
|
||||||
);
|
|
||||||
return command.schemaVersion ===
|
|
||||||
legacyDistributionBoardStructureCommandSchemaVersion
|
|
||||||
? toLegacyStructureCommand(inverse)
|
|
||||||
: inverse;
|
|
||||||
}
|
}
|
||||||
throw new Error("Unsupported distribution-board structure command.");
|
throw new Error("Unsupported distribution-board structure command.");
|
||||||
}
|
}
|
||||||
@@ -101,7 +83,7 @@ export class DistributionBoardStructureProjectCommandRepository
|
|||||||
private insert(
|
private insert(
|
||||||
database: AppDatabase,
|
database: AppDatabase,
|
||||||
projectId: string,
|
projectId: string,
|
||||||
structure: DistributionBoardStructureSnapshot
|
structure: NormalizedDistributionBoardStructureSnapshot
|
||||||
) {
|
) {
|
||||||
if (
|
if (
|
||||||
structure.distributionBoard.projectId !== projectId ||
|
structure.distributionBoard.projectId !== projectId ||
|
||||||
@@ -160,7 +142,26 @@ export class DistributionBoardStructureProjectCommandRepository
|
|||||||
)
|
)
|
||||||
.limit(1)
|
.limit(1)
|
||||||
.get();
|
.get();
|
||||||
if (existingBoard || existingList || existingSection) {
|
const existingComponent =
|
||||||
|
structure.components.length === 0
|
||||||
|
? undefined
|
||||||
|
: database
|
||||||
|
.select({ id: distributionBoardComponents.id })
|
||||||
|
.from(distributionBoardComponents)
|
||||||
|
.where(
|
||||||
|
inArray(
|
||||||
|
distributionBoardComponents.id,
|
||||||
|
structure.components.map((component) => component.id)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.limit(1)
|
||||||
|
.get();
|
||||||
|
if (
|
||||||
|
existingBoard ||
|
||||||
|
existingList ||
|
||||||
|
existingSection ||
|
||||||
|
existingComponent
|
||||||
|
) {
|
||||||
throw new Error("Distribution-board structure id already exists.");
|
throw new Error("Distribution-board structure id already exists.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,15 +172,20 @@ export class DistributionBoardStructureProjectCommandRepository
|
|||||||
database.insert(circuitLists).values(structure.circuitList).run();
|
database.insert(circuitLists).values(structure.circuitList).run();
|
||||||
database
|
database
|
||||||
.insert(circuitSections)
|
.insert(circuitSections)
|
||||||
.values(structure.sections.map(withInitialGroupIdentity))
|
.values(structure.sections)
|
||||||
.run();
|
.run();
|
||||||
return createDistributionBoardDeleteProjectCommand(structure);
|
if (structure.components.length > 0) {
|
||||||
|
database
|
||||||
|
.insert(distributionBoardComponents)
|
||||||
|
.values(structure.components)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private delete(
|
private delete(
|
||||||
database: AppDatabase,
|
database: AppDatabase,
|
||||||
projectId: string,
|
projectId: string,
|
||||||
structure: DistributionBoardStructureSnapshot
|
structure: NormalizedDistributionBoardStructureSnapshot
|
||||||
) {
|
) {
|
||||||
const board = database
|
const board = database
|
||||||
.select()
|
.select()
|
||||||
@@ -202,11 +208,30 @@ export class DistributionBoardStructureProjectCommandRepository
|
|||||||
asc(circuitSections.id)
|
asc(circuitSections.id)
|
||||||
)
|
)
|
||||||
.all();
|
.all();
|
||||||
|
const components = database
|
||||||
|
.select()
|
||||||
|
.from(distributionBoardComponents)
|
||||||
|
.where(
|
||||||
|
eq(
|
||||||
|
distributionBoardComponents.circuitListId,
|
||||||
|
structure.circuitList.id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.orderBy(
|
||||||
|
asc(distributionBoardComponents.sortOrder),
|
||||||
|
asc(distributionBoardComponents.id)
|
||||||
|
)
|
||||||
|
.all();
|
||||||
if (
|
if (
|
||||||
!board ||
|
!board ||
|
||||||
!list ||
|
!list ||
|
||||||
board.projectId !== projectId ||
|
board.projectId !== projectId ||
|
||||||
!structureMatches(structure, { board, list, sections })
|
!structureMatches(structure, {
|
||||||
|
board,
|
||||||
|
list,
|
||||||
|
sections,
|
||||||
|
components,
|
||||||
|
})
|
||||||
) {
|
) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Distribution-board structure changed before deletion."
|
"Distribution-board structure changed before deletion."
|
||||||
@@ -239,7 +264,6 @@ export class DistributionBoardStructureProjectCommandRepository
|
|||||||
if (deleted.changes !== 1) {
|
if (deleted.changes !== 1) {
|
||||||
throw new Error("Distribution board could not be deleted.");
|
throw new Error("Distribution board could not be deleted.");
|
||||||
}
|
}
|
||||||
return createDistributionBoardInsertProjectCommand(structure);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private update(
|
private update(
|
||||||
@@ -354,28 +378,6 @@ function assertSupplyTypeEnabled(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function toLegacyStructureCommand(
|
|
||||||
command:
|
|
||||||
| ReturnType<typeof createDistributionBoardInsertProjectCommand>
|
|
||||||
| ReturnType<typeof createDistributionBoardDeleteProjectCommand>
|
|
||||||
): DistributionBoardStructureProjectCommand {
|
|
||||||
const {
|
|
||||||
floorId: _floorId,
|
|
||||||
supplyType: _supplyType,
|
|
||||||
...distributionBoard
|
|
||||||
} = command.payload.structure.distributionBoard;
|
|
||||||
return {
|
|
||||||
schemaVersion: legacyDistributionBoardStructureCommandSchemaVersion,
|
|
||||||
type: command.type,
|
|
||||||
payload: {
|
|
||||||
structure: {
|
|
||||||
...command.payload.structure,
|
|
||||||
distributionBoard,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} as DistributionBoardStructureProjectCommand;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDistributionBoardFieldValue<
|
function getDistributionBoardFieldValue<
|
||||||
TField extends DistributionBoardUpdateField,
|
TField extends DistributionBoardUpdateField,
|
||||||
>(
|
>(
|
||||||
@@ -386,11 +388,12 @@ function getDistributionBoardFieldValue<
|
|||||||
}
|
}
|
||||||
|
|
||||||
function structureMatches(
|
function structureMatches(
|
||||||
expected: DistributionBoardStructureSnapshot,
|
expected: NormalizedDistributionBoardStructureSnapshot,
|
||||||
actual: {
|
actual: {
|
||||||
board: typeof distributionBoards.$inferSelect;
|
board: typeof distributionBoards.$inferSelect;
|
||||||
list: typeof circuitLists.$inferSelect;
|
list: typeof circuitLists.$inferSelect;
|
||||||
sections: Array<typeof circuitSections.$inferSelect>;
|
sections: Array<typeof circuitSections.$inferSelect>;
|
||||||
|
components: Array<typeof distributionBoardComponents.$inferSelect>;
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
const {
|
const {
|
||||||
@@ -404,7 +407,8 @@ function structureMatches(
|
|||||||
actualDistributionBoard
|
actualDistributionBoard
|
||||||
) ||
|
) ||
|
||||||
!sameRecord(expected.circuitList, actual.list) ||
|
!sameRecord(expected.circuitList, actual.list) ||
|
||||||
expected.sections.length !== actual.sections.length
|
expected.sections.length !== actual.sections.length ||
|
||||||
|
expected.components.length !== actual.components.length
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -413,59 +417,32 @@ function structureMatches(
|
|||||||
left.sortOrder - right.sortOrder ||
|
left.sortOrder - right.sortOrder ||
|
||||||
left.id.localeCompare(right.id)
|
left.id.localeCompare(right.id)
|
||||||
);
|
);
|
||||||
return expectedSections.every((section, index) => {
|
const sectionsMatch = expectedSections.every((section, index) =>
|
||||||
const actualSection = actual.sections[index];
|
sameRecord(section, actual.sections[index])
|
||||||
if (
|
);
|
||||||
actualSection.category !== initialCategoryBySectionKey(section.key) ||
|
const expectedComponents = [...expected.components].sort(
|
||||||
actualSection.groupNumber !== initialGroupNumberBySectionKey(section.key)
|
(left, right) =>
|
||||||
) {
|
left.sortOrder - right.sortOrder ||
|
||||||
return false;
|
left.id.localeCompare(right.id)
|
||||||
}
|
);
|
||||||
const {
|
|
||||||
category: _category,
|
|
||||||
groupNumber: _groupNumber,
|
|
||||||
...comparableActualSection
|
|
||||||
} = actualSection;
|
|
||||||
return sameRecord(section, comparableActualSection);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function withInitialGroupIdentity(
|
|
||||||
section: DistributionBoardStructureSnapshot["sections"][number]
|
|
||||||
): typeof circuitSections.$inferInsert {
|
|
||||||
return {
|
|
||||||
...section,
|
|
||||||
category: initialCategoryBySectionKey(section.key),
|
|
||||||
groupNumber: initialGroupNumberBySectionKey(section.key),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function initialCategoryBySectionKey(
|
|
||||||
key: string
|
|
||||||
): CircuitGroupCategory | null {
|
|
||||||
if (
|
|
||||||
key === "lighting" ||
|
|
||||||
key === "single_phase" ||
|
|
||||||
key === "three_phase"
|
|
||||||
) {
|
|
||||||
return key;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function initialGroupNumberBySectionKey(key: string): number | null {
|
|
||||||
return initialCategoryBySectionKey(key) === null ? null : 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
function sameRecord(
|
|
||||||
expected: Record<string, unknown>,
|
|
||||||
actual: Record<string, unknown>
|
|
||||||
) {
|
|
||||||
const expectedEntries = Object.entries(expected);
|
|
||||||
return (
|
return (
|
||||||
expectedEntries.length === Object.keys(actual).length &&
|
sectionsMatch &&
|
||||||
expectedEntries.every(
|
expectedComponents.every((component, index) =>
|
||||||
([key, value]) => actual[key] === value
|
sameRecord(component, actual.components[index])
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function sameRecord(
|
||||||
|
expected: object,
|
||||||
|
actual: object
|
||||||
|
) {
|
||||||
|
const expectedEntries = Object.entries(expected);
|
||||||
|
const actualRecord = actual as Record<string, unknown>;
|
||||||
|
return (
|
||||||
|
expectedEntries.length === Object.keys(actual).length &&
|
||||||
|
expectedEntries.every(
|
||||||
|
([key, value]) => actualRecord[key] === value
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,15 @@ import {
|
|||||||
distributionBoardSupplyTypes,
|
distributionBoardSupplyTypes,
|
||||||
type DistributionBoardSupplyType,
|
type DistributionBoardSupplyType,
|
||||||
} from "../../shared/constants/distribution-board.js";
|
} from "../../shared/constants/distribution-board.js";
|
||||||
|
import {
|
||||||
|
defaultMainSwitchComponent,
|
||||||
|
defaultSurgeProtectiveDeviceComponent,
|
||||||
|
} from "../../shared/constants/distribution-board-component.js";
|
||||||
|
import type { CircuitGroupCategory } from "../../shared/constants/circuit-group.js";
|
||||||
|
import type {
|
||||||
|
DistributionBoardComponentPlacement,
|
||||||
|
DistributionBoardComponentRole,
|
||||||
|
} from "../../shared/constants/distribution-board-component.js";
|
||||||
import type { SerializedProjectCommand } from "./project-command.model.js";
|
import type { SerializedProjectCommand } from "./project-command.model.js";
|
||||||
|
|
||||||
export const distributionBoardInsertCommandType =
|
export const distributionBoardInsertCommandType =
|
||||||
@@ -10,9 +19,10 @@ export const distributionBoardInsertCommandType =
|
|||||||
export const distributionBoardDeleteCommandType =
|
export const distributionBoardDeleteCommandType =
|
||||||
"distribution-board.delete" as const;
|
"distribution-board.delete" as const;
|
||||||
export const legacyDistributionBoardStructureCommandSchemaVersion = 1 as const;
|
export const legacyDistributionBoardStructureCommandSchemaVersion = 1 as const;
|
||||||
export const distributionBoardStructureCommandSchemaVersion = 2 as const;
|
export const previousDistributionBoardStructureCommandSchemaVersion = 2 as const;
|
||||||
|
export const distributionBoardStructureCommandSchemaVersion = 3 as const;
|
||||||
|
|
||||||
export const defaultCircuitSectionDefinitions = [
|
const legacyDefaultCircuitSectionDefinitions = [
|
||||||
{
|
{
|
||||||
key: "lighting",
|
key: "lighting",
|
||||||
displayName: "Lighting",
|
displayName: "Lighting",
|
||||||
@@ -39,6 +49,33 @@ export const defaultCircuitSectionDefinitions = [
|
|||||||
},
|
},
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
|
export const defaultCircuitSectionDefinitions = [
|
||||||
|
{
|
||||||
|
key: "lighting",
|
||||||
|
displayName: "Beleuchtung 1",
|
||||||
|
prefix: "-1F1.",
|
||||||
|
sortOrder: 10,
|
||||||
|
category: "lighting",
|
||||||
|
groupNumber: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "single_phase",
|
||||||
|
displayName: "1-phasig 1",
|
||||||
|
prefix: "-2F1.",
|
||||||
|
sortOrder: 20,
|
||||||
|
category: "single_phase",
|
||||||
|
groupNumber: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "three_phase",
|
||||||
|
displayName: "3-phasig 1",
|
||||||
|
prefix: "-3F1.",
|
||||||
|
sortOrder: 30,
|
||||||
|
category: "three_phase",
|
||||||
|
groupNumber: 1,
|
||||||
|
},
|
||||||
|
] as const;
|
||||||
|
|
||||||
interface LegacyDistributionBoardStructureSnapshot {
|
interface LegacyDistributionBoardStructureSnapshot {
|
||||||
distributionBoard: {
|
distributionBoard: {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -46,7 +83,22 @@ interface LegacyDistributionBoardStructureSnapshot {
|
|||||||
name: string;
|
name: string;
|
||||||
};
|
};
|
||||||
circuitList: DistributionBoardStructureSnapshot["circuitList"];
|
circuitList: DistributionBoardStructureSnapshot["circuitList"];
|
||||||
sections: DistributionBoardStructureSnapshot["sections"];
|
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 {
|
||||||
@@ -70,7 +122,31 @@ export interface DistributionBoardStructureSnapshot {
|
|||||||
displayName: string;
|
displayName: string;
|
||||||
prefix: string;
|
prefix: string;
|
||||||
sortOrder: number;
|
sortOrder: number;
|
||||||
|
category: CircuitGroupCategory;
|
||||||
|
groupNumber: number;
|
||||||
}>;
|
}>;
|
||||||
|
components: Array<{
|
||||||
|
id: string;
|
||||||
|
circuitListId: string;
|
||||||
|
sectionId: null;
|
||||||
|
equipmentIdentifier: string;
|
||||||
|
name: string;
|
||||||
|
role: DistributionBoardComponentRole;
|
||||||
|
placement: DistributionBoardComponentPlacement;
|
||||||
|
sortOrder: number;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NormalizedDistributionBoardStructureSnapshot {
|
||||||
|
distributionBoard: DistributionBoardStructureSnapshot["distributionBoard"];
|
||||||
|
circuitList: DistributionBoardStructureSnapshot["circuitList"];
|
||||||
|
sections: Array<
|
||||||
|
LegacyCircuitSectionSnapshot & {
|
||||||
|
category: CircuitGroupCategory | null;
|
||||||
|
groupNumber: number | null;
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
components: DistributionBoardStructureSnapshot["components"];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DistributionBoardStructureCommandPayload<
|
interface DistributionBoardStructureCommandPayload<
|
||||||
@@ -97,6 +173,16 @@ interface LegacyDistributionBoardStructureProjectCommand
|
|||||||
| typeof distributionBoardDeleteCommandType;
|
| 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 CurrentDistributionBoardStructureProjectCommand {
|
||||||
type: typeof distributionBoardInsertCommandType;
|
type: typeof distributionBoardInsertCommandType;
|
||||||
@@ -110,6 +196,7 @@ export interface DistributionBoardDeleteProjectCommand
|
|||||||
export type DistributionBoardStructureProjectCommand =
|
export type DistributionBoardStructureProjectCommand =
|
||||||
| DistributionBoardInsertProjectCommand
|
| DistributionBoardInsertProjectCommand
|
||||||
| DistributionBoardDeleteProjectCommand
|
| DistributionBoardDeleteProjectCommand
|
||||||
|
| PreviousDistributionBoardStructureProjectCommand
|
||||||
| LegacyDistributionBoardStructureProjectCommand;
|
| LegacyDistributionBoardStructureProjectCommand;
|
||||||
|
|
||||||
export function createDistributionBoardStructureSnapshot(
|
export function createDistributionBoardStructureSnapshot(
|
||||||
@@ -143,6 +230,22 @@ export function createDistributionBoardStructureSnapshot(
|
|||||||
circuitListId: structureId,
|
circuitListId: structureId,
|
||||||
...section,
|
...section,
|
||||||
})),
|
})),
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
id: crypto.randomUUID(),
|
||||||
|
circuitListId: structureId,
|
||||||
|
sectionId: null,
|
||||||
|
...defaultMainSwitchComponent,
|
||||||
|
sortOrder: 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: crypto.randomUUID(),
|
||||||
|
circuitListId: structureId,
|
||||||
|
sectionId: null,
|
||||||
|
...defaultSurgeProtectiveDeviceComponent,
|
||||||
|
sortOrder: 20,
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
assertDistributionBoardStructureSnapshot(structure);
|
assertDistributionBoardStructureSnapshot(structure);
|
||||||
return structure;
|
return structure;
|
||||||
@@ -192,32 +295,71 @@ export function assertDistributionBoardDeleteProjectCommand(
|
|||||||
|
|
||||||
export function normalizeDistributionBoardStructureProjectCommand(
|
export function normalizeDistributionBoardStructureProjectCommand(
|
||||||
command: DistributionBoardStructureProjectCommand
|
command: DistributionBoardStructureProjectCommand
|
||||||
): DistributionBoardInsertProjectCommand | DistributionBoardDeleteProjectCommand {
|
): NormalizedDistributionBoardStructureSnapshot {
|
||||||
if (
|
if (
|
||||||
command.schemaVersion ===
|
command.schemaVersion ===
|
||||||
legacyDistributionBoardStructureCommandSchemaVersion
|
legacyDistributionBoardStructureCommandSchemaVersion
|
||||||
) {
|
) {
|
||||||
const structure: DistributionBoardStructureSnapshot = {
|
return {
|
||||||
...command.payload.structure,
|
|
||||||
distributionBoard: {
|
distributionBoard: {
|
||||||
...command.payload.structure.distributionBoard,
|
...command.payload.structure.distributionBoard,
|
||||||
floorId: null,
|
floorId: null,
|
||||||
supplyType: null,
|
supplyType: null,
|
||||||
},
|
},
|
||||||
|
circuitList: command.payload.structure.circuitList,
|
||||||
|
sections: command.payload.structure.sections.map(
|
||||||
|
normalizeLegacySection
|
||||||
|
),
|
||||||
|
components: [],
|
||||||
};
|
};
|
||||||
return command.type === distributionBoardInsertCommandType
|
|
||||||
? createDistributionBoardInsertProjectCommand(structure)
|
|
||||||
: createDistributionBoardDeleteProjectCommand(structure);
|
|
||||||
}
|
}
|
||||||
return command as
|
if (
|
||||||
| DistributionBoardInsertProjectCommand
|
command.schemaVersion ===
|
||||||
| DistributionBoardDeleteProjectCommand;
|
previousDistributionBoardStructureCommandSchemaVersion
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
...command.payload.structure,
|
||||||
|
sections: command.payload.structure.sections.map(
|
||||||
|
normalizeLegacySection
|
||||||
|
),
|
||||||
|
components: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return command.payload.structure;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function invertDistributionBoardStructureProjectCommand(
|
||||||
|
command: DistributionBoardStructureProjectCommand
|
||||||
|
): DistributionBoardStructureProjectCommand {
|
||||||
|
return {
|
||||||
|
...command,
|
||||||
|
type:
|
||||||
|
command.type === distributionBoardInsertCommandType
|
||||||
|
? distributionBoardDeleteCommandType
|
||||||
|
: distributionBoardInsertCommandType,
|
||||||
|
} 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, false);
|
assertDistributionBoardStructureSnapshotVersion(structure, "current");
|
||||||
}
|
}
|
||||||
|
|
||||||
function assertDistributionBoardStructureProjectCommand(
|
function assertDistributionBoardStructureProjectCommand(
|
||||||
@@ -239,36 +381,57 @@ function assertDistributionBoardStructureProjectCommand(
|
|||||||
) {
|
) {
|
||||||
assertDistributionBoardStructureSnapshotVersion(
|
assertDistributionBoardStructureSnapshotVersion(
|
||||||
command.payload.structure,
|
command.payload.structure,
|
||||||
true
|
"legacy"
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
command.schemaVersion !== distributionBoardStructureCommandSchemaVersion
|
command.schemaVersion ===
|
||||||
|
previousDistributionBoardStructureCommandSchemaVersion
|
||||||
|
) {
|
||||||
|
assertDistributionBoardStructureSnapshotVersion(
|
||||||
|
command.payload.structure,
|
||||||
|
"previous"
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
command.schemaVersion !==
|
||||||
|
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,
|
||||||
false
|
"current"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function assertDistributionBoardStructureSnapshotVersion(
|
function assertDistributionBoardStructureSnapshotVersion(
|
||||||
structure: unknown,
|
structure: unknown,
|
||||||
legacy: boolean
|
version: "legacy" | "previous" | "current"
|
||||||
) {
|
) {
|
||||||
if (!isPlainObject(structure) || Object.keys(structure).length !== 3) {
|
const current = version === "current";
|
||||||
|
if (
|
||||||
|
!isPlainObject(structure) ||
|
||||||
|
Object.keys(structure).length !== (current ? 4 : 3)
|
||||||
|
) {
|
||||||
throw new Error("Distribution-board structure is invalid.");
|
throw new Error("Distribution-board structure is invalid.");
|
||||||
}
|
}
|
||||||
const { distributionBoard, circuitList, sections } = structure;
|
const { distributionBoard, circuitList, sections, components } =
|
||||||
|
structure;
|
||||||
|
const expectedSections = current
|
||||||
|
? defaultCircuitSectionDefinitions
|
||||||
|
: legacyDefaultCircuitSectionDefinitions;
|
||||||
if (
|
if (
|
||||||
!isPlainObject(distributionBoard) ||
|
!isPlainObject(distributionBoard) ||
|
||||||
Object.keys(distributionBoard).length !== (legacy ? 3 : 5) ||
|
Object.keys(distributionBoard).length !==
|
||||||
|
(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 !== defaultCircuitSectionDefinitions.length
|
sections.length !== expectedSections.length ||
|
||||||
|
(current && (!Array.isArray(components) || components.length !== 2))
|
||||||
) {
|
) {
|
||||||
throw new Error("Distribution-board structure is incomplete.");
|
throw new Error("Distribution-board structure is incomplete.");
|
||||||
}
|
}
|
||||||
@@ -278,7 +441,7 @@ function assertDistributionBoardStructureSnapshotVersion(
|
|||||||
`distributionBoard.${field}`
|
`distributionBoard.${field}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (!legacy) {
|
if (version !== "legacy") {
|
||||||
assertNullableId(distributionBoard.floorId, "distributionBoard.floorId");
|
assertNullableId(distributionBoard.floorId, "distributionBoard.floorId");
|
||||||
assertNullableSupplyType(distributionBoard.supplyType);
|
assertNullableSupplyType(distributionBoard.supplyType);
|
||||||
}
|
}
|
||||||
@@ -296,8 +459,11 @@ function assertDistributionBoardStructureSnapshotVersion(
|
|||||||
const sectionIds = new Set<string>();
|
const sectionIds = new Set<string>();
|
||||||
for (let index = 0; index < sections.length; index += 1) {
|
for (let index = 0; index < sections.length; index += 1) {
|
||||||
const section = sections[index];
|
const section = sections[index];
|
||||||
const expected = defaultCircuitSectionDefinitions[index];
|
const expected = expectedSections[index];
|
||||||
if (!isPlainObject(section) || Object.keys(section).length !== 6) {
|
if (
|
||||||
|
!isPlainObject(section) ||
|
||||||
|
Object.keys(section).length !== (current ? 8 : 6)
|
||||||
|
) {
|
||||||
throw new Error("Default circuit section is invalid.");
|
throw new Error("Default circuit section is invalid.");
|
||||||
}
|
}
|
||||||
assertNonEmptyString(section.id, "section.id");
|
assertNonEmptyString(section.id, "section.id");
|
||||||
@@ -310,13 +476,64 @@ function assertDistributionBoardStructureSnapshotVersion(
|
|||||||
section.key !== expected.key ||
|
section.key !== expected.key ||
|
||||||
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 &&
|
||||||
|
(!("category" in expected) ||
|
||||||
|
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."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (current) {
|
||||||
|
assertDefaultComponents(components, circuitList.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertDefaultComponents(
|
||||||
|
components: unknown,
|
||||||
|
circuitListId: unknown
|
||||||
|
) {
|
||||||
|
if (!Array.isArray(components)) {
|
||||||
|
throw new Error("Default distribution-board components are invalid.");
|
||||||
|
}
|
||||||
|
const expectedComponents = [
|
||||||
|
{ ...defaultMainSwitchComponent, sortOrder: 10 },
|
||||||
|
{ ...defaultSurgeProtectiveDeviceComponent, sortOrder: 20 },
|
||||||
|
];
|
||||||
|
const componentIds = new Set<string>();
|
||||||
|
for (let index = 0; index < expectedComponents.length; index += 1) {
|
||||||
|
const component = components[index];
|
||||||
|
const expected = expectedComponents[index];
|
||||||
|
if (!isPlainObject(component) || Object.keys(component).length !== 8) {
|
||||||
|
throw new Error(
|
||||||
|
"Default distribution-board component is invalid."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
assertNonEmptyString(component.id, "component.id");
|
||||||
|
if (componentIds.has(component.id)) {
|
||||||
|
throw new Error(
|
||||||
|
"Default distribution-board component ids must be unique."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
componentIds.add(component.id);
|
||||||
|
if (
|
||||||
|
component.circuitListId !== circuitListId ||
|
||||||
|
component.sectionId !== null ||
|
||||||
|
component.equipmentIdentifier !== expected.equipmentIdentifier ||
|
||||||
|
component.name !== expected.name ||
|
||||||
|
component.role !== expected.role ||
|
||||||
|
component.placement !== expected.placement ||
|
||||||
|
component.sortOrder !== expected.sortOrder
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
"Distribution-board structure has invalid default components."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function assertNullableId(value: unknown, field: string) {
|
function assertNullableId(value: unknown, field: string) {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { ProjectHistoryRepository } from "../src/db/repositories/project-history
|
|||||||
import { circuitLists } from "../src/db/schema/circuit-lists.js";
|
import { circuitLists } from "../src/db/schema/circuit-lists.js";
|
||||||
import { circuitSections } from "../src/db/schema/circuit-sections.js";
|
import { circuitSections } from "../src/db/schema/circuit-sections.js";
|
||||||
import { circuits } from "../src/db/schema/circuits.js";
|
import { circuits } from "../src/db/schema/circuits.js";
|
||||||
|
import { distributionBoardComponents } from "../src/db/schema/distribution-board-components.js";
|
||||||
import { distributionBoards } from "../src/db/schema/distribution-boards.js";
|
import { distributionBoards } from "../src/db/schema/distribution-boards.js";
|
||||||
import { floors } from "../src/db/schema/floors.js";
|
import { floors } from "../src/db/schema/floors.js";
|
||||||
import { projectRevisions } from "../src/db/schema/project-revisions.js";
|
import { projectRevisions } from "../src/db/schema/project-revisions.js";
|
||||||
@@ -51,10 +52,51 @@ function getStructureCounts(context: DatabaseContext) {
|
|||||||
boards: context.db.select().from(distributionBoards).all().length,
|
boards: context.db.select().from(distributionBoards).all().length,
|
||||||
lists: context.db.select().from(circuitLists).all().length,
|
lists: context.db.select().from(circuitLists).all().length,
|
||||||
sections: context.db.select().from(circuitSections).all().length,
|
sections: context.db.select().from(circuitSections).all().length,
|
||||||
|
components: context.db
|
||||||
|
.select()
|
||||||
|
.from(distributionBoardComponents)
|
||||||
|
.all().length,
|
||||||
revisions: context.db.select().from(projectRevisions).all().length,
|
revisions: context.db.select().from(projectRevisions).all().length,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function legacySections(circuitListId: string) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
id: "legacy-lighting",
|
||||||
|
circuitListId,
|
||||||
|
key: "lighting",
|
||||||
|
displayName: "Lighting",
|
||||||
|
prefix: "-1F",
|
||||||
|
sortOrder: 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "legacy-single-phase",
|
||||||
|
circuitListId,
|
||||||
|
key: "single_phase",
|
||||||
|
displayName: "Single-phase circuits",
|
||||||
|
prefix: "-2F",
|
||||||
|
sortOrder: 20,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "legacy-three-phase",
|
||||||
|
circuitListId,
|
||||||
|
key: "three_phase",
|
||||||
|
displayName: "Three-phase circuits",
|
||||||
|
prefix: "-3F",
|
||||||
|
sortOrder: 30,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "legacy-unassigned",
|
||||||
|
circuitListId,
|
||||||
|
key: "unassigned",
|
||||||
|
displayName: "Unassigned",
|
||||||
|
prefix: "-UF",
|
||||||
|
sortOrder: 90,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
describe("distribution-board structure project command", () => {
|
describe("distribution-board structure project command", () => {
|
||||||
it("builds the fixed setup and sends the expected frontend revision", async () => {
|
it("builds the fixed setup and sends the expected frontend revision", async () => {
|
||||||
const structure = createDistributionBoardStructureSnapshot(
|
const structure = createDistributionBoardStructureSnapshot(
|
||||||
@@ -74,17 +116,26 @@ describe("distribution-board structure project command", () => {
|
|||||||
section.prefix,
|
section.prefix,
|
||||||
]),
|
]),
|
||||||
[
|
[
|
||||||
["lighting", "-1F"],
|
["lighting", "-1F1."],
|
||||||
["single_phase", "-2F"],
|
["single_phase", "-2F1."],
|
||||||
["three_phase", "-3F"],
|
["three_phase", "-3F1."],
|
||||||
["unassigned", "-UF"],
|
]
|
||||||
|
);
|
||||||
|
assert.deepEqual(
|
||||||
|
structure.components.map((component) => [
|
||||||
|
component.equipmentIdentifier,
|
||||||
|
component.name,
|
||||||
|
]),
|
||||||
|
[
|
||||||
|
["-Q0", "Hauptschalter"],
|
||||||
|
["-FA", "Überspannungsableiter"],
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
assert.throws(
|
assert.throws(
|
||||||
() =>
|
() =>
|
||||||
createDistributionBoardInsertProjectCommand({
|
createDistributionBoardInsertProjectCommand({
|
||||||
...structure,
|
...structure,
|
||||||
sections: structure.sections.slice(0, 3),
|
sections: structure.sections.slice(0, 2),
|
||||||
}),
|
}),
|
||||||
/incomplete/
|
/incomplete/
|
||||||
);
|
);
|
||||||
@@ -150,7 +201,8 @@ describe("distribution-board structure project command", () => {
|
|||||||
assert.deepEqual(getStructureCounts(context), {
|
assert.deepEqual(getStructureCounts(context), {
|
||||||
boards: 1,
|
boards: 1,
|
||||||
lists: 1,
|
lists: 1,
|
||||||
sections: 4,
|
sections: 3,
|
||||||
|
components: 2,
|
||||||
revisions: 1,
|
revisions: 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -167,6 +219,7 @@ describe("distribution-board structure project command", () => {
|
|||||||
boards: 0,
|
boards: 0,
|
||||||
lists: 0,
|
lists: 0,
|
||||||
sections: 0,
|
sections: 0,
|
||||||
|
components: 0,
|
||||||
revisions: 2,
|
revisions: 2,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -182,7 +235,8 @@ describe("distribution-board structure project command", () => {
|
|||||||
assert.deepEqual(getStructureCounts(context), {
|
assert.deepEqual(getStructureCounts(context), {
|
||||||
boards: 1,
|
boards: 1,
|
||||||
lists: 1,
|
lists: 1,
|
||||||
sections: 4,
|
sections: 3,
|
||||||
|
components: 2,
|
||||||
revisions: 3,
|
revisions: 3,
|
||||||
});
|
});
|
||||||
assert.ok(
|
assert.ok(
|
||||||
@@ -274,8 +328,9 @@ describe("distribution-board structure project command", () => {
|
|||||||
type: "distribution-board.insert",
|
type: "distribution-board.insert",
|
||||||
payload: {
|
payload: {
|
||||||
structure: {
|
structure: {
|
||||||
...current,
|
|
||||||
distributionBoard: legacyBoard,
|
distributionBoard: legacyBoard,
|
||||||
|
circuitList: current.circuitList,
|
||||||
|
sections: legacySections(current.circuitList.id),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as unknown as DistributionBoardStructureProjectCommand;
|
} as unknown as DistributionBoardStructureProjectCommand;
|
||||||
@@ -291,6 +346,59 @@ describe("distribution-board structure project command", () => {
|
|||||||
.get();
|
.get();
|
||||||
assert.equal(inserted?.floorId, null);
|
assert.equal(inserted?.floorId, null);
|
||||||
assert.equal(inserted?.supplyType, null);
|
assert.equal(inserted?.supplyType, null);
|
||||||
|
assert.equal(
|
||||||
|
context.db.select().from(circuitSections).all().length,
|
||||||
|
4
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
context.db.select().from(distributionBoardComponents).all()
|
||||||
|
.length,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
context.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps stored version-two setup commands component-free", () => {
|
||||||
|
const context = createTestDatabase();
|
||||||
|
try {
|
||||||
|
const repository =
|
||||||
|
new DistributionBoardStructureProjectCommandRepository(
|
||||||
|
context.db
|
||||||
|
);
|
||||||
|
const current = createDistributionBoardStructureSnapshot(
|
||||||
|
"project-1",
|
||||||
|
"UV Version 2"
|
||||||
|
);
|
||||||
|
const command = {
|
||||||
|
schemaVersion: 2,
|
||||||
|
type: "distribution-board.insert",
|
||||||
|
payload: {
|
||||||
|
structure: {
|
||||||
|
distributionBoard: current.distributionBoard,
|
||||||
|
circuitList: current.circuitList,
|
||||||
|
sections: legacySections(current.circuitList.id),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as DistributionBoardStructureProjectCommand;
|
||||||
|
|
||||||
|
const inserted = repository.execute({
|
||||||
|
projectId: "project-1",
|
||||||
|
expectedRevision: 0,
|
||||||
|
source: "user",
|
||||||
|
command,
|
||||||
|
});
|
||||||
|
assert.equal(inserted.inverse.schemaVersion, 2);
|
||||||
|
assert.equal(
|
||||||
|
context.db.select().from(circuitSections).all().length,
|
||||||
|
4
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
context.db.select().from(distributionBoardComponents).all()
|
||||||
|
.length,
|
||||||
|
0
|
||||||
|
);
|
||||||
} finally {
|
} finally {
|
||||||
context.close();
|
context.close();
|
||||||
}
|
}
|
||||||
@@ -545,6 +653,7 @@ describe("distribution-board structure project command", () => {
|
|||||||
boards: 0,
|
boards: 0,
|
||||||
lists: 0,
|
lists: 0,
|
||||||
sections: 0,
|
sections: 0,
|
||||||
|
components: 0,
|
||||||
revisions: 0,
|
revisions: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -661,6 +770,7 @@ describe("distribution-board structure project command", () => {
|
|||||||
boards: 0,
|
boards: 0,
|
||||||
lists: 0,
|
lists: 0,
|
||||||
sections: 0,
|
sections: 0,
|
||||||
|
components: 0,
|
||||||
revisions: 0,
|
revisions: 0,
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -243,7 +243,13 @@ describe("project snapshot repository", () => {
|
|||||||
);
|
);
|
||||||
assert.equal(state.circuits.length, 1);
|
assert.equal(state.circuits.length, 1);
|
||||||
assert.equal(state.circuitProtectionDevices.length, 1);
|
assert.equal(state.circuitProtectionDevices.length, 1);
|
||||||
assert.equal(state.distributionBoardComponents.length, 1);
|
assert.equal(
|
||||||
|
state.distributionBoardComponents.length,
|
||||||
|
context.db
|
||||||
|
.select()
|
||||||
|
.from(distributionBoardComponents)
|
||||||
|
.all().length
|
||||||
|
);
|
||||||
assert.equal(
|
assert.equal(
|
||||||
state.distributionBoardComponentProtectionDevices.length,
|
state.distributionBoardComponentProtectionDevices.length,
|
||||||
1
|
1
|
||||||
@@ -427,18 +433,26 @@ describe("portable project transfer", () => {
|
|||||||
copied.state.circuits[0].id,
|
copied.state.circuits[0].id,
|
||||||
source.state.circuits[0].id
|
source.state.circuits[0].id
|
||||||
);
|
);
|
||||||
assert.notEqual(
|
assert.equal(copied.state.distributionBoardComponents.length, 3);
|
||||||
copied.state.distributionBoardComponents[0].id,
|
|
||||||
source.state.distributionBoardComponents[0].id
|
|
||||||
);
|
|
||||||
assert.equal(
|
assert.equal(
|
||||||
copied.state.circuitProtectionDevices[0].circuitId,
|
copied.state.circuitProtectionDevices[0].circuitId,
|
||||||
copied.state.circuits[0].id
|
copied.state.circuits[0].id
|
||||||
);
|
);
|
||||||
assert.equal(
|
assert.equal(
|
||||||
|
copied.state.distributionBoardComponents.some(
|
||||||
|
(component) =>
|
||||||
|
component.id ===
|
||||||
|
copied.state
|
||||||
|
.distributionBoardComponentProtectionDevices[0]
|
||||||
|
.componentId
|
||||||
|
),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
assert.notEqual(
|
||||||
copied.state.distributionBoardComponentProtectionDevices[0]
|
copied.state.distributionBoardComponentProtectionDevices[0]
|
||||||
.componentId,
|
.componentId,
|
||||||
copied.state.distributionBoardComponents[0].id
|
source.state.distributionBoardComponentProtectionDevices[0]
|
||||||
|
.componentId
|
||||||
);
|
);
|
||||||
assert.equal(
|
assert.equal(
|
||||||
copied.state.circuits[0].deviceRows[0].linkedProjectDeviceId,
|
copied.state.circuits[0].deviceRows[0].linkedProjectDeviceId,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { AppDatabase } from "../../src/db/database-context.js";
|
import type { AppDatabase } from "../../src/db/database-context.js";
|
||||||
import { circuitLists } from "../../src/db/schema/circuit-lists.js";
|
import { circuitLists } from "../../src/db/schema/circuit-lists.js";
|
||||||
import { circuitSections } from "../../src/db/schema/circuit-sections.js";
|
import { circuitSections } from "../../src/db/schema/circuit-sections.js";
|
||||||
|
import { distributionBoardComponents } from "../../src/db/schema/distribution-board-components.js";
|
||||||
import { distributionBoards } from "../../src/db/schema/distribution-boards.js";
|
import { distributionBoards } from "../../src/db/schema/distribution-boards.js";
|
||||||
import { createDistributionBoardStructureSnapshot } from "../../src/domain/models/distribution-board-structure-project-command.model.js";
|
import { createDistributionBoardStructureSnapshot } from "../../src/domain/models/distribution-board-structure-project-command.model.js";
|
||||||
|
|
||||||
@@ -21,6 +22,9 @@ export class DistributionBoardFixtureRepository {
|
|||||||
.run();
|
.run();
|
||||||
tx.insert(circuitLists).values(structure.circuitList).run();
|
tx.insert(circuitLists).values(structure.circuitList).run();
|
||||||
tx.insert(circuitSections).values(structure.sections).run();
|
tx.insert(circuitSections).values(structure.sections).run();
|
||||||
|
tx.insert(distributionBoardComponents)
|
||||||
|
.values(structure.components)
|
||||||
|
.run();
|
||||||
});
|
});
|
||||||
return structure.distributionBoard;
|
return structure.distributionBoard;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user