Persist circuit group moves

This commit is contained in:
2026-07-30 20:03:11 +02:00
parent 9ad91887b2
commit 3399fcd88b
15 changed files with 652 additions and 4 deletions
@@ -0,0 +1,92 @@
import { parseGroupedEquipmentIdentifier } from "../services/circuit-group-numbering.js";
import type { CircuitGroupMovePlan } from "../services/circuit-group-move-planning.js";
import type { SerializedProjectCommand } from "./project-command.model.js";
export const circuitGroupMoveCommandType = "circuit.move-group" as const;
export const circuitGroupMoveCommandSchemaVersion = 1 as const;
export interface CircuitGroupMoveProjectCommand
extends SerializedProjectCommand<CircuitGroupMovePlan> {
schemaVersion: typeof circuitGroupMoveCommandSchemaVersion;
type: typeof circuitGroupMoveCommandType;
}
export function createCircuitGroupMoveProjectCommand(
plan: CircuitGroupMovePlan
): CircuitGroupMoveProjectCommand {
const command: CircuitGroupMoveProjectCommand = {
schemaVersion: circuitGroupMoveCommandSchemaVersion,
type: circuitGroupMoveCommandType,
payload: plan,
};
assertCircuitGroupMoveProjectCommand(command);
return command;
}
export function assertCircuitGroupMoveProjectCommand(
command: SerializedProjectCommand<unknown>
): asserts command is CircuitGroupMoveProjectCommand {
if (
command.schemaVersion !== circuitGroupMoveCommandSchemaVersion ||
command.type !== circuitGroupMoveCommandType ||
!isPlainObject(command.payload) ||
Object.keys(command.payload).length !== 8
) {
throw new Error("Unsupported circuit group-move command.");
}
for (const field of [
"circuitId",
"circuitListId",
"expectedSectionId",
"targetSectionId",
"expectedEquipmentIdentifier",
"targetEquipmentIdentifier",
] as const) {
if (
typeof command.payload[field] !== "string" ||
!command.payload[field].trim()
) {
throw new Error(`Circuit group-move ${field} is invalid.`);
}
}
if (
command.payload.expectedSectionId === command.payload.targetSectionId ||
!Number.isFinite(command.payload.expectedSortOrder) ||
!Number.isFinite(command.payload.targetSortOrder)
) {
throw new Error("Circuit group-move positions are invalid.");
}
const expected = parseGroupedEquipmentIdentifier(
command.payload.expectedEquipmentIdentifier as string
);
const target = parseGroupedEquipmentIdentifier(
command.payload.targetEquipmentIdentifier as string
);
if (
expected?.kind !== "circuit" ||
target?.kind !== "circuit" ||
expected.category !== target.category ||
expected.groupNumber === target.groupNumber
) {
throw new Error("Circuit group-move BMKs are incompatible.");
}
}
export function invertCircuitGroupMovePlan(
plan: CircuitGroupMovePlan
): CircuitGroupMovePlan {
return {
circuitId: plan.circuitId,
circuitListId: plan.circuitListId,
expectedSectionId: plan.targetSectionId,
targetSectionId: plan.expectedSectionId,
expectedEquipmentIdentifier: plan.targetEquipmentIdentifier,
targetEquipmentIdentifier: plan.expectedEquipmentIdentifier,
expectedSortOrder: plan.targetSortOrder,
targetSortOrder: plan.expectedSortOrder,
};
}
function isPlainObject(value: unknown): value is Record<string, unknown> {
return value !== null && typeof value === "object" && !Array.isArray(value);
}
@@ -0,0 +1,22 @@
import type { CircuitGroupMoveProjectCommand } from "../models/circuit-group-move-project-command.model.js";
import type {
AppendedProjectRevision,
ProjectRevisionSource,
} from "./project-revision.store.js";
export interface ExecuteCircuitGroupMoveCommandInput {
projectId: string;
expectedRevision: number;
source: ProjectRevisionSource;
description?: string;
actorId?: string;
historyTargetChangeSetId?: string;
command: CircuitGroupMoveProjectCommand;
}
export interface CircuitGroupMoveProjectCommandStore {
execute(input: ExecuteCircuitGroupMoveCommandInput): {
revision: AppendedProjectRevision;
inverse: CircuitGroupMoveProjectCommand;
};
}
@@ -100,6 +100,7 @@ import type { CircuitSectionRenumberProjectCommandStore } from "../ports/circuit
import type { CircuitStructureProjectCommandStore } from "../ports/circuit-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 { CircuitGroupMoveProjectCommandStore } from "../ports/circuit-group-move-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 {
@@ -126,6 +127,10 @@ import {
assertCircuitGroupRenumberProjectCommand,
circuitGroupRenumberCommandType,
} from "../models/circuit-group-renumber-project-command.model.js";
import {
assertCircuitGroupMoveProjectCommand,
circuitGroupMoveCommandType,
} from "../models/circuit-group-move-project-command.model.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 { ProjectDeviceRowSyncProjectCommandStore } from "../ports/project-device-row-sync-project-command.store.js";
@@ -163,6 +168,7 @@ export class ProjectCommandService implements ProjectCommandExecutor {
private readonly distributionBoardComponentStructureStore: DistributionBoardComponentStructureProjectCommandStore,
private readonly circuitGroupStructureStore: CircuitGroupStructureProjectCommandStore,
private readonly circuitGroupRenumberStore: CircuitGroupRenumberProjectCommandStore,
private readonly circuitGroupMoveStore: CircuitGroupMoveProjectCommandStore,
private readonly historyStore: ProjectHistoryStore
) {}
@@ -386,6 +392,13 @@ export class ProjectCommandService implements ProjectCommandExecutor {
command: input.command,
}).revision;
}
case circuitGroupMoveCommandType: {
assertCircuitGroupMoveProjectCommand(input.command);
return this.circuitGroupMoveStore.execute({
...input,
command: input.command,
}).revision;
}
case projectFloorInsertCommandType: {
assertProjectFloorInsertProjectCommand(input.command);
return this.projectLocationStructureStore.execute({