import crypto from "node:crypto"; import { and, eq, inArray } from "drizzle-orm"; import type { CircuitDeviceRowTransactionStore, CreateCircuitDeviceRowTransactionInput, CreateCircuitWithDeviceRowsTransactionInput, MoveCircuitDeviceRowsTransactionInput, } from "../../domain/ports/circuit-device-row-transaction.store.js"; import type { AppDatabase } from "../database-context.js"; import { circuitDeviceRows } from "../schema/circuit-device-rows.js"; import { circuits } from "../schema/circuits.js"; import { toCircuitCreateValues } from "./circuit.persistence.js"; import { toCircuitDeviceRowCreateValues, } from "./circuit-device-row.persistence.js"; export class CircuitDeviceRowTransactionRepository implements CircuitDeviceRowTransactionStore { constructor(private readonly database: AppDatabase) {} createInCircuit(input: CreateCircuitDeviceRowTransactionInput) { const id = crypto.randomUUID(); this.database.transaction((tx) => { const [circuit] = tx .select({ id: circuits.id }) .from(circuits) .where(eq(circuits.id, input.circuitId)) .limit(1) .all(); if (!circuit) { throw new Error("Der Stromkreis ist ungültig."); } const existingRows = tx .select({ sortOrder: circuitDeviceRows.sortOrder }) .from(circuitDeviceRows) .where(eq(circuitDeviceRows.circuitId, input.circuitId)) .all(); const lastSortOrder = existingRows.reduce( (highest, row) => Math.max(highest, row.sortOrder), 0 ); const sortOrder = input.sortOrder ?? lastSortOrder + 10; tx .insert(circuitDeviceRows) .values(toCircuitDeviceRowCreateValues(id, { ...input, sortOrder })) .run(); tx .update(circuits) .set({ isReserve: 0 }) .where(eq(circuits.id, input.circuitId)) .run(); }); return id; } createCircuitWithDeviceRows(input: CreateCircuitWithDeviceRowsTransactionInput) { if (input.deviceRows.length === 0) { throw new Error("Mindestens eine Gerätezeile ist erforderlich."); } const circuitId = crypto.randomUUID(); const rowIds = input.deviceRows.map(() => crypto.randomUUID()); this.database.transaction((tx) => { tx .insert(circuits) .values( toCircuitCreateValues(circuitId, { ...input.circuit, isReserve: false, }) ) .run(); for (let index = 0; index < input.deviceRows.length; index += 1) { const row = input.deviceRows[index]; tx .insert(circuitDeviceRows) .values( toCircuitDeviceRowCreateValues(rowIds[index], { ...row, circuitId, sortOrder: row.sortOrder ?? (index + 1) * 10, }) ) .run(); } }); return { circuitId, rowIds }; } deleteFromCircuit(rowId: string, expectedCircuitId: string) { this.database.transaction((tx) => { const [row] = tx .select({ id: circuitDeviceRows.id, circuitId: circuitDeviceRows.circuitId }) .from(circuitDeviceRows) .where(eq(circuitDeviceRows.id, rowId)) .limit(1) .all(); if (!row || row.circuitId !== expectedCircuitId) { throw new Error("Die Gerätezeile wurde vor dem Löschen verändert."); } const result = tx .delete(circuitDeviceRows) .where( and( eq(circuitDeviceRows.id, rowId), eq(circuitDeviceRows.circuitId, expectedCircuitId) ) ) .run(); if (result.changes !== 1) { throw new Error("Die Gerätezeile konnte nicht gelöscht werden."); } const remainingRows = tx .select({ id: circuitDeviceRows.id }) .from(circuitDeviceRows) .where(eq(circuitDeviceRows.circuitId, expectedCircuitId)) .all(); tx .update(circuits) .set({ isReserve: remainingRows.length === 0 ? 1 : 0 }) .where(eq(circuits.id, expectedCircuitId)) .run(); }); } moveRows(input: MoveCircuitDeviceRowsTransactionInput) { if (input.rows.length === 0) { throw new Error("Keine Gerätezeilen zum Verschieben angegeben."); } if (Boolean(input.targetCircuitId) === Boolean(input.createTargetCircuit)) { throw new Error("Für die Mehrfachverschiebung muss genau ein Ziel angegeben werden."); } const rowIds = input.rows.map((row) => row.id); if (new Set(rowIds).size !== rowIds.length) { throw new Error("Gerätezeilen dürfen nicht mehrfach ausgewählt sein."); } const createdCircuitId = input.createTargetCircuit ? crypto.randomUUID() : undefined; const targetCircuitId = input.targetCircuitId ?? createdCircuitId!; this.database.transaction((tx) => { const persistedRows = tx .select({ id: circuitDeviceRows.id, circuitId: circuitDeviceRows.circuitId }) .from(circuitDeviceRows) .where(inArray(circuitDeviceRows.id, rowIds)) .all(); if (persistedRows.length !== input.rows.length) { throw new Error("Eine oder mehrere Gerätezeilen sind ungültig."); } const persistedRowsById = new Map(persistedRows.map((row) => [row.id, row])); for (const expectedRow of input.rows) { if ( persistedRowsById.get(expectedRow.id)?.circuitId !== expectedRow.expectedCircuitId ) { throw new Error( "Eine Gerätezeile wurde vor Abschluss der Verschiebung verändert." ); } } let targetCircuit: { id: string; circuitListId: string }; if (input.createTargetCircuit) { tx .insert(circuits) .values({ id: targetCircuitId, circuitListId: input.createTargetCircuit.circuitListId, sectionId: input.createTargetCircuit.sectionId, equipmentIdentifier: input.createTargetCircuit.equipmentIdentifier, displayName: input.createTargetCircuit.displayName, sortOrder: input.createTargetCircuit.sortOrder, isReserve: 0, }) .run(); targetCircuit = { id: targetCircuitId, circuitListId: input.createTargetCircuit.circuitListId, }; } else { const [persistedTargetCircuit] = tx .select({ id: circuits.id, circuitListId: circuits.circuitListId }) .from(circuits) .where(eq(circuits.id, targetCircuitId)) .limit(1) .all(); if (!persistedTargetCircuit) { throw new Error("Der Zielstromkreis ist ungültig."); } targetCircuit = persistedTargetCircuit; } const sourceCircuitIds = [ ...new Set(input.rows.map((row) => row.expectedCircuitId)), ]; const sourceCircuits = tx .select({ id: circuits.id, circuitListId: circuits.circuitListId }) .from(circuits) .where(inArray(circuits.id, sourceCircuitIds)) .all(); if (sourceCircuits.length !== sourceCircuitIds.length) { throw new Error("Einer oder mehrere Quellstromkreise sind ungültig."); } if ( sourceCircuits.some( (circuit) => circuit.circuitListId !== targetCircuit.circuitListId ) ) { throw new Error( "Alle verschobenen Zeilen müssen zur Stromkreisliste des Ziels gehören." ); } const movedRowIdSet = new Set(rowIds); const retainedTargetRows = tx .select({ id: circuitDeviceRows.id, sortOrder: circuitDeviceRows.sortOrder }) .from(circuitDeviceRows) .where(eq(circuitDeviceRows.circuitId, targetCircuitId)) .all() .filter((row) => !movedRowIdSet.has(row.id)); const lastTargetSortOrder = retainedTargetRows.reduce( (highest, row) => Math.max(highest, row.sortOrder), 0 ); for (let index = 0; index < input.rows.length; index += 1) { const row = input.rows[index]; const result = tx .update(circuitDeviceRows) .set({ circuitId: targetCircuitId, sortOrder: lastTargetSortOrder + (index + 1) * 10, }) .where( and( eq(circuitDeviceRows.id, row.id), eq(circuitDeviceRows.circuitId, row.expectedCircuitId) ) ) .run(); if (result.changes !== 1) { throw new Error( "Eine Gerätezeile wurde vor Abschluss der Verschiebung verändert." ); } } for (const sourceCircuitId of sourceCircuitIds) { const remainingRows = tx .select({ id: circuitDeviceRows.id }) .from(circuitDeviceRows) .where(eq(circuitDeviceRows.circuitId, sourceCircuitId)) .all(); tx .update(circuits) .set({ isReserve: remainingRows.length === 0 ? 1 : 0 }) .where(eq(circuits.id, sourceCircuitId)) .run(); } tx .update(circuits) .set({ isReserve: 0 }) .where(eq(circuits.id, targetCircuitId)) .run(); }); return { movedRowIds: rowIds, targetCircuitId, createdCircuitId, }; } }