Add Revit circuit list object feed

This commit is contained in:
Julian Appel 2026-08-02 18:59:01 +02:00
parent a69b7b603f
commit 74bea1a3c9
11 changed files with 287 additions and 0 deletions

View file

@ -0,0 +1,88 @@
import type {
ExternalModelStateSnapshot,
} from "../domain/external-model-contracts.js";
export interface ExternalCircuitListObjectDto {
id: string;
ifcGuid: string;
selectionMarker: string;
familyAndType: string;
sourceCircuitIdentifier: string;
displayName: string | null;
category: string | null;
connectionKind: string | null;
effectiveQuantity: number;
powerPerUnitW: number | null;
sourceRoomNumber: string;
sourceRoomName: string;
roomId: string | null;
linkedProjectDeviceId: string | null;
circuitDeviceRowId: string | null;
presenceStatus: "present" | "missing";
}
export interface ExternalCircuitListObjectsDto {
sourceName: string | null;
objectCount: number;
unassignedObjectCount: number;
objects: ExternalCircuitListObjectDto[];
}
export function createExternalCircuitListObjects(input: {
state: ExternalModelStateSnapshot;
distributionBoardId: string;
}): ExternalCircuitListObjectsDto {
const mappingById = new Map(
input.state.roomMappings.map((mapping) => [mapping.id, mapping])
);
const objects = input.state.objects
.filter((object) => object.distributionBoardId === input.distributionBoardId)
.map((object) => {
const mapping = object.externalRoomMappingId === null
? null
: mappingById.get(object.externalRoomMappingId) ?? null;
return {
id: object.id,
ifcGuid: object.ifcGuid,
selectionMarker: object.acceptedSourceValues.selectionMarker,
familyAndType: object.acceptedSourceValues.familyAndType,
sourceCircuitIdentifier: object.acceptedSourceValues.circuitIdentifier,
displayName: object.planningValues.displayName,
category: object.planningValues.category,
connectionKind: object.planningValues.connectionKind,
effectiveQuantity: object.planningValues.effectiveQuantity,
powerPerUnitW: object.planningValues.powerPerUnitW,
sourceRoomNumber:
mapping?.sourceRoomNumber ?? object.acceptedSourceValues.roomNumber,
sourceRoomName:
mapping?.sourceRoomName ?? object.acceptedSourceValues.roomName,
roomId: mapping?.roomId ?? null,
linkedProjectDeviceId: object.linkedProjectDeviceId,
circuitDeviceRowId: object.circuitDeviceRowId,
presenceStatus: object.presenceStatus,
} satisfies ExternalCircuitListObjectDto;
})
.sort(compareExternalObjects);
return {
sourceName: input.state.source?.name ?? null,
objectCount: objects.length,
unassignedObjectCount: objects.filter(
(object) => object.circuitDeviceRowId === null
).length,
objects,
};
}
function compareExternalObjects(
left: ExternalCircuitListObjectDto,
right: ExternalCircuitListObjectDto
) {
const assignmentOrder = Number(left.circuitDeviceRowId !== null) -
Number(right.circuitDeviceRowId !== null);
if (assignmentOrder !== 0) return assignmentOrder;
return left.sourceRoomNumber.localeCompare(right.sourceRoomNumber, "de", {
numeric: true,
}) || left.selectionMarker.localeCompare(right.selectionMarker, "de", {
numeric: true,
}) || left.ifcGuid.localeCompare(right.ifcGuid);
}

View file

@ -428,6 +428,32 @@ export interface CircuitTreeResponseDto {
footerComponents: CircuitTreeComponentDto[];
}
export interface ExternalCircuitListObjectDto {
id: string;
ifcGuid: string;
selectionMarker: string;
familyAndType: string;
sourceCircuitIdentifier: string;
displayName: string | null;
category: string | null;
connectionKind: string | null;
effectiveQuantity: number;
powerPerUnitW: number | null;
sourceRoomNumber: string;
sourceRoomName: string;
roomId: string | null;
linkedProjectDeviceId: string | null;
circuitDeviceRowId: string | null;
presenceStatus: "present" | "missing";
}
export interface ExternalCircuitListObjectsDto {
sourceName: string | null;
objectCount: number;
unassignedObjectCount: number;
objects: ExternalCircuitListObjectDto[];
}
export interface CreateCircuitInputDto {
sectionId: string;
equipmentIdentifier: string;

View file

@ -23,6 +23,7 @@ import type {
ProjectRoomCommandResultDto,
ProjectDeviceSyncCommandResultDto,
ProjectDeviceSyncPreviewDto,
ExternalCircuitListObjectsDto,
ProjectDto,
RoomDto,
CircuitTreeResponseDto,
@ -346,6 +347,15 @@ export function getExternalCsvConfiguration(projectId: string) {
);
}
export function getExternalCircuitListObjects(
projectId: string,
circuitListId: string
) {
return request<ExternalCircuitListObjectsDto>(
`/api/projects/${projectId}/circuit-lists/${circuitListId}/external-objects`
);
}
export function updateExternalCsvConfiguration(
projectId: string,
expectedRevision: number,

View file

@ -5,6 +5,7 @@ import { ExternalCsvParseError } from "../../external-model/csv/external-csv-tra
import { createExternalCsvPreview } from "../../external-model/application/external-csv-preview.js";
import { createExternalInitialImportPlan } from "../../external-model/application/external-initial-import-plan.js";
import { buildExternalInitialImportTarget } from "../../external-model/application/external-initial-import-target.js";
import { createExternalCircuitListObjects } from "../../external-model/application/external-circuit-list-objects.js";
import { parseExternalCsv } from "../../external-model/csv/external-csv-transport.js";
import {
createEmptyExternalModelState,
@ -24,6 +25,7 @@ import {
floorRepository,
projectDeviceRepository,
roomRepository,
circuitListRepository,
} from "../composition/application-repositories.js";
import { projectCommandService } from "../composition/project-command-stores.js";
import { respondWithProjectCommandError } from "./project-command.controller.js";
@ -38,6 +40,27 @@ export function getExternalCsvConfiguration(req: Request, res: Response) {
return res.json({ configuration: result.configuration });
}
export async function getExternalCircuitListObjects(req: Request, res: Response) {
const projectId = getProjectId(req, res);
if (!projectId) return;
const { circuitListId } = req.params;
if (typeof circuitListId !== "string" || !circuitListId.trim()) {
return res.status(400).json({ error: "Invalid circuitListId" });
}
const circuitList = await circuitListRepository.findById(projectId, circuitListId);
if (!circuitList) {
return res.status(404).json({ error: "Circuit list not found" });
}
const state = externalModelStateRepository.getByProject(projectId);
if (!state.projectExists) {
return res.status(404).json({ error: "Project not found" });
}
return res.json(createExternalCircuitListObjects({
state: state.state,
distributionBoardId: circuitList.distributionBoardId,
}));
}
export function updateExternalCsvConfiguration(req: Request, res: Response) {
const projectId = getProjectId(req, res);
if (!projectId) return;

View file

@ -41,6 +41,7 @@ import {
previewExternalCsv,
planExternalInitialImport,
updateExternalCsvConfiguration,
getExternalCircuitListObjects,
} from "../controllers/external-csv.controller.js";
export const projectRouter = Router();
@ -58,6 +59,10 @@ projectRouter.post(
"/:projectId/external-csv/initial-import/plan",
planExternalInitialImport
);
projectRouter.get(
"/:projectId/circuit-lists/:circuitListId/external-objects",
getExternalCircuitListObjects
);
projectRouter.post(
"/:projectId/external-csv/initial-import/apply",
applyExternalInitialImport