Configure circuit protection

This commit is contained in:
Julian Appel 2026-07-31 07:45:48 +02:00
parent 18ca5eb3d4
commit 8898117ed4
18 changed files with 642 additions and 14 deletions

View file

@ -58,6 +58,9 @@ import type {
import type {
CircuitGroupSnapshot,
} from "../../domain/models/circuit-group-structure-project-command.model";
import type {
CircuitProtectionSnapshot,
} from "../../domain/models/circuit-protection-project-command.model";
async function request<T>(url: string, init?: RequestInit): Promise<T> {
const response = await fetch(url, {
@ -430,6 +433,25 @@ export function deleteCircuitGroupCommand(
);
}
export function updateCircuitProtectionCommand(
projectId: string,
expectedRevision: number,
circuitId: string,
expected: CircuitProtectionSnapshot | null,
target: CircuitProtectionSnapshot
) {
return executeProjectCommand(
projectId,
expectedRevision,
{
schemaVersion: 1,
type: "circuit-protection.update",
payload: { circuitId, expected, target },
},
"Stromkreisschutz bearbeiten"
);
}
export function updateCircuitById(
projectId: string,
expectedRevision: number,

View file

@ -1,6 +1,7 @@
import type { CircuitTreeCircuitDto, CircuitTreeDeviceRowDto } from "../types.js";
import type { CircuitUpdatePatch } from "../../domain/models/circuit-project-command.model.js";
import type { CircuitDeviceRowUpdatePatch } from "../../domain/models/circuit-device-row-project-command.model.js";
import { protectionDeviceTypeLabels } from "../../shared/constants/protection-device";
export type CellKey =
| "equipmentIdentifier"
@ -472,18 +473,54 @@ export function getDeviceValue(device: CircuitTreeDeviceRowDto, key: CellKey): G
}
export function getCircuitValue(circuit: CircuitTreeCircuitDto, key: CellKey): GridValue {
const protection = circuit.protectionDevice;
const protectionCharacteristic = protection
? [
protection.fuseUtilizationCategory,
protection.tripCharacteristic,
protection.rcdType ? `Typ ${protection.rcdType}` : undefined,
protection.ratedResidualCurrentMa !== undefined
? `${protection.ratedResidualCurrentMa} mA`
: undefined,
]
.filter(Boolean)
.join(" · ")
: undefined;
switch (key) {
case "equipmentIdentifier": return circuit.equipmentIdentifier;
case "displayName": return circuit.displayName;
case "circuitTotalPower": return circuit.circuitTotalPower;
case "protectionType": return circuit.protectionType;
case "protectionRatedCurrent": return circuit.protectionRatedCurrent;
case "protectionCharacteristic": return circuit.protectionCharacteristic;
case "protectionType":
return protection
? protectionDeviceTypeLabels[protection.type]
: circuit.protectionType;
case "protectionRatedCurrent":
return protection?.ratedCurrentA ?? circuit.protectionRatedCurrent;
case "protectionCharacteristic":
return protectionCharacteristic || circuit.protectionCharacteristic;
case "protectionSummary": {
const current = circuit.protectionRatedCurrent !== undefined && circuit.protectionRatedCurrent !== null
? `${circuit.protectionRatedCurrent}A`
if (!protection) {
const legacyCurrent =
circuit.protectionRatedCurrent !== undefined &&
circuit.protectionRatedCurrent !== null
? `${circuit.protectionRatedCurrent}A`
: "";
return [
circuit.protectionType,
legacyCurrent,
circuit.protectionCharacteristic,
].filter(Boolean).join(" ").trim() || undefined;
}
const currentValue =
protection.ratedCurrentA;
const current = currentValue !== undefined && currentValue !== null
? `${currentValue} A`
: "";
return [circuit.protectionType, current, circuit.protectionCharacteristic].filter(Boolean).join(" ").trim() || undefined;
return [
protectionDeviceTypeLabels[protection.type],
current,
protectionCharacteristic,
].filter(Boolean).join(" · ").trim() || undefined;
}
case "cableSummary": {
const length = circuit.cableLength !== undefined && circuit.cableLength !== null ? `${circuit.cableLength} m` : "";
@ -539,7 +576,14 @@ export function compareSortValues(left: GridValue, right: GridValue): number {
}
export function getCellKind(rowType: RowType, key: CellKey): CellKind {
if (key === "rowTotalPower" || key === "circuitTotalPower") return "computed";
if (
key === "rowTotalPower" ||
key === "circuitTotalPower" ||
key === "protectionType" ||
key === "protectionRatedCurrent" ||
key === "protectionCharacteristic" ||
key === "protectionSummary"
) return "computed";
if (key === "voltage") return "readonly";
if (rowType === "section") return "readonly";
if (rowType === "placeholder") {

View file

@ -0,0 +1,36 @@
import type {
CircuitProtectionSnapshot,
} from "../../domain/models/circuit-protection-project-command.model";
import { createDefaultCircuitProtection } from "../../domain/services/protection-device-defaults";
import type { CircuitGroupCategory } from "../../shared/constants/circuit-group";
import type {
CircuitTreeProtectionDeviceDto,
} from "../types";
export function toCircuitProtectionSnapshot(
circuitId: string,
protection: CircuitTreeProtectionDeviceDto
): CircuitProtectionSnapshot {
return {
circuitId,
type: protection.type,
ratedCurrentA: protection.ratedCurrentA,
fuseUtilizationCategory:
protection.fuseUtilizationCategory ?? null,
tripCharacteristic: protection.tripCharacteristic ?? null,
rcdType: protection.rcdType ?? null,
ratedResidualCurrentMa:
protection.ratedResidualCurrentMa ?? null,
};
}
export function getCircuitProtectionEditorInitialValue(
category: CircuitGroupCategory | undefined,
protection: CircuitTreeProtectionDeviceDto | undefined
): CircuitTreeProtectionDeviceDto {
if (protection) {
return protection;
}
const fallback = createDefaultCircuitProtection(category ?? "lighting");
return { ...fallback };
}

View file

@ -8,6 +8,8 @@ import type {
CreateCircuitDeviceRowInputDto,
CreateCircuitInputDto,
} from "../types.js";
import type { CircuitGroupCategory } from "../../shared/constants/circuit-group";
import { createDefaultCircuitProtection } from "../../domain/services/protection-device-defaults";
export function buildCircuitDeviceRowInsertSnapshot(input: {
id: string;
@ -46,8 +48,12 @@ export function buildCircuitInsertSnapshot(input: {
circuitListId: string;
values: CreateCircuitInputDto;
deviceRows: CircuitDeviceRowSnapshot[];
category?: CircuitGroupCategory;
}): CircuitSnapshot {
const { id, circuitListId, values, deviceRows } = input;
const { id, circuitListId, values, deviceRows, category } = input;
const defaultProtection = category
? createDefaultCircuitProtection(category)
: null;
return {
id,
circuitListId,
@ -69,5 +75,30 @@ export function buildCircuitInsertSnapshot(input: {
isReserve: deviceRows.length === 0,
remark: values.remark ?? null,
deviceRows,
...(defaultProtection
? {
protectionDevice: {
circuitId: id,
type: defaultProtection.type,
ratedCurrentA: defaultProtection.ratedCurrentA,
fuseUtilizationCategory:
"fuseUtilizationCategory" in defaultProtection
? defaultProtection.fuseUtilizationCategory
: null,
tripCharacteristic:
"tripCharacteristic" in defaultProtection
? defaultProtection.tripCharacteristic
: null,
rcdType:
"rcdType" in defaultProtection
? defaultProtection.rcdType
: null,
ratedResidualCurrentMa:
"ratedResidualCurrentMa" in defaultProtection
? defaultProtection.ratedResidualCurrentMa
: null,
},
}
: {}),
};
}