182 lines
6.1 KiB
TypeScript
182 lines
6.1 KiB
TypeScript
import crypto from "node:crypto";
|
|
import { and, asc, eq, inArray, ne } from "drizzle-orm";
|
|
import { db } from "../client.js";
|
|
import { circuits } from "../schema/circuits.js";
|
|
|
|
export class CircuitRepository {
|
|
async findById(circuitId: string) {
|
|
const [row] = await db.select().from(circuits).where(eq(circuits.id, circuitId)).limit(1);
|
|
return row ?? null;
|
|
}
|
|
|
|
async listByCircuitList(circuitListId: string) {
|
|
return db
|
|
.select()
|
|
.from(circuits)
|
|
.where(eq(circuits.circuitListId, circuitListId))
|
|
.orderBy(asc(circuits.sortOrder), asc(circuits.equipmentIdentifier));
|
|
}
|
|
|
|
async create(input: {
|
|
circuitListId: string;
|
|
sectionId: string;
|
|
equipmentIdentifier: string;
|
|
displayName?: string;
|
|
sortOrder: number;
|
|
protectionType?: string;
|
|
protectionRatedCurrent?: number;
|
|
protectionCharacteristic?: string;
|
|
cableType?: string;
|
|
cableCrossSection?: string;
|
|
cableLength?: number;
|
|
voltage?: number;
|
|
remark?: string;
|
|
rcdAssignment?: string;
|
|
terminalDesignation?: string;
|
|
status?: string;
|
|
isReserve?: boolean;
|
|
}) {
|
|
const id = crypto.randomUUID();
|
|
await db.insert(circuits).values({
|
|
id,
|
|
circuitListId: input.circuitListId,
|
|
sectionId: input.sectionId,
|
|
equipmentIdentifier: input.equipmentIdentifier,
|
|
displayName: input.displayName ?? null,
|
|
sortOrder: input.sortOrder,
|
|
protectionType: input.protectionType ?? null,
|
|
protectionRatedCurrent: input.protectionRatedCurrent ?? null,
|
|
protectionCharacteristic: input.protectionCharacteristic ?? null,
|
|
cableType: input.cableType ?? null,
|
|
cableCrossSection: input.cableCrossSection ?? null,
|
|
cableLength: input.cableLength ?? null,
|
|
rcdAssignment: input.rcdAssignment ?? null,
|
|
terminalDesignation: input.terminalDesignation ?? null,
|
|
voltage: input.voltage ?? null,
|
|
status: input.status ?? null,
|
|
isReserve: input.isReserve ? 1 : 0,
|
|
remark: input.remark ?? null,
|
|
});
|
|
return id;
|
|
}
|
|
|
|
async update(
|
|
circuitId: string,
|
|
input: {
|
|
sectionId: string;
|
|
equipmentIdentifier: string;
|
|
displayName?: string;
|
|
sortOrder: number;
|
|
protectionType?: string;
|
|
protectionRatedCurrent?: number;
|
|
protectionCharacteristic?: string;
|
|
cableType?: string;
|
|
cableCrossSection?: string;
|
|
cableLength?: number;
|
|
rcdAssignment?: string;
|
|
terminalDesignation?: string;
|
|
voltage?: number;
|
|
status?: string;
|
|
isReserve: boolean;
|
|
remark?: string;
|
|
}
|
|
) {
|
|
await db
|
|
.update(circuits)
|
|
.set({
|
|
sectionId: input.sectionId,
|
|
equipmentIdentifier: input.equipmentIdentifier,
|
|
displayName: input.displayName ?? null,
|
|
sortOrder: input.sortOrder,
|
|
protectionType: input.protectionType ?? null,
|
|
protectionRatedCurrent: input.protectionRatedCurrent ?? null,
|
|
protectionCharacteristic: input.protectionCharacteristic ?? null,
|
|
cableType: input.cableType ?? null,
|
|
cableCrossSection: input.cableCrossSection ?? null,
|
|
cableLength: input.cableLength ?? null,
|
|
rcdAssignment: input.rcdAssignment ?? null,
|
|
terminalDesignation: input.terminalDesignation ?? null,
|
|
voltage: input.voltage ?? null,
|
|
status: input.status ?? null,
|
|
isReserve: input.isReserve ? 1 : 0,
|
|
remark: input.remark ?? null,
|
|
})
|
|
.where(eq(circuits.id, circuitId));
|
|
}
|
|
|
|
async delete(circuitId: string) {
|
|
await db.delete(circuits).where(eq(circuits.id, circuitId));
|
|
}
|
|
|
|
async existsByEquipmentIdentifier(circuitListId: string, equipmentIdentifier: string, excludeCircuitId?: string) {
|
|
const rows = await db
|
|
.select({ id: circuits.id })
|
|
.from(circuits)
|
|
.where(
|
|
excludeCircuitId
|
|
? and(
|
|
eq(circuits.circuitListId, circuitListId),
|
|
eq(circuits.equipmentIdentifier, equipmentIdentifier),
|
|
ne(circuits.id, excludeCircuitId)
|
|
)
|
|
: and(eq(circuits.circuitListId, circuitListId), eq(circuits.equipmentIdentifier, equipmentIdentifier))
|
|
)
|
|
.limit(1);
|
|
return Boolean(rows.length);
|
|
}
|
|
|
|
async listBySection(sectionId: string) {
|
|
return db
|
|
.select()
|
|
.from(circuits)
|
|
.where(eq(circuits.sectionId, sectionId))
|
|
.orderBy(asc(circuits.sortOrder), asc(circuits.equipmentIdentifier));
|
|
}
|
|
|
|
async updateEquipmentIdentifiersSafely(
|
|
circuitListId: string,
|
|
updates: Array<{ id: string; equipmentIdentifier: string }>,
|
|
tempNamespace: string
|
|
) {
|
|
if (updates.length === 0) {
|
|
return;
|
|
}
|
|
// better-sqlite3 transactions are synchronous callbacks. Do not make this callback
|
|
// async or return a Promise, otherwise statements may run outside the transaction scope.
|
|
db.transaction((tx) => {
|
|
const ids = updates.map((entry) => entry.id);
|
|
const existing = tx
|
|
.select({ id: circuits.id })
|
|
.from(circuits)
|
|
.where(and(eq(circuits.circuitListId, circuitListId), inArray(circuits.id, ids)))
|
|
.all();
|
|
if (existing.length !== ids.length) {
|
|
throw new Error("One or more circuit ids are invalid for circuit list.");
|
|
}
|
|
|
|
// Direct identifier swaps can violate UNIQUE(circuit_list_id, equipment_identifier)
|
|
// mid-update (for example A->B while B->A). Two-phase strategy prevents that:
|
|
// 1) assign unique temporary identifiers for all affected circuits
|
|
// 2) assign final user-visible identifiers
|
|
const stamp = Date.now();
|
|
for (let index = 0; index < updates.length; index += 1) {
|
|
const entry = updates[index];
|
|
const tempIdentifier = `__tmp_renumber_${tempNamespace}_${stamp}_${index}`;
|
|
tx
|
|
.update(circuits)
|
|
.set({ equipmentIdentifier: tempIdentifier })
|
|
.where(and(eq(circuits.circuitListId, circuitListId), eq(circuits.id, entry.id)))
|
|
.run();
|
|
}
|
|
|
|
for (const entry of updates) {
|
|
tx
|
|
.update(circuits)
|
|
.set({ equipmentIdentifier: entry.equipmentIdentifier })
|
|
.where(and(eq(circuits.circuitListId, circuitListId), eq(circuits.id, entry.id)))
|
|
.run();
|
|
}
|
|
});
|
|
}
|
|
}
|