import { CircuitDeviceRowRepository } from "../../db/repositories/circuit-device-row.repository.js"; import { CircuitListRepository } from "../../db/repositories/circuit-list.repository.js"; import { CircuitRepository } from "../../db/repositories/circuit.repository.js"; import { CircuitSectionRepository } from "../../db/repositories/circuit-section.repository.js"; import { ProjectDeviceRepository } from "../../db/repositories/project-device.repository.js"; import type { CreateCircuitDeviceRowInput, CreateCircuitInput, MoveCircuitDeviceRowInput, ReorderSectionCircuitsInput, UpdateSectionEquipmentIdentifiersInput, UpdateCircuitDeviceRowInput, UpdateCircuitInput, } from "../../shared/validation/circuit.schemas.js"; import { CircuitNumberingService } from "./circuit-numbering.service.js"; export class CircuitWriteService { private readonly circuitRepository: CircuitRepository; private readonly circuitSectionRepository: CircuitSectionRepository; private readonly circuitListRepository: CircuitListRepository; private readonly deviceRowRepository: CircuitDeviceRowRepository; private readonly projectDeviceRepository: ProjectDeviceRepository; private readonly numberingService: CircuitNumberingService; constructor(deps?: { circuitRepository?: CircuitRepository; circuitSectionRepository?: CircuitSectionRepository; circuitListRepository?: CircuitListRepository; deviceRowRepository?: CircuitDeviceRowRepository; projectDeviceRepository?: ProjectDeviceRepository; numberingService?: CircuitNumberingService; }) { this.circuitRepository = deps?.circuitRepository ?? new CircuitRepository(); this.circuitSectionRepository = deps?.circuitSectionRepository ?? new CircuitSectionRepository(); this.circuitListRepository = deps?.circuitListRepository ?? new CircuitListRepository(); this.deviceRowRepository = deps?.deviceRowRepository ?? new CircuitDeviceRowRepository(); this.projectDeviceRepository = deps?.projectDeviceRepository ?? new ProjectDeviceRepository(); this.numberingService = deps?.numberingService ?? new CircuitNumberingService(); } private async assertSectionInList(sectionId: string, circuitListId: string) { const section = await this.circuitSectionRepository.findById(sectionId); if (!section) { throw new Error("Invalid section id."); } if (section.circuitListId !== circuitListId) { throw new Error("Section does not belong to circuit list."); } return section; } private async assertUniqueEquipmentIdentifier( circuitListId: string, equipmentIdentifier: string, excludeCircuitId?: string ) { const exists = await this.circuitRepository.existsByEquipmentIdentifier( circuitListId, equipmentIdentifier, excludeCircuitId ); if (exists) { throw new Error("Duplicate equipmentIdentifier in circuit list."); } } private async assertValidLinkedProjectDevice(circuitId: string, linkedProjectDeviceId?: string) { if (!linkedProjectDeviceId) { return; } const circuit = await this.circuitRepository.findById(circuitId); if (!circuit) { throw new Error("Invalid circuit id."); } const list = await this.circuitListRepository.findByIdByListIdOnly(circuit.circuitListId); if (!list) { throw new Error("Circuit list not found."); } const device = await this.projectDeviceRepository.findById(list.projectId, linkedProjectDeviceId); if (!device) { throw new Error("Invalid linked project device id."); } } async createCircuit(projectId: string, circuitListId: string, input: CreateCircuitInput) { const list = await this.circuitListRepository.findById(projectId, circuitListId); if (!list) { throw new Error("Circuit list not found in project."); } await this.assertSectionInList(input.sectionId, circuitListId); await this.assertUniqueEquipmentIdentifier(circuitListId, input.equipmentIdentifier); const id = await this.circuitRepository.create({ circuitListId, sectionId: input.sectionId, equipmentIdentifier: input.equipmentIdentifier, displayName: input.displayName, sortOrder: input.sortOrder, protectionType: input.protectionType, protectionRatedCurrent: input.protectionRatedCurrent, protectionCharacteristic: input.protectionCharacteristic, cableType: input.cableType, cableCrossSection: input.cableCrossSection, cableLength: input.cableLength, rcdAssignment: input.rcdAssignment, terminalDesignation: input.terminalDesignation, voltage: input.voltage, status: input.status, isReserve: input.isReserve ?? true, remark: input.remark, }); return this.circuitRepository.findById(id); } async updateCircuit(circuitId: string, input: UpdateCircuitInput) { const current = await this.circuitRepository.findById(circuitId); if (!current) { throw new Error("Invalid circuit id."); } const sectionId = input.sectionId ?? current.sectionId; const equipmentIdentifier = input.equipmentIdentifier ?? current.equipmentIdentifier; const sortOrder = input.sortOrder ?? current.sortOrder; await this.assertSectionInList(sectionId, current.circuitListId); await this.assertUniqueEquipmentIdentifier(current.circuitListId, equipmentIdentifier, circuitId); await this.circuitRepository.update(circuitId, { sectionId, equipmentIdentifier, displayName: input.displayName ?? current.displayName ?? undefined, sortOrder, protectionType: input.protectionType ?? current.protectionType ?? undefined, protectionRatedCurrent: input.protectionRatedCurrent ?? current.protectionRatedCurrent ?? undefined, protectionCharacteristic: input.protectionCharacteristic ?? current.protectionCharacteristic ?? undefined, cableType: input.cableType ?? current.cableType ?? undefined, cableCrossSection: input.cableCrossSection ?? current.cableCrossSection ?? undefined, cableLength: input.cableLength ?? current.cableLength ?? undefined, rcdAssignment: input.rcdAssignment ?? current.rcdAssignment ?? undefined, terminalDesignation: input.terminalDesignation ?? current.terminalDesignation ?? undefined, voltage: input.voltage ?? current.voltage ?? undefined, status: input.status ?? current.status ?? undefined, isReserve: input.isReserve ?? Boolean(current.isReserve), remark: input.remark ?? current.remark ?? undefined, }); return this.circuitRepository.findById(circuitId); } async deleteCircuit(circuitId: string) { const current = await this.circuitRepository.findById(circuitId); if (!current) { throw new Error("Invalid circuit id."); } await this.circuitRepository.delete(circuitId); } async createDeviceRow(circuitId: string, input: CreateCircuitDeviceRowInput) { const circuit = await this.circuitRepository.findById(circuitId); if (!circuit) { throw new Error("Invalid circuit id."); } await this.assertValidLinkedProjectDevice(circuitId, input.linkedProjectDeviceId); const existingRows = await this.deviceRowRepository.countByCircuit(circuitId); const rowId = await this.deviceRowRepository.create({ circuitId, linkedProjectDeviceId: input.linkedProjectDeviceId, sortOrder: input.sortOrder ?? (existingRows + 1) * 10, name: input.name, displayName: input.displayName, phaseType: input.phaseType, connectionKind: input.connectionKind, costGroup: input.costGroup, category: input.category, level: input.level, roomId: input.roomId, roomNumberSnapshot: input.roomNumberSnapshot, roomNameSnapshot: input.roomNameSnapshot, quantity: input.quantity, powerPerUnit: input.powerPerUnit, simultaneityFactor: input.simultaneityFactor, cosPhi: input.cosPhi, remark: input.remark, overriddenFields: input.overriddenFields, }); if (Boolean(circuit.isReserve)) { await this.circuitRepository.update(circuit.id, { sectionId: circuit.sectionId, equipmentIdentifier: circuit.equipmentIdentifier, displayName: circuit.displayName ?? undefined, sortOrder: circuit.sortOrder, protectionType: circuit.protectionType ?? undefined, protectionRatedCurrent: circuit.protectionRatedCurrent ?? undefined, protectionCharacteristic: circuit.protectionCharacteristic ?? undefined, cableType: circuit.cableType ?? undefined, cableCrossSection: circuit.cableCrossSection ?? undefined, cableLength: circuit.cableLength ?? undefined, rcdAssignment: circuit.rcdAssignment ?? undefined, terminalDesignation: circuit.terminalDesignation ?? undefined, voltage: circuit.voltage ?? undefined, status: circuit.status ?? undefined, isReserve: false, remark: circuit.remark ?? undefined, }); } return this.deviceRowRepository.findById(rowId); } async updateDeviceRow(rowId: string, input: UpdateCircuitDeviceRowInput) { const current = await this.deviceRowRepository.findById(rowId); if (!current) { throw new Error("Invalid device row id."); } await this.assertValidLinkedProjectDevice(current.circuitId, input.linkedProjectDeviceId); await this.deviceRowRepository.update(rowId, { linkedProjectDeviceId: input.linkedProjectDeviceId ?? current.linkedProjectDeviceId ?? undefined, name: input.name ?? current.name, displayName: input.displayName ?? current.displayName, phaseType: input.phaseType ?? current.phaseType ?? undefined, connectionKind: input.connectionKind ?? current.connectionKind ?? undefined, costGroup: input.costGroup ?? current.costGroup ?? undefined, category: input.category ?? current.category ?? undefined, level: input.level ?? current.level ?? undefined, roomId: input.roomId ?? current.roomId ?? undefined, roomNumberSnapshot: input.roomNumberSnapshot ?? current.roomNumberSnapshot ?? undefined, roomNameSnapshot: input.roomNameSnapshot ?? current.roomNameSnapshot ?? undefined, quantity: input.quantity ?? current.quantity, powerPerUnit: input.powerPerUnit ?? current.powerPerUnit, simultaneityFactor: input.simultaneityFactor ?? current.simultaneityFactor, cosPhi: input.cosPhi ?? current.cosPhi ?? undefined, remark: input.remark ?? current.remark ?? undefined, overriddenFields: input.overriddenFields ?? current.overriddenFields ?? undefined, }); return this.deviceRowRepository.findById(rowId); } async deleteDeviceRow(rowId: string) { const current = await this.deviceRowRepository.findById(rowId); if (!current) { throw new Error("Invalid device row id."); } const circuit = await this.circuitRepository.findById(current.circuitId); if (!circuit) { throw new Error("Invalid circuit id."); } await this.deviceRowRepository.delete(rowId); const remaining = await this.deviceRowRepository.countByCircuit(current.circuitId); if (remaining === 0) { await this.circuitRepository.update(circuit.id, { sectionId: circuit.sectionId, equipmentIdentifier: circuit.equipmentIdentifier, displayName: circuit.displayName ?? undefined, sortOrder: circuit.sortOrder, protectionType: circuit.protectionType ?? undefined, protectionRatedCurrent: circuit.protectionRatedCurrent ?? undefined, protectionCharacteristic: circuit.protectionCharacteristic ?? undefined, cableType: circuit.cableType ?? undefined, cableCrossSection: circuit.cableCrossSection ?? undefined, cableLength: circuit.cableLength ?? undefined, rcdAssignment: circuit.rcdAssignment ?? undefined, terminalDesignation: circuit.terminalDesignation ?? undefined, voltage: circuit.voltage ?? undefined, status: circuit.status ?? undefined, isReserve: true, remark: circuit.remark ?? undefined, }); } } async moveDeviceRow(rowId: string, input: MoveCircuitDeviceRowInput) { const row = await this.deviceRowRepository.findById(rowId); if (!row) { throw new Error("Invalid device row id."); } const sourceCircuit = await this.circuitRepository.findById(row.circuitId); if (!sourceCircuit) { throw new Error("Invalid circuit id."); } let targetCircuit = input.targetCircuitId ? await this.circuitRepository.findById(input.targetCircuitId) : null; if (input.targetCircuitId && !targetCircuit) { throw new Error("Invalid target circuit id."); } if (!targetCircuit) { if (!input.targetSectionId || !input.createNewCircuit) { throw new Error("Invalid move target."); } const section = await this.assertSectionInList(input.targetSectionId, sourceCircuit.circuitListId); const nextIdentifier = await this.numberingService.getNextIdentifier(section.id); const sectionCircuits = await this.circuitRepository.listBySection(section.id); const nextSortOrder = sectionCircuits.length > 0 ? Math.max(...sectionCircuits.map((circuit) => circuit.sortOrder)) + 10 : 10; const createdId = await this.circuitRepository.create({ circuitListId: sourceCircuit.circuitListId, sectionId: section.id, equipmentIdentifier: nextIdentifier, displayName: "New circuit", sortOrder: nextSortOrder, isReserve: false, }); targetCircuit = await this.circuitRepository.findById(createdId); if (!targetCircuit) { throw new Error("Failed to create target circuit."); } } if (targetCircuit.circuitListId !== sourceCircuit.circuitListId) { throw new Error("Target circuit does not belong to same circuit list."); } if (targetCircuit.id === sourceCircuit.id) { return this.deviceRowRepository.findById(rowId); } const targetCount = await this.deviceRowRepository.countByCircuit(targetCircuit.id); await this.deviceRowRepository.moveToCircuit(rowId, targetCircuit.id, (targetCount + 1) * 10); const sourceRemaining = await this.deviceRowRepository.countByCircuit(sourceCircuit.id); if (sourceRemaining === 0) { await this.circuitRepository.update(sourceCircuit.id, { sectionId: sourceCircuit.sectionId, equipmentIdentifier: sourceCircuit.equipmentIdentifier, displayName: sourceCircuit.displayName ?? undefined, sortOrder: sourceCircuit.sortOrder, protectionType: sourceCircuit.protectionType ?? undefined, protectionRatedCurrent: sourceCircuit.protectionRatedCurrent ?? undefined, protectionCharacteristic: sourceCircuit.protectionCharacteristic ?? undefined, cableType: sourceCircuit.cableType ?? undefined, cableCrossSection: sourceCircuit.cableCrossSection ?? undefined, cableLength: sourceCircuit.cableLength ?? undefined, rcdAssignment: sourceCircuit.rcdAssignment ?? undefined, terminalDesignation: sourceCircuit.terminalDesignation ?? undefined, voltage: sourceCircuit.voltage ?? undefined, status: sourceCircuit.status ?? undefined, isReserve: true, remark: sourceCircuit.remark ?? undefined, }); } if (Boolean(targetCircuit.isReserve)) { await this.circuitRepository.update(targetCircuit.id, { sectionId: targetCircuit.sectionId, equipmentIdentifier: targetCircuit.equipmentIdentifier, displayName: targetCircuit.displayName ?? undefined, sortOrder: targetCircuit.sortOrder, protectionType: targetCircuit.protectionType ?? undefined, protectionRatedCurrent: targetCircuit.protectionRatedCurrent ?? undefined, protectionCharacteristic: targetCircuit.protectionCharacteristic ?? undefined, cableType: targetCircuit.cableType ?? undefined, cableCrossSection: targetCircuit.cableCrossSection ?? undefined, cableLength: targetCircuit.cableLength ?? undefined, rcdAssignment: targetCircuit.rcdAssignment ?? undefined, terminalDesignation: targetCircuit.terminalDesignation ?? undefined, voltage: targetCircuit.voltage ?? undefined, status: targetCircuit.status ?? undefined, isReserve: false, remark: targetCircuit.remark ?? undefined, }); } return this.deviceRowRepository.findById(rowId); } async getNextIdentifier(sectionId: string) { return this.numberingService.getNextIdentifier(sectionId); } async renumberSection(sectionId: string) { const section = await this.circuitSectionRepository.findById(sectionId); if (!section) { throw new Error("Invalid section id."); } const sectionCircuits = await this.circuitRepository.listBySection(sectionId); const otherCircuits = (await this.circuitRepository.listByCircuitList(section.circuitListId)).filter( (circuit) => circuit.sectionId !== sectionId ); const otherIdentifiers = new Set(otherCircuits.map((circuit) => circuit.equipmentIdentifier)); const finalAssignments: Array<{ id: string; equipmentIdentifier: string }> = []; let index = 1; for (const circuit of sectionCircuits) { let candidate = `${section.prefix}${index}`; while (otherIdentifiers.has(candidate)) { index += 1; candidate = `${section.prefix}${index}`; } finalAssignments.push({ id: circuit.id, equipmentIdentifier: candidate }); index += 1; } await this.circuitRepository.updateEquipmentIdentifiersSafely( section.circuitListId, finalAssignments, sectionId ); return this.circuitRepository.listBySection(sectionId); } async updateSectionEquipmentIdentifiers( sectionId: string, input: UpdateSectionEquipmentIdentifiersInput ) { const section = await this.circuitSectionRepository.findById(sectionId); if (!section) { throw new Error("Invalid section id."); } const sectionCircuits = await this.circuitRepository.listBySection(sectionId); const sectionIds = new Set(sectionCircuits.map((circuit) => circuit.id)); if (input.identifiers.length !== sectionCircuits.length) { throw new Error("identifiers must include all circuits in the section."); } for (const entry of input.identifiers) { if (!sectionIds.has(entry.circuitId)) { throw new Error("Circuit id does not belong to section."); } } await this.circuitRepository.updateEquipmentIdentifiersSafely( section.circuitListId, input.identifiers.map((entry) => ({ id: entry.circuitId, equipmentIdentifier: entry.equipmentIdentifier })), sectionId ); return this.circuitRepository.listBySection(sectionId); } async reorderCircuitsInSection(sectionId: string, input: ReorderSectionCircuitsInput) { const section = await this.circuitSectionRepository.findById(sectionId); if (!section) { throw new Error("Invalid section id."); } const sectionCircuits = await this.circuitRepository.listBySection(sectionId); const sectionIds = new Set(sectionCircuits.map((circuit) => circuit.id)); if (sectionCircuits.length !== input.orderedCircuitIds.length) { throw new Error("orderedCircuitIds must include all circuits of the section."); } for (const circuitId of input.orderedCircuitIds) { if (!sectionIds.has(circuitId)) { throw new Error("Circuit id does not belong to section."); } } for (let index = 0; index < input.orderedCircuitIds.length; index += 1) { const circuitId = input.orderedCircuitIds[index]; const circuit = sectionCircuits.find((entry) => entry.id === circuitId); if (!circuit) { throw new Error("Invalid circuit id."); } await this.circuitRepository.update(circuit.id, { sectionId: circuit.sectionId, equipmentIdentifier: circuit.equipmentIdentifier, displayName: circuit.displayName ?? undefined, sortOrder: (index + 1) * 10, protectionType: circuit.protectionType ?? undefined, protectionRatedCurrent: circuit.protectionRatedCurrent ?? undefined, protectionCharacteristic: circuit.protectionCharacteristic ?? undefined, cableType: circuit.cableType ?? undefined, cableCrossSection: circuit.cableCrossSection ?? undefined, cableLength: circuit.cableLength ?? undefined, rcdAssignment: circuit.rcdAssignment ?? undefined, terminalDesignation: circuit.terminalDesignation ?? undefined, voltage: circuit.voltage ?? undefined, status: circuit.status ?? undefined, isReserve: Boolean(circuit.isReserve), remark: circuit.remark ?? undefined, }); } return this.circuitRepository.listBySection(sectionId); } }