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 { 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; }