Add configurable distribution supply types

This commit is contained in:
2026-07-29 09:31:20 +02:00
parent 194bc9c0b1
commit b1a11397b3
43 changed files with 5697 additions and 126 deletions
@@ -1,11 +1,16 @@
import crypto from "node:crypto";
import {
distributionBoardSupplyTypes,
type DistributionBoardSupplyType,
} from "../../shared/constants/distribution-board.js";
import type { SerializedProjectCommand } from "./project-command.model.js";
export const distributionBoardInsertCommandType =
"distribution-board.insert" as const;
export const distributionBoardDeleteCommandType =
"distribution-board.delete" as const;
export const distributionBoardStructureCommandSchemaVersion = 1 as const;
export const legacyDistributionBoardStructureCommandSchemaVersion = 1 as const;
export const distributionBoardStructureCommandSchemaVersion = 2 as const;
export const defaultCircuitSectionDefinitions = [
{
@@ -34,11 +39,23 @@ export const defaultCircuitSectionDefinitions = [
},
] as const;
interface LegacyDistributionBoardStructureSnapshot {
distributionBoard: {
id: string;
projectId: string;
name: string;
};
circuitList: DistributionBoardStructureSnapshot["circuitList"];
sections: DistributionBoardStructureSnapshot["sections"];
}
export interface DistributionBoardStructureSnapshot {
distributionBoard: {
id: string;
projectId: string;
name: string;
floorId: string | null;
supplyType: DistributionBoardSupplyType | null;
};
circuitList: {
id: string;
@@ -56,29 +73,52 @@ export interface DistributionBoardStructureSnapshot {
}>;
}
interface DistributionBoardStructureCommandPayload {
structure: DistributionBoardStructureSnapshot;
interface DistributionBoardStructureCommandPayload<
TStructure = DistributionBoardStructureSnapshot,
> {
structure: TStructure;
}
interface CurrentDistributionBoardStructureProjectCommand
extends SerializedProjectCommand<DistributionBoardStructureCommandPayload> {
schemaVersion: typeof distributionBoardStructureCommandSchemaVersion;
type:
| typeof distributionBoardInsertCommandType
| typeof distributionBoardDeleteCommandType;
}
interface LegacyDistributionBoardStructureProjectCommand
extends SerializedProjectCommand<
DistributionBoardStructureCommandPayload<LegacyDistributionBoardStructureSnapshot>
> {
schemaVersion: typeof legacyDistributionBoardStructureCommandSchemaVersion;
type:
| typeof distributionBoardInsertCommandType
| typeof distributionBoardDeleteCommandType;
}
export interface DistributionBoardInsertProjectCommand
extends SerializedProjectCommand<DistributionBoardStructureCommandPayload> {
schemaVersion: typeof distributionBoardStructureCommandSchemaVersion;
extends CurrentDistributionBoardStructureProjectCommand {
type: typeof distributionBoardInsertCommandType;
}
export interface DistributionBoardDeleteProjectCommand
extends SerializedProjectCommand<DistributionBoardStructureCommandPayload> {
schemaVersion: typeof distributionBoardStructureCommandSchemaVersion;
extends CurrentDistributionBoardStructureProjectCommand {
type: typeof distributionBoardDeleteCommandType;
}
export type DistributionBoardStructureProjectCommand =
| DistributionBoardInsertProjectCommand
| DistributionBoardDeleteProjectCommand;
| DistributionBoardDeleteProjectCommand
| LegacyDistributionBoardStructureProjectCommand;
export function createDistributionBoardStructureSnapshot(
projectId: string,
name: string
name: string,
input: {
floorId?: string | null;
supplyType?: DistributionBoardSupplyType | null;
} = {}
): DistributionBoardStructureSnapshot {
const normalizedName = name.trim();
assertNonEmptyString(projectId, "projectId");
@@ -89,6 +129,8 @@ export function createDistributionBoardStructureSnapshot(
id: structureId,
projectId,
name: normalizedName,
floorId: input.floorId ?? null,
supplyType: input.supplyType ?? null,
},
circuitList: {
id: structureId,
@@ -132,7 +174,7 @@ export function createDistributionBoardDeleteProjectCommand(
export function assertDistributionBoardInsertProjectCommand(
command: SerializedProjectCommand<unknown>
): asserts command is DistributionBoardInsertProjectCommand {
): asserts command is DistributionBoardStructureProjectCommand {
assertDistributionBoardStructureProjectCommand(
command,
distributionBoardInsertCommandType
@@ -141,23 +183,88 @@ export function assertDistributionBoardInsertProjectCommand(
export function assertDistributionBoardDeleteProjectCommand(
command: SerializedProjectCommand<unknown>
): asserts command is DistributionBoardDeleteProjectCommand {
): asserts command is DistributionBoardStructureProjectCommand {
assertDistributionBoardStructureProjectCommand(
command,
distributionBoardDeleteCommandType
);
}
export function normalizeDistributionBoardStructureProjectCommand(
command: DistributionBoardStructureProjectCommand
): DistributionBoardInsertProjectCommand | DistributionBoardDeleteProjectCommand {
if (
command.schemaVersion ===
legacyDistributionBoardStructureCommandSchemaVersion
) {
const structure: DistributionBoardStructureSnapshot = {
...command.payload.structure,
distributionBoard: {
...command.payload.structure.distributionBoard,
floorId: null,
supplyType: null,
},
};
return command.type === distributionBoardInsertCommandType
? createDistributionBoardInsertProjectCommand(structure)
: createDistributionBoardDeleteProjectCommand(structure);
}
return command as
| DistributionBoardInsertProjectCommand
| DistributionBoardDeleteProjectCommand;
}
export function assertDistributionBoardStructureSnapshot(
structure: unknown
): asserts structure is DistributionBoardStructureSnapshot {
assertDistributionBoardStructureSnapshotVersion(structure, false);
}
function assertDistributionBoardStructureProjectCommand(
command: SerializedProjectCommand<unknown>,
expectedType:
| typeof distributionBoardInsertCommandType
| typeof distributionBoardDeleteCommandType
) {
if (
command.type !== expectedType ||
!isPlainObject(command.payload) ||
Object.keys(command.payload).length !== 1
) {
throw new Error("Unsupported distribution-board structure command.");
}
if (
command.schemaVersion ===
legacyDistributionBoardStructureCommandSchemaVersion
) {
assertDistributionBoardStructureSnapshotVersion(
command.payload.structure,
true
);
return;
}
if (
command.schemaVersion !== distributionBoardStructureCommandSchemaVersion
) {
throw new Error("Unsupported distribution-board structure command.");
}
assertDistributionBoardStructureSnapshotVersion(
command.payload.structure,
false
);
}
function assertDistributionBoardStructureSnapshotVersion(
structure: unknown,
legacy: boolean
) {
if (!isPlainObject(structure) || Object.keys(structure).length !== 3) {
throw new Error("Distribution-board structure is invalid.");
}
const { distributionBoard, circuitList, sections } = structure;
if (
!isPlainObject(distributionBoard) ||
Object.keys(distributionBoard).length !== 3 ||
Object.keys(distributionBoard).length !== (legacy ? 3 : 5) ||
!isPlainObject(circuitList) ||
Object.keys(circuitList).length !== 4 ||
!Array.isArray(sections) ||
@@ -165,8 +272,15 @@ export function assertDistributionBoardStructureSnapshot(
) {
throw new Error("Distribution-board structure is incomplete.");
}
for (const [field, value] of Object.entries(distributionBoard)) {
assertNonEmptyString(value, `distributionBoard.${field}`);
for (const field of ["id", "projectId", "name"] as const) {
assertNonEmptyString(
distributionBoard[field],
`distributionBoard.${field}`
);
}
if (!legacy) {
assertNullableId(distributionBoard.floorId, "distributionBoard.floorId");
assertNullableSupplyType(distributionBoard.supplyType);
}
for (const [field, value] of Object.entries(circuitList)) {
assertNonEmptyString(value, `circuitList.${field}`);
@@ -174,22 +288,16 @@ export function assertDistributionBoardStructureSnapshot(
if (
circuitList.projectId !== distributionBoard.projectId ||
circuitList.distributionBoardId !== distributionBoard.id ||
circuitList.name !==
`${distributionBoard.name} Stromkreisliste`
circuitList.name !== `${distributionBoard.name} Stromkreisliste`
) {
throw new Error(
"Distribution board and circuit list are inconsistent."
);
throw new Error("Distribution board and circuit list are inconsistent.");
}
const sectionIds = new Set<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
) {
if (!isPlainObject(section) || Object.keys(section).length !== 6) {
throw new Error("Default circuit section is invalid.");
}
assertNonEmptyString(section.id, "section.id");
@@ -211,22 +319,23 @@ export function assertDistributionBoardStructureSnapshot(
}
}
function assertDistributionBoardStructureProjectCommand(
command: SerializedProjectCommand<unknown>,
expectedType:
| typeof distributionBoardInsertCommandType
| typeof distributionBoardDeleteCommandType
) {
if (
command.schemaVersion !==
distributionBoardStructureCommandSchemaVersion ||
command.type !== expectedType ||
!isPlainObject(command.payload) ||
Object.keys(command.payload).length !== 1
) {
throw new Error("Unsupported distribution-board structure command.");
function assertNullableId(value: unknown, field: string) {
if (value !== null) {
assertNonEmptyString(value, field);
}
}
function assertNullableSupplyType(
value: unknown
): asserts value is DistributionBoardSupplyType | null {
if (
value !== null &&
!distributionBoardSupplyTypes.includes(
value as DistributionBoardSupplyType
)
) {
throw new Error("distributionBoard.supplyType is invalid.");
}
assertDistributionBoardStructureSnapshot(command.payload.structure);
}
function assertNonEmptyString(