Delete populated circuit groups safely

This commit is contained in:
2026-07-31 08:01:55 +02:00
parent 93ec294759
commit ab989cc637
6 changed files with 268 additions and 17 deletions
+34 -15
View File
@@ -55,11 +55,13 @@ import {
buildCircuitGroupMovePlan,
buildCircuitGroupRenumberPlan,
buildCircuitGroupReorderAssignments,
buildCircuitGroupSubtreeSnapshot,
buildNewCircuitGroupSnapshot,
canMoveCircuitToGroup,
canRenumberCircuitGroups,
canDeleteCircuitGroup,
renameCircuitGroupSnapshot,
summarizeCircuitGroupSubtree,
toCircuitGroupSnapshot,
} from "../utils/circuit-group-editing";
import {
@@ -87,6 +89,7 @@ import {
deleteCircuitCommand,
deleteCircuitDeviceRowCommand,
deleteCircuitGroupCommand,
deleteCircuitGroupSubtreeCommand,
deleteDistributionBoardComponentCommand,
getCircuitTree,
getNextCircuitIdentifier,
@@ -1222,23 +1225,40 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
});
}
async function handleDeleteEmptyCircuitGroup(
async function handleDeleteCircuitGroup(
section: CircuitTreeResponseDto["sections"][number]
) {
if (!canDeleteCircuitGroup(section)) {
return;
}
if (!confirm(`Leere Stromkreisgruppe „${section.displayName}“ entfernen?`)) {
const isEmpty = canDeleteCircuitGroup(section);
const subtree = isEmpty
? null
: buildCircuitGroupSubtreeSnapshot(section, circuitListId);
const summary = subtree
? summarizeCircuitGroupSubtree(subtree)
: null;
const warning = isEmpty
? `Leere Stromkreisgruppe „${section.displayName}“ entfernen?`
: `Stromkreisgruppe „${section.displayName}“ vollständig entfernen?\n\n` +
`Dabei werden ${summary!.circuitCount} Stromkreis(e), ${summary!.deviceRowCount} Gerätezeile(n) und ${summary!.protectionComponentCount} Gruppenschutzgerät(e) gelöscht.\n\n` +
"Die Änderung kann über die projektweite Versionshistorie rückgängig gemacht werden.";
if (!confirm(warning)) {
return;
}
await runCommand({
label: "Leere Stromkreisgruppe entfernen",
label: isEmpty
? "Leere Stromkreisgruppe entfernen"
: "Befüllte Stromkreisgruppe entfernen",
redo: async () => {
const result = await deleteCircuitGroupCommand(
projectId,
getExpectedProjectRevision(),
toCircuitGroupSnapshot(section, circuitListId)
);
const result = subtree
? await deleteCircuitGroupSubtreeCommand(
projectId,
getExpectedProjectRevision(),
subtree
)
: await deleteCircuitGroupCommand(
projectId,
getExpectedProjectRevision(),
toCircuitGroupSnapshot(section, circuitListId)
);
applyProjectCommandResult(result);
setCircuitGroupEditorIntent(null);
return null;
@@ -3711,14 +3731,13 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
<button
type="button"
tabIndex={-1}
disabled={!canDeleteCircuitGroup(section)}
title={
canDeleteCircuitGroup(section)
? undefined
: "Befüllte Gruppen werden später über einen eigenen Warn- und Löschdialog entfernt."
? "Leere Stromkreisgruppe entfernen"
: "Befüllte Stromkreisgruppe mit vollständiger Warnung entfernen"
}
onClick={() =>
void handleDeleteEmptyCircuitGroup(section)
void handleDeleteCircuitGroup(section)
}
>
Gruppe entfernen
+20
View File
@@ -68,6 +68,9 @@ import type {
import type {
CircuitGroupMovePlan,
} from "../../domain/services/circuit-group-move-planning";
import type {
CircuitGroupSubtreeSnapshot,
} from "../../domain/models/circuit-group-subtree-snapshot.model";
async function request<T>(url: string, init?: RequestInit): Promise<T> {
const response = await fetch(url, {
@@ -440,6 +443,23 @@ export function deleteCircuitGroupCommand(
);
}
export function deleteCircuitGroupSubtreeCommand(
projectId: string,
expectedRevision: number,
snapshot: CircuitGroupSubtreeSnapshot
) {
return executeProjectCommand(
projectId,
expectedRevision,
{
schemaVersion: 1,
type: "circuit-group.delete-subtree",
payload: { snapshot },
},
"Befüllte Stromkreisgruppe entfernen"
);
}
export function reorderCircuitGroupsCommand(
projectId: string,
expectedRevision: number,
+116
View File
@@ -16,6 +16,12 @@ import type {
import type {
CircuitGroupMovePlan,
} from "../../domain/services/circuit-group-move-planning";
import type {
CircuitGroupSubtreeSnapshot,
} from "../../domain/models/circuit-group-subtree-snapshot.model";
import {
toDistributionBoardComponentSnapshot,
} from "./distribution-board-component-editing";
export function toCircuitGroupSnapshot(
section: CircuitTreeSectionDto,
@@ -86,6 +92,116 @@ export function canDeleteCircuitGroup(
return section.circuits.length === 0 && section.components.length === 0;
}
export function buildCircuitGroupSubtreeSnapshot(
section: CircuitTreeSectionDto,
circuitListId: string
): CircuitGroupSubtreeSnapshot {
const components = [...section.components]
.sort(
(left, right) =>
left.sortOrder - right.sortOrder ||
left.id.localeCompare(right.id)
)
.map(toDistributionBoardComponentSnapshot);
const circuits = [...section.circuits]
.sort(
(left, right) =>
left.sortOrder - right.sortOrder ||
left.id.localeCompare(right.id)
)
.map((circuit) => ({
circuit: {
id: circuit.id,
circuitListId: circuit.circuitListId,
sectionId: circuit.sectionId,
equipmentIdentifier: circuit.equipmentIdentifier,
displayName: circuit.displayName ?? null,
sortOrder: circuit.sortOrder,
protectionType: circuit.protectionType ?? null,
protectionRatedCurrent:
circuit.protectionRatedCurrent ?? null,
protectionCharacteristic:
circuit.protectionCharacteristic ?? null,
cableType: circuit.cableType ?? null,
cableCrossSection: circuit.cableCrossSection ?? null,
cableLength: circuit.cableLength ?? null,
rcdAssignment: circuit.rcdAssignment ?? null,
terminalDesignation: circuit.terminalDesignation ?? null,
voltage: circuit.voltage ?? null,
controlRequirement: circuit.controlRequirement ?? null,
status: circuit.status ?? null,
isReserve: circuit.isReserve,
remark: circuit.remark ?? null,
deviceRows: [...circuit.deviceRows]
.sort(
(left, right) =>
left.sortOrder - right.sortOrder ||
left.id.localeCompare(right.id)
)
.map((row) => ({
id: row.id,
circuitId: circuit.id,
linkedProjectDeviceId:
row.linkedProjectDeviceId ?? null,
legacyConsumerId: row.legacyConsumerId ?? null,
sortOrder: row.sortOrder,
name: row.name,
displayName: row.displayName,
phaseType: row.phaseType ?? null,
connectionKind: row.connectionKind ?? null,
costGroup: row.costGroup ?? null,
category: row.category ?? null,
level: row.level ?? null,
roomId: row.roomId ?? null,
roomNumberSnapshot: row.roomNumberSnapshot ?? null,
roomNameSnapshot: row.roomNameSnapshot ?? null,
quantity: row.quantity,
powerPerUnit: row.powerPerUnit,
simultaneityFactor: row.simultaneityFactor,
cosPhi: row.cosPhi ?? null,
remark: row.remark ?? null,
overriddenFields: row.overriddenFields ?? null,
})),
},
protectionDevice: circuit.protectionDevice
? {
circuitId: circuit.id,
type: circuit.protectionDevice.type,
ratedCurrentA:
circuit.protectionDevice.ratedCurrentA,
fuseUtilizationCategory:
circuit.protectionDevice.fuseUtilizationCategory ??
null,
tripCharacteristic:
circuit.protectionDevice.tripCharacteristic ?? null,
rcdType: circuit.protectionDevice.rcdType ?? null,
ratedResidualCurrentMa:
circuit.protectionDevice.ratedResidualCurrentMa ??
null,
}
: null,
}));
return {
group: toCircuitGroupSnapshot(section, circuitListId),
components,
circuits,
};
}
export function summarizeCircuitGroupSubtree(
snapshot: CircuitGroupSubtreeSnapshot
) {
return {
circuitCount: snapshot.circuits.length,
deviceRowCount: snapshot.circuits.reduce(
(count, entry) =>
count + entry.circuit.deviceRows.length,
0
),
protectionComponentCount: snapshot.components.length,
};
}
export function buildCircuitGroupReorderAssignments(
sections: readonly CircuitTreeSectionDto[],
groupId: string,