Persist circuit group moves
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import {
|
||||
assertCircuitGroupMoveProjectCommand,
|
||||
createCircuitGroupMoveProjectCommand,
|
||||
invertCircuitGroupMovePlan,
|
||||
} from "../../domain/models/circuit-group-move-project-command.model.js";
|
||||
import type {
|
||||
CircuitGroupMoveProjectCommandStore,
|
||||
ExecuteCircuitGroupMoveCommandInput,
|
||||
} from "../../domain/ports/circuit-group-move-project-command.store.js";
|
||||
import { parseGroupedEquipmentIdentifier } from "../../domain/services/circuit-group-numbering.js";
|
||||
import type { AppDatabase } from "../database-context.js";
|
||||
import { circuitLists } from "../schema/circuit-lists.js";
|
||||
import { circuitSections } from "../schema/circuit-sections.js";
|
||||
import { circuits } from "../schema/circuits.js";
|
||||
import { executeProjectCommandTransaction } from "./project-command-transaction.persistence.js";
|
||||
|
||||
export class CircuitGroupMoveProjectCommandRepository
|
||||
implements CircuitGroupMoveProjectCommandStore
|
||||
{
|
||||
constructor(private readonly database: AppDatabase) {}
|
||||
|
||||
execute(input: ExecuteCircuitGroupMoveCommandInput) {
|
||||
assertCircuitGroupMoveProjectCommand(input.command);
|
||||
return executeProjectCommandTransaction(
|
||||
this.database,
|
||||
input,
|
||||
(tx) => this.applyCommand(tx, input)
|
||||
);
|
||||
}
|
||||
|
||||
private applyCommand(
|
||||
database: AppDatabase,
|
||||
input: ExecuteCircuitGroupMoveCommandInput
|
||||
) {
|
||||
const plan = input.command.payload;
|
||||
const list = database
|
||||
.select({ projectId: circuitLists.projectId })
|
||||
.from(circuitLists)
|
||||
.where(eq(circuitLists.id, plan.circuitListId))
|
||||
.get();
|
||||
if (!list || list.projectId !== input.projectId) {
|
||||
throw new Error("Circuit list does not belong to project.");
|
||||
}
|
||||
const source = database
|
||||
.select()
|
||||
.from(circuitSections)
|
||||
.where(eq(circuitSections.id, plan.expectedSectionId))
|
||||
.get();
|
||||
const target = database
|
||||
.select()
|
||||
.from(circuitSections)
|
||||
.where(eq(circuitSections.id, plan.targetSectionId))
|
||||
.get();
|
||||
const expectedBmk = parseGroupedEquipmentIdentifier(
|
||||
plan.expectedEquipmentIdentifier
|
||||
);
|
||||
const targetBmk = parseGroupedEquipmentIdentifier(
|
||||
plan.targetEquipmentIdentifier
|
||||
);
|
||||
if (
|
||||
!source ||
|
||||
!target ||
|
||||
source.circuitListId !== plan.circuitListId ||
|
||||
target.circuitListId !== plan.circuitListId ||
|
||||
source.category === null ||
|
||||
source.category !== target.category ||
|
||||
source.category !== expectedBmk?.category ||
|
||||
source.groupNumber !== expectedBmk.groupNumber ||
|
||||
target.category !== targetBmk?.category ||
|
||||
target.groupNumber !== targetBmk.groupNumber
|
||||
) {
|
||||
throw new Error(
|
||||
"Circuit move requires matching source and target groups."
|
||||
);
|
||||
}
|
||||
const updated = database
|
||||
.update(circuits)
|
||||
.set({
|
||||
sectionId: plan.targetSectionId,
|
||||
equipmentIdentifier: plan.targetEquipmentIdentifier,
|
||||
sortOrder: plan.targetSortOrder,
|
||||
})
|
||||
.where(
|
||||
and(
|
||||
eq(circuits.id, plan.circuitId),
|
||||
eq(circuits.circuitListId, plan.circuitListId),
|
||||
eq(circuits.sectionId, plan.expectedSectionId),
|
||||
eq(
|
||||
circuits.equipmentIdentifier,
|
||||
plan.expectedEquipmentIdentifier
|
||||
),
|
||||
eq(circuits.sortOrder, plan.expectedSortOrder)
|
||||
)
|
||||
)
|
||||
.run();
|
||||
if (updated.changes !== 1) {
|
||||
throw new Error("Circuit changed before group move.");
|
||||
}
|
||||
return createCircuitGroupMoveProjectCommand(
|
||||
invertCircuitGroupMovePlan(plan)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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({
|
||||
|
||||
@@ -41,6 +41,7 @@ const commandTypeLabels: Record<string, string> = {
|
||||
"circuit-group.update": "Stromkreisgruppe bearbeitet",
|
||||
"circuit-group.reorder": "Stromkreisgruppen sortiert",
|
||||
"circuit-group.renumber": "Stromkreisgruppen neu nummeriert",
|
||||
"circuit.move-group": "Stromkreis in andere Gruppe verschoben",
|
||||
"project-floor.insert": "Geschoss angelegt",
|
||||
"project-floor.delete": "Geschoss entfernt",
|
||||
"project-room.insert": "Raum angelegt",
|
||||
|
||||
@@ -8,6 +8,7 @@ import { CircuitSectionRenumberProjectCommandRepository } from "../../db/reposit
|
||||
import { CircuitStructureProjectCommandRepository } from "../../db/repositories/circuit-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 { CircuitGroupMoveProjectCommandRepository } from "../../db/repositories/circuit-group-move-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 { ProjectHistoryRepository } from "../../db/repositories/project-history.repository.js";
|
||||
@@ -32,6 +33,8 @@ export const circuitGroupStructureProjectCommandStore =
|
||||
new CircuitGroupStructureProjectCommandRepository(db);
|
||||
export const circuitGroupRenumberProjectCommandStore =
|
||||
new CircuitGroupRenumberProjectCommandRepository(db);
|
||||
export const circuitGroupMoveProjectCommandStore =
|
||||
new CircuitGroupMoveProjectCommandRepository(db);
|
||||
export const distributionBoardStructureProjectCommandStore =
|
||||
new DistributionBoardStructureProjectCommandRepository(db);
|
||||
export const distributionBoardComponentStructureProjectCommandStore =
|
||||
@@ -71,5 +74,6 @@ export const projectCommandService = new ProjectCommandService(
|
||||
distributionBoardComponentStructureProjectCommandStore,
|
||||
circuitGroupStructureProjectCommandStore,
|
||||
circuitGroupRenumberProjectCommandStore,
|
||||
circuitGroupMoveProjectCommandStore,
|
||||
projectHistoryStore
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user