Add device row history commands
This commit is contained in:
@@ -0,0 +1,262 @@
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import {
|
||||
assertCircuitDeviceRowDeleteProjectCommand,
|
||||
assertCircuitDeviceRowInsertProjectCommand,
|
||||
circuitDeviceRowDeleteCommandType,
|
||||
circuitDeviceRowInsertCommandType,
|
||||
createCircuitDeviceRowDeleteProjectCommand,
|
||||
createCircuitDeviceRowInsertProjectCommand,
|
||||
type CircuitDeviceRowSnapshot,
|
||||
type CircuitDeviceRowStructureProjectCommand,
|
||||
} from "../../domain/models/circuit-device-row-structure-project-command.model.js";
|
||||
import type {
|
||||
CircuitDeviceRowStructureProjectCommandStore,
|
||||
ExecuteCircuitDeviceRowStructureCommandInput,
|
||||
} from "../../domain/ports/circuit-device-row-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 { circuits } from "../schema/circuits.js";
|
||||
import { projectDevices } from "../schema/project-devices.js";
|
||||
import { rooms } from "../schema/rooms.js";
|
||||
import { applyProjectHistoryTransition } from "./project-history.persistence.js";
|
||||
import { appendProjectRevision } from "./project-revision.persistence.js";
|
||||
|
||||
export class CircuitDeviceRowStructureProjectCommandRepository
|
||||
implements CircuitDeviceRowStructureProjectCommandStore
|
||||
{
|
||||
constructor(private readonly database: AppDatabase) {}
|
||||
|
||||
execute(input: ExecuteCircuitDeviceRowStructureCommandInput) {
|
||||
return this.database.transaction((tx) => {
|
||||
const inverse = this.applyCommand(
|
||||
tx,
|
||||
input.projectId,
|
||||
input.source,
|
||||
input.command
|
||||
);
|
||||
const revision = appendProjectRevision(tx, {
|
||||
projectId: input.projectId,
|
||||
expectedRevision: input.expectedRevision,
|
||||
source: input.source,
|
||||
description: input.description,
|
||||
actorId: input.actorId,
|
||||
forward: input.command,
|
||||
inverse,
|
||||
});
|
||||
applyProjectHistoryTransition(tx, {
|
||||
projectId: input.projectId,
|
||||
source: input.source,
|
||||
recordedChangeSetId: revision.changeSetId,
|
||||
targetChangeSetId: input.historyTargetChangeSetId,
|
||||
});
|
||||
return { revision, inverse };
|
||||
});
|
||||
}
|
||||
|
||||
private applyCommand(
|
||||
database: AppDatabase,
|
||||
projectId: string,
|
||||
source: ExecuteCircuitDeviceRowStructureCommandInput["source"],
|
||||
command: CircuitDeviceRowStructureProjectCommand
|
||||
): CircuitDeviceRowStructureProjectCommand {
|
||||
if (command.type === circuitDeviceRowInsertCommandType) {
|
||||
assertCircuitDeviceRowInsertProjectCommand(command);
|
||||
return this.insert(
|
||||
database,
|
||||
projectId,
|
||||
source,
|
||||
command.payload.row
|
||||
);
|
||||
}
|
||||
if (command.type === circuitDeviceRowDeleteCommandType) {
|
||||
assertCircuitDeviceRowDeleteProjectCommand(command);
|
||||
return this.delete(
|
||||
database,
|
||||
projectId,
|
||||
command.payload.rowId,
|
||||
command.payload.expectedCircuitId
|
||||
);
|
||||
}
|
||||
throw new Error("Unsupported circuit device-row structure command.");
|
||||
}
|
||||
|
||||
private insert(
|
||||
database: AppDatabase,
|
||||
projectId: string,
|
||||
source: ExecuteCircuitDeviceRowStructureCommandInput["source"],
|
||||
row: CircuitDeviceRowSnapshot
|
||||
) {
|
||||
if (source === "user" && row.legacyConsumerId !== null) {
|
||||
throw new Error(
|
||||
"User commands cannot assign legacy consumer ids."
|
||||
);
|
||||
}
|
||||
this.assertCircuitInProject(database, projectId, row.circuitId);
|
||||
this.assertReferencesInProject(database, projectId, row);
|
||||
|
||||
const existing = database
|
||||
.select({ id: circuitDeviceRows.id })
|
||||
.from(circuitDeviceRows)
|
||||
.where(eq(circuitDeviceRows.id, row.id))
|
||||
.get();
|
||||
if (existing) {
|
||||
throw new Error("Circuit device-row id already exists.");
|
||||
}
|
||||
|
||||
database.insert(circuitDeviceRows).values(row).run();
|
||||
const circuitUpdate = database
|
||||
.update(circuits)
|
||||
.set({ isReserve: 0 })
|
||||
.where(eq(circuits.id, row.circuitId))
|
||||
.run();
|
||||
if (circuitUpdate.changes !== 1) {
|
||||
throw new Error("Circuit changed before device-row insertion.");
|
||||
}
|
||||
|
||||
return createCircuitDeviceRowDeleteProjectCommand(
|
||||
row.id,
|
||||
row.circuitId
|
||||
);
|
||||
}
|
||||
|
||||
private delete(
|
||||
database: AppDatabase,
|
||||
projectId: string,
|
||||
rowId: string,
|
||||
expectedCircuitId: string
|
||||
) {
|
||||
const row = database
|
||||
.select()
|
||||
.from(circuitDeviceRows)
|
||||
.where(eq(circuitDeviceRows.id, rowId))
|
||||
.get();
|
||||
if (!row || row.circuitId !== expectedCircuitId) {
|
||||
throw new Error(
|
||||
"Circuit device row changed before command execution."
|
||||
);
|
||||
}
|
||||
this.assertCircuitInProject(database, projectId, row.circuitId);
|
||||
|
||||
const result = database
|
||||
.delete(circuitDeviceRows)
|
||||
.where(
|
||||
and(
|
||||
eq(circuitDeviceRows.id, rowId),
|
||||
eq(circuitDeviceRows.circuitId, expectedCircuitId)
|
||||
)
|
||||
)
|
||||
.run();
|
||||
if (result.changes !== 1) {
|
||||
throw new Error("Circuit device row could not be deleted.");
|
||||
}
|
||||
|
||||
const remainingRow = database
|
||||
.select({ id: circuitDeviceRows.id })
|
||||
.from(circuitDeviceRows)
|
||||
.where(eq(circuitDeviceRows.circuitId, expectedCircuitId))
|
||||
.limit(1)
|
||||
.get();
|
||||
const circuitUpdate = database
|
||||
.update(circuits)
|
||||
.set({ isReserve: remainingRow ? 0 : 1 })
|
||||
.where(eq(circuits.id, expectedCircuitId))
|
||||
.run();
|
||||
if (circuitUpdate.changes !== 1) {
|
||||
throw new Error("Circuit changed before device-row deletion.");
|
||||
}
|
||||
|
||||
return createCircuitDeviceRowInsertProjectCommand(
|
||||
toCircuitDeviceRowSnapshot(row)
|
||||
);
|
||||
}
|
||||
|
||||
private assertCircuitInProject(
|
||||
database: AppDatabase,
|
||||
projectId: string,
|
||||
circuitId: string
|
||||
) {
|
||||
const circuit = database
|
||||
.select({ id: circuits.id })
|
||||
.from(circuits)
|
||||
.innerJoin(
|
||||
circuitLists,
|
||||
eq(circuitLists.id, circuits.circuitListId)
|
||||
)
|
||||
.where(
|
||||
and(
|
||||
eq(circuits.id, circuitId),
|
||||
eq(circuitLists.projectId, projectId)
|
||||
)
|
||||
)
|
||||
.get();
|
||||
if (!circuit) {
|
||||
throw new Error("Circuit does not belong to project.");
|
||||
}
|
||||
}
|
||||
|
||||
private assertReferencesInProject(
|
||||
database: AppDatabase,
|
||||
projectId: string,
|
||||
row: CircuitDeviceRowSnapshot
|
||||
) {
|
||||
if (row.linkedProjectDeviceId !== null) {
|
||||
const device = database
|
||||
.select({ id: projectDevices.id })
|
||||
.from(projectDevices)
|
||||
.where(
|
||||
and(
|
||||
eq(projectDevices.id, row.linkedProjectDeviceId),
|
||||
eq(projectDevices.projectId, projectId)
|
||||
)
|
||||
)
|
||||
.get();
|
||||
if (!device) {
|
||||
throw new Error("Invalid linked project device id.");
|
||||
}
|
||||
}
|
||||
if (row.roomId !== null) {
|
||||
const room = database
|
||||
.select({ id: rooms.id })
|
||||
.from(rooms)
|
||||
.where(
|
||||
and(
|
||||
eq(rooms.id, row.roomId),
|
||||
eq(rooms.projectId, projectId)
|
||||
)
|
||||
)
|
||||
.get();
|
||||
if (!room) {
|
||||
throw new Error("Invalid room id.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function toCircuitDeviceRowSnapshot(
|
||||
row: typeof circuitDeviceRows.$inferSelect
|
||||
): CircuitDeviceRowSnapshot {
|
||||
return {
|
||||
id: row.id,
|
||||
circuitId: row.circuitId,
|
||||
linkedProjectDeviceId: row.linkedProjectDeviceId,
|
||||
legacyConsumerId: row.legacyConsumerId,
|
||||
sortOrder: row.sortOrder,
|
||||
name: row.name,
|
||||
displayName: row.displayName,
|
||||
phaseType: row.phaseType,
|
||||
connectionKind: row.connectionKind,
|
||||
costGroup: row.costGroup,
|
||||
category: row.category,
|
||||
level: row.level,
|
||||
roomId: row.roomId,
|
||||
roomNumberSnapshot: row.roomNumberSnapshot,
|
||||
roomNameSnapshot: row.roomNameSnapshot,
|
||||
quantity: row.quantity,
|
||||
powerPerUnit: row.powerPerUnit,
|
||||
simultaneityFactor: row.simultaneityFactor,
|
||||
cosPhi: row.cosPhi,
|
||||
remark: row.remark,
|
||||
overriddenFields: row.overriddenFields,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user