Add component update commands

This commit is contained in:
2026-07-30 19:42:09 +02:00
parent 604604f056
commit 69b020339e
11 changed files with 547 additions and 21 deletions
@@ -2,10 +2,13 @@ import { and, eq } from "drizzle-orm";
import {
assertDistributionBoardComponentDeleteProjectCommand,
assertDistributionBoardComponentInsertProjectCommand,
assertDistributionBoardComponentUpdateProjectCommand,
createDistributionBoardComponentDeleteProjectCommand,
createDistributionBoardComponentInsertProjectCommand,
createDistributionBoardComponentUpdateProjectCommand,
distributionBoardComponentDeleteCommandType,
distributionBoardComponentInsertCommandType,
distributionBoardComponentUpdateCommandType,
type DistributionBoardComponentSnapshot,
type DistributionBoardComponentStructureProjectCommand,
} from "../../domain/models/distribution-board-component-structure-project-command.model.js";
@@ -59,6 +62,19 @@ export class DistributionBoardComponentStructureProjectCommandRepository
command.payload.snapshot
);
}
if (command.type === distributionBoardComponentUpdateCommandType) {
assertDistributionBoardComponentUpdateProjectCommand(command);
this.update(
database,
projectId,
command.payload.expected,
command.payload.target
);
return createDistributionBoardComponentUpdateProjectCommand(
command.payload.target,
command.payload.expected
);
}
throw new Error(
"Unsupported distribution-board component structure command."
);
@@ -139,6 +155,60 @@ export class DistributionBoardComponentStructureProjectCommandRepository
}
}
private update(
database: AppDatabase,
projectId: string,
expected: DistributionBoardComponentSnapshot,
target: DistributionBoardComponentSnapshot
) {
this.assertOwnership(database, projectId, expected);
this.assertOwnership(database, projectId, target);
const component = database
.select()
.from(distributionBoardComponents)
.where(eq(distributionBoardComponents.id, expected.component.id))
.get();
const protection = database
.select()
.from(distributionBoardComponentProtectionDevices)
.where(
eq(
distributionBoardComponentProtectionDevices.componentId,
expected.component.id
)
)
.get();
if (
!component ||
!sameRecord(expected.component, component) ||
!sameNullableRecord(expected.protectionDevice, protection)
) {
throw new Error(
"Distribution-board component changed before update."
);
}
database
.update(distributionBoardComponents)
.set(target.component)
.where(eq(distributionBoardComponents.id, expected.component.id))
.run();
if (
expected.protectionDevice !== null &&
target.protectionDevice !== null
) {
database
.update(distributionBoardComponentProtectionDevices)
.set(target.protectionDevice)
.where(
eq(
distributionBoardComponentProtectionDevices.componentId,
expected.component.id
)
)
.run();
}
}
private assertOwnership(
database: AppDatabase,
projectId: string,
@@ -13,6 +13,8 @@ export const distributionBoardComponentInsertCommandType =
"distribution-board-component.insert" as const;
export const distributionBoardComponentDeleteCommandType =
"distribution-board-component.delete" as const;
export const distributionBoardComponentUpdateCommandType =
"distribution-board-component.update" as const;
export const distributionBoardComponentStructureCommandSchemaVersion =
1 as const;
@@ -47,6 +49,11 @@ interface DistributionBoardComponentStructurePayload {
snapshot: DistributionBoardComponentSnapshot;
}
interface DistributionBoardComponentUpdatePayload {
expected: DistributionBoardComponentSnapshot;
target: DistributionBoardComponentSnapshot;
}
export interface DistributionBoardComponentInsertProjectCommand
extends SerializedProjectCommand<DistributionBoardComponentStructurePayload> {
schemaVersion: typeof distributionBoardComponentStructureCommandSchemaVersion;
@@ -59,9 +66,16 @@ export interface DistributionBoardComponentDeleteProjectCommand
type: typeof distributionBoardComponentDeleteCommandType;
}
export interface DistributionBoardComponentUpdateProjectCommand
extends SerializedProjectCommand<DistributionBoardComponentUpdatePayload> {
schemaVersion: typeof distributionBoardComponentStructureCommandSchemaVersion;
type: typeof distributionBoardComponentUpdateCommandType;
}
export type DistributionBoardComponentStructureProjectCommand =
| DistributionBoardComponentInsertProjectCommand
| DistributionBoardComponentDeleteProjectCommand;
| DistributionBoardComponentDeleteProjectCommand
| DistributionBoardComponentUpdateProjectCommand;
export function createDistributionBoardComponentInsertProjectCommand(
snapshot: DistributionBoardComponentSnapshot
@@ -89,6 +103,20 @@ export function createDistributionBoardComponentDeleteProjectCommand(
return command;
}
export function createDistributionBoardComponentUpdateProjectCommand(
expected: DistributionBoardComponentSnapshot,
target: DistributionBoardComponentSnapshot
): DistributionBoardComponentUpdateProjectCommand {
const command: DistributionBoardComponentUpdateProjectCommand = {
schemaVersion:
distributionBoardComponentStructureCommandSchemaVersion,
type: distributionBoardComponentUpdateCommandType,
payload: { expected, target },
};
assertDistributionBoardComponentUpdateProjectCommand(command);
return command;
}
export function assertDistributionBoardComponentInsertProjectCommand(
command: SerializedProjectCommand<unknown>
): asserts command is DistributionBoardComponentInsertProjectCommand {
@@ -107,6 +135,28 @@ export function assertDistributionBoardComponentDeleteProjectCommand(
);
}
export function assertDistributionBoardComponentUpdateProjectCommand(
command: SerializedProjectCommand<unknown>
): asserts command is DistributionBoardComponentUpdateProjectCommand {
if (
command.schemaVersion !==
distributionBoardComponentStructureCommandSchemaVersion ||
command.type !== distributionBoardComponentUpdateCommandType ||
!isPlainObject(command.payload) ||
Object.keys(command.payload).length !== 2
) {
throw new Error(
"Unsupported distribution-board component update command."
);
}
assertDistributionBoardComponentSnapshot(command.payload.expected);
assertDistributionBoardComponentSnapshot(command.payload.target);
assertSameComponentIdentity(
command.payload.expected,
command.payload.target
);
}
function assertStructureCommand(
command: SerializedProjectCommand<unknown>,
type:
@@ -127,6 +177,25 @@ function assertStructureCommand(
assertDistributionBoardComponentSnapshot(command.payload.snapshot);
}
function assertSameComponentIdentity(
expected: DistributionBoardComponentSnapshot,
target: DistributionBoardComponentSnapshot
) {
for (const field of [
"id",
"circuitListId",
"sectionId",
"role",
"placement",
] as const) {
if (expected.component[field] !== target.component[field]) {
throw new Error(
"Distribution-board component updates cannot change ownership or role."
);
}
}
}
export function assertDistributionBoardComponentSnapshot(
value: unknown
): asserts value is DistributionBoardComponentSnapshot {
@@ -44,8 +44,10 @@ import {
import {
assertDistributionBoardComponentDeleteProjectCommand,
assertDistributionBoardComponentInsertProjectCommand,
assertDistributionBoardComponentUpdateProjectCommand,
distributionBoardComponentDeleteCommandType,
distributionBoardComponentInsertCommandType,
distributionBoardComponentUpdateCommandType,
} from "../models/distribution-board-component-structure-project-command.model.js";
import {
assertDistributionBoardDeleteProjectCommand,
@@ -322,6 +324,15 @@ export class ProjectCommandService implements ProjectCommandExecutor {
command: input.command,
}).revision;
}
case distributionBoardComponentUpdateCommandType: {
assertDistributionBoardComponentUpdateProjectCommand(
input.command
);
return this.distributionBoardComponentStructureStore.execute({
...input,
command: input.command,
}).revision;
}
case projectFloorInsertCommandType: {
assertProjectFloorInsertProjectCommand(input.command);
return this.projectLocationStructureStore.execute({
@@ -35,6 +35,7 @@ const commandTypeLabels: Record<string, string> = {
"distribution-board.delete": "Verteilung entfernt",
"distribution-board-component.insert": "Verteilergerät angelegt",
"distribution-board-component.delete": "Verteilergerät entfernt",
"distribution-board-component.update": "Verteilergerät bearbeitet",
"project-floor.insert": "Geschoss angelegt",
"project-floor.delete": "Geschoss entfernt",
"project-room.insert": "Raum angelegt",