Documentation

This commit is contained in:
Julian Appel 2026-05-07 22:55:15 +02:00
parent b1e19a88d5
commit 7580ad0ade
11 changed files with 584 additions and 1 deletions

View file

@ -39,6 +39,7 @@ export class CircuitWriteService {
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) {
@ -50,6 +51,7 @@ export class CircuitWriteService {
return section;
}
// Enforces BMK uniqueness inside one circuit list.
private async assertUniqueEquipmentIdentifier(
circuitListId: string,
equipmentIdentifier: string,
@ -65,6 +67,7 @@ export class CircuitWriteService {
}
}
// Validates linked project-device id against owning project of the circuit list.
private async assertValidLinkedProjectDevice(circuitId: string, linkedProjectDeviceId?: string) {
if (!linkedProjectDeviceId) {
return;
@ -184,6 +187,7 @@ export class CircuitWriteService {
overriddenFields: input.overriddenFields,
});
// Reserve circuits become active as soon as at least one device row exists.
if (Boolean(circuit.isReserve)) {
await this.circuitRepository.update(circuit.id, {
sectionId: circuit.sectionId,
@ -247,6 +251,7 @@ export class CircuitWriteService {
}
await this.deviceRowRepository.delete(rowId);
const remaining = await this.deviceRowRepository.countByCircuit(current.circuitId);
// When last row is removed, keep circuit and mark it reserve instead of deleting it.
if (remaining === 0) {
await this.circuitRepository.update(circuit.id, {
sectionId: circuit.sectionId,
@ -287,6 +292,7 @@ export class CircuitWriteService {
throw new Error("Invalid target circuit id.");
}
// Placeholder-target move creates a new circuit explicitly; no implicit renumbering of others.
if (!targetCircuit) {
if (!input.targetSectionId || !input.createNewCircuit) {
throw new Error("Invalid move target.");
@ -321,6 +327,7 @@ export class CircuitWriteService {
await this.deviceRowRepository.moveToCircuit(rowId, targetCircuit.id, (targetCount + 1) * 10);
const sourceRemaining = await this.deviceRowRepository.countByCircuit(sourceCircuit.id);
// Source circuit becomes reserve when all rows are moved away.
if (sourceRemaining === 0) {
await this.circuitRepository.update(sourceCircuit.id, {
sectionId: sourceCircuit.sectionId,
@ -342,6 +349,7 @@ export class CircuitWriteService {
});
}
// Target circuit is no longer reserve once it receives moved rows.
if (Boolean(targetCircuit.isReserve)) {
await this.circuitRepository.update(targetCircuit.id, {
sectionId: targetCircuit.sectionId,
@ -367,6 +375,8 @@ export class CircuitWriteService {
}
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.");
@ -400,6 +410,7 @@ export class CircuitWriteService {
throw new Error("Invalid target circuit id.");
}
// Bulk placeholder move creates exactly one new circuit as common target.
if (!targetCircuit) {
if (!input.targetSectionId || !input.createNewCircuit) {
throw new Error("Invalid move target.");
@ -423,6 +434,7 @@ export class CircuitWriteService {
}
}
// Any source circuit emptied by bulk move is preserved as reserve circuit.
for (const sourceCircuit of sourceCircuits.values()) {
if (sourceCircuit.circuitListId !== targetCircuit.circuitListId) {
throw new Error("All moved rows must belong to same circuit list as target.");
@ -493,6 +505,8 @@ export class CircuitWriteService {
}
async renumberSection(sectionId: string) {
// Explicit renumber operation for one section only.
// Never renumbers other sections and never runs implicitly during move/sort operations.
const section = await this.circuitSectionRepository.findById(sectionId);
if (!section) {
throw new Error("Invalid section id.");
@ -514,6 +528,7 @@ export class CircuitWriteService {
finalAssignments.push({ id: circuit.id, equipmentIdentifier: candidate });
index += 1;
}
// Uses safe two-phase identifier update to avoid UNIQUE collisions during swaps.
await this.circuitRepository.updateEquipmentIdentifiersSafely(
section.circuitListId,
finalAssignments,
@ -551,6 +566,7 @@ export class CircuitWriteService {
}
async reorderCircuitsInSection(sectionId: string, input: ReorderSectionCircuitsInput) {
// Reorder updates sortOrder only. BMKs remain unchanged; users may renumber explicitly later.
const section = await this.circuitSectionRepository.findById(sectionId);
if (!section) {
throw new Error("Invalid section id.");