Replace Revit CSV backend with Linear XLSX
This commit is contained in:
parent
52d3b1a270
commit
1cfeb06ec7
65 changed files with 4764 additions and 3717 deletions
|
|
@ -1,105 +0,0 @@
|
|||
import { eq } from "drizzle-orm";
|
||||
import {
|
||||
assertExternalCsvConfigurationUpdateProjectCommand,
|
||||
createExternalCsvConfigurationUpdateProjectCommand,
|
||||
type ExternalCsvConfigurationSnapshot,
|
||||
} from "../../domain/models/external-csv-configuration-project-command.model.js";
|
||||
import type {
|
||||
ExecuteExternalCsvConfigurationCommandInput,
|
||||
ExternalCsvConfigurationProjectCommandStore,
|
||||
} from "../../domain/ports/external-csv-configuration-project-command.store.js";
|
||||
import type { AppDatabase } from "../database-context.js";
|
||||
import { externalCsvConfigurations } from "../schema/external-csv-configurations.js";
|
||||
import { projects } from "../schema/projects.js";
|
||||
import { executeProjectCommandTransaction } from "./project-command-transaction.persistence.js";
|
||||
|
||||
export class ExternalCsvConfigurationProjectCommandRepository
|
||||
implements ExternalCsvConfigurationProjectCommandStore
|
||||
{
|
||||
constructor(private readonly database: AppDatabase) {}
|
||||
|
||||
execute(input: ExecuteExternalCsvConfigurationCommandInput) {
|
||||
assertExternalCsvConfigurationUpdateProjectCommand(input.command);
|
||||
return executeProjectCommandTransaction(this.database, input, (tx) => {
|
||||
const project = tx
|
||||
.select({ id: projects.id })
|
||||
.from(projects)
|
||||
.where(eq(projects.id, input.projectId))
|
||||
.get();
|
||||
if (!project) {
|
||||
throw new Error("Project not found.");
|
||||
}
|
||||
const current = tx
|
||||
.select()
|
||||
.from(externalCsvConfigurations)
|
||||
.where(eq(externalCsvConfigurations.projectId, input.projectId))
|
||||
.get() ?? null;
|
||||
assertSameSnapshot(current, input.command.payload.expected);
|
||||
const target = input.command.payload.target;
|
||||
if (target !== null && target.projectId !== input.projectId) {
|
||||
throw new Error("External CSV configuration belongs to a different project.");
|
||||
}
|
||||
if (sameSnapshot(current, target)) {
|
||||
throw new Error("External CSV configuration did not change.");
|
||||
}
|
||||
if (
|
||||
input.source === "user" &&
|
||||
target !== null &&
|
||||
target.configurationVersion !== (current?.configurationVersion ?? 0) + 1
|
||||
) {
|
||||
throw new Error("External CSV configuration version must advance by one.");
|
||||
}
|
||||
|
||||
if (target === null) {
|
||||
tx.delete(externalCsvConfigurations)
|
||||
.where(eq(externalCsvConfigurations.projectId, input.projectId))
|
||||
.run();
|
||||
} else if (current === null) {
|
||||
tx.insert(externalCsvConfigurations).values(target).run();
|
||||
} else {
|
||||
const updated = tx
|
||||
.update(externalCsvConfigurations)
|
||||
.set({
|
||||
configurationVersion: target.configurationVersion,
|
||||
configuration: target.configuration,
|
||||
})
|
||||
.where(eq(externalCsvConfigurations.id, current.id))
|
||||
.run();
|
||||
if (updated.changes !== 1) {
|
||||
throw new Error("External CSV configuration changed before update.");
|
||||
}
|
||||
}
|
||||
return createExternalCsvConfigurationUpdateProjectCommand(target, current);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function assertSameSnapshot(
|
||||
current: ExternalCsvConfigurationSnapshot | null,
|
||||
expected: ExternalCsvConfigurationSnapshot | null
|
||||
) {
|
||||
if (!sameSnapshot(current, expected)) {
|
||||
throw new Error("External CSV configuration changed before update.");
|
||||
}
|
||||
}
|
||||
|
||||
function sameSnapshot(
|
||||
left: ExternalCsvConfigurationSnapshot | null,
|
||||
right: ExternalCsvConfigurationSnapshot | null
|
||||
) {
|
||||
return canonicalJson(left) === canonicalJson(right);
|
||||
}
|
||||
|
||||
function canonicalJson(value: unknown): string {
|
||||
if (Array.isArray(value)) {
|
||||
return `[${value.map(canonicalJson).join(",")}]`;
|
||||
}
|
||||
if (value !== null && typeof value === "object") {
|
||||
const record = value as Record<string, unknown>;
|
||||
return `{${Object.keys(record)
|
||||
.sort()
|
||||
.map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`)
|
||||
.join(",")}}`;
|
||||
}
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
|
|
@ -1,252 +0,0 @@
|
|||
import crypto from "node:crypto";
|
||||
import { eq } from "drizzle-orm";
|
||||
import {
|
||||
assertExternalInitialImportProjectCommand,
|
||||
createExternalInitialImportProjectCommand,
|
||||
isEmptyExternalModelState,
|
||||
} from "../../domain/models/external-initial-import-project-command.model.js";
|
||||
import type { ExternalInitialImportProjectCommandStore } from "../../domain/ports/external-initial-import-project-command.store.js";
|
||||
import { createExternalCsvPreview } from "../../external-model/application/external-csv-preview.js";
|
||||
import { parseExternalCsv } from "../../external-model/csv/external-csv-transport.js";
|
||||
import type { ExternalModelStateSnapshot } from "../../external-model/domain/external-model-contracts.js";
|
||||
import {
|
||||
createExternalRoomKey,
|
||||
indexExternalObjectsByIfcGuid,
|
||||
projectInitialExternalObject,
|
||||
} from "../../external-model/domain/external-model-matching.js";
|
||||
import type { AppDatabase } from "../database-context.js";
|
||||
import { distributionBoards } from "../schema/distribution-boards.js";
|
||||
import { externalCsvConfigurations } from "../schema/external-csv-configurations.js";
|
||||
import { externalImportBatches } from "../schema/external-import-batches.js";
|
||||
import { externalModelObjects } from "../schema/external-model-objects.js";
|
||||
import { externalModelSources } from "../schema/external-model-sources.js";
|
||||
import { externalRoomMappings } from "../schema/external-room-mappings.js";
|
||||
import { projectDevices } from "../schema/project-devices.js";
|
||||
import { rooms } from "../schema/rooms.js";
|
||||
import { executeProjectCommandTransaction } from "./project-command-transaction.persistence.js";
|
||||
import { ExternalModelStateRepository } from "./external-model-state.repository.js";
|
||||
|
||||
export class ExternalInitialImportProjectCommandRepository
|
||||
implements ExternalInitialImportProjectCommandStore
|
||||
{
|
||||
constructor(private readonly database: AppDatabase) {}
|
||||
|
||||
execute(input: Parameters<ExternalInitialImportProjectCommandStore["execute"]>[0]) {
|
||||
assertExternalInitialImportProjectCommand(input.command);
|
||||
return executeProjectCommandTransaction(this.database, input, (tx) => {
|
||||
const currentResult = new ExternalModelStateRepository(tx)
|
||||
.getByProject(input.projectId);
|
||||
if (!currentResult.projectExists) throw new Error("Project not found.");
|
||||
const expected = input.command.payload.expected;
|
||||
if (!sameState(currentResult.state, expected)) {
|
||||
throw new Error("External model state changed before initial import.");
|
||||
}
|
||||
const target = input.command.payload.target;
|
||||
if (!isEmptyExternalModelState(target)) {
|
||||
validatePopulatedTarget(tx, input, target);
|
||||
}
|
||||
|
||||
replaceExternalState(tx, input.projectId, target);
|
||||
const persisted = new ExternalModelStateRepository(tx)
|
||||
.getByProject(input.projectId).state;
|
||||
if (!sameState(persisted, target)) {
|
||||
throw new Error("Persisted external initial import differs from its target.");
|
||||
}
|
||||
return createExternalInitialImportProjectCommand(target, currentResult.state);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function validatePopulatedTarget(
|
||||
database: AppDatabase,
|
||||
input: Parameters<ExternalInitialImportProjectCommandStore["execute"]>[0],
|
||||
target: ExternalModelStateSnapshot
|
||||
) {
|
||||
const source = target.source!;
|
||||
const batch = target.importBatches[0];
|
||||
if (source.projectId !== input.projectId) {
|
||||
throw new Error("External initial import belongs to a different project.");
|
||||
}
|
||||
if (
|
||||
input.source === "user" &&
|
||||
batch.appliedProjectRevision !== input.expectedRevision + 1
|
||||
) {
|
||||
throw new Error("External initial import revision is invalid.");
|
||||
}
|
||||
const configuration = database
|
||||
.select()
|
||||
.from(externalCsvConfigurations)
|
||||
.where(eq(externalCsvConfigurations.projectId, input.projectId))
|
||||
.get();
|
||||
if (
|
||||
!configuration ||
|
||||
configuration.configurationVersion !== batch.configurationVersion ||
|
||||
canonicalJson(configuration.configuration) !==
|
||||
canonicalJson(batch.configurationSnapshot)
|
||||
) {
|
||||
throw new Error("External initial import configuration changed before apply.");
|
||||
}
|
||||
|
||||
const bytes = decodeCanonicalBase64(batch.originalContentBase64);
|
||||
const sha256 = crypto.createHash("sha256").update(bytes).digest("hex");
|
||||
if (sha256 !== batch.sha256) {
|
||||
throw new Error("External initial import checksum is invalid.");
|
||||
}
|
||||
const parsedDocument = parseExternalCsv(bytes, batch.configurationSnapshot);
|
||||
if (canonicalJson(parsedDocument) !== canonicalJson(batch.document)) {
|
||||
throw new Error("External initial import document differs from the original bytes.");
|
||||
}
|
||||
const preview = createExternalCsvPreview({
|
||||
fileName: batch.fileName,
|
||||
bytes,
|
||||
configuration: batch.configurationSnapshot,
|
||||
});
|
||||
const targetByIfcGuid = indexExternalObjectsByIfcGuid(target.objects);
|
||||
if (targetByIfcGuid.size !== preview.objects.length) {
|
||||
throw new Error("External initial import must contain every CSV object exactly once.");
|
||||
}
|
||||
const mappingsById = new Map(
|
||||
target.roomMappings.map((mapping) => [mapping.id, mapping])
|
||||
);
|
||||
const expectedRoomKeys = new Set<string>();
|
||||
for (const candidate of preview.objects) {
|
||||
const object = targetByIfcGuid.get(candidate.ifcGuid);
|
||||
if (!object) {
|
||||
throw new Error(`External initial import is missing IFCGUID ${candidate.ifcGuid}.`);
|
||||
}
|
||||
const projected = projectInitialExternalObject(
|
||||
candidate,
|
||||
batch.configurationSnapshot
|
||||
);
|
||||
if (canonicalJson(object.acceptedSourceValues) !== canonicalJson(projected.sourceValues)) {
|
||||
throw new Error("External initial import source values differ from the CSV.");
|
||||
}
|
||||
for (const field of Object.keys(projected.planningValues) as Array<keyof typeof projected.planningValues>) {
|
||||
if (
|
||||
!object.overriddenFields.includes(field) &&
|
||||
canonicalJson(object.planningValues[field]) !==
|
||||
canonicalJson(projected.planningValues[field])
|
||||
) {
|
||||
throw new Error(`External initial planning field ${field} changed without an override.`);
|
||||
}
|
||||
}
|
||||
const roomKey = createExternalRoomKey(candidate.roomNumber, candidate.roomName);
|
||||
if (roomKey === null) {
|
||||
if (object.externalRoomMappingId !== null) {
|
||||
throw new Error("External object without a source room must not use a room mapping.");
|
||||
}
|
||||
} else {
|
||||
expectedRoomKeys.add(roomKey);
|
||||
const mapping = object.externalRoomMappingId === null
|
||||
? null
|
||||
: mappingsById.get(object.externalRoomMappingId);
|
||||
if (!mapping || mapping.normalizedSourceRoomKey !== roomKey) {
|
||||
throw new Error("External object source room mapping is incomplete.");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (
|
||||
target.roomMappings.length !== expectedRoomKeys.size ||
|
||||
target.roomMappings.some(
|
||||
(mapping) => !expectedRoomKeys.has(mapping.normalizedSourceRoomKey)
|
||||
)
|
||||
) {
|
||||
throw new Error("External initial import room mappings do not match CSV rooms.");
|
||||
}
|
||||
validateInternalLinks(database, input.projectId, target);
|
||||
}
|
||||
|
||||
function validateInternalLinks(
|
||||
database: AppDatabase,
|
||||
projectId: string,
|
||||
target: ExternalModelStateSnapshot
|
||||
) {
|
||||
const roomIds = new Set(
|
||||
database.select({ id: rooms.id }).from(rooms)
|
||||
.where(eq(rooms.projectId, projectId)).all().map((row) => row.id)
|
||||
);
|
||||
const boardIds = new Set(
|
||||
database.select({ id: distributionBoards.id }).from(distributionBoards)
|
||||
.where(eq(distributionBoards.projectId, projectId)).all().map((row) => row.id)
|
||||
);
|
||||
const deviceIds = new Set(
|
||||
database.select({ id: projectDevices.id }).from(projectDevices)
|
||||
.where(eq(projectDevices.projectId, projectId)).all().map((row) => row.id)
|
||||
);
|
||||
for (const mapping of target.roomMappings) {
|
||||
if (mapping.roomId !== null && !roomIds.has(mapping.roomId)) {
|
||||
throw new Error("External room mapping references another project.");
|
||||
}
|
||||
if (
|
||||
mapping.defaultDistributionBoardId !== null &&
|
||||
!boardIds.has(mapping.defaultDistributionBoardId)
|
||||
) {
|
||||
throw new Error("External room mapping distribution board belongs to another project.");
|
||||
}
|
||||
}
|
||||
for (const object of target.objects) {
|
||||
if (object.distributionBoardId !== null && !boardIds.has(object.distributionBoardId)) {
|
||||
throw new Error("External object distribution board belongs to another project.");
|
||||
}
|
||||
if (object.linkedProjectDeviceId !== null && !deviceIds.has(object.linkedProjectDeviceId)) {
|
||||
throw new Error("External object project device belongs to another project.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function replaceExternalState(
|
||||
database: AppDatabase,
|
||||
projectId: string,
|
||||
target: ExternalModelStateSnapshot
|
||||
) {
|
||||
database.delete(externalModelSources)
|
||||
.where(eq(externalModelSources.projectId, projectId)).run();
|
||||
if (target.source === null) return;
|
||||
database.insert(externalModelSources).values(target.source).run();
|
||||
database.insert(externalImportBatches).values(
|
||||
target.importBatches.map(({ originalContentBase64, ...batch }) => ({
|
||||
...batch,
|
||||
originalBytes: Buffer.from(originalContentBase64, "base64"),
|
||||
}))
|
||||
).run();
|
||||
if (target.roomMappings.length) {
|
||||
database.insert(externalRoomMappings).values(target.roomMappings).run();
|
||||
}
|
||||
if (target.objects.length) {
|
||||
database.insert(externalModelObjects).values(target.objects).run();
|
||||
}
|
||||
}
|
||||
|
||||
function decodeCanonicalBase64(value: string) {
|
||||
const bytes = Buffer.from(value, "base64");
|
||||
if (bytes.toString("base64") !== value) {
|
||||
throw new Error("External initial import bytes are not canonical base64.");
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
function sameState(left: ExternalModelStateSnapshot, right: ExternalModelStateSnapshot) {
|
||||
return canonicalJson(sortState(left)) === canonicalJson(sortState(right));
|
||||
}
|
||||
|
||||
function sortState(state: ExternalModelStateSnapshot): ExternalModelStateSnapshot {
|
||||
const byId = <T extends { id: string }>(entries: readonly T[]) =>
|
||||
[...entries].sort((left, right) => left.id.localeCompare(right.id));
|
||||
return {
|
||||
source: state.source,
|
||||
importBatches: byId(state.importBatches),
|
||||
roomMappings: byId(state.roomMappings),
|
||||
objects: byId(state.objects),
|
||||
};
|
||||
}
|
||||
|
||||
function canonicalJson(value: unknown): string {
|
||||
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
|
||||
if (value !== null && typeof value === "object") {
|
||||
const record = value as Record<string, unknown>;
|
||||
return `{${Object.keys(record).sort().map(
|
||||
(key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`
|
||||
).join(",")}}`;
|
||||
}
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
import { eq } from "drizzle-orm";
|
||||
import {
|
||||
assertExternalModelConfigurationUpdateProjectCommand,
|
||||
createExternalModelConfigurationUpdateProjectCommand,
|
||||
type ExternalModelConfigurationSnapshot,
|
||||
} from "../../domain/models/external-model-configuration-project-command.model.js";
|
||||
import type {
|
||||
ExecuteExternalModelConfigurationCommandInput,
|
||||
ExternalModelConfigurationProjectCommandStore,
|
||||
} from "../../domain/ports/external-model-configuration-project-command.store.js";
|
||||
import type { AppDatabase } from "../database-context.js";
|
||||
import { externalModelConfigurations } from "../schema/external-model-configurations.js";
|
||||
import { projects } from "../schema/projects.js";
|
||||
import { executeProjectCommandTransaction } from "./project-command-transaction.persistence.js";
|
||||
|
||||
export class ExternalModelConfigurationProjectCommandRepository
|
||||
implements ExternalModelConfigurationProjectCommandStore {
|
||||
constructor(private readonly database: AppDatabase) {}
|
||||
|
||||
execute(input: ExecuteExternalModelConfigurationCommandInput) {
|
||||
assertExternalModelConfigurationUpdateProjectCommand(input.command);
|
||||
return executeProjectCommandTransaction(this.database, input, (tx) => {
|
||||
const project = tx.select({ id: projects.id }).from(projects)
|
||||
.where(eq(projects.id, input.projectId)).get();
|
||||
if (!project) throw new Error("Project not found.");
|
||||
const current = tx.select().from(externalModelConfigurations)
|
||||
.where(eq(externalModelConfigurations.projectId, input.projectId)).get() ?? null;
|
||||
assertSameSnapshot(current, input.command.payload.expected);
|
||||
const target = input.command.payload.target;
|
||||
if (target !== null && target.projectId !== input.projectId) {
|
||||
throw new Error("Die Revit-XLSX-Konfiguration gehört zu einem anderen Projekt.");
|
||||
}
|
||||
if (sameSnapshot(current, target)) {
|
||||
throw new Error("Die Revit-XLSX-Konfiguration wurde nicht geändert.");
|
||||
}
|
||||
if (target === null) {
|
||||
tx.delete(externalModelConfigurations)
|
||||
.where(eq(externalModelConfigurations.projectId, input.projectId)).run();
|
||||
} else if (current === null) {
|
||||
tx.insert(externalModelConfigurations).values(target).run();
|
||||
} else {
|
||||
const updated = tx.update(externalModelConfigurations)
|
||||
.set({ configuration: target.configuration })
|
||||
.where(eq(externalModelConfigurations.id, current.id)).run();
|
||||
if (updated.changes !== 1) {
|
||||
throw new Error("Die Revit-XLSX-Konfiguration wurde zwischenzeitlich geändert.");
|
||||
}
|
||||
}
|
||||
return createExternalModelConfigurationUpdateProjectCommand(target, current);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function assertSameSnapshot(
|
||||
current: ExternalModelConfigurationSnapshot | null,
|
||||
expected: ExternalModelConfigurationSnapshot | null
|
||||
) {
|
||||
if (!sameSnapshot(current, expected)) {
|
||||
throw new Error("Die Revit-XLSX-Konfiguration wurde zwischenzeitlich geändert.");
|
||||
}
|
||||
}
|
||||
|
||||
function sameSnapshot(
|
||||
left: ExternalModelConfigurationSnapshot | null,
|
||||
right: ExternalModelConfigurationSnapshot | null
|
||||
) {
|
||||
return canonicalJson(left) === canonicalJson(right);
|
||||
}
|
||||
|
||||
function canonicalJson(value: unknown): string {
|
||||
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
|
||||
if (value !== null && typeof value === "object") {
|
||||
const record = value as Record<string, unknown>;
|
||||
return `{${Object.keys(record).sort().map(
|
||||
(key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`
|
||||
).join(",")}}`;
|
||||
}
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
|
|
@ -1,12 +1,11 @@
|
|||
import { eq } from "drizzle-orm";
|
||||
import type { ExternalCsvConfigurationReader } from "../../domain/ports/external-csv-configuration.reader.js";
|
||||
import type { ExternalModelConfigurationReader } from "../../domain/ports/external-model-configuration.reader.js";
|
||||
import type { AppDatabase } from "../database-context.js";
|
||||
import { externalCsvConfigurations } from "../schema/external-csv-configurations.js";
|
||||
import { externalModelConfigurations } from "../schema/external-model-configurations.js";
|
||||
import { projects } from "../schema/projects.js";
|
||||
|
||||
export class ExternalCsvConfigurationRepository
|
||||
implements ExternalCsvConfigurationReader
|
||||
{
|
||||
export class ExternalModelConfigurationRepository
|
||||
implements ExternalModelConfigurationReader {
|
||||
constructor(private readonly database: AppDatabase) {}
|
||||
|
||||
getByProject(projectId: string) {
|
||||
|
|
@ -15,13 +14,11 @@ export class ExternalCsvConfigurationRepository
|
|||
.from(projects)
|
||||
.where(eq(projects.id, projectId))
|
||||
.get();
|
||||
if (!project) {
|
||||
return { projectExists: false, configuration: null };
|
||||
}
|
||||
if (!project) return { projectExists: false, configuration: null };
|
||||
const configuration = this.database
|
||||
.select()
|
||||
.from(externalCsvConfigurations)
|
||||
.where(eq(externalCsvConfigurations.projectId, projectId))
|
||||
.from(externalModelConfigurations)
|
||||
.where(eq(externalModelConfigurations.projectId, projectId))
|
||||
.get() ?? null;
|
||||
return { projectExists: true, configuration };
|
||||
}
|
||||
|
|
@ -55,7 +55,10 @@ export class ExternalModelStateRepository implements ExternalModelStateReader {
|
|||
.select()
|
||||
.from(externalModelObjects)
|
||||
.where(eq(externalModelObjects.sourceId, source.id))
|
||||
.orderBy(asc(externalModelObjects.ifcGuid), asc(externalModelObjects.id))
|
||||
.orderBy(
|
||||
asc(externalModelObjects.worksharingId),
|
||||
asc(externalModelObjects.id)
|
||||
)
|
||||
.all();
|
||||
|
||||
return {
|
||||
|
|
@ -64,7 +67,10 @@ export class ExternalModelStateRepository implements ExternalModelStateReader {
|
|||
source,
|
||||
importBatches: batches.map(({ originalBytes, ...batch }) => ({
|
||||
...batch,
|
||||
originalContentBase64: Buffer.from(originalBytes).toString("base64"),
|
||||
originalContentBase64:
|
||||
originalBytes === null
|
||||
? null
|
||||
: Buffer.from(originalBytes).toString("base64"),
|
||||
})),
|
||||
roomMappings,
|
||||
objects,
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ export function assertExternalObjectsCompatibleWithRow(input: {
|
|||
const roomIdByMappingId = new Map(mappings.map((mapping) => [mapping.id, mapping.roomId]));
|
||||
const markers = new Set(
|
||||
input.allLinkedObjects.map((object) =>
|
||||
object.acceptedSourceValues.selectionMarker.trim()
|
||||
scalarText(object.acceptedSourceValues.selectionMarker).trim()
|
||||
)
|
||||
);
|
||||
if (markers.size > 1) {
|
||||
|
|
@ -118,13 +118,17 @@ function matchesRowPlanningValues(
|
|||
row.category === planning.category &&
|
||||
row.connectionKind === planning.connectionKind &&
|
||||
(planning.powerPerUnitW === null || row.powerPerUnit === planning.powerPerUnitW / 1000) &&
|
||||
row.simultaneityFactor === planning.simultaneityFactor &&
|
||||
row.simultaneityFactor === (planning.simultaneityFactor ?? 1) &&
|
||||
row.cosPhi === planning.cosPhi &&
|
||||
row.costGroup === planning.costGroup &&
|
||||
row.remark === planning.remark
|
||||
);
|
||||
}
|
||||
|
||||
function scalarText(value: string | number | boolean | null) {
|
||||
return value === null ? "" : String(value);
|
||||
}
|
||||
|
||||
function canonicalJson(value: unknown): string {
|
||||
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
|
||||
if (value !== null && typeof value === "object") {
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ import { projectDevices } from "../schema/project-devices.js";
|
|||
import { projectChangeSets } from "../schema/project-change-sets.js";
|
||||
import { projects } from "../schema/projects.js";
|
||||
import { rooms } from "../schema/rooms.js";
|
||||
import { externalCsvConfigurations } from "../schema/external-csv-configurations.js";
|
||||
import { externalModelConfigurations } from "../schema/external-model-configurations.js";
|
||||
import { externalImportBatches } from "../schema/external-import-batches.js";
|
||||
import { externalModelObjects } from "../schema/external-model-objects.js";
|
||||
import { externalModelSources } from "../schema/external-model-sources.js";
|
||||
|
|
@ -241,7 +241,7 @@ function canonicalProjectState(state: ProjectStateSnapshot) {
|
|||
buildings: byId(state.buildings),
|
||||
floors: byId(state.floors),
|
||||
rooms: byId(state.rooms),
|
||||
externalCsvConfiguration: state.externalCsvConfiguration,
|
||||
externalModelConfiguration: state.externalModelConfiguration,
|
||||
externalModel: {
|
||||
source: state.externalModel.source,
|
||||
importBatches: byId(state.externalModel.importBatches),
|
||||
|
|
@ -283,8 +283,8 @@ function replaceProjectState(
|
|||
.where(eq(externalModelSources.projectId, state.project.id))
|
||||
.run();
|
||||
database
|
||||
.delete(externalCsvConfigurations)
|
||||
.where(eq(externalCsvConfigurations.projectId, state.project.id))
|
||||
.delete(externalModelConfigurations)
|
||||
.where(eq(externalModelConfigurations.projectId, state.project.id))
|
||||
.run();
|
||||
database
|
||||
.delete(distributionBoards)
|
||||
|
|
@ -330,10 +330,10 @@ function replaceProjectState(
|
|||
|
||||
insertMany(database, buildings, state.buildings);
|
||||
insertMany(database, floors, state.floors);
|
||||
if (state.externalCsvConfiguration !== null) {
|
||||
if (state.externalModelConfiguration !== null) {
|
||||
database
|
||||
.insert(externalCsvConfigurations)
|
||||
.values(state.externalCsvConfiguration)
|
||||
.insert(externalModelConfigurations)
|
||||
.values(state.externalModelConfiguration)
|
||||
.run();
|
||||
}
|
||||
insertMany(database, rooms, state.rooms);
|
||||
|
|
@ -387,7 +387,10 @@ function replaceProjectState(
|
|||
state.externalModel.importBatches.map(
|
||||
({ originalContentBase64, ...batch }) => ({
|
||||
...batch,
|
||||
originalBytes: Buffer.from(originalContentBase64, "base64"),
|
||||
originalBytes:
|
||||
originalContentBase64 === null
|
||||
? null
|
||||
: Buffer.from(originalContentBase64, "base64"),
|
||||
})
|
||||
)
|
||||
);
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import { floors } from "../schema/floors.js";
|
|||
import { projectDevices } from "../schema/project-devices.js";
|
||||
import { projects } from "../schema/projects.js";
|
||||
import { rooms } from "../schema/rooms.js";
|
||||
import { externalCsvConfigurations } from "../schema/external-csv-configurations.js";
|
||||
import { externalModelConfigurations } from "../schema/external-model-configurations.js";
|
||||
import { ExternalModelStateRepository } from "./external-model-state.repository.js";
|
||||
|
||||
export interface PersistedProjectStateSnapshot {
|
||||
|
|
@ -66,10 +66,10 @@ export function readProjectStateSnapshot(
|
|||
.where(eq(distributionBoards.projectId, projectId))
|
||||
.orderBy(asc(distributionBoards.name), asc(distributionBoards.id))
|
||||
.all();
|
||||
const externalCsvConfiguration = database
|
||||
const externalModelConfiguration = database
|
||||
.select()
|
||||
.from(externalCsvConfigurations)
|
||||
.where(eq(externalCsvConfigurations.projectId, projectId))
|
||||
.from(externalModelConfigurations)
|
||||
.where(eq(externalModelConfigurations.projectId, projectId))
|
||||
.get() ?? null;
|
||||
const externalModel = new ExternalModelStateRepository(database)
|
||||
.getByProject(projectId).state;
|
||||
|
|
@ -266,7 +266,7 @@ export function readProjectStateSnapshot(
|
|||
projectEditor: project.projectEditor,
|
||||
},
|
||||
distributionBoards: boardRows,
|
||||
externalCsvConfiguration,
|
||||
externalModelConfiguration,
|
||||
externalModel,
|
||||
circuitLists: listRows,
|
||||
circuitSections: sectionRows,
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import { floors } from "../schema/floors.js";
|
|||
import { projectDevices } from "../schema/project-devices.js";
|
||||
import { projects } from "../schema/projects.js";
|
||||
import { rooms } from "../schema/rooms.js";
|
||||
import { externalCsvConfigurations } from "../schema/external-csv-configurations.js";
|
||||
import { externalModelConfigurations } from "../schema/external-model-configurations.js";
|
||||
import { externalImportBatches } from "../schema/external-import-batches.js";
|
||||
import { externalModelObjects } from "../schema/external-model-objects.js";
|
||||
import { externalModelSources } from "../schema/external-model-sources.js";
|
||||
|
|
@ -163,10 +163,10 @@ function insertProjectState(
|
|||
.insert(projects)
|
||||
.values({ ...state.project, currentRevision: 0 })
|
||||
.run();
|
||||
if (state.externalCsvConfiguration !== null) {
|
||||
if (state.externalModelConfiguration !== null) {
|
||||
database
|
||||
.insert(externalCsvConfigurations)
|
||||
.values(state.externalCsvConfiguration)
|
||||
.insert(externalModelConfigurations)
|
||||
.values(state.externalModelConfiguration)
|
||||
.run();
|
||||
}
|
||||
insertMany(database, buildings, state.buildings);
|
||||
|
|
@ -240,7 +240,10 @@ function insertProjectState(
|
|||
state.externalModel.importBatches.map(
|
||||
({ originalContentBase64, ...batch }) => ({
|
||||
...batch,
|
||||
originalBytes: Buffer.from(originalContentBase64, "base64"),
|
||||
originalBytes:
|
||||
originalContentBase64 === null
|
||||
? null
|
||||
: Buffer.from(originalContentBase64, "base64"),
|
||||
})
|
||||
)
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue