Add virtualized Revit object planning

This commit is contained in:
Julian Appel 2026-09-30 10:03:36 +02:00
parent bfdd048269
commit 4ac32e103d
17 changed files with 277 additions and 9 deletions

View file

@ -0,0 +1,30 @@
import type { ExternalModelObjectSnapshot } from "../../external-model/domain/external-model-contracts.js";
import { parseExternalModelStateSnapshot } from "./project-state-snapshot.model.js";
import type { SerializedProjectCommand } from "./project-command.model.js";
export const externalObjectPlanningUpdateCommandType = "external-object.update-planning" as const;
export interface ExternalObjectPlanningUpdateProjectCommand extends SerializedProjectCommand<{ objects: Array<{ expected: ExternalModelObjectSnapshot; target: ExternalModelObjectSnapshot }> }> {
schemaVersion: 1;
type: typeof externalObjectPlanningUpdateCommandType;
}
export function createExternalObjectPlanningUpdateProjectCommand(objects: Array<{ expected: ExternalModelObjectSnapshot; target: ExternalModelObjectSnapshot }>): ExternalObjectPlanningUpdateProjectCommand {
const command: ExternalObjectPlanningUpdateProjectCommand = { schemaVersion: 1, type: externalObjectPlanningUpdateCommandType, payload: { objects } };
assertExternalObjectPlanningUpdateProjectCommand(command); return command;
}
export function invertExternalObjectPlanningUpdateProjectCommand(command: ExternalObjectPlanningUpdateProjectCommand) { return createExternalObjectPlanningUpdateProjectCommand(command.payload.objects.map(({ expected, target }) => ({ expected: target, target: expected }))); }
export function assertExternalObjectPlanningUpdateProjectCommand(command: SerializedProjectCommand<unknown>): asserts command is ExternalObjectPlanningUpdateProjectCommand {
if (command.schemaVersion !== 1 || command.type !== externalObjectPlanningUpdateCommandType || !isRecord(command.payload) || !Array.isArray(command.payload.objects) || command.payload.objects.length === 0) throw new Error("Nicht unterstützter Revit-Objektplanungs-Command.");
const ids = new Set<string>();
for (const transition of command.payload.objects) {
if (!isRecord(transition)) throw new Error("Ungültiger Revit-Objektübergang.");
const expected = parseObject(transition.expected); const target = parseObject(transition.target);
if (expected.id !== target.id || ids.has(expected.id)) throw new Error("Doppelte oder abweichende Revit-Objekt-ID.");
ids.add(expected.id);
if (expected.circuitDeviceRowId !== null || target.circuitDeviceRowId !== null) throw new Error("Verknüpfte Revit-Objekte werden über ihre Gerätezeile geplant.");
const left = { ...expected, planningValues: undefined, overriddenFields: undefined, distributionBoardId: undefined, linkedProjectDeviceId: undefined };
const right = { ...target, planningValues: undefined, overriddenFields: undefined, distributionBoardId: undefined, linkedProjectDeviceId: undefined };
if (JSON.stringify(left) !== JSON.stringify(right) || JSON.stringify(expected) === JSON.stringify(target)) throw new Error("Der Objektplanungs-Command ändert unzulässige oder keine Felder.");
}
}
function parseObject(value: unknown) { return parseExternalModelStateSnapshot({ source: null, importBatches: [], roomMappings: [], objects: [value] }).objects[0]!; }
function isRecord(value: unknown): value is Record<string, unknown> { return value !== null && typeof value === "object" && !Array.isArray(value); }

View file

@ -0,0 +1,3 @@
import type { ExternalObjectPlanningUpdateProjectCommand } from "../models/external-object-planning-project-command.model.js";
import type { AppendedProjectRevision, ProjectRevisionSource } from "./project-revision.store.js";
export interface ExternalObjectPlanningProjectCommandStore { execute(input: { projectId: string; expectedRevision: number; source: ProjectRevisionSource; description?: string; actorId?: string; historyTargetChangeSetId?: string; command: ExternalObjectPlanningUpdateProjectCommand }): { revision: AppendedProjectRevision; inverse: ExternalObjectPlanningUpdateProjectCommand }; }

View file

@ -182,6 +182,8 @@ import {
externalInitialImportCommandType,
} from "../models/external-initial-import-project-command.model.js";
import type { ExternalInitialImportProjectCommandStore } from "../ports/external-initial-import-project-command.store.js";
import { assertExternalObjectPlanningUpdateProjectCommand, externalObjectPlanningUpdateCommandType } from "../models/external-object-planning-project-command.model.js";
import type { ExternalObjectPlanningProjectCommandStore } from "../ports/external-object-planning-project-command.store.js";
import {
assertExternalObjectRowAssignmentProjectCommand,
externalObjectRowAssignmentCommandType,
@ -248,7 +250,8 @@ export class ProjectCommandService implements ProjectCommandExecutor {
private readonly externalObjectNewRowStore?: ExternalObjectNewRowProjectCommandStore,
private readonly externalObjectNewCircuitStore?: ExternalObjectNewCircuitProjectCommandStore,
private readonly estimateBalanceStore?: EstimateBalanceProjectCommandStore,
private readonly externalInitialImportStore?: ExternalInitialImportProjectCommandStore
private readonly externalInitialImportStore?: ExternalInitialImportProjectCommandStore,
private readonly externalObjectPlanningStore?: ExternalObjectPlanningProjectCommandStore
) {}
executeUser(
@ -647,6 +650,11 @@ export class ProjectCommandService implements ProjectCommandExecutor {
command: input.command,
}).revision;
}
case externalObjectPlanningUpdateCommandType: {
assertExternalObjectPlanningUpdateProjectCommand(input.command);
if (!this.externalObjectPlanningStore) throw new Error("External object planning store is not available.");
return this.externalObjectPlanningStore.execute({ ...input, command: input.command }).revision;
}
case externalObjectRowAssignmentCommandType: {
assertExternalObjectRowAssignmentProjectCommand(input.command);
if (!this.externalObjectRowAssignmentStore) {