import { and, eq } from "drizzle-orm"; import { assertCircuitSectionRenumberProjectCommand, createCircuitSectionRenumberProjectCommand, type CircuitSectionRenumberAssignment, } from "../../domain/models/circuit-section-renumber-project-command.model.js"; import type { CircuitSectionRenumberProjectCommandStore, ExecuteCircuitSectionRenumberCommandInput, } from "../../domain/ports/circuit-section-renumber-project-command.store.js"; import type { AppDatabase } from "../database-context.js"; import { circuitLists } from "../schema/circuit-lists.js"; import { circuitSections } from "../schema/circuit-sections.js"; import { circuits } from "../schema/circuits.js"; import { executeProjectCommandTransaction } from "./project-command-transaction.persistence.js"; export class CircuitSectionRenumberProjectCommandRepository implements CircuitSectionRenumberProjectCommandStore { constructor(private readonly database: AppDatabase) {} execute(input: ExecuteCircuitSectionRenumberCommandInput) { assertCircuitSectionRenumberProjectCommand(input.command); return executeProjectCommandTransaction( this.database, input, (tx) => this.applyCommand(tx, input) ); } private applyCommand( tx: AppDatabase, input: ExecuteCircuitSectionRenumberCommandInput ) { const section = tx .select({ id: circuitSections.id, circuitListId: circuitSections.circuitListId, projectId: circuitLists.projectId, }) .from(circuitSections) .innerJoin( circuitLists, eq(circuitLists.id, circuitSections.circuitListId) ) .where(eq(circuitSections.id, input.command.payload.sectionId)) .get(); if (!section || section.projectId !== input.projectId) { throw new Error( "Circuit section does not belong to project." ); } const sectionCircuits = tx .select({ id: circuits.id, circuitListId: circuits.circuitListId, equipmentIdentifier: circuits.equipmentIdentifier, }) .from(circuits) .where(eq(circuits.sectionId, section.id)) .all(); const assignments = input.command.payload.assignments; if ( sectionCircuits.length !== assignments.length || sectionCircuits.some( (circuit) => circuit.circuitListId !== section.circuitListId ) ) { throw new Error( "Circuit renumber must include every circuit in the section." ); } const circuitsById = new Map( sectionCircuits.map((circuit) => [circuit.id, circuit]) ); for (const assignment of assignments) { const circuit = circuitsById.get(assignment.circuitId); if ( !circuit || circuit.equipmentIdentifier !== assignment.expectedEquipmentIdentifier ) { throw new Error( "Circuit changed before section renumber execution." ); } } const listCircuits = tx .select({ id: circuits.id, equipmentIdentifier: circuits.equipmentIdentifier, }) .from(circuits) .where(eq(circuits.circuitListId, section.circuitListId)) .all(); const sectionCircuitIds = new Set( assignments.map((assignment) => assignment.circuitId) ); const targetIdentifiers = new Set( assignments.map( (assignment) => assignment.targetEquipmentIdentifier ) ); const conflictingCircuit = listCircuits.find( (circuit) => !sectionCircuitIds.has(circuit.id) && targetIdentifiers.has(circuit.equipmentIdentifier) ); if (conflictingCircuit) { throw new Error( "Target equipment identifier already exists in circuit list." ); } const inverse = createCircuitSectionRenumberProjectCommand( section.id, assignments.map((assignment) => ({ circuitId: assignment.circuitId, expectedEquipmentIdentifier: assignment.targetEquipmentIdentifier, targetEquipmentIdentifier: assignment.expectedEquipmentIdentifier, })) ); const temporaryIdentifiers = this.createTemporaryIdentifiers( section.id, assignments, listCircuits.map((circuit) => circuit.equipmentIdentifier) ); for (const assignment of assignments) { if ( assignment.expectedEquipmentIdentifier === assignment.targetEquipmentIdentifier ) { continue; } const temporaryIdentifier = temporaryIdentifiers.get( assignment.circuitId )!; const updated = tx .update(circuits) .set({ equipmentIdentifier: temporaryIdentifier }) .where( and( eq(circuits.id, assignment.circuitId), eq(circuits.sectionId, section.id), eq(circuits.circuitListId, section.circuitListId), eq( circuits.equipmentIdentifier, assignment.expectedEquipmentIdentifier ) ) ) .run(); if (updated.changes !== 1) { throw new Error( "Circuit changed during section renumber execution." ); } } for (const assignment of assignments) { if ( assignment.expectedEquipmentIdentifier === assignment.targetEquipmentIdentifier ) { continue; } const temporaryIdentifier = temporaryIdentifiers.get( assignment.circuitId )!; const updated = tx .update(circuits) .set({ equipmentIdentifier: assignment.targetEquipmentIdentifier, }) .where( and( eq(circuits.id, assignment.circuitId), eq(circuits.sectionId, section.id), eq(circuits.circuitListId, section.circuitListId), eq( circuits.equipmentIdentifier, temporaryIdentifier ) ) ) .run(); if (updated.changes !== 1) { throw new Error( "Circuit changed during final section renumber execution." ); } } return inverse; } private createTemporaryIdentifiers( sectionId: string, assignments: CircuitSectionRenumberAssignment[], occupiedIdentifiers: string[] ) { const occupied = new Set([ ...occupiedIdentifiers, ...assignments.map( (assignment) => assignment.targetEquipmentIdentifier ), ]); const temporaryIdentifiers = new Map(); for (let index = 0; index < assignments.length; index += 1) { const assignment = assignments[index]; if ( assignment.expectedEquipmentIdentifier === assignment.targetEquipmentIdentifier ) { continue; } const base = `__tmp_renumber_command_${sectionId}_${index}`; let candidate = base; while (occupied.has(candidate)) { candidate = `${candidate}_`; } occupied.add(candidate); temporaryIdentifiers.set(assignment.circuitId, candidate); } return temporaryIdentifiers; } }