Persist device row moves
This commit is contained in:
@@ -1,75 +0,0 @@
|
||||
export interface CreateCircuitDeviceRowTransactionInput {
|
||||
circuitId: string;
|
||||
linkedProjectDeviceId?: string;
|
||||
sortOrder?: number;
|
||||
name: string;
|
||||
displayName: string;
|
||||
phaseType?: string;
|
||||
connectionKind?: string;
|
||||
costGroup?: string;
|
||||
category?: string;
|
||||
level?: string;
|
||||
roomId?: string;
|
||||
roomNumberSnapshot?: string;
|
||||
roomNameSnapshot?: string;
|
||||
quantity: number;
|
||||
powerPerUnit: number;
|
||||
simultaneityFactor: number;
|
||||
cosPhi?: number;
|
||||
remark?: string;
|
||||
overriddenFields?: string;
|
||||
}
|
||||
|
||||
export interface CreateCircuitTransactionInput {
|
||||
circuitListId: string;
|
||||
sectionId: string;
|
||||
equipmentIdentifier: string;
|
||||
displayName?: string;
|
||||
sortOrder: number;
|
||||
protectionType?: string;
|
||||
protectionRatedCurrent?: number;
|
||||
protectionCharacteristic?: string;
|
||||
cableType?: string;
|
||||
cableCrossSection?: string;
|
||||
cableLength?: number;
|
||||
voltage?: number;
|
||||
controlRequirement?: string;
|
||||
remark?: string;
|
||||
rcdAssignment?: string;
|
||||
terminalDesignation?: string;
|
||||
status?: string;
|
||||
}
|
||||
|
||||
export interface CreateCircuitWithDeviceRowsTransactionInput {
|
||||
circuit: CreateCircuitTransactionInput;
|
||||
deviceRows: Array<Omit<CreateCircuitDeviceRowTransactionInput, "circuitId">>;
|
||||
}
|
||||
|
||||
export interface MoveCircuitDeviceRowsTransactionInput {
|
||||
rows: Array<{ id: string; expectedCircuitId: string }>;
|
||||
targetCircuitId?: string;
|
||||
createTargetCircuit?: {
|
||||
circuitListId: string;
|
||||
sectionId: string;
|
||||
equipmentIdentifier: string;
|
||||
displayName: string;
|
||||
sortOrder: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface MoveCircuitDeviceRowsTransactionResult {
|
||||
movedRowIds: string[];
|
||||
targetCircuitId: string;
|
||||
createdCircuitId?: string;
|
||||
}
|
||||
|
||||
export interface CircuitDeviceRowTransactionStore {
|
||||
createInCircuit(input: CreateCircuitDeviceRowTransactionInput): string;
|
||||
createCircuitWithDeviceRows(
|
||||
input: CreateCircuitWithDeviceRowsTransactionInput
|
||||
): { circuitId: string; rowIds: string[] };
|
||||
deleteFromCircuit(rowId: string, expectedCircuitId: string): void;
|
||||
moveRows(
|
||||
input: MoveCircuitDeviceRowsTransactionInput
|
||||
): MoveCircuitDeviceRowsTransactionResult;
|
||||
}
|
||||
@@ -1,11 +1,7 @@
|
||||
import { CircuitDeviceRowRepository } from "../../db/repositories/circuit-device-row.repository.js";
|
||||
import type { CircuitDeviceRowTransactionStore } from "../ports/circuit-device-row-transaction.store.js";
|
||||
import type { CircuitSectionTransactionStore } from "../ports/circuit-section-transaction.store.js";
|
||||
import { CircuitRepository } from "../../db/repositories/circuit.repository.js";
|
||||
import { CircuitSectionRepository } from "../../db/repositories/circuit-section.repository.js";
|
||||
import type {
|
||||
MoveCircuitDeviceRowInput,
|
||||
MoveCircuitDeviceRowsBulkInput,
|
||||
ReorderSectionCircuitsInput,
|
||||
UpdateSectionEquipmentIdentifiersInput,
|
||||
} from "../../shared/validation/circuit.schemas.js";
|
||||
@@ -14,46 +10,21 @@ import { CircuitNumberingService } from "./circuit-numbering.service.js";
|
||||
export class CircuitWriteService {
|
||||
private readonly circuitRepository: CircuitRepository;
|
||||
private readonly circuitSectionRepository: CircuitSectionRepository;
|
||||
private readonly deviceRowRepository: CircuitDeviceRowRepository;
|
||||
private readonly deviceRowTransactionStore?: CircuitDeviceRowTransactionStore;
|
||||
private readonly circuitSectionTransactionStore?: CircuitSectionTransactionStore;
|
||||
private readonly numberingService: CircuitNumberingService;
|
||||
|
||||
constructor(deps?: {
|
||||
circuitRepository?: CircuitRepository;
|
||||
circuitSectionRepository?: CircuitSectionRepository;
|
||||
deviceRowRepository?: CircuitDeviceRowRepository;
|
||||
deviceRowTransactionStore?: CircuitDeviceRowTransactionStore;
|
||||
circuitSectionTransactionStore?: CircuitSectionTransactionStore;
|
||||
numberingService?: CircuitNumberingService;
|
||||
}) {
|
||||
this.circuitRepository = deps?.circuitRepository ?? new CircuitRepository();
|
||||
this.circuitSectionRepository = deps?.circuitSectionRepository ?? new CircuitSectionRepository();
|
||||
this.deviceRowRepository = deps?.deviceRowRepository ?? new CircuitDeviceRowRepository();
|
||||
this.deviceRowTransactionStore = deps?.deviceRowTransactionStore;
|
||||
this.circuitSectionTransactionStore = deps?.circuitSectionTransactionStore;
|
||||
this.numberingService = deps?.numberingService ?? new CircuitNumberingService();
|
||||
}
|
||||
|
||||
// Ensures writes never connect a section to the wrong circuit list.
|
||||
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 getDeviceRowTransactionStore() {
|
||||
if (!this.deviceRowTransactionStore) {
|
||||
throw new Error("Circuit device-row transactions are not configured.");
|
||||
}
|
||||
return this.deviceRowTransactionStore;
|
||||
}
|
||||
|
||||
private getCircuitSectionTransactionStore() {
|
||||
if (!this.circuitSectionTransactionStore) {
|
||||
throw new Error("Circuit-section transactions are not configured.");
|
||||
@@ -61,157 +32,6 @@ export class CircuitWriteService {
|
||||
return this.circuitSectionTransactionStore;
|
||||
}
|
||||
|
||||
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 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.");
|
||||
}
|
||||
|
||||
let createTargetCircuit:
|
||||
| {
|
||||
circuitListId: string;
|
||||
sectionId: string;
|
||||
equipmentIdentifier: string;
|
||||
displayName: string;
|
||||
sortOrder: number;
|
||||
}
|
||||
| undefined;
|
||||
|
||||
// Placeholder-target move prepares a new circuit explicitly. Its creation and
|
||||
// the row assignment are committed together without renumbering other circuits.
|
||||
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;
|
||||
createTargetCircuit = {
|
||||
circuitListId: sourceCircuit.circuitListId,
|
||||
sectionId: section.id,
|
||||
equipmentIdentifier: nextIdentifier,
|
||||
displayName: "Neuer Stromkreis",
|
||||
sortOrder: nextSortOrder,
|
||||
};
|
||||
}
|
||||
|
||||
if (targetCircuit && 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);
|
||||
}
|
||||
|
||||
this.getDeviceRowTransactionStore().moveRows({
|
||||
rows: [{ id: row.id, expectedCircuitId: row.circuitId }],
|
||||
targetCircuitId: targetCircuit?.id,
|
||||
createTargetCircuit,
|
||||
});
|
||||
|
||||
return this.deviceRowRepository.findById(rowId);
|
||||
}
|
||||
|
||||
async moveDeviceRowsBulk(input: MoveCircuitDeviceRowsBulkInput) {
|
||||
// Bulk move keeps input order and resolves all source circuits first so undo can
|
||||
// restore per-source assignment deterministically.
|
||||
const uniqueRowIds = [...new Set(input.rowIds)];
|
||||
if (uniqueRowIds.length === 0) {
|
||||
throw new Error("No device rows provided.");
|
||||
}
|
||||
|
||||
const rows = [];
|
||||
for (const rowId of uniqueRowIds) {
|
||||
const row = await this.deviceRowRepository.findById(rowId);
|
||||
if (!row) {
|
||||
throw new Error("Invalid device row id.");
|
||||
}
|
||||
rows.push(row);
|
||||
}
|
||||
|
||||
const sourceCircuits = new Map<string, Awaited<ReturnType<CircuitRepository["findById"]>>>();
|
||||
for (const row of rows) {
|
||||
if (!sourceCircuits.has(row.circuitId)) {
|
||||
const circuit = await this.circuitRepository.findById(row.circuitId);
|
||||
if (!circuit) {
|
||||
throw new Error("Invalid circuit id.");
|
||||
}
|
||||
sourceCircuits.set(row.circuitId, circuit);
|
||||
}
|
||||
}
|
||||
|
||||
const referenceSourceCircuit = sourceCircuits.get(rows[0].circuitId)!;
|
||||
let targetCircuit = input.targetCircuitId
|
||||
? await this.circuitRepository.findById(input.targetCircuitId)
|
||||
: null;
|
||||
if (input.targetCircuitId && !targetCircuit) {
|
||||
throw new Error("Invalid target circuit id.");
|
||||
}
|
||||
|
||||
// Bulk placeholder move prepares exactly one new circuit as common target.
|
||||
// Its actual creation happens together with the row moves in one transaction.
|
||||
let createTargetCircuit:
|
||||
| {
|
||||
circuitListId: string;
|
||||
sectionId: string;
|
||||
equipmentIdentifier: string;
|
||||
displayName: string;
|
||||
sortOrder: number;
|
||||
}
|
||||
| undefined;
|
||||
if (!targetCircuit) {
|
||||
if (!input.targetSectionId || !input.createNewCircuit) {
|
||||
throw new Error("Invalid move target.");
|
||||
}
|
||||
const section = await this.assertSectionInList(input.targetSectionId, referenceSourceCircuit.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;
|
||||
createTargetCircuit = {
|
||||
circuitListId: referenceSourceCircuit.circuitListId,
|
||||
sectionId: section.id,
|
||||
equipmentIdentifier: nextIdentifier,
|
||||
displayName: "Neuer Stromkreis",
|
||||
sortOrder: nextSortOrder,
|
||||
};
|
||||
}
|
||||
|
||||
const targetCircuitListId = targetCircuit?.circuitListId ?? createTargetCircuit!.circuitListId;
|
||||
for (const sourceCircuit of sourceCircuits.values()) {
|
||||
if (sourceCircuit.circuitListId !== targetCircuitListId) {
|
||||
throw new Error("All moved rows must belong to same circuit list as target.");
|
||||
}
|
||||
}
|
||||
|
||||
return this.getDeviceRowTransactionStore().moveRows({
|
||||
rows: rows.map((row) => ({ id: row.id, expectedCircuitId: row.circuitId })),
|
||||
targetCircuitId: targetCircuit?.id,
|
||||
createTargetCircuit,
|
||||
});
|
||||
}
|
||||
|
||||
async getNextIdentifier(sectionId: string) {
|
||||
return this.numberingService.getNextIdentifier(sectionId);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user