import { and, eq } from "drizzle-orm"; import { assertCircuitDeviceRowDeleteProjectCommand, assertCircuitDeviceRowInsertProjectCommand, circuitDeviceRowDeleteCommandType, circuitDeviceRowInsertCommandType, createCircuitDeviceRowDeleteProjectCommand, createCircuitDeviceRowInsertProjectCommand, type CircuitDeviceRowSnapshot, type CircuitDeviceRowStructureProjectCommand, } from "../../domain/models/circuit-device-row-structure-project-command.model.js"; import type { CircuitDeviceRowStructureProjectCommandStore, ExecuteCircuitDeviceRowStructureCommandInput, } from "../../domain/ports/circuit-device-row-structure-project-command.store.js"; import type { AppDatabase } from "../database-context.js"; import { isElectricalPhaseType } from "../../domain/services/project-voltage.service.js"; import { circuitDeviceRows } from "../schema/circuit-device-rows.js"; import { circuitLists } from "../schema/circuit-lists.js"; import { circuits } from "../schema/circuits.js"; import { assertCircuitDeviceRowReferencesInProject, toCircuitDeviceRowInsertValues, toCircuitDeviceRowSnapshot, } from "./circuit-device-row-structure.persistence.js"; import { executeProjectCommandTransaction } from "./project-command-transaction.persistence.js"; import { updateDerivedCircuitVoltage } from "./project-voltage.persistence.js"; export class CircuitDeviceRowStructureProjectCommandRepository implements CircuitDeviceRowStructureProjectCommandStore { constructor(private readonly database: AppDatabase) {} execute(input: ExecuteCircuitDeviceRowStructureCommandInput) { return executeProjectCommandTransaction( this.database, input, (tx) => this.applyCommand( tx, input.projectId, input.source, input.command ) ); } private applyCommand( database: AppDatabase, projectId: string, source: ExecuteCircuitDeviceRowStructureCommandInput["source"], command: CircuitDeviceRowStructureProjectCommand ): CircuitDeviceRowStructureProjectCommand { if (command.type === circuitDeviceRowInsertCommandType) { assertCircuitDeviceRowInsertProjectCommand(command); return this.insert( database, projectId, source, command.payload.row ); } if (command.type === circuitDeviceRowDeleteCommandType) { assertCircuitDeviceRowDeleteProjectCommand(command); return this.delete( database, projectId, command.payload.rowId, command.payload.expectedCircuitId ); } throw new Error("Unsupported circuit device-row structure command."); } private insert( database: AppDatabase, projectId: string, source: ExecuteCircuitDeviceRowStructureCommandInput["source"], row: CircuitDeviceRowSnapshot ) { if (source === "user" && !isElectricalPhaseType(row.phaseType)) { throw new Error("Device-row phase type is invalid."); } this.assertCircuitInProject(database, projectId, row.circuitId); assertCircuitDeviceRowReferencesInProject(database, projectId, row); const existing = database .select({ id: circuitDeviceRows.id }) .from(circuitDeviceRows) .where(eq(circuitDeviceRows.id, row.id)) .get(); if (existing) { throw new Error("Circuit device-row id already exists."); } database .insert(circuitDeviceRows) .values(toCircuitDeviceRowInsertValues(row)) .run(); const circuitUpdate = database .update(circuits) .set({ isReserve: 0 }) .where(eq(circuits.id, row.circuitId)) .run(); if (circuitUpdate.changes !== 1) { throw new Error("Circuit changed before device-row insertion."); } updateDerivedCircuitVoltage(database, projectId, row.circuitId); return createCircuitDeviceRowDeleteProjectCommand( row.id, row.circuitId ); } private delete( database: AppDatabase, projectId: string, rowId: string, expectedCircuitId: string ) { const row = database .select() .from(circuitDeviceRows) .where(eq(circuitDeviceRows.id, rowId)) .get(); if (!row || row.circuitId !== expectedCircuitId) { throw new Error( "Circuit device row changed before command execution." ); } this.assertCircuitInProject(database, projectId, row.circuitId); const result = database .delete(circuitDeviceRows) .where( and( eq(circuitDeviceRows.id, rowId), eq(circuitDeviceRows.circuitId, expectedCircuitId) ) ) .run(); if (result.changes !== 1) { throw new Error("Circuit device row could not be deleted."); } const remainingRow = database .select({ id: circuitDeviceRows.id }) .from(circuitDeviceRows) .where(eq(circuitDeviceRows.circuitId, expectedCircuitId)) .limit(1) .get(); const circuitUpdate = database .update(circuits) .set({ isReserve: remainingRow ? 0 : 1 }) .where(eq(circuits.id, expectedCircuitId)) .run(); if (circuitUpdate.changes !== 1) { throw new Error("Circuit changed before device-row deletion."); } updateDerivedCircuitVoltage( database, projectId, expectedCircuitId ); return createCircuitDeviceRowInsertProjectCommand( toCircuitDeviceRowSnapshot(row) ); } private assertCircuitInProject( database: AppDatabase, projectId: string, circuitId: string ) { const circuit = database .select({ id: circuits.id }) .from(circuits) .innerJoin( circuitLists, eq(circuitLists.id, circuits.circuitListId) ) .where( and( eq(circuits.id, circuitId), eq(circuitLists.projectId, projectId) ) ) .get(); if (!circuit) { throw new Error("Circuit does not belong to project."); } } }