import { and, eq, inArray } from "drizzle-orm"; import { assertCircuitDeviceRowQuantity } from "../../domain/calculations/circuit-device-row-quantity.js"; import { assertExternalObjectDeleteMissingProjectCommand, invertExternalObjectDeleteMissingProjectCommand } from "../../domain/models/external-object-delete-missing-project-command.model.js"; import type { ExternalObjectDeleteMissingProjectCommandStore } from "../../domain/ports/external-object-delete-missing-project-command.store.js"; import type { AppDatabase } from "../database-context.js"; import { circuitDeviceRows } from "../schema/circuit-device-rows.js"; import { circuitLists } from "../schema/circuit-lists.js"; import { circuits } from "../schema/circuits.js"; import { externalModelObjects } from "../schema/external-model-objects.js"; import { externalModelSources } from "../schema/external-model-sources.js"; import { externalImportBatches } from "../schema/external-import-batches.js"; import { externalRoomMappings } from "../schema/external-room-mappings.js"; import { distributionBoards } from "../schema/distribution-boards.js"; import { projectDevices } from "../schema/project-devices.js"; import { toCircuitDeviceRowSnapshot } from "./circuit-device-row-structure.persistence.js"; import { snapshotsEqual, toExternalObjectSnapshot } from "./external-object-assignment.persistence.js"; import { executeProjectCommandTransaction } from "./project-command-transaction.persistence.js"; export class ExternalObjectDeleteMissingProjectCommandRepository implements ExternalObjectDeleteMissingProjectCommandStore { constructor(private readonly database: AppDatabase) {} execute(input: Parameters[0]) { assertExternalObjectDeleteMissingProjectCommand(input.command); return executeProjectCommandTransaction(this.database, input, (tx) => { const rowById = new Map(input.command.payload.rows.map((transition) => [transition.expected.id, transition])); const rowIds = [...rowById.keys()]; const rowContexts = rowIds.length ? tx.select({ row: circuitDeviceRows, projectId: circuitLists.projectId }).from(circuitDeviceRows).innerJoin(circuits, eq(circuits.id, circuitDeviceRows.circuitId)).innerJoin(circuitLists, eq(circuitLists.id, circuits.circuitListId)).where(inArray(circuitDeviceRows.id, rowIds)).all() : []; if (rowContexts.length !== rowIds.length || rowContexts.some((entry) => entry.projectId !== input.projectId || !snapshotsEqual(toCircuitDeviceRowSnapshot(entry.row), rowById.get(entry.row.id)!.expected))) throw new Error("Eine Gerätezeile wurde zwischenzeitlich geändert."); const currentObjects = tx.select().from(externalModelObjects).where(eq(externalModelObjects.projectId, input.projectId)).all().map(toExternalObjectSnapshot); const currentById = new Map(currentObjects.map((object) => [object.id, object])); for (const transition of input.command.payload.objects) { const current = currentById.get((transition.expected ?? transition.target)!.id) ?? null; if (transition.expected === null ? current !== null : current === null || !snapshotsEqual(current, transition.expected)) throw new Error("Ein fehlendes Revit-Objekt wurde zwischenzeitlich geändert."); } const sourceIds = ids(tx.select({ id: externalModelSources.id }).from(externalModelSources).where(eq(externalModelSources.projectId, input.projectId)).all()); const batchIds = ids(tx.select({ id: externalImportBatches.id }).from(externalImportBatches).where(eq(externalImportBatches.projectId, input.projectId)).all()); const mappingIds = ids(tx.select({ id: externalRoomMappings.id }).from(externalRoomMappings).where(eq(externalRoomMappings.projectId, input.projectId)).all()); const boardIds = ids(tx.select({ id: distributionBoards.id }).from(distributionBoards).where(eq(distributionBoards.projectId, input.projectId)).all()); const deviceIds = ids(tx.select({ id: projectDevices.id }).from(projectDevices).where(eq(projectDevices.projectId, input.projectId)).all()); for (const { target } of input.command.payload.objects) if (target && (target.projectId !== input.projectId || !sourceIds.has(target.sourceId) || !batchIds.has(target.lastSeenImportBatchId) || !batchIds.has(target.lastAcceptedImportBatchId) || (target.externalRoomMappingId !== null && !mappingIds.has(target.externalRoomMappingId)) || (target.distributionBoardId !== null && !boardIds.has(target.distributionBoardId)) || (target.linkedProjectDeviceId !== null && !deviceIds.has(target.linkedProjectDeviceId)))) throw new Error("Ein wiederhergestelltes Revit-Objekt verweist auf ein anderes Projekt."); const targetById = new Map(currentObjects.map((object) => [object.id, object])); for (const transition of input.command.payload.objects) { const id = (transition.expected ?? transition.target)!.id; if (transition.target) targetById.set(id, transition.target); else targetById.delete(id); } for (const transition of input.command.payload.rows) assertCircuitDeviceRowQuantity({ quantity: transition.target.quantity, manualQuantity: transition.target.manualQuantity ?? transition.target.quantity, externalObjects: [...targetById.values()].filter((object) => object.circuitDeviceRowId === transition.target.id).map((object) => ({ effectiveQuantity: object.planningValues.effectiveQuantity })) }); for (const transition of input.command.payload.rows) { const result = tx.update(circuitDeviceRows).set({ quantity: transition.target.quantity }).where(and(eq(circuitDeviceRows.id, transition.expected.id), eq(circuitDeviceRows.quantity, transition.expected.quantity))).run(); if (result.changes !== 1) throw new Error("Eine Gerätezeile wurde während des Löschens geändert."); } for (const transition of input.command.payload.objects) { const object = transition.expected ?? transition.target!; if (transition.target === null) { const result = tx.delete(externalModelObjects).where(and(eq(externalModelObjects.id, object.id), eq(externalModelObjects.projectId, input.projectId))).run(); if (result.changes !== 1) throw new Error("Ein fehlendes Revit-Objekt konnte nicht gelöscht werden."); } else tx.insert(externalModelObjects).values(transition.target).run(); } return invertExternalObjectDeleteMissingProjectCommand(input.command); }); } } function ids(entries: Array<{ id: string }>) { return new Set(entries.map((entry) => entry.id)); }