Create protected board defaults

This commit is contained in:
2026-07-30 19:30:53 +02:00
parent 012308984d
commit 9d4d4082fe
10 changed files with 504 additions and 156 deletions
@@ -3,6 +3,15 @@ import {
distributionBoardSupplyTypes,
type DistributionBoardSupplyType,
} 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";
export const distributionBoardInsertCommandType =
@@ -10,9 +19,10 @@ export const distributionBoardInsertCommandType =
export const distributionBoardDeleteCommandType =
"distribution-board.delete" 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",
displayName: "Lighting",
@@ -39,6 +49,33 @@ export const defaultCircuitSectionDefinitions = [
},
] 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 {
distributionBoard: {
id: string;
@@ -46,7 +83,22 @@ interface LegacyDistributionBoardStructureSnapshot {
name: string;
};
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 {
@@ -70,7 +122,31 @@ export interface DistributionBoardStructureSnapshot {
displayName: string;
prefix: string;
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<
@@ -97,6 +173,16 @@ interface LegacyDistributionBoardStructureProjectCommand
| typeof distributionBoardDeleteCommandType;
}
interface PreviousDistributionBoardStructureProjectCommand
extends SerializedProjectCommand<
DistributionBoardStructureCommandPayload<PreviousDistributionBoardStructureSnapshot>
> {
schemaVersion: typeof previousDistributionBoardStructureCommandSchemaVersion;
type:
| typeof distributionBoardInsertCommandType
| typeof distributionBoardDeleteCommandType;
}
export interface DistributionBoardInsertProjectCommand
extends CurrentDistributionBoardStructureProjectCommand {
type: typeof distributionBoardInsertCommandType;
@@ -110,6 +196,7 @@ export interface DistributionBoardDeleteProjectCommand
export type DistributionBoardStructureProjectCommand =
| DistributionBoardInsertProjectCommand
| DistributionBoardDeleteProjectCommand
| PreviousDistributionBoardStructureProjectCommand
| LegacyDistributionBoardStructureProjectCommand;
export function createDistributionBoardStructureSnapshot(
@@ -143,6 +230,22 @@ export function createDistributionBoardStructureSnapshot(
circuitListId: structureId,
...section,
})),
components: [
{
id: crypto.randomUUID(),
circuitListId: structureId,
sectionId: null,
...defaultMainSwitchComponent,
sortOrder: 10,
},
{
id: crypto.randomUUID(),
circuitListId: structureId,
sectionId: null,
...defaultSurgeProtectiveDeviceComponent,
sortOrder: 20,
},
],
};
assertDistributionBoardStructureSnapshot(structure);
return structure;
@@ -192,32 +295,71 @@ export function assertDistributionBoardDeleteProjectCommand(
export function normalizeDistributionBoardStructureProjectCommand(
command: DistributionBoardStructureProjectCommand
): DistributionBoardInsertProjectCommand | DistributionBoardDeleteProjectCommand {
): NormalizedDistributionBoardStructureSnapshot {
if (
command.schemaVersion ===
legacyDistributionBoardStructureCommandSchemaVersion
) {
const structure: DistributionBoardStructureSnapshot = {
...command.payload.structure,
return {
distributionBoard: {
...command.payload.structure.distributionBoard,
floorId: 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
| DistributionBoardInsertProjectCommand
| DistributionBoardDeleteProjectCommand;
if (
command.schemaVersion ===
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(
structure: unknown
): asserts structure is DistributionBoardStructureSnapshot {
assertDistributionBoardStructureSnapshotVersion(structure, false);
assertDistributionBoardStructureSnapshotVersion(structure, "current");
}
function assertDistributionBoardStructureProjectCommand(
@@ -239,36 +381,57 @@ function assertDistributionBoardStructureProjectCommand(
) {
assertDistributionBoardStructureSnapshotVersion(
command.payload.structure,
true
"legacy"
);
return;
}
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.");
}
assertDistributionBoardStructureSnapshotVersion(
command.payload.structure,
false
"current"
);
}
function assertDistributionBoardStructureSnapshotVersion(
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.");
}
const { distributionBoard, circuitList, sections } = structure;
const { distributionBoard, circuitList, sections, components } =
structure;
const expectedSections = current
? defaultCircuitSectionDefinitions
: legacyDefaultCircuitSectionDefinitions;
if (
!isPlainObject(distributionBoard) ||
Object.keys(distributionBoard).length !== (legacy ? 3 : 5) ||
Object.keys(distributionBoard).length !==
(version === "legacy" ? 3 : 5) ||
!isPlainObject(circuitList) ||
Object.keys(circuitList).length !== 4 ||
!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.");
}
@@ -278,7 +441,7 @@ function assertDistributionBoardStructureSnapshotVersion(
`distributionBoard.${field}`
);
}
if (!legacy) {
if (version !== "legacy") {
assertNullableId(distributionBoard.floorId, "distributionBoard.floorId");
assertNullableSupplyType(distributionBoard.supplyType);
}
@@ -296,8 +459,11 @@ function assertDistributionBoardStructureSnapshotVersion(
const sectionIds = new Set<string>();
for (let index = 0; index < sections.length; index += 1) {
const section = sections[index];
const expected = defaultCircuitSectionDefinitions[index];
if (!isPlainObject(section) || Object.keys(section).length !== 6) {
const expected = expectedSections[index];
if (
!isPlainObject(section) ||
Object.keys(section).length !== (current ? 8 : 6)
) {
throw new Error("Default circuit section is invalid.");
}
assertNonEmptyString(section.id, "section.id");
@@ -310,13 +476,64 @@ function assertDistributionBoardStructureSnapshotVersion(
section.key !== expected.key ||
section.displayName !== expected.displayName ||
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(
"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) {