278 lines
8.2 KiB
TypeScript
278 lines
8.2 KiB
TypeScript
import { and, asc, eq, inArray } from "drizzle-orm";
|
|
import {
|
|
assertCircuitDeleteProjectCommand,
|
|
assertCircuitInsertProjectCommand,
|
|
circuitDeleteCommandType,
|
|
circuitInsertCommandType,
|
|
createCircuitDeleteProjectCommand,
|
|
createCircuitInsertProjectCommand,
|
|
type CircuitSnapshot,
|
|
type CircuitStructureProjectCommand,
|
|
} from "../../domain/models/circuit-structure-project-command.model.js";
|
|
import type {
|
|
CircuitStructureProjectCommandStore,
|
|
ExecuteCircuitStructureCommandInput,
|
|
} from "../../domain/ports/circuit-structure-project-command.store.js";
|
|
import type { AppDatabase } from "../database-context.js";
|
|
import { circuitDeviceRows } from "../schema/circuit-device-rows.js";
|
|
import { circuitLists } from "../schema/circuit-lists.js";
|
|
import { circuitSections } from "../schema/circuit-sections.js";
|
|
import { circuits } from "../schema/circuits.js";
|
|
import {
|
|
assertCircuitDeviceRowReferencesInProject,
|
|
toCircuitDeviceRowSnapshot,
|
|
} from "./circuit-device-row-structure.persistence.js";
|
|
import { executeProjectCommandTransaction } from "./project-command-transaction.persistence.js";
|
|
|
|
export class CircuitStructureProjectCommandRepository
|
|
implements CircuitStructureProjectCommandStore
|
|
{
|
|
constructor(private readonly database: AppDatabase) {}
|
|
|
|
execute(input: ExecuteCircuitStructureCommandInput) {
|
|
return executeProjectCommandTransaction(
|
|
this.database,
|
|
input,
|
|
(tx) =>
|
|
this.applyCommand(
|
|
tx,
|
|
input.projectId,
|
|
input.source,
|
|
input.command
|
|
)
|
|
);
|
|
}
|
|
|
|
private applyCommand(
|
|
database: AppDatabase,
|
|
projectId: string,
|
|
source: ExecuteCircuitStructureCommandInput["source"],
|
|
command: CircuitStructureProjectCommand
|
|
): CircuitStructureProjectCommand {
|
|
if (command.type === circuitInsertCommandType) {
|
|
assertCircuitInsertProjectCommand(command);
|
|
return this.insert(
|
|
database,
|
|
projectId,
|
|
source,
|
|
command.payload.circuit
|
|
);
|
|
}
|
|
if (command.type === circuitDeleteCommandType) {
|
|
assertCircuitDeleteProjectCommand(command);
|
|
return this.delete(
|
|
database,
|
|
projectId,
|
|
command.payload.circuitId,
|
|
command.payload.expectedCircuitListId
|
|
);
|
|
}
|
|
throw new Error("Unsupported circuit structure command.");
|
|
}
|
|
|
|
private insert(
|
|
database: AppDatabase,
|
|
projectId: string,
|
|
source: ExecuteCircuitStructureCommandInput["source"],
|
|
snapshot: CircuitSnapshot
|
|
) {
|
|
this.assertCircuitLocation(database, projectId, snapshot);
|
|
|
|
const existingCircuit = database
|
|
.select({ id: circuits.id })
|
|
.from(circuits)
|
|
.where(eq(circuits.id, snapshot.id))
|
|
.get();
|
|
if (existingCircuit) {
|
|
throw new Error("Circuit id already exists.");
|
|
}
|
|
const duplicateIdentifier = database
|
|
.select({ id: circuits.id })
|
|
.from(circuits)
|
|
.where(
|
|
and(
|
|
eq(circuits.circuitListId, snapshot.circuitListId),
|
|
eq(
|
|
circuits.equipmentIdentifier,
|
|
snapshot.equipmentIdentifier
|
|
)
|
|
)
|
|
)
|
|
.get();
|
|
if (duplicateIdentifier) {
|
|
throw new Error(
|
|
"Duplicate equipmentIdentifier in circuit list."
|
|
);
|
|
}
|
|
|
|
if (snapshot.deviceRows.length > 0) {
|
|
const rowIds = snapshot.deviceRows.map((row) => row.id);
|
|
const existingRow = database
|
|
.select({ id: circuitDeviceRows.id })
|
|
.from(circuitDeviceRows)
|
|
.where(inArray(circuitDeviceRows.id, rowIds))
|
|
.limit(1)
|
|
.get();
|
|
if (existingRow) {
|
|
throw new Error("Circuit device-row id already exists.");
|
|
}
|
|
for (const row of snapshot.deviceRows) {
|
|
if (source === "user" && row.legacyConsumerId !== null) {
|
|
throw new Error(
|
|
"User commands cannot assign legacy consumer ids."
|
|
);
|
|
}
|
|
assertCircuitDeviceRowReferencesInProject(
|
|
database,
|
|
projectId,
|
|
row
|
|
);
|
|
}
|
|
}
|
|
|
|
database
|
|
.insert(circuits)
|
|
.values({
|
|
id: snapshot.id,
|
|
circuitListId: snapshot.circuitListId,
|
|
sectionId: snapshot.sectionId,
|
|
equipmentIdentifier: snapshot.equipmentIdentifier,
|
|
displayName: snapshot.displayName,
|
|
sortOrder: snapshot.sortOrder,
|
|
protectionType: snapshot.protectionType,
|
|
protectionRatedCurrent: snapshot.protectionRatedCurrent,
|
|
protectionCharacteristic: snapshot.protectionCharacteristic,
|
|
cableType: snapshot.cableType,
|
|
cableCrossSection: snapshot.cableCrossSection,
|
|
cableLength: snapshot.cableLength,
|
|
rcdAssignment: snapshot.rcdAssignment,
|
|
terminalDesignation: snapshot.terminalDesignation,
|
|
voltage: snapshot.voltage,
|
|
controlRequirement: snapshot.controlRequirement,
|
|
status: snapshot.status,
|
|
isReserve: snapshot.isReserve ? 1 : 0,
|
|
remark: snapshot.remark,
|
|
})
|
|
.run();
|
|
if (snapshot.deviceRows.length > 0) {
|
|
database.insert(circuitDeviceRows).values(snapshot.deviceRows).run();
|
|
}
|
|
|
|
return createCircuitDeleteProjectCommand(
|
|
snapshot.id,
|
|
snapshot.circuitListId
|
|
);
|
|
}
|
|
|
|
private delete(
|
|
database: AppDatabase,
|
|
projectId: string,
|
|
circuitId: string,
|
|
expectedCircuitListId: string
|
|
) {
|
|
const circuit = database
|
|
.select()
|
|
.from(circuits)
|
|
.where(eq(circuits.id, circuitId))
|
|
.get();
|
|
if (!circuit || circuit.circuitListId !== expectedCircuitListId) {
|
|
throw new Error("Circuit changed before command execution.");
|
|
}
|
|
const owningList = database
|
|
.select({ id: circuitLists.id })
|
|
.from(circuitLists)
|
|
.where(
|
|
and(
|
|
eq(circuitLists.id, circuit.circuitListId),
|
|
eq(circuitLists.projectId, projectId)
|
|
)
|
|
)
|
|
.get();
|
|
if (!owningList) {
|
|
throw new Error("Circuit does not belong to project.");
|
|
}
|
|
|
|
const rows = database
|
|
.select()
|
|
.from(circuitDeviceRows)
|
|
.where(eq(circuitDeviceRows.circuitId, circuit.id))
|
|
.orderBy(
|
|
asc(circuitDeviceRows.sortOrder),
|
|
asc(circuitDeviceRows.id)
|
|
)
|
|
.all();
|
|
const inverse = createCircuitInsertProjectCommand({
|
|
id: circuit.id,
|
|
circuitListId: circuit.circuitListId,
|
|
sectionId: circuit.sectionId,
|
|
equipmentIdentifier: circuit.equipmentIdentifier,
|
|
displayName: circuit.displayName,
|
|
sortOrder: circuit.sortOrder,
|
|
protectionType: circuit.protectionType,
|
|
protectionRatedCurrent: circuit.protectionRatedCurrent,
|
|
protectionCharacteristic: circuit.protectionCharacteristic,
|
|
cableType: circuit.cableType,
|
|
cableCrossSection: circuit.cableCrossSection,
|
|
cableLength: circuit.cableLength,
|
|
rcdAssignment: circuit.rcdAssignment,
|
|
terminalDesignation: circuit.terminalDesignation,
|
|
voltage: circuit.voltage,
|
|
controlRequirement: circuit.controlRequirement,
|
|
status: circuit.status,
|
|
isReserve: Boolean(circuit.isReserve),
|
|
remark: circuit.remark,
|
|
deviceRows: rows.map(toCircuitDeviceRowSnapshot),
|
|
});
|
|
|
|
const result = database
|
|
.delete(circuits)
|
|
.where(
|
|
and(
|
|
eq(circuits.id, circuitId),
|
|
eq(circuits.circuitListId, expectedCircuitListId)
|
|
)
|
|
)
|
|
.run();
|
|
if (result.changes !== 1) {
|
|
throw new Error("Circuit could not be deleted.");
|
|
}
|
|
return inverse;
|
|
}
|
|
|
|
private assertCircuitLocation(
|
|
database: AppDatabase,
|
|
projectId: string,
|
|
snapshot: CircuitSnapshot
|
|
) {
|
|
const list = database
|
|
.select({ id: circuitLists.id })
|
|
.from(circuitLists)
|
|
.where(
|
|
and(
|
|
eq(circuitLists.id, snapshot.circuitListId),
|
|
eq(circuitLists.projectId, projectId)
|
|
)
|
|
)
|
|
.get();
|
|
if (!list) {
|
|
throw new Error("Circuit list does not belong to project.");
|
|
}
|
|
const section = database
|
|
.select({ id: circuitSections.id })
|
|
.from(circuitSections)
|
|
.where(
|
|
and(
|
|
eq(circuitSections.id, snapshot.sectionId),
|
|
eq(
|
|
circuitSections.circuitListId,
|
|
snapshot.circuitListId
|
|
)
|
|
)
|
|
)
|
|
.get();
|
|
if (!section) {
|
|
throw new Error("Section does not belong to circuit list.");
|
|
}
|
|
}
|
|
}
|