Persist populated group deletion
This commit is contained in:
@@ -326,6 +326,12 @@ groups of the same category. The stored target BMK is highest target suffix
|
|||||||
plus one at planning time and is never recalculated for Redo. Only circuit
|
plus one at planning time and is never recalculated for Redo. Only circuit
|
||||||
group, BMK and sort order change; device rows and circuit protection retain the
|
group, BMK and sort order change; device rows and circuit protection retain the
|
||||||
stable circuit id.
|
stable circuit id.
|
||||||
|
Confirmed populated-group deletion uses
|
||||||
|
`circuit-group.delete-subtree`/`circuit-group.restore-subtree`. The exact
|
||||||
|
snapshot contains the group, optional component protections, complete circuits,
|
||||||
|
circuit protections and all device-row link/override metadata. Delete
|
||||||
|
re-captures and compares the subtree before cascading; restore preserves every
|
||||||
|
UUID in foreign-key-safe order.
|
||||||
Distribution-board floor assignment and a project-enabled supply type use
|
Distribution-board floor assignment and a project-enabled supply type use
|
||||||
`distribution-board.update`; both values are snapshot/export fields and one
|
`distribution-board.update`; both values are snapshot/export fields and one
|
||||||
persistent undo step.
|
persistent undo step.
|
||||||
|
|||||||
@@ -336,7 +336,13 @@ Gruppenoperationen. Er umfasst Gruppe, optionale Gruppenkomponenten samt
|
|||||||
1:1-Schutzdaten, vollständige Stromkreise samt 1:1-Schutzdaten und sämtliche
|
1:1-Schutzdaten, vollständige Stromkreise samt 1:1-Schutzdaten und sämtliche
|
||||||
Gerätezeilen einschließlich Link- und Override-Metadaten. Seine
|
Gerätezeilen einschließlich Link- und Override-Metadaten. Seine
|
||||||
Warnzusammenfassung liefert Vorsicherung/FI sowie Stromkreis- und
|
Warnzusammenfassung liefert Vorsicherung/FI sowie Stromkreis- und
|
||||||
Gerätezeilenanzahl. Persistentes Löschen/Wiederherstellen folgt separat.
|
Gerätezeilenanzahl.
|
||||||
|
`circuit-group.delete-subtree` nimmt den aktuellen Unterbaum unmittelbar vor
|
||||||
|
dem Löschen erneut auf und verlangt exakte Übereinstimmung mit dem bestätigten
|
||||||
|
Snapshot. `circuit-group.restore-subtree` schreibt ihn in FK-sicherer Reihenfolge
|
||||||
|
mit denselben UUIDs zurück. Beide Richtungen laufen als vollständige inverse
|
||||||
|
Projekt-Commands; späte Fehler hinterlassen weder Teillöschungen noch
|
||||||
|
Teilwiederherstellungen.
|
||||||
`distribution-board.update` versioniert Etage, Netzart und den
|
`distribution-board.update` versioniert Etage, Netzart und den
|
||||||
verteilerweiten Gleichzeitigkeitsfaktor gemeinsam und stellt alle Werte über
|
verteilerweiten Gleichzeitigkeitsfaktor gemeinsam und stellt alle Werte über
|
||||||
dauerhaftes Undo/Redo wieder her. Der Faktor liegt zwischen `0` und `1` und
|
dauerhaftes Undo/Redo wieder her. Der Faktor liegt zwischen `0` und `1` und
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ requirements and intended sequencing, not proof of implementation.
|
|||||||
- [x] Phase D2a: deterministic same-category circuit-move plan.
|
- [x] Phase D2a: deterministic same-category circuit-move plan.
|
||||||
- [x] Phase D2b: persistent same-category circuit-move command.
|
- [x] Phase D2b: persistent same-category circuit-move command.
|
||||||
- [x] Phase D3a: complete validated group-subtree snapshot and warning summary.
|
- [x] Phase D3a: complete validated group-subtree snapshot and warning summary.
|
||||||
- [ ] Phase D3b: persistent populated-group delete/restore command.
|
- [x] Phase D3b: persistent populated-group delete/restore command.
|
||||||
- [ ] Phase E: editor projection and editing.
|
- [ ] Phase E: editor projection and editing.
|
||||||
- [ ] Phase F: documentation and full GUI verification.
|
- [ ] Phase F: documentation and full GUI verification.
|
||||||
- [ ] Keep full electrical sizing and cable-dimensioning rules separate until
|
- [ ] Keep full electrical sizing and cable-dimensioning rules separate until
|
||||||
|
|||||||
@@ -662,9 +662,7 @@ Acceptance:
|
|||||||
|
|
||||||
### D. Group Numbering and Circuit Moves
|
### D. Group Numbering and Circuit Moves
|
||||||
|
|
||||||
Status: In progress. Group renumbering D1 and deterministic circuit-move
|
Status: Complete.
|
||||||
planning and persistent moves D2 plus the deletion snapshot D3a are complete;
|
|
||||||
persistent populated deletion D3b remains pending.
|
|
||||||
|
|
||||||
- implement nested identifier generation
|
- implement nested identifier generation
|
||||||
- support same-category cross-group circuit moves
|
- support same-category cross-group circuit moves
|
||||||
@@ -721,6 +719,16 @@ Implemented in D3a:
|
|||||||
- the snapshot derives the confirmation summary for upstream protection, RCD,
|
- the snapshot derives the confirmation summary for upstream protection, RCD,
|
||||||
circuit count and device-row count
|
circuit count and device-row count
|
||||||
|
|
||||||
|
Implemented in D3b:
|
||||||
|
|
||||||
|
- `circuit-group.delete-subtree` requires the exact previously confirmed
|
||||||
|
snapshot and re-captures the full current subtree before deletion
|
||||||
|
- `circuit-group.restore-subtree` restores stable UUIDs in foreign-key-safe
|
||||||
|
order, including both protection layers and row link/override metadata
|
||||||
|
- deletion and restoration are exact persistent inverses for Undo/Redo
|
||||||
|
- stale confirmation state and late history failures leave no partial delete
|
||||||
|
or restore
|
||||||
|
|
||||||
Acceptance:
|
Acceptance:
|
||||||
|
|
||||||
- target identifiers use highest suffix plus one
|
- target identifiers use highest suffix plus one
|
||||||
|
|||||||
@@ -0,0 +1,257 @@
|
|||||||
|
import { asc, eq } from "drizzle-orm";
|
||||||
|
import {
|
||||||
|
circuitGroupDeleteSubtreeCommandType,
|
||||||
|
circuitGroupRestoreSubtreeCommandType,
|
||||||
|
assertCircuitGroupDeleteSubtreeProjectCommand,
|
||||||
|
assertCircuitGroupRestoreSubtreeProjectCommand,
|
||||||
|
createCircuitGroupDeleteSubtreeProjectCommand,
|
||||||
|
createCircuitGroupRestoreSubtreeProjectCommand,
|
||||||
|
type CircuitGroupSubtreeProjectCommand,
|
||||||
|
} from "../../domain/models/circuit-group-subtree-project-command.model.js";
|
||||||
|
import type {
|
||||||
|
CircuitGroupSubtreeSnapshot,
|
||||||
|
CircuitProtectionDeviceSnapshot,
|
||||||
|
} from "../../domain/models/circuit-group-subtree-snapshot.model.js";
|
||||||
|
import type {
|
||||||
|
CircuitGroupSubtreeProjectCommandStore,
|
||||||
|
ExecuteCircuitGroupSubtreeCommandInput,
|
||||||
|
} from "../../domain/ports/circuit-group-subtree-project-command.store.js";
|
||||||
|
import type { AppDatabase } from "../database-context.js";
|
||||||
|
import { circuitDeviceRows } from "../schema/circuit-device-rows.js";
|
||||||
|
import { circuitLists } from "../schema/circuit-lists.js";
|
||||||
|
import { circuitProtectionDevices } from "../schema/circuit-protection-devices.js";
|
||||||
|
import { circuitSections } from "../schema/circuit-sections.js";
|
||||||
|
import { circuits } from "../schema/circuits.js";
|
||||||
|
import { distributionBoardComponentProtectionDevices } from "../schema/distribution-board-component-protection-devices.js";
|
||||||
|
import { distributionBoardComponents } from "../schema/distribution-board-components.js";
|
||||||
|
import { executeProjectCommandTransaction } from "./project-command-transaction.persistence.js";
|
||||||
|
|
||||||
|
export class CircuitGroupSubtreeProjectCommandRepository
|
||||||
|
implements CircuitGroupSubtreeProjectCommandStore
|
||||||
|
{
|
||||||
|
constructor(private readonly database: AppDatabase) {}
|
||||||
|
|
||||||
|
capture(projectId: string, groupId: string) {
|
||||||
|
return this.captureFrom(this.database, projectId, groupId);
|
||||||
|
}
|
||||||
|
|
||||||
|
execute(input: ExecuteCircuitGroupSubtreeCommandInput) {
|
||||||
|
if (input.command.type === circuitGroupDeleteSubtreeCommandType) {
|
||||||
|
assertCircuitGroupDeleteSubtreeProjectCommand(input.command);
|
||||||
|
} else {
|
||||||
|
assertCircuitGroupRestoreSubtreeProjectCommand(input.command);
|
||||||
|
}
|
||||||
|
return executeProjectCommandTransaction(
|
||||||
|
this.database,
|
||||||
|
input,
|
||||||
|
(tx) => this.applyCommand(tx, input.projectId, input.command)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private applyCommand(
|
||||||
|
database: AppDatabase,
|
||||||
|
projectId: string,
|
||||||
|
command: CircuitGroupSubtreeProjectCommand
|
||||||
|
) {
|
||||||
|
if (command.type === circuitGroupDeleteSubtreeCommandType) {
|
||||||
|
const current = this.captureFrom(
|
||||||
|
database,
|
||||||
|
projectId,
|
||||||
|
command.payload.snapshot.group.id
|
||||||
|
);
|
||||||
|
if (!sameSnapshot(current, command.payload.snapshot)) {
|
||||||
|
throw new Error(
|
||||||
|
"Circuit-group subtree changed before deletion."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const deleted = database
|
||||||
|
.delete(circuitSections)
|
||||||
|
.where(eq(circuitSections.id, current.group.id))
|
||||||
|
.run();
|
||||||
|
if (deleted.changes !== 1) {
|
||||||
|
throw new Error("Circuit-group subtree could not be deleted.");
|
||||||
|
}
|
||||||
|
return createCircuitGroupRestoreSubtreeProjectCommand(
|
||||||
|
command.payload.snapshot
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (command.type === circuitGroupRestoreSubtreeCommandType) {
|
||||||
|
this.restore(database, projectId, command.payload.snapshot);
|
||||||
|
return createCircuitGroupDeleteSubtreeProjectCommand(
|
||||||
|
command.payload.snapshot
|
||||||
|
);
|
||||||
|
}
|
||||||
|
throw new Error("Unsupported circuit-group subtree command.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private captureFrom(
|
||||||
|
database: AppDatabase,
|
||||||
|
projectId: string,
|
||||||
|
groupId: string
|
||||||
|
): CircuitGroupSubtreeSnapshot {
|
||||||
|
const group = database
|
||||||
|
.select()
|
||||||
|
.from(circuitSections)
|
||||||
|
.where(eq(circuitSections.id, groupId))
|
||||||
|
.get();
|
||||||
|
if (!group || group.category === null || group.groupNumber === null) {
|
||||||
|
throw new Error("Circuit group not found.");
|
||||||
|
}
|
||||||
|
this.assertListOwnership(database, projectId, group.circuitListId);
|
||||||
|
const components = database
|
||||||
|
.select()
|
||||||
|
.from(distributionBoardComponents)
|
||||||
|
.where(eq(distributionBoardComponents.sectionId, group.id))
|
||||||
|
.orderBy(
|
||||||
|
asc(distributionBoardComponents.sortOrder),
|
||||||
|
asc(distributionBoardComponents.id)
|
||||||
|
)
|
||||||
|
.all()
|
||||||
|
.map((component) => ({
|
||||||
|
component: {
|
||||||
|
...component,
|
||||||
|
role: component.role as
|
||||||
|
| "group_upstream_protection"
|
||||||
|
| "group_residual_current_protection",
|
||||||
|
},
|
||||||
|
protectionDevice:
|
||||||
|
database
|
||||||
|
.select()
|
||||||
|
.from(distributionBoardComponentProtectionDevices)
|
||||||
|
.where(
|
||||||
|
eq(
|
||||||
|
distributionBoardComponentProtectionDevices.componentId,
|
||||||
|
component.id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.get() ?? null,
|
||||||
|
}));
|
||||||
|
const circuitSnapshots = database
|
||||||
|
.select()
|
||||||
|
.from(circuits)
|
||||||
|
.where(eq(circuits.sectionId, group.id))
|
||||||
|
.orderBy(asc(circuits.sortOrder), asc(circuits.id))
|
||||||
|
.all()
|
||||||
|
.map((circuit) => {
|
||||||
|
const { isReserve, ...values } = circuit;
|
||||||
|
return {
|
||||||
|
circuit: {
|
||||||
|
...values,
|
||||||
|
isReserve: Boolean(isReserve),
|
||||||
|
deviceRows: database
|
||||||
|
.select()
|
||||||
|
.from(circuitDeviceRows)
|
||||||
|
.where(eq(circuitDeviceRows.circuitId, circuit.id))
|
||||||
|
.orderBy(
|
||||||
|
asc(circuitDeviceRows.sortOrder),
|
||||||
|
asc(circuitDeviceRows.id)
|
||||||
|
)
|
||||||
|
.all(),
|
||||||
|
},
|
||||||
|
protectionDevice:
|
||||||
|
(database
|
||||||
|
.select()
|
||||||
|
.from(circuitProtectionDevices)
|
||||||
|
.where(eq(circuitProtectionDevices.circuitId, circuit.id))
|
||||||
|
.get() as CircuitProtectionDeviceSnapshot | undefined) ??
|
||||||
|
null,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
group: {
|
||||||
|
...group,
|
||||||
|
category: group.category,
|
||||||
|
groupNumber: group.groupNumber,
|
||||||
|
},
|
||||||
|
components,
|
||||||
|
circuits: circuitSnapshots,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private restore(
|
||||||
|
database: AppDatabase,
|
||||||
|
projectId: string,
|
||||||
|
snapshot: CircuitGroupSubtreeSnapshot
|
||||||
|
) {
|
||||||
|
this.assertListOwnership(
|
||||||
|
database,
|
||||||
|
projectId,
|
||||||
|
snapshot.group.circuitListId
|
||||||
|
);
|
||||||
|
const existing = database
|
||||||
|
.select({ id: circuitSections.id })
|
||||||
|
.from(circuitSections)
|
||||||
|
.where(eq(circuitSections.id, snapshot.group.id))
|
||||||
|
.get();
|
||||||
|
if (existing) {
|
||||||
|
throw new Error("Circuit group already exists before restoration.");
|
||||||
|
}
|
||||||
|
database.insert(circuitSections).values(snapshot.group).run();
|
||||||
|
for (const component of snapshot.components) {
|
||||||
|
database
|
||||||
|
.insert(distributionBoardComponents)
|
||||||
|
.values(component.component)
|
||||||
|
.run();
|
||||||
|
if (component.protectionDevice !== null) {
|
||||||
|
database
|
||||||
|
.insert(distributionBoardComponentProtectionDevices)
|
||||||
|
.values(component.protectionDevice)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const entry of snapshot.circuits) {
|
||||||
|
const { deviceRows, isReserve, ...circuit } = entry.circuit;
|
||||||
|
database
|
||||||
|
.insert(circuits)
|
||||||
|
.values({ ...circuit, isReserve: isReserve ? 1 : 0 })
|
||||||
|
.run();
|
||||||
|
if (entry.protectionDevice !== null) {
|
||||||
|
database
|
||||||
|
.insert(circuitProtectionDevices)
|
||||||
|
.values(entry.protectionDevice)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
if (deviceRows.length > 0) {
|
||||||
|
database.insert(circuitDeviceRows).values(deviceRows).run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private assertListOwnership(
|
||||||
|
database: AppDatabase,
|
||||||
|
projectId: string,
|
||||||
|
circuitListId: string
|
||||||
|
) {
|
||||||
|
const list = database
|
||||||
|
.select({ projectId: circuitLists.projectId })
|
||||||
|
.from(circuitLists)
|
||||||
|
.where(eq(circuitLists.id, circuitListId))
|
||||||
|
.get();
|
||||||
|
if (!list || list.projectId !== projectId) {
|
||||||
|
throw new Error("Circuit-group list does not belong to project.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function sameSnapshot(
|
||||||
|
left: CircuitGroupSubtreeSnapshot,
|
||||||
|
right: CircuitGroupSubtreeSnapshot
|
||||||
|
) {
|
||||||
|
return canonicalJson(left) === canonicalJson(right);
|
||||||
|
}
|
||||||
|
|
||||||
|
function canonicalJson(value: unknown): string {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return `[${value.map(canonicalJson).join(",")}]`;
|
||||||
|
}
|
||||||
|
if (value !== null && typeof value === "object") {
|
||||||
|
return `{${Object.entries(value)
|
||||||
|
.sort(([left], [right]) => left.localeCompare(right))
|
||||||
|
.map(
|
||||||
|
([key, child]) =>
|
||||||
|
`${JSON.stringify(key)}:${canonicalJson(child)}`
|
||||||
|
)
|
||||||
|
.join(",")}}`;
|
||||||
|
}
|
||||||
|
return JSON.stringify(value) ?? "null";
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
import {
|
||||||
|
assertCircuitGroupSubtreeSnapshot,
|
||||||
|
type CircuitGroupSubtreeSnapshot,
|
||||||
|
} from "./circuit-group-subtree-snapshot.model.js";
|
||||||
|
import type { SerializedProjectCommand } from "./project-command.model.js";
|
||||||
|
|
||||||
|
export const circuitGroupDeleteSubtreeCommandType =
|
||||||
|
"circuit-group.delete-subtree" as const;
|
||||||
|
export const circuitGroupRestoreSubtreeCommandType =
|
||||||
|
"circuit-group.restore-subtree" as const;
|
||||||
|
export const circuitGroupSubtreeCommandSchemaVersion = 1 as const;
|
||||||
|
|
||||||
|
interface Payload {
|
||||||
|
snapshot: CircuitGroupSubtreeSnapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CircuitGroupDeleteSubtreeProjectCommand
|
||||||
|
extends SerializedProjectCommand<Payload> {
|
||||||
|
schemaVersion: typeof circuitGroupSubtreeCommandSchemaVersion;
|
||||||
|
type: typeof circuitGroupDeleteSubtreeCommandType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CircuitGroupRestoreSubtreeProjectCommand
|
||||||
|
extends SerializedProjectCommand<Payload> {
|
||||||
|
schemaVersion: typeof circuitGroupSubtreeCommandSchemaVersion;
|
||||||
|
type: typeof circuitGroupRestoreSubtreeCommandType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CircuitGroupSubtreeProjectCommand =
|
||||||
|
| CircuitGroupDeleteSubtreeProjectCommand
|
||||||
|
| CircuitGroupRestoreSubtreeProjectCommand;
|
||||||
|
|
||||||
|
export function createCircuitGroupDeleteSubtreeProjectCommand(
|
||||||
|
snapshot: CircuitGroupSubtreeSnapshot
|
||||||
|
): CircuitGroupDeleteSubtreeProjectCommand {
|
||||||
|
const command: CircuitGroupDeleteSubtreeProjectCommand = {
|
||||||
|
schemaVersion: circuitGroupSubtreeCommandSchemaVersion,
|
||||||
|
type: circuitGroupDeleteSubtreeCommandType,
|
||||||
|
payload: { snapshot },
|
||||||
|
};
|
||||||
|
assertCircuitGroupDeleteSubtreeProjectCommand(command);
|
||||||
|
return command;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createCircuitGroupRestoreSubtreeProjectCommand(
|
||||||
|
snapshot: CircuitGroupSubtreeSnapshot
|
||||||
|
): CircuitGroupRestoreSubtreeProjectCommand {
|
||||||
|
const command: CircuitGroupRestoreSubtreeProjectCommand = {
|
||||||
|
schemaVersion: circuitGroupSubtreeCommandSchemaVersion,
|
||||||
|
type: circuitGroupRestoreSubtreeCommandType,
|
||||||
|
payload: { snapshot },
|
||||||
|
};
|
||||||
|
assertCircuitGroupRestoreSubtreeProjectCommand(command);
|
||||||
|
return command;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function assertCircuitGroupDeleteSubtreeProjectCommand(
|
||||||
|
command: SerializedProjectCommand<unknown>
|
||||||
|
): asserts command is CircuitGroupDeleteSubtreeProjectCommand {
|
||||||
|
assertCommand(command, circuitGroupDeleteSubtreeCommandType);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function assertCircuitGroupRestoreSubtreeProjectCommand(
|
||||||
|
command: SerializedProjectCommand<unknown>
|
||||||
|
): asserts command is CircuitGroupRestoreSubtreeProjectCommand {
|
||||||
|
assertCommand(command, circuitGroupRestoreSubtreeCommandType);
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertCommand(
|
||||||
|
command: SerializedProjectCommand<unknown>,
|
||||||
|
type:
|
||||||
|
| typeof circuitGroupDeleteSubtreeCommandType
|
||||||
|
| typeof circuitGroupRestoreSubtreeCommandType
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
command.schemaVersion !== circuitGroupSubtreeCommandSchemaVersion ||
|
||||||
|
command.type !== type ||
|
||||||
|
!isPlainObject(command.payload) ||
|
||||||
|
Object.keys(command.payload).length !== 1
|
||||||
|
) {
|
||||||
|
throw new Error("Unsupported circuit-group subtree command.");
|
||||||
|
}
|
||||||
|
assertCircuitGroupSubtreeSnapshot(command.payload.snapshot);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
||||||
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import type { CircuitGroupSubtreeSnapshot } from "../models/circuit-group-subtree-snapshot.model.js";
|
||||||
|
import type { CircuitGroupSubtreeProjectCommand } from "../models/circuit-group-subtree-project-command.model.js";
|
||||||
|
import type {
|
||||||
|
AppendedProjectRevision,
|
||||||
|
ProjectRevisionSource,
|
||||||
|
} from "./project-revision.store.js";
|
||||||
|
|
||||||
|
export interface ExecuteCircuitGroupSubtreeCommandInput {
|
||||||
|
projectId: string;
|
||||||
|
expectedRevision: number;
|
||||||
|
source: ProjectRevisionSource;
|
||||||
|
description?: string;
|
||||||
|
actorId?: string;
|
||||||
|
historyTargetChangeSetId?: string;
|
||||||
|
command: CircuitGroupSubtreeProjectCommand;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CircuitGroupSubtreeProjectCommandStore {
|
||||||
|
capture(projectId: string, groupId: string): CircuitGroupSubtreeSnapshot;
|
||||||
|
execute(input: ExecuteCircuitGroupSubtreeCommandInput): {
|
||||||
|
revision: AppendedProjectRevision;
|
||||||
|
inverse: CircuitGroupSubtreeProjectCommand;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -101,6 +101,7 @@ import type { CircuitStructureProjectCommandStore } from "../ports/circuit-struc
|
|||||||
import type { CircuitGroupStructureProjectCommandStore } from "../ports/circuit-group-structure-project-command.store.js";
|
import type { CircuitGroupStructureProjectCommandStore } from "../ports/circuit-group-structure-project-command.store.js";
|
||||||
import type { CircuitGroupRenumberProjectCommandStore } from "../ports/circuit-group-renumber-project-command.store.js";
|
import type { CircuitGroupRenumberProjectCommandStore } from "../ports/circuit-group-renumber-project-command.store.js";
|
||||||
import type { CircuitGroupMoveProjectCommandStore } from "../ports/circuit-group-move-project-command.store.js";
|
import type { CircuitGroupMoveProjectCommandStore } from "../ports/circuit-group-move-project-command.store.js";
|
||||||
|
import type { CircuitGroupSubtreeProjectCommandStore } from "../ports/circuit-group-subtree-project-command.store.js";
|
||||||
import type { DistributionBoardStructureProjectCommandStore } from "../ports/distribution-board-structure-project-command.store.js";
|
import type { DistributionBoardStructureProjectCommandStore } from "../ports/distribution-board-structure-project-command.store.js";
|
||||||
import type { DistributionBoardComponentStructureProjectCommandStore } from "../ports/distribution-board-component-structure-project-command.store.js";
|
import type { DistributionBoardComponentStructureProjectCommandStore } from "../ports/distribution-board-component-structure-project-command.store.js";
|
||||||
import type {
|
import type {
|
||||||
@@ -131,6 +132,12 @@ import {
|
|||||||
assertCircuitGroupMoveProjectCommand,
|
assertCircuitGroupMoveProjectCommand,
|
||||||
circuitGroupMoveCommandType,
|
circuitGroupMoveCommandType,
|
||||||
} from "../models/circuit-group-move-project-command.model.js";
|
} from "../models/circuit-group-move-project-command.model.js";
|
||||||
|
import {
|
||||||
|
assertCircuitGroupDeleteSubtreeProjectCommand,
|
||||||
|
assertCircuitGroupRestoreSubtreeProjectCommand,
|
||||||
|
circuitGroupDeleteSubtreeCommandType,
|
||||||
|
circuitGroupRestoreSubtreeCommandType,
|
||||||
|
} from "../models/circuit-group-subtree-project-command.model.js";
|
||||||
import type { ProjectLocationStructureProjectCommandStore } from "../ports/project-location-structure-project-command.store.js";
|
import type { ProjectLocationStructureProjectCommandStore } from "../ports/project-location-structure-project-command.store.js";
|
||||||
import type { ProjectDeviceProjectCommandStore } from "../ports/project-device-project-command.store.js";
|
import type { ProjectDeviceProjectCommandStore } from "../ports/project-device-project-command.store.js";
|
||||||
import type { ProjectDeviceRowSyncProjectCommandStore } from "../ports/project-device-row-sync-project-command.store.js";
|
import type { ProjectDeviceRowSyncProjectCommandStore } from "../ports/project-device-row-sync-project-command.store.js";
|
||||||
@@ -169,6 +176,7 @@ export class ProjectCommandService implements ProjectCommandExecutor {
|
|||||||
private readonly circuitGroupStructureStore: CircuitGroupStructureProjectCommandStore,
|
private readonly circuitGroupStructureStore: CircuitGroupStructureProjectCommandStore,
|
||||||
private readonly circuitGroupRenumberStore: CircuitGroupRenumberProjectCommandStore,
|
private readonly circuitGroupRenumberStore: CircuitGroupRenumberProjectCommandStore,
|
||||||
private readonly circuitGroupMoveStore: CircuitGroupMoveProjectCommandStore,
|
private readonly circuitGroupMoveStore: CircuitGroupMoveProjectCommandStore,
|
||||||
|
private readonly circuitGroupSubtreeStore: CircuitGroupSubtreeProjectCommandStore,
|
||||||
private readonly historyStore: ProjectHistoryStore
|
private readonly historyStore: ProjectHistoryStore
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@@ -399,6 +407,20 @@ export class ProjectCommandService implements ProjectCommandExecutor {
|
|||||||
command: input.command,
|
command: input.command,
|
||||||
}).revision;
|
}).revision;
|
||||||
}
|
}
|
||||||
|
case circuitGroupDeleteSubtreeCommandType: {
|
||||||
|
assertCircuitGroupDeleteSubtreeProjectCommand(input.command);
|
||||||
|
return this.circuitGroupSubtreeStore.execute({
|
||||||
|
...input,
|
||||||
|
command: input.command,
|
||||||
|
}).revision;
|
||||||
|
}
|
||||||
|
case circuitGroupRestoreSubtreeCommandType: {
|
||||||
|
assertCircuitGroupRestoreSubtreeProjectCommand(input.command);
|
||||||
|
return this.circuitGroupSubtreeStore.execute({
|
||||||
|
...input,
|
||||||
|
command: input.command,
|
||||||
|
}).revision;
|
||||||
|
}
|
||||||
case projectFloorInsertCommandType: {
|
case projectFloorInsertCommandType: {
|
||||||
assertProjectFloorInsertProjectCommand(input.command);
|
assertProjectFloorInsertProjectCommand(input.command);
|
||||||
return this.projectLocationStructureStore.execute({
|
return this.projectLocationStructureStore.execute({
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ const commandTypeLabels: Record<string, string> = {
|
|||||||
"circuit-group.reorder": "Stromkreisgruppen sortiert",
|
"circuit-group.reorder": "Stromkreisgruppen sortiert",
|
||||||
"circuit-group.renumber": "Stromkreisgruppen neu nummeriert",
|
"circuit-group.renumber": "Stromkreisgruppen neu nummeriert",
|
||||||
"circuit.move-group": "Stromkreis in andere Gruppe verschoben",
|
"circuit.move-group": "Stromkreis in andere Gruppe verschoben",
|
||||||
|
"circuit-group.delete-subtree": "Stromkreisgruppe vollständig entfernt",
|
||||||
|
"circuit-group.restore-subtree": "Stromkreisgruppe vollständig wiederhergestellt",
|
||||||
"project-floor.insert": "Geschoss angelegt",
|
"project-floor.insert": "Geschoss angelegt",
|
||||||
"project-floor.delete": "Geschoss entfernt",
|
"project-floor.delete": "Geschoss entfernt",
|
||||||
"project-room.insert": "Raum angelegt",
|
"project-room.insert": "Raum angelegt",
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { CircuitStructureProjectCommandRepository } from "../../db/repositories/
|
|||||||
import { CircuitGroupStructureProjectCommandRepository } from "../../db/repositories/circuit-group-structure-project-command.repository.js";
|
import { CircuitGroupStructureProjectCommandRepository } from "../../db/repositories/circuit-group-structure-project-command.repository.js";
|
||||||
import { CircuitGroupRenumberProjectCommandRepository } from "../../db/repositories/circuit-group-renumber-project-command.repository.js";
|
import { CircuitGroupRenumberProjectCommandRepository } from "../../db/repositories/circuit-group-renumber-project-command.repository.js";
|
||||||
import { CircuitGroupMoveProjectCommandRepository } from "../../db/repositories/circuit-group-move-project-command.repository.js";
|
import { CircuitGroupMoveProjectCommandRepository } from "../../db/repositories/circuit-group-move-project-command.repository.js";
|
||||||
|
import { CircuitGroupSubtreeProjectCommandRepository } from "../../db/repositories/circuit-group-subtree-project-command.repository.js";
|
||||||
import { DistributionBoardStructureProjectCommandRepository } from "../../db/repositories/distribution-board-structure-project-command.repository.js";
|
import { DistributionBoardStructureProjectCommandRepository } from "../../db/repositories/distribution-board-structure-project-command.repository.js";
|
||||||
import { DistributionBoardComponentStructureProjectCommandRepository } from "../../db/repositories/distribution-board-component-structure-project-command.repository.js";
|
import { DistributionBoardComponentStructureProjectCommandRepository } from "../../db/repositories/distribution-board-component-structure-project-command.repository.js";
|
||||||
import { ProjectHistoryRepository } from "../../db/repositories/project-history.repository.js";
|
import { ProjectHistoryRepository } from "../../db/repositories/project-history.repository.js";
|
||||||
@@ -35,6 +36,8 @@ export const circuitGroupRenumberProjectCommandStore =
|
|||||||
new CircuitGroupRenumberProjectCommandRepository(db);
|
new CircuitGroupRenumberProjectCommandRepository(db);
|
||||||
export const circuitGroupMoveProjectCommandStore =
|
export const circuitGroupMoveProjectCommandStore =
|
||||||
new CircuitGroupMoveProjectCommandRepository(db);
|
new CircuitGroupMoveProjectCommandRepository(db);
|
||||||
|
export const circuitGroupSubtreeProjectCommandStore =
|
||||||
|
new CircuitGroupSubtreeProjectCommandRepository(db);
|
||||||
export const distributionBoardStructureProjectCommandStore =
|
export const distributionBoardStructureProjectCommandStore =
|
||||||
new DistributionBoardStructureProjectCommandRepository(db);
|
new DistributionBoardStructureProjectCommandRepository(db);
|
||||||
export const distributionBoardComponentStructureProjectCommandStore =
|
export const distributionBoardComponentStructureProjectCommandStore =
|
||||||
@@ -75,5 +78,6 @@ export const projectCommandService = new ProjectCommandService(
|
|||||||
circuitGroupStructureProjectCommandStore,
|
circuitGroupStructureProjectCommandStore,
|
||||||
circuitGroupRenumberProjectCommandStore,
|
circuitGroupRenumberProjectCommandStore,
|
||||||
circuitGroupMoveProjectCommandStore,
|
circuitGroupMoveProjectCommandStore,
|
||||||
|
circuitGroupSubtreeProjectCommandStore,
|
||||||
projectHistoryStore
|
projectHistoryStore
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import "./circuit-group-renumber-project-command.repository.test.js";
|
|||||||
import { createCircuitGroupMovePlan } from "../src/domain/services/circuit-group-move-planning.js";
|
import { createCircuitGroupMovePlan } from "../src/domain/services/circuit-group-move-planning.js";
|
||||||
import "./circuit-group-move-project-command.repository.test.js";
|
import "./circuit-group-move-project-command.repository.test.js";
|
||||||
import "./circuit-group-subtree-snapshot.test.js";
|
import "./circuit-group-subtree-snapshot.test.js";
|
||||||
|
import "./circuit-group-subtree-project-command.repository.test.js";
|
||||||
|
|
||||||
describe("circuit group numbering", () => {
|
describe("circuit group numbering", () => {
|
||||||
it("formats the agreed identifiers including the leading hyphen", () => {
|
it("formats the agreed identifiers including the leading hyphen", () => {
|
||||||
|
|||||||
@@ -0,0 +1,269 @@
|
|||||||
|
import path from "node:path";
|
||||||
|
import assert from "node:assert/strict";
|
||||||
|
import { describe, it } from "node:test";
|
||||||
|
import { eq } from "drizzle-orm";
|
||||||
|
import { migrate } from "drizzle-orm/better-sqlite3/migrator";
|
||||||
|
import {
|
||||||
|
createDatabaseContext,
|
||||||
|
type DatabaseContext,
|
||||||
|
} from "../src/db/database-context.js";
|
||||||
|
import { CircuitGroupSubtreeProjectCommandRepository } from "../src/db/repositories/circuit-group-subtree-project-command.repository.js";
|
||||||
|
import { ProjectHistoryRepository } from "../src/db/repositories/project-history.repository.js";
|
||||||
|
import { circuitDeviceRows } from "../src/db/schema/circuit-device-rows.js";
|
||||||
|
import { circuitLists } from "../src/db/schema/circuit-lists.js";
|
||||||
|
import { circuitProtectionDevices } from "../src/db/schema/circuit-protection-devices.js";
|
||||||
|
import { circuitSections } from "../src/db/schema/circuit-sections.js";
|
||||||
|
import { circuits } from "../src/db/schema/circuits.js";
|
||||||
|
import { distributionBoardComponentProtectionDevices } from "../src/db/schema/distribution-board-component-protection-devices.js";
|
||||||
|
import { distributionBoardComponents } from "../src/db/schema/distribution-board-components.js";
|
||||||
|
import { projectRevisions } from "../src/db/schema/project-revisions.js";
|
||||||
|
import { projects } from "../src/db/schema/projects.js";
|
||||||
|
import { createCircuitGroupDeleteSubtreeProjectCommand } from "../src/domain/models/circuit-group-subtree-project-command.model.js";
|
||||||
|
import { DistributionBoardFixtureRepository } from "./support/distribution-board-fixture.js";
|
||||||
|
|
||||||
|
function createTestDatabase(): DatabaseContext {
|
||||||
|
const context = createDatabaseContext(":memory:");
|
||||||
|
migrate(context.db, {
|
||||||
|
migrationsFolder: path.resolve("src", "db", "migrations"),
|
||||||
|
});
|
||||||
|
context.db
|
||||||
|
.insert(projects)
|
||||||
|
.values({ id: "project-1", name: "Projekt" })
|
||||||
|
.run();
|
||||||
|
new DistributionBoardFixtureRepository(
|
||||||
|
context.db
|
||||||
|
).createWithCircuitListAndDefaultSections("project-1", "UV-01");
|
||||||
|
const list = context.db.select().from(circuitLists).get()!;
|
||||||
|
const group = context.db
|
||||||
|
.select()
|
||||||
|
.from(circuitSections)
|
||||||
|
.where(eq(circuitSections.category, "lighting"))
|
||||||
|
.get()!;
|
||||||
|
context.db
|
||||||
|
.insert(distributionBoardComponents)
|
||||||
|
.values({
|
||||||
|
id: "subtree-rcd",
|
||||||
|
circuitListId: list.id,
|
||||||
|
sectionId: group.id,
|
||||||
|
equipmentIdentifier: "-1Q1.0",
|
||||||
|
name: "Gruppen-FI",
|
||||||
|
role: "group_residual_current_protection",
|
||||||
|
placement: "group",
|
||||||
|
sortOrder: 10,
|
||||||
|
})
|
||||||
|
.run();
|
||||||
|
context.db
|
||||||
|
.insert(distributionBoardComponentProtectionDevices)
|
||||||
|
.values({
|
||||||
|
componentId: "subtree-rcd",
|
||||||
|
type: "FI",
|
||||||
|
ratedCurrentA: 40,
|
||||||
|
rcdType: "A",
|
||||||
|
ratedResidualCurrentMa: 30,
|
||||||
|
})
|
||||||
|
.run();
|
||||||
|
context.db
|
||||||
|
.insert(circuits)
|
||||||
|
.values({
|
||||||
|
id: "subtree-circuit",
|
||||||
|
circuitListId: list.id,
|
||||||
|
sectionId: group.id,
|
||||||
|
equipmentIdentifier: "-1F1.1",
|
||||||
|
displayName: "Beleuchtung",
|
||||||
|
sortOrder: 10,
|
||||||
|
voltage: 230,
|
||||||
|
isReserve: 0,
|
||||||
|
})
|
||||||
|
.run();
|
||||||
|
context.db
|
||||||
|
.insert(circuitProtectionDevices)
|
||||||
|
.values({
|
||||||
|
circuitId: "subtree-circuit",
|
||||||
|
type: "LS",
|
||||||
|
ratedCurrentA: 10,
|
||||||
|
tripCharacteristic: "B",
|
||||||
|
})
|
||||||
|
.run();
|
||||||
|
context.db
|
||||||
|
.insert(circuitDeviceRows)
|
||||||
|
.values({
|
||||||
|
id: "subtree-row",
|
||||||
|
circuitId: "subtree-circuit",
|
||||||
|
linkedProjectDeviceId: null,
|
||||||
|
legacyConsumerId: null,
|
||||||
|
sortOrder: 10,
|
||||||
|
name: "Leuchte",
|
||||||
|
displayName: "Leuchte",
|
||||||
|
phaseType: "single_phase",
|
||||||
|
connectionKind: null,
|
||||||
|
costGroup: null,
|
||||||
|
category: null,
|
||||||
|
level: null,
|
||||||
|
roomId: null,
|
||||||
|
roomNumberSnapshot: null,
|
||||||
|
roomNameSnapshot: null,
|
||||||
|
quantity: 2,
|
||||||
|
powerPerUnit: 0.03,
|
||||||
|
simultaneityFactor: 1,
|
||||||
|
cosPhi: 0.95,
|
||||||
|
remark: null,
|
||||||
|
overriddenFields: "[\"displayName\"]",
|
||||||
|
})
|
||||||
|
.run();
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("circuit-group subtree project command", () => {
|
||||||
|
it("deletes and restores the exact populated subtree through undo/redo", () => {
|
||||||
|
const context = createTestDatabase();
|
||||||
|
try {
|
||||||
|
const repository =
|
||||||
|
new CircuitGroupSubtreeProjectCommandRepository(context.db);
|
||||||
|
const groupId = context.db
|
||||||
|
.select()
|
||||||
|
.from(circuits)
|
||||||
|
.where(eq(circuits.id, "subtree-circuit"))
|
||||||
|
.get()!.sectionId;
|
||||||
|
const snapshot = repository.capture("project-1", groupId);
|
||||||
|
const command =
|
||||||
|
createCircuitGroupDeleteSubtreeProjectCommand(snapshot);
|
||||||
|
const deleted = repository.execute({
|
||||||
|
projectId: "project-1",
|
||||||
|
expectedRevision: 0,
|
||||||
|
source: "user",
|
||||||
|
command,
|
||||||
|
});
|
||||||
|
assert.equal(
|
||||||
|
context.db
|
||||||
|
.select()
|
||||||
|
.from(circuitSections)
|
||||||
|
.where(eq(circuitSections.id, groupId))
|
||||||
|
.get(),
|
||||||
|
undefined
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
context.db
|
||||||
|
.select()
|
||||||
|
.from(circuitDeviceRows)
|
||||||
|
.where(eq(circuitDeviceRows.id, "subtree-row"))
|
||||||
|
.get(),
|
||||||
|
undefined
|
||||||
|
);
|
||||||
|
|
||||||
|
repository.execute({
|
||||||
|
projectId: "project-1",
|
||||||
|
expectedRevision: 1,
|
||||||
|
source: "undo",
|
||||||
|
historyTargetChangeSetId: new ProjectHistoryRepository(
|
||||||
|
context.db
|
||||||
|
).getNextCommand("project-1", "undo")?.changeSetId,
|
||||||
|
command: deleted.inverse,
|
||||||
|
});
|
||||||
|
assert.deepEqual(
|
||||||
|
repository.capture("project-1", groupId),
|
||||||
|
snapshot
|
||||||
|
);
|
||||||
|
|
||||||
|
repository.execute({
|
||||||
|
projectId: "project-1",
|
||||||
|
expectedRevision: 2,
|
||||||
|
source: "redo",
|
||||||
|
historyTargetChangeSetId: new ProjectHistoryRepository(
|
||||||
|
context.db
|
||||||
|
).getNextCommand("project-1", "redo")?.changeSetId,
|
||||||
|
command,
|
||||||
|
});
|
||||||
|
assert.equal(
|
||||||
|
context.db
|
||||||
|
.select()
|
||||||
|
.from(circuits)
|
||||||
|
.where(eq(circuits.id, "subtree-circuit"))
|
||||||
|
.get(),
|
||||||
|
undefined
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
context.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects a subtree changed after confirmation", () => {
|
||||||
|
const context = createTestDatabase();
|
||||||
|
try {
|
||||||
|
const repository =
|
||||||
|
new CircuitGroupSubtreeProjectCommandRepository(context.db);
|
||||||
|
const groupId = context.db
|
||||||
|
.select()
|
||||||
|
.from(circuits)
|
||||||
|
.where(eq(circuits.id, "subtree-circuit"))
|
||||||
|
.get()!.sectionId;
|
||||||
|
const snapshot = repository.capture("project-1", groupId);
|
||||||
|
context.db
|
||||||
|
.update(circuitDeviceRows)
|
||||||
|
.set({ displayName: "Geändert" })
|
||||||
|
.where(eq(circuitDeviceRows.id, "subtree-row"))
|
||||||
|
.run();
|
||||||
|
assert.throws(
|
||||||
|
() =>
|
||||||
|
repository.execute({
|
||||||
|
projectId: "project-1",
|
||||||
|
expectedRevision: 0,
|
||||||
|
source: "user",
|
||||||
|
command:
|
||||||
|
createCircuitGroupDeleteSubtreeProjectCommand(snapshot),
|
||||||
|
}),
|
||||||
|
/changed before deletion/
|
||||||
|
);
|
||||||
|
assert.ok(
|
||||||
|
context.db
|
||||||
|
.select()
|
||||||
|
.from(circuitSections)
|
||||||
|
.where(eq(circuitSections.id, groupId))
|
||||||
|
.get()
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
context.db.select().from(projectRevisions).all().length,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
context.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rolls back a cascaded deletion when history persistence fails", () => {
|
||||||
|
const context = createTestDatabase();
|
||||||
|
try {
|
||||||
|
const repository =
|
||||||
|
new CircuitGroupSubtreeProjectCommandRepository(context.db);
|
||||||
|
const groupId = context.db
|
||||||
|
.select()
|
||||||
|
.from(circuits)
|
||||||
|
.where(eq(circuits.id, "subtree-circuit"))
|
||||||
|
.get()!.sectionId;
|
||||||
|
const snapshot = repository.capture("project-1", groupId);
|
||||||
|
context.sqlite.exec(`
|
||||||
|
CREATE TRIGGER fail_group_subtree_history
|
||||||
|
BEFORE INSERT ON project_history_stack_entries
|
||||||
|
BEGIN
|
||||||
|
SELECT RAISE(ABORT, 'forced group subtree history failure');
|
||||||
|
END;
|
||||||
|
`);
|
||||||
|
assert.throws(
|
||||||
|
() =>
|
||||||
|
repository.execute({
|
||||||
|
projectId: "project-1",
|
||||||
|
expectedRevision: 0,
|
||||||
|
source: "user",
|
||||||
|
command:
|
||||||
|
createCircuitGroupDeleteSubtreeProjectCommand(snapshot),
|
||||||
|
}),
|
||||||
|
/forced group subtree history failure/
|
||||||
|
);
|
||||||
|
assert.deepEqual(
|
||||||
|
repository.capture("project-1", groupId),
|
||||||
|
snapshot
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
context.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -17,6 +17,7 @@ import { CircuitStructureProjectCommandRepository } from "../src/db/repositories
|
|||||||
import { CircuitGroupStructureProjectCommandRepository } from "../src/db/repositories/circuit-group-structure-project-command.repository.js";
|
import { CircuitGroupStructureProjectCommandRepository } from "../src/db/repositories/circuit-group-structure-project-command.repository.js";
|
||||||
import { CircuitGroupRenumberProjectCommandRepository } from "../src/db/repositories/circuit-group-renumber-project-command.repository.js";
|
import { CircuitGroupRenumberProjectCommandRepository } from "../src/db/repositories/circuit-group-renumber-project-command.repository.js";
|
||||||
import { CircuitGroupMoveProjectCommandRepository } from "../src/db/repositories/circuit-group-move-project-command.repository.js";
|
import { CircuitGroupMoveProjectCommandRepository } from "../src/db/repositories/circuit-group-move-project-command.repository.js";
|
||||||
|
import { CircuitGroupSubtreeProjectCommandRepository } from "../src/db/repositories/circuit-group-subtree-project-command.repository.js";
|
||||||
import { DistributionBoardFixtureRepository } from "./support/distribution-board-fixture.js";
|
import { DistributionBoardFixtureRepository } from "./support/distribution-board-fixture.js";
|
||||||
import { DistributionBoardStructureProjectCommandRepository } from "../src/db/repositories/distribution-board-structure-project-command.repository.js";
|
import { DistributionBoardStructureProjectCommandRepository } from "../src/db/repositories/distribution-board-structure-project-command.repository.js";
|
||||||
import { DistributionBoardComponentStructureProjectCommandRepository } from "../src/db/repositories/distribution-board-component-structure-project-command.repository.js";
|
import { DistributionBoardComponentStructureProjectCommandRepository } from "../src/db/repositories/distribution-board-component-structure-project-command.repository.js";
|
||||||
@@ -59,6 +60,7 @@ import { createCircuitGroupRenumberProjectCommand } from "../src/domain/models/c
|
|||||||
import { createCircuitGroupRenumberPlan } from "../src/domain/services/circuit-group-renumbering.js";
|
import { createCircuitGroupRenumberPlan } from "../src/domain/services/circuit-group-renumbering.js";
|
||||||
import { createCircuitGroupMoveProjectCommand } from "../src/domain/models/circuit-group-move-project-command.model.js";
|
import { createCircuitGroupMoveProjectCommand } from "../src/domain/models/circuit-group-move-project-command.model.js";
|
||||||
import { createCircuitGroupMovePlan } from "../src/domain/services/circuit-group-move-planning.js";
|
import { createCircuitGroupMovePlan } from "../src/domain/services/circuit-group-move-planning.js";
|
||||||
|
import { createCircuitGroupDeleteSubtreeProjectCommand } from "../src/domain/models/circuit-group-subtree-project-command.model.js";
|
||||||
import {
|
import {
|
||||||
createDistributionBoardInsertProjectCommand,
|
createDistributionBoardInsertProjectCommand,
|
||||||
createDistributionBoardStructureSnapshot,
|
createDistributionBoardStructureSnapshot,
|
||||||
@@ -146,6 +148,7 @@ function createService(context: DatabaseContext) {
|
|||||||
new CircuitGroupStructureProjectCommandRepository(context.db),
|
new CircuitGroupStructureProjectCommandRepository(context.db),
|
||||||
new CircuitGroupRenumberProjectCommandRepository(context.db),
|
new CircuitGroupRenumberProjectCommandRepository(context.db),
|
||||||
new CircuitGroupMoveProjectCommandRepository(context.db),
|
new CircuitGroupMoveProjectCommandRepository(context.db),
|
||||||
|
new CircuitGroupSubtreeProjectCommandRepository(context.db),
|
||||||
new ProjectHistoryRepository(context.db)
|
new ProjectHistoryRepository(context.db)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1447,6 +1450,47 @@ describe("project command service", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("dispatches populated circuit-group deletion and restoration", () => {
|
||||||
|
const context = createTestDatabase();
|
||||||
|
try {
|
||||||
|
const repository =
|
||||||
|
new CircuitGroupSubtreeProjectCommandRepository(context.db);
|
||||||
|
const groupId = context.db
|
||||||
|
.select()
|
||||||
|
.from(circuits)
|
||||||
|
.where(eq(circuits.id, "circuit-1"))
|
||||||
|
.get()!.sectionId;
|
||||||
|
const snapshot = repository.capture("project-1", groupId);
|
||||||
|
const deleted = createService(context).executeUser({
|
||||||
|
projectId: "project-1",
|
||||||
|
expectedRevision: 0,
|
||||||
|
command:
|
||||||
|
createCircuitGroupDeleteSubtreeProjectCommand(snapshot),
|
||||||
|
});
|
||||||
|
assert.equal(deleted.history.currentRevision, 1);
|
||||||
|
assert.equal(
|
||||||
|
context.db
|
||||||
|
.select()
|
||||||
|
.from(circuitSections)
|
||||||
|
.where(eq(circuitSections.id, groupId))
|
||||||
|
.get(),
|
||||||
|
undefined
|
||||||
|
);
|
||||||
|
|
||||||
|
const restored = createService(context).undo({
|
||||||
|
projectId: "project-1",
|
||||||
|
expectedRevision: 1,
|
||||||
|
});
|
||||||
|
assert.equal(restored.history.currentRevision, 2);
|
||||||
|
assert.deepEqual(
|
||||||
|
repository.capture("project-1", groupId),
|
||||||
|
snapshot
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
context.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it("dispatches floor and room setup with their persisted inverses", () => {
|
it("dispatches floor and room setup with their persisted inverses", () => {
|
||||||
const context = createTestDatabase();
|
const context = createTestDatabase();
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { CircuitStructureProjectCommandRepository } from "../src/db/repositories
|
|||||||
import { CircuitGroupStructureProjectCommandRepository } from "../src/db/repositories/circuit-group-structure-project-command.repository.js";
|
import { CircuitGroupStructureProjectCommandRepository } from "../src/db/repositories/circuit-group-structure-project-command.repository.js";
|
||||||
import { CircuitGroupRenumberProjectCommandRepository } from "../src/db/repositories/circuit-group-renumber-project-command.repository.js";
|
import { CircuitGroupRenumberProjectCommandRepository } from "../src/db/repositories/circuit-group-renumber-project-command.repository.js";
|
||||||
import { CircuitGroupMoveProjectCommandRepository } from "../src/db/repositories/circuit-group-move-project-command.repository.js";
|
import { CircuitGroupMoveProjectCommandRepository } from "../src/db/repositories/circuit-group-move-project-command.repository.js";
|
||||||
|
import { CircuitGroupSubtreeProjectCommandRepository } from "../src/db/repositories/circuit-group-subtree-project-command.repository.js";
|
||||||
import { DistributionBoardFixtureRepository } from "./support/distribution-board-fixture.js";
|
import { DistributionBoardFixtureRepository } from "./support/distribution-board-fixture.js";
|
||||||
import { DistributionBoardStructureProjectCommandRepository } from "../src/db/repositories/distribution-board-structure-project-command.repository.js";
|
import { DistributionBoardStructureProjectCommandRepository } from "../src/db/repositories/distribution-board-structure-project-command.repository.js";
|
||||||
import { DistributionBoardComponentStructureProjectCommandRepository } from "../src/db/repositories/distribution-board-component-structure-project-command.repository.js";
|
import { DistributionBoardComponentStructureProjectCommandRepository } from "../src/db/repositories/distribution-board-component-structure-project-command.repository.js";
|
||||||
@@ -201,6 +202,7 @@ function createService(context: DatabaseContext) {
|
|||||||
new CircuitGroupStructureProjectCommandRepository(context.db),
|
new CircuitGroupStructureProjectCommandRepository(context.db),
|
||||||
new CircuitGroupRenumberProjectCommandRepository(context.db),
|
new CircuitGroupRenumberProjectCommandRepository(context.db),
|
||||||
new CircuitGroupMoveProjectCommandRepository(context.db),
|
new CircuitGroupMoveProjectCommandRepository(context.db),
|
||||||
|
new CircuitGroupSubtreeProjectCommandRepository(context.db),
|
||||||
new ProjectHistoryRepository(context.db)
|
new ProjectHistoryRepository(context.db)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -175,6 +175,22 @@ describe("project version history presentation", () => {
|
|||||||
),
|
),
|
||||||
"Stromkreis in andere Gruppe verschoben"
|
"Stromkreis in andere Gruppe verschoben"
|
||||||
);
|
);
|
||||||
|
assert.equal(
|
||||||
|
getProjectRevisionDescription(
|
||||||
|
revision(14, {
|
||||||
|
commandType: "circuit-group.delete-subtree",
|
||||||
|
})
|
||||||
|
),
|
||||||
|
"Stromkreisgruppe vollständig entfernt"
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
getProjectRevisionDescription(
|
||||||
|
revision(15, {
|
||||||
|
commandType: "circuit-group.restore-subtree",
|
||||||
|
})
|
||||||
|
),
|
||||||
|
"Stromkreisgruppe vollständig wiederhergestellt"
|
||||||
|
);
|
||||||
assert.equal(getProjectSnapshotKindLabel("named"), "Benannt");
|
assert.equal(getProjectSnapshotKindLabel("named"), "Benannt");
|
||||||
assert.equal(
|
assert.equal(
|
||||||
getProjectSnapshotKindLabel("automatic"),
|
getProjectSnapshotKindLabel("automatic"),
|
||||||
|
|||||||
Reference in New Issue
Block a user