From ab989cc637cd0362850a29d01f6c4758b2fc2645 Mon Sep 17 00:00:00 2001 From: Julian Appel Date: Fri, 31 Jul 2026 08:01:55 +0200 Subject: [PATCH] Delete populated circuit groups safely --- docs/current-architecture.md | 7 ++ docs/spec/08-current-product-backlog.md | 4 +- .../components/circuit-tree-editor.tsx | 49 +++++--- src/frontend/utils/api.ts | 20 +++ src/frontend/utils/circuit-group-editing.ts | 116 ++++++++++++++++++ tests/circuit-group-numbering.test.ts | 89 ++++++++++++++ 6 files changed, 268 insertions(+), 17 deletions(-) diff --git a/docs/current-architecture.md b/docs/current-architecture.md index deec590..98feb35 100644 --- a/docs/current-architecture.md +++ b/docs/current-architecture.md @@ -400,6 +400,13 @@ Stromkreis in eine andere Gruppe derselben Kategorie verschieben. Der atomare `circuit.move-group`-Befehl erhält Gerätezeilen und Schutzdaten, setzt die Zielposition und vergibt dort die höchste vorhandene Stromkreisnummer plus eins; Lücken werden nicht automatisch gefüllt. +Befüllte Gruppen werden im Editor erst nach einer Warnung mit Anzahl der +enthaltenen Stromkreise, Gerätezeilen und Gruppenschutzgeräte gelöscht. Der +Client sendet dafür den vollständigen aktuellen Unterbaum an +`circuit-group.delete-subtree`; der Server vergleicht ihn innerhalb derselben +Transaktion mit dem Datenbankstand. Undo stellt Gruppe, Schutzgeräte, +Stromkreise, Gerätereihen und Verknüpfungs-/Override-Metadaten vollständig +wieder her. `distribution-board.update` versioniert Etage, Netzart und den verteilerweiten Gleichzeitigkeitsfaktor gemeinsam und stellt alle Werte über dauerhaftes Undo/Redo wieder her. Der Faktor liegt zwischen `0` und `1` und diff --git a/docs/spec/08-current-product-backlog.md b/docs/spec/08-current-product-backlog.md index 07d9664..2d929c3 100644 --- a/docs/spec/08-current-product-backlog.md +++ b/docs/spec/08-current-product-backlog.md @@ -39,10 +39,10 @@ requirements and intended sequencing, not proof of implementation. - [x] Phase E3b2a: persistent circuit-protection update command. - [x] Phase E3b2b: circuit-protection defaults on insert and editor modal. - [x] Phase E4a: persistent same-category group reorder controls. -- [ ] Phase E4b: explicit group renumber, circuit moves and populated-delete warning. +- [x] Phase E4b: explicit group renumber, circuit moves and populated-delete warning. - [x] Explicit same-category group renumbering updates prefixes and all child BMK atomically. - [x] Single-circuit moves between same-category groups with automatic next-free BMK assignment. - - [ ] Populated-group delete warning and explicit subtree deletion. + - [x] Populated-group delete warning and explicit subtree deletion. - [ ] Phase E: editor projection and editing. - [ ] Phase F: documentation and full GUI verification. - [ ] Keep full electrical sizing and cable-dimensioning rules separate until diff --git a/src/frontend/components/circuit-tree-editor.tsx b/src/frontend/components/circuit-tree-editor.tsx index 029c317..b2bed2b 100644 --- a/src/frontend/components/circuit-tree-editor.tsx +++ b/src/frontend/components/circuit-tree-editor.tsx @@ -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