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

@ -24,6 +24,7 @@ import { ProjectStateRestoreCommandRepository } from "../../db/repositories/proj
import { ProjectCommandService } from "../../domain/services/project-command.service.js";
import { ExternalModelConfigurationProjectCommandRepository } from "../../db/repositories/external-model-configuration-project-command.repository.js";
import { ExternalInitialImportProjectCommandRepository } from "../../db/repositories/external-initial-import-project-command.repository.js";
import { ExternalObjectPlanningProjectCommandRepository } from "../../db/repositories/external-object-planning-project-command.repository.js";
import { ExternalObjectRowAssignmentProjectCommandRepository } from "../../db/repositories/external-object-row-assignment-project-command.repository.js";
import { ExternalObjectNewRowProjectCommandRepository } from "../../db/repositories/external-object-new-row-project-command.repository.js";
import { ExternalObjectNewCircuitProjectCommandRepository } from "../../db/repositories/external-object-new-circuit-project-command.repository.js";
@ -75,6 +76,8 @@ export const externalModelConfigurationProjectCommandStore =
new ExternalModelConfigurationProjectCommandRepository(db);
export const externalInitialImportProjectCommandStore =
new ExternalInitialImportProjectCommandRepository(db);
export const externalObjectPlanningProjectCommandStore =
new ExternalObjectPlanningProjectCommandRepository(db);
export const externalObjectRowAssignmentProjectCommandStore =
new ExternalObjectRowAssignmentProjectCommandRepository(db);
export const externalObjectNewRowProjectCommandStore =
@ -111,5 +114,6 @@ export const projectCommandService = new ProjectCommandService(
externalObjectNewRowProjectCommandStore,
externalObjectNewCircuitProjectCommandStore,
estimateBalanceProjectCommandStore,
externalInitialImportProjectCommandStore
externalInitialImportProjectCommandStore,
externalObjectPlanningProjectCommandStore
);

View file

@ -0,0 +1,56 @@
import type { Request, Response } from "express";
import { createExternalObjectPlanningUpdateProjectCommand } from "../../domain/models/external-object-planning-project-command.model.js";
import type { ExternalModelObjectPlanningValues } from "../../external-model/domain/external-model-contracts.js";
import { distributionBoardRepository, externalModelStateRepository, projectDeviceRepository, projectRepository } from "../composition/application-repositories.js";
import { projectCommandService } from "../composition/project-command-stores.js";
import { respondWithProjectCommandError } from "./project-command.controller.js";
export async function listExternalObjects(req: Request, res: Response) {
const projectId = String(req.params.projectId ?? "");
const [project, boards, devices] = await Promise.all([projectRepository.findById(projectId), distributionBoardRepository.listByProject(projectId), projectDeviceRepository.listByProject(projectId)]);
if (!project) return res.status(404).json({ error: "Projekt nicht gefunden." });
const state = externalModelStateRepository.getByProject(projectId).state;
const mappings = new Map(state.roomMappings.map((mapping) => [mapping.id, mapping]));
return res.json({
currentRevision: project.currentRevision,
sourceName: state.source?.name ?? null,
distributionBoards: boards.map(({ id, name }) => ({ id, name })),
projectDevices: devices.map(({ id, name, displayName }) => ({ id, name, displayName })),
objects: state.objects.map((object) => {
const mapping = object.externalRoomMappingId ? mappings.get(object.externalRoomMappingId) : null;
return { id: object.id, worksharingId: object.worksharingId, ifcGuid: object.ifcGuid, deviceTypeId: object.deviceTypeId, familyAndType: object.acceptedSourceValues.familyAndType, selectionMarker: scalarText(object.acceptedSourceValues.selectionMarker), sourceCircuitIdentifier: scalarText(object.acceptedSourceValues.circuitCombined) || scalarText(object.acceptedSourceValues.circuitIdentifier), sourceRoomNumber: scalarText(object.acceptedSourceValues.roomNumber), sourceRoomName: scalarText(object.acceptedSourceValues.roomName), roomId: mapping?.roomId ?? null, distributionBoardId: object.distributionBoardId, linkedProjectDeviceId: object.linkedProjectDeviceId, circuitDeviceRowId: object.circuitDeviceRowId, presenceStatus: object.presenceStatus, planningValues: object.planningValues };
}),
});
}
export function updateExternalObjects(req: Request, res: Response) {
const projectId = String(req.params.projectId ?? "");
try {
const expectedRevision = req.body?.expectedRevision;
if (!Number.isSafeInteger(expectedRevision) || expectedRevision < 0) throw new Error("Ungültige Projektrevision.");
const objectIds = req.body?.objectIds;
const patch = req.body?.patch;
if (!Array.isArray(objectIds) || objectIds.length === 0 || new Set(objectIds).size !== objectIds.length || !isRecord(patch)) throw new Error("Objektauswahl oder Änderungen sind ungültig.");
const state = externalModelStateRepository.getByProject(projectId);
if (!state.projectExists) return res.status(404).json({ error: "Projekt nicht gefunden." });
const byId = new Map(state.state.objects.map((object) => [object.id, object]));
const transitions = objectIds.map((id: unknown) => {
if (typeof id !== "string") throw new Error("Ungültige Revit-Objekt-ID.");
const expected = byId.get(id);
if (!expected) throw new Error("Ein ausgewähltes Revit-Objekt wurde nicht gefunden.");
if (expected.circuitDeviceRowId !== null) throw new Error("Verknüpfte Revit-Objekte folgen ihrer Gerätezeile und können hier nicht geändert werden.");
const planningValues = { ...expected.planningValues };
const overriddenFields = new Set(expected.overriddenFields);
for (const field of planningFields) if (hasOwn(patch, field)) { planningValues[field] = patch[field] as never; overriddenFields.add(field); }
return { expected, target: { ...expected, planningValues, overriddenFields: [...overriddenFields], distributionBoardId: hasOwn(patch, "distributionBoardId") ? nullableId(patch.distributionBoardId) : expected.distributionBoardId, linkedProjectDeviceId: hasOwn(patch, "linkedProjectDeviceId") ? nullableId(patch.linkedProjectDeviceId) : expected.linkedProjectDeviceId } };
});
const result = projectCommandService.executeUser({ projectId, expectedRevision, description: `${transitions.length} Revit-Objekte bearbeitet`, command: createExternalObjectPlanningUpdateProjectCommand(transitions) });
return res.json(result);
} catch (error) { return respondWithProjectCommandError(error, res); }
}
const planningFields = ["displayName", "category", "connectionKind", "effectiveQuantity", "powerPerUnitW", "simultaneityFactor", "cosPhi", "costGroup", "remark"] as const satisfies readonly (keyof ExternalModelObjectPlanningValues)[];
function scalarText(value: string | number | boolean | null) { return value === null ? "" : String(value); }
function isRecord(value: unknown): value is Record<string, unknown> { return value !== null && typeof value === "object" && !Array.isArray(value); }
function hasOwn(value: object, key: PropertyKey) { return Object.prototype.hasOwnProperty.call(value, key); }
function nullableId(value: unknown) { if (value === null || value === "") return null; if (typeof value !== "string") throw new Error("Ungültige Zuordnungs-ID."); return value; }

View file

@ -63,6 +63,7 @@ import {
getExternalInitialImportStatus,
planExternalInitialImport,
} from "../controllers/external-initial-import.controller.js";
import { listExternalObjects, updateExternalObjects } from "../controllers/external-objects.controller.js";
export const projectRouter = Router();
@ -90,6 +91,8 @@ projectRouter.post("/:projectId/external-model/configuration/detect", detectExte
projectRouter.get("/:projectId/external-model/import-status", getExternalInitialImportStatus);
projectRouter.post("/:projectId/external-model/initial-import/plan", planExternalInitialImport);
projectRouter.post("/:projectId/external-model/initial-import/apply", applyExternalInitialImport);
projectRouter.get("/:projectId/external-model/objects", listExternalObjects);
projectRouter.put("/:projectId/external-model/objects", updateExternalObjects);
projectRouter.get("/:projectId/distribution-boards", listDistributionBoardsByProject);
projectRouter.post("/:projectId/distribution-boards", createDistributionBoard);
projectRouter.post(