forked from jappel/leistungsbilanz-ts
Add Revit return workbook export
This commit is contained in:
parent
77d775fde1
commit
1a4c8cad8c
17 changed files with 3783 additions and 6 deletions
40
tests/external-export-batch.repository.test.ts
Normal file
40
tests/external-export-batch.repository.test.ts
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
import path from "node:path";
|
||||
import assert from "node:assert/strict";
|
||||
import { describe, it } from "node:test";
|
||||
import { migrate } from "drizzle-orm/better-sqlite3/migrator";
|
||||
import { createDatabaseContext } from "../src/db/database-context.js";
|
||||
import { ExternalExportBatchRepository } from "../src/db/repositories/external-export-batch.repository.js";
|
||||
import { projects } from "../src/db/schema/projects.js";
|
||||
import { createDefaultExternalModelConfiguration } from "../src/external-model/configuration/external-model-configuration.js";
|
||||
|
||||
describe("external export batch repository", () => {
|
||||
it("appends immutable audit metadata without changing the project revision", () => {
|
||||
const context = createDatabaseContext(":memory:");
|
||||
try {
|
||||
migrate(context.db, { migrationsFolder: path.resolve("src", "db", "migrations") });
|
||||
context.db.insert(projects).values({ id: "project-1", name: "Projekt", currentRevision: 7 }).run();
|
||||
const repository = new ExternalExportBatchRepository(context.db);
|
||||
const configuration = createDefaultExternalModelConfiguration();
|
||||
repository.append({
|
||||
id: "export-1", projectId: "project-1", projectRevision: 7,
|
||||
importBatchId: "historical-import-without-foreign-key", exportedAtIso: "2026-09-30T10:00:00.000Z",
|
||||
fileName: "Projekt_Revit-Rueckgabe_2026-09-30.xlsx", sha256: "a".repeat(64),
|
||||
configurationSnapshot: configuration, configurationSha256: "b".repeat(64), scope: "all", objectCount: 4,
|
||||
});
|
||||
configuration.columns.designation = "Nachträglich geändert";
|
||||
repository.append({
|
||||
id: "export-2", projectId: "project-1", projectRevision: 7,
|
||||
importBatchId: "historical-import-without-foreign-key", exportedAtIso: "2026-09-30T11:00:00.000Z",
|
||||
fileName: "Projekt_Revit-Rueckgabe_2026-09-30.xlsx", sha256: "c".repeat(64),
|
||||
configurationSnapshot: configuration, configurationSha256: "d".repeat(64), scope: "selected", objectCount: 1,
|
||||
});
|
||||
|
||||
const batches = repository.listByProject("project-1");
|
||||
assert.deepEqual(batches.map((batch) => batch.id), ["export-2", "export-1"]);
|
||||
assert.notEqual(batches[0]!.configurationSnapshot.columns.designation, batches[1]!.configurationSnapshot.columns.designation);
|
||||
assert.equal(context.db.select({ revision: projects.currentRevision }).from(projects).get()!.revision, 7);
|
||||
} finally {
|
||||
context.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
43
tests/external-xlsx-return-projection.test.ts
Normal file
43
tests/external-xlsx-return-projection.test.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import assert from "node:assert/strict";
|
||||
import { describe, it } from "node:test";
|
||||
import { projectExternalXlsxReturn } from "../src/external-model/application/external-xlsx-return-projection.js";
|
||||
import type { ExternalModelObjectSnapshot } from "../src/external-model/domain/external-model-contracts.js";
|
||||
|
||||
function object(): ExternalModelObjectSnapshot {
|
||||
return {
|
||||
id: "object-1", projectId: "project-1", sourceId: "source-1",
|
||||
worksharingId: "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee-ffffffff", ifcGuid: null,
|
||||
deviceTypeId: "type-1", lastSeenImportBatchId: "batch-1", lastAcceptedImportBatchId: "batch-1",
|
||||
acceptedSourceValues: { rowNumber: 2, roomNumber: "101", roomName: "Büro", familyAndType: "Steckdose", selectionMarker: "SD", socketCount: 2, sourceRemark: null, circuitCombined: "UV 1-2F1.1", boardName: "UV 1", circuitIdentifier: "-2F1.1", designation: "Steckdose", electricalPowerW: "240", voltageV: 230, currentA: "1,16" },
|
||||
planningValues: { displayName: "Steckdose", category: "single_phase", connectionKind: "socket", effectiveQuantity: 2, powerPerUnitW: 120, simultaneityFactor: 1, cosPhi: 0.9, costGroup: null, remark: null },
|
||||
overriddenFields: [], externalRoomMappingId: null, distributionBoardId: "board-1", linkedProjectDeviceId: null, circuitDeviceRowId: "row-1", presenceStatus: "present",
|
||||
};
|
||||
}
|
||||
|
||||
describe("external XLSX return projection", () => {
|
||||
it("projects typed row values and compares normalized imported scalars", () => {
|
||||
const result = projectExternalXlsxReturn({ object: object(), circuitMode: "combined", singlePhaseVoltageV: 230, threePhaseVoltageV: 400, row: { rowId: "row-1", displayName: "Steckdose", powerPerUnitKw: 0.12, cosPhi: 0.9, phaseType: "single_phase", category: "single_phase", circuitIdentifier: "-2F1.1", circuitVoltageV: 230, distributionBoardName: "UV 1" } });
|
||||
assert.deepEqual(result.values, { circuitCombined: "UV 1-2F1.1", designation: "Steckdose", electricalPowerW: 240, voltageV: 230, currentA: 1.16 });
|
||||
assert.equal(result.differsFromImported, false);
|
||||
assert.deepEqual(result.warnings, []);
|
||||
});
|
||||
|
||||
it("uses object planning without a row and leaves an uncomputable current empty", () => {
|
||||
const input = object(); input.circuitDeviceRowId = null; input.planningValues.cosPhi = null;
|
||||
const result = projectExternalXlsxReturn({ object: input, row: null, circuitMode: "separate", singlePhaseVoltageV: 230, threePhaseVoltageV: 400 });
|
||||
assert.equal(result.values.boardName, "");
|
||||
assert.equal(result.values.circuitIdentifier, "");
|
||||
assert.equal(result.values.electricalPowerW, 240);
|
||||
assert.equal(result.values.currentA, null);
|
||||
assert.equal(result.differsFromImported, true);
|
||||
assert.match(result.warnings.join(" "), /Stromstärke/);
|
||||
});
|
||||
|
||||
it("uses three-phase current and object-specific effective quantity", () => {
|
||||
const input = object(); input.planningValues.category = "three_phase"; input.planningValues.effectiveQuantity = 3;
|
||||
const result = projectExternalXlsxReturn({ object: input, row: null, circuitMode: "combined", singlePhaseVoltageV: 230, threePhaseVoltageV: 400 });
|
||||
assert.equal(result.values.electricalPowerW, 360);
|
||||
assert.equal(result.values.voltageV, 400);
|
||||
assert.equal(result.values.currentA, 0.58);
|
||||
});
|
||||
});
|
||||
|
|
@ -2,7 +2,7 @@ import assert from "node:assert/strict";
|
|||
import { readFileSync } from "node:fs";
|
||||
import { describe, it } from "node:test";
|
||||
import { createDefaultExternalModelConfiguration } from "../src/external-model/configuration/external-model-configuration.js";
|
||||
import { applyExternalInitialImport, detectExternalModelWorkbook, getExternalInitialImportStatus, getExternalModelConfiguration, getExternalProjectObjects, planExternalInitialImport, updateExternalModelConfiguration, updateExternalProjectObjects } from "../src/frontend/utils/api.js";
|
||||
import { applyExternalInitialImport, createExternalReturnWorkbook, detectExternalModelWorkbook, getExternalInitialImportStatus, getExternalModelConfiguration, getExternalProjectObjects, planExternalInitialImport, updateExternalModelConfiguration, updateExternalProjectObjects } from "../src/frontend/utils/api.js";
|
||||
|
||||
describe("Revit XLSX settings", () => {
|
||||
it("uses the project-scoped configuration endpoint with expectedRevision", async () => {
|
||||
|
|
@ -66,6 +66,8 @@ describe("Revit XLSX settings", () => {
|
|||
assert.match(wizard, /GZF 1/);
|
||||
assert.match(objectTable, /viewportHeight/);
|
||||
assert.match(objectTable, /Sammeländerung anwenden/);
|
||||
assert.match(objectTable, /Rückgabe-Excel herunterladen/);
|
||||
assert.match(objectTable, /Nur geänderte Objekte/);
|
||||
});
|
||||
|
||||
it("loads one project object list and sends revision-safe bulk updates", async () => {
|
||||
|
|
@ -76,4 +78,16 @@ describe("Revit XLSX settings", () => {
|
|||
finally { globalThis.fetch = originalFetch; }
|
||||
assert.deepEqual(requests, [{ url: "/api/projects/project-1/external-model/objects", method: "GET", body: undefined }, { url: "/api/projects/project-1/external-model/objects", method: "PUT", body: { expectedRevision: 4, objectIds: ["object-1"], patch: { simultaneityFactor: 0.7 } } }]);
|
||||
});
|
||||
|
||||
it("creates a revision-safe return workbook for the requested scope", async () => {
|
||||
const requests: Array<{ url: string; method: string; body: unknown }> = [];
|
||||
const originalFetch = globalThis.fetch;
|
||||
globalThis.fetch = async (input, init) => {
|
||||
requests.push({ url: String(input), method: init?.method ?? "GET", body: init?.body ? JSON.parse(String(init.body)) : undefined });
|
||||
return new Response(JSON.stringify({ fileName: "return.xlsx", contentBase64: "", sha256: "a".repeat(64), objectCount: 1, warnings: [] }), { status: 200, headers: { "Content-Type": "application/json" } });
|
||||
};
|
||||
try { await createExternalReturnWorkbook("project-1", 7, "selected", ["object-1"]); }
|
||||
finally { globalThis.fetch = originalFetch; }
|
||||
assert.deepEqual(requests, [{ url: "/api/projects/project-1/external-model/return-workbook", method: "POST", body: { expectedRevision: 7, scope: "selected", selectedObjectIds: ["object-1"] } }]);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue