Make device row writes atomic
This commit is contained in:
@@ -26,6 +26,13 @@ export interface CircuitDeviceRowUpdateInput {
|
||||
overriddenFields?: string;
|
||||
}
|
||||
|
||||
export interface CircuitDeviceRowCreateInput extends CircuitDeviceRowUpdateInput {
|
||||
circuitId: string;
|
||||
linkedProjectDeviceId?: string;
|
||||
legacyConsumerId?: string;
|
||||
sortOrder: number;
|
||||
}
|
||||
|
||||
export interface CircuitDeviceRowsBulkMoveInput {
|
||||
rows: Array<{ id: string; expectedCircuitId: string }>;
|
||||
targetCircuitId?: string;
|
||||
@@ -60,6 +67,16 @@ function toUpdateValues(input: CircuitDeviceRowUpdateInput) {
|
||||
};
|
||||
}
|
||||
|
||||
function toCreateValues(id: string, input: CircuitDeviceRowCreateInput) {
|
||||
return {
|
||||
id,
|
||||
circuitId: input.circuitId,
|
||||
legacyConsumerId: input.legacyConsumerId ?? null,
|
||||
sortOrder: input.sortOrder,
|
||||
...toUpdateValues(input),
|
||||
};
|
||||
}
|
||||
|
||||
export class CircuitDeviceRowRepository {
|
||||
async findById(rowId: string) {
|
||||
const [row] = await db.select().from(circuitDeviceRows).where(eq(circuitDeviceRows.id, rowId)).limit(1);
|
||||
@@ -141,51 +158,43 @@ export class CircuitDeviceRowRepository {
|
||||
.where(and(eq(circuitLists.projectId, projectId), inArray(circuitDeviceRows.id, rowIds)));
|
||||
}
|
||||
|
||||
async create(input: {
|
||||
circuitId: string;
|
||||
linkedProjectDeviceId?: string;
|
||||
legacyConsumerId?: 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;
|
||||
}) {
|
||||
async create(input: CircuitDeviceRowCreateInput) {
|
||||
const id = crypto.randomUUID();
|
||||
await db.insert(circuitDeviceRows).values({
|
||||
id,
|
||||
circuitId: input.circuitId,
|
||||
linkedProjectDeviceId: input.linkedProjectDeviceId ?? null,
|
||||
legacyConsumerId: input.legacyConsumerId ?? null,
|
||||
sortOrder: input.sortOrder,
|
||||
name: input.name,
|
||||
displayName: input.displayName,
|
||||
phaseType: input.phaseType ?? null,
|
||||
connectionKind: input.connectionKind ?? null,
|
||||
costGroup: input.costGroup ?? null,
|
||||
category: input.category ?? null,
|
||||
level: input.level ?? null,
|
||||
roomId: input.roomId ?? null,
|
||||
roomNumberSnapshot: input.roomNumberSnapshot ?? null,
|
||||
roomNameSnapshot: input.roomNameSnapshot ?? null,
|
||||
quantity: input.quantity,
|
||||
powerPerUnit: input.powerPerUnit,
|
||||
simultaneityFactor: input.simultaneityFactor,
|
||||
cosPhi: input.cosPhi ?? null,
|
||||
remark: input.remark ?? null,
|
||||
overriddenFields: input.overriddenFields ?? null,
|
||||
await db.insert(circuitDeviceRows).values(toCreateValues(id, input));
|
||||
return id;
|
||||
}
|
||||
|
||||
createInCircuitTransactional(
|
||||
input: Omit<CircuitDeviceRowCreateInput, "sortOrder"> & { sortOrder?: number }
|
||||
) {
|
||||
const id = crypto.randomUUID();
|
||||
db.transaction((tx) => {
|
||||
const [circuit] = tx
|
||||
.select({ id: circuits.id })
|
||||
.from(circuits)
|
||||
.where(eq(circuits.id, input.circuitId))
|
||||
.limit(1)
|
||||
.all();
|
||||
if (!circuit) {
|
||||
throw new Error("Der Stromkreis ist ungültig.");
|
||||
}
|
||||
|
||||
const existingRows = tx
|
||||
.select({ sortOrder: circuitDeviceRows.sortOrder })
|
||||
.from(circuitDeviceRows)
|
||||
.where(eq(circuitDeviceRows.circuitId, input.circuitId))
|
||||
.all();
|
||||
const lastSortOrder = existingRows.reduce(
|
||||
(highest, row) => Math.max(highest, row.sortOrder),
|
||||
0
|
||||
);
|
||||
const sortOrder = input.sortOrder ?? lastSortOrder + 10;
|
||||
|
||||
tx
|
||||
.insert(circuitDeviceRows)
|
||||
.values(toCreateValues(id, { ...input, sortOrder }))
|
||||
.run();
|
||||
tx.update(circuits).set({ isReserve: 0 }).where(eq(circuits.id, input.circuitId)).run();
|
||||
});
|
||||
return id;
|
||||
}
|
||||
@@ -262,6 +271,44 @@ export class CircuitDeviceRowRepository {
|
||||
await db.delete(circuitDeviceRows).where(eq(circuitDeviceRows.id, rowId));
|
||||
}
|
||||
|
||||
deleteFromCircuitTransactional(rowId: string, expectedCircuitId: string) {
|
||||
db.transaction((tx) => {
|
||||
const [row] = tx
|
||||
.select({ id: circuitDeviceRows.id, circuitId: circuitDeviceRows.circuitId })
|
||||
.from(circuitDeviceRows)
|
||||
.where(eq(circuitDeviceRows.id, rowId))
|
||||
.limit(1)
|
||||
.all();
|
||||
if (!row || row.circuitId !== expectedCircuitId) {
|
||||
throw new Error("Die Gerätezeile wurde vor dem Löschen verändert.");
|
||||
}
|
||||
|
||||
const result = tx
|
||||
.delete(circuitDeviceRows)
|
||||
.where(
|
||||
and(
|
||||
eq(circuitDeviceRows.id, rowId),
|
||||
eq(circuitDeviceRows.circuitId, expectedCircuitId)
|
||||
)
|
||||
)
|
||||
.run();
|
||||
if (result.changes !== 1) {
|
||||
throw new Error("Die Gerätezeile konnte nicht gelöscht werden.");
|
||||
}
|
||||
|
||||
const remainingRows = tx
|
||||
.select({ id: circuitDeviceRows.id })
|
||||
.from(circuitDeviceRows)
|
||||
.where(eq(circuitDeviceRows.circuitId, expectedCircuitId))
|
||||
.all();
|
||||
tx
|
||||
.update(circuits)
|
||||
.set({ isReserve: remainingRows.length === 0 ? 1 : 0 })
|
||||
.where(eq(circuits.id, expectedCircuitId))
|
||||
.run();
|
||||
});
|
||||
}
|
||||
|
||||
async moveToCircuit(rowId: string, targetCircuitId: string, sortOrder: number) {
|
||||
await db
|
||||
.update(circuitDeviceRows)
|
||||
|
||||
@@ -171,11 +171,10 @@ export class CircuitWriteService {
|
||||
}
|
||||
await this.assertValidLinkedProjectDevice(circuitId, input.linkedProjectDeviceId);
|
||||
|
||||
const existingRows = await this.deviceRowRepository.countByCircuit(circuitId);
|
||||
const rowId = await this.deviceRowRepository.create({
|
||||
const rowId = this.deviceRowRepository.createInCircuitTransactional({
|
||||
circuitId,
|
||||
linkedProjectDeviceId: input.linkedProjectDeviceId,
|
||||
sortOrder: input.sortOrder ?? (existingRows + 1) * 10,
|
||||
sortOrder: input.sortOrder,
|
||||
name: input.name,
|
||||
displayName: input.displayName,
|
||||
phaseType: input.phaseType,
|
||||
@@ -194,29 +193,6 @@ 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,
|
||||
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,
|
||||
controlRequirement: circuit.controlRequirement ?? undefined,
|
||||
status: circuit.status ?? undefined,
|
||||
isReserve: false,
|
||||
remark: circuit.remark ?? undefined,
|
||||
});
|
||||
}
|
||||
|
||||
return this.deviceRowRepository.findById(rowId);
|
||||
}
|
||||
|
||||
@@ -272,30 +248,7 @@ export class CircuitWriteService {
|
||||
if (!circuit) {
|
||||
throw new Error("Invalid circuit id.");
|
||||
}
|
||||
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,
|
||||
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,
|
||||
controlRequirement: circuit.controlRequirement ?? undefined,
|
||||
status: circuit.status ?? undefined,
|
||||
isReserve: true,
|
||||
remark: circuit.remark ?? undefined,
|
||||
});
|
||||
}
|
||||
this.deviceRowRepository.deleteFromCircuitTransactional(rowId, circuit.id);
|
||||
}
|
||||
|
||||
async moveDeviceRow(rowId: string, input: MoveCircuitDeviceRowInput) {
|
||||
|
||||
Reference in New Issue
Block a user