Add Revit initial import apply API

This commit is contained in:
2026-08-02 17:42:29 +02:00
parent c9e5fdd876
commit 8646f346e6
9 changed files with 370 additions and 2 deletions
+14
View File
@@ -1,6 +1,7 @@
import assert from "node:assert/strict";
import { describe, it } from "node:test";
import {
applyExternalInitialImportSchema,
planExternalInitialImportSchema,
previewExternalCsvSchema,
updateExternalCsvConfigurationSchema,
@@ -31,6 +32,19 @@ describe("external CSV API contracts", () => {
}).success,
true
);
assert.equal(
applyExternalInitialImportSchema.safeParse({
expectedRevision: 5,
expectedConfigurationVersion: 2,
expectedSha256: "a".repeat(64),
fileName: "revit.csv",
contentBase64: "YWJj",
sourceName: "Revit-Gesamtmodell",
roomDecisions: [],
familyProjectDeviceDecisions: [],
}).success,
true
);
});
it("rejects stale-shaped, oversized and unknown request fields", () => {
@@ -1,6 +1,8 @@
import assert from "node:assert/strict";
import { describe, it } from "node:test";
import { createExternalInitialImportPlan } from "../src/external-model/application/external-initial-import-plan.js";
import { buildExternalInitialImportTarget } from "../src/external-model/application/external-initial-import-target.js";
import { parseExternalCsv } from "../src/external-model/csv/external-csv-transport.js";
import {
externalCsvTestConfiguration,
firstIfcGuid,
@@ -66,4 +68,82 @@ describe("external initial import plan", () => {
assert.equal(plan.existing.distributionBoards[0].id, "board-1");
assert.equal(plan.existing.projectDevices[0].id, "device-1");
});
it("builds a stable row-free target only after every family is classified", () => {
const configuration = structuredClone(externalCsvTestConfiguration);
for (const exactFamilyAndType of [
"Steckdose: Doppelsteckdose",
"Steckdose: Standard",
]) {
configuration.familyTypeRules.push({
exactFamilyAndType,
internalDeviceType: "Steckdose",
connectionKind: "socket",
category: "single_phase",
quantityRule: exactFamilyAndType.includes("Doppel")
? { kind: "mapped-column" }
: { kind: "fixed", quantity: 1 },
displayNameSuggestion: { kind: "selection-marker" },
});
}
const bytes = utf8Bytes(quotedRevitCsv, true);
const plan = createExternalInitialImportPlan({
fileName: "revit.csv",
bytes,
configurationVersion: 2,
configuration,
floors: [],
rooms: [],
distributionBoards: [],
projectDevices: [],
});
let id = 0;
const target = buildExternalInitialImportTarget({
projectId: "project-1",
expectedRevision: 7,
sourceName: "Revit-Gesamtmodell",
importedAtIso: "2026-08-02T17:00:00.000Z",
configurationVersion: 2,
configuration,
originalContentBase64: Buffer.from(bytes).toString("base64"),
document: parseExternalCsv(bytes, configuration),
plan,
roomDecisions: plan.sourceRooms.map((room) => ({
sourceRoomKey: room.sourceRoomKey,
roomId: null,
defaultDistributionBoardId: null,
})),
familyProjectDeviceDecisions: plan.familyGroups.map((group) => ({
familyAndType: group.familyAndType,
projectDeviceId: null,
})),
createId: () => `generated-${++id}`,
});
assert.equal(target.importBatches[0].appliedProjectRevision, 8);
assert.equal(target.importBatches[0].configurationVersion, 2);
assert.equal(target.objects.length, 2);
assert.ok(target.objects.every((object) => object.circuitDeviceRowId === null));
assert.ok(target.objects.every((object) => object.overriddenFields.length === 0));
assert.throws(
() => buildExternalInitialImportTarget({
projectId: "project-1",
expectedRevision: 7,
sourceName: "Revit-Gesamtmodell",
importedAtIso: "2026-08-02T17:00:00.000Z",
configurationVersion: 2,
configuration,
originalContentBase64: Buffer.from(bytes).toString("base64"),
document: parseExternalCsv(bytes, configuration),
plan,
roomDecisions: [],
familyProjectDeviceDecisions: plan.familyGroups.map((group) => ({
familyAndType: group.familyAndType,
projectDeviceId: null,
})),
createId: () => "unused",
}),
/Quellraum-Entscheidungen/
);
});
});