143 lines
6 KiB
TypeScript
143 lines
6 KiB
TypeScript
import { and, eq, inArray } from "drizzle-orm";
|
|
import {
|
|
assertCircuitDeviceRowQuantity,
|
|
} from "../../domain/calculations/circuit-device-row-quantity.js";
|
|
import {
|
|
assertExternalObjectRowAssignmentProjectCommand,
|
|
invertExternalObjectRowAssignmentProjectCommand,
|
|
} from "../../domain/models/external-object-row-assignment-project-command.model.js";
|
|
import type { ExternalObjectRowAssignmentProjectCommandStore } from "../../domain/ports/external-object-row-assignment-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 { externalModelObjects } from "../schema/external-model-objects.js";
|
|
import { toCircuitDeviceRowSnapshot } from "./circuit-device-row-structure.persistence.js";
|
|
import { executeProjectCommandTransaction } from "./project-command-transaction.persistence.js";
|
|
import {
|
|
applyExternalObjectLinks,
|
|
assertExternalObjectsCompatibleWithRow,
|
|
loadExpectedExternalObjectTransitions,
|
|
snapshotsEqual,
|
|
toExternalObjectSnapshot,
|
|
} from "./external-object-assignment.persistence.js";
|
|
|
|
export class ExternalObjectRowAssignmentProjectCommandRepository
|
|
implements ExternalObjectRowAssignmentProjectCommandStore
|
|
{
|
|
constructor(private readonly database: AppDatabase) {}
|
|
|
|
execute(input: Parameters<ExternalObjectRowAssignmentProjectCommandStore["execute"]>[0]) {
|
|
assertExternalObjectRowAssignmentProjectCommand(input.command);
|
|
return executeProjectCommandTransaction(this.database, input, (tx) => {
|
|
this.apply(tx, input.projectId, input.command);
|
|
return invertExternalObjectRowAssignmentProjectCommand(input.command);
|
|
});
|
|
}
|
|
|
|
private apply(
|
|
database: AppDatabase,
|
|
projectId: string,
|
|
command: Parameters<ExternalObjectRowAssignmentProjectCommandStore["execute"]>[0]["command"]
|
|
) {
|
|
const rowTransitions = new Map(
|
|
command.payload.rows.map((transition) => [transition.expected.id, transition])
|
|
);
|
|
const objectTransitions = new Map(
|
|
command.payload.objects.map((transition) => [transition.expected.id, transition])
|
|
);
|
|
const rowIds = [...rowTransitions.keys()];
|
|
const currentRows = database.select().from(circuitDeviceRows)
|
|
.where(inArray(circuitDeviceRows.id, rowIds)).all();
|
|
if (currentRows.length !== rowIds.length) {
|
|
throw new Error("External object assignment target no longer exists.");
|
|
}
|
|
for (const row of currentRows) {
|
|
const expected = rowTransitions.get(row.id)!.expected;
|
|
if (!snapshotsEqual(toCircuitDeviceRowSnapshot(row), expected)) {
|
|
throw new Error("Circuit device row changed before external object assignment.");
|
|
}
|
|
}
|
|
loadExpectedExternalObjectTransitions(
|
|
database,
|
|
projectId,
|
|
command.payload.objects,
|
|
"External object changed before row assignment."
|
|
);
|
|
|
|
const rowContexts = database
|
|
.select({
|
|
rowId: circuitDeviceRows.id,
|
|
projectId: circuitLists.projectId,
|
|
distributionBoardId: circuitLists.distributionBoardId,
|
|
category: circuitSections.category,
|
|
})
|
|
.from(circuitDeviceRows)
|
|
.innerJoin(circuits, eq(circuits.id, circuitDeviceRows.circuitId))
|
|
.innerJoin(circuitLists, eq(circuitLists.id, circuits.circuitListId))
|
|
.innerJoin(circuitSections, eq(circuitSections.id, circuits.sectionId))
|
|
.where(inArray(circuitDeviceRows.id, rowIds))
|
|
.all();
|
|
if (
|
|
rowContexts.length !== rowIds.length ||
|
|
rowContexts.some((context) =>
|
|
context.projectId !== projectId || context.category === null
|
|
)
|
|
) {
|
|
throw new Error("Circuit device row belongs to another project.");
|
|
}
|
|
const contextByRowId = new Map(rowContexts.map((context) => [context.rowId, context]));
|
|
|
|
const allProjectObjects = database.select().from(externalModelObjects)
|
|
.where(eq(externalModelObjects.projectId, projectId)).all()
|
|
.map(toExternalObjectSnapshot);
|
|
const targetObjects = allProjectObjects.map((object) =>
|
|
objectTransitions.get(object.id)?.target ?? object
|
|
);
|
|
const confirmedConflicts = new Set(command.payload.confirmedConflictObjectIds);
|
|
|
|
for (const transition of command.payload.rows) {
|
|
const linkedObjects = targetObjects.filter(
|
|
(object) => object.circuitDeviceRowId === transition.target.id
|
|
);
|
|
const assignedObjects = command.payload.objects
|
|
.map(({ target }) => target)
|
|
.filter((object) => object.circuitDeviceRowId === transition.target.id);
|
|
const context = contextByRowId.get(transition.target.id)!;
|
|
assertExternalObjectsCompatibleWithRow({
|
|
database,
|
|
projectId,
|
|
row: transition.target,
|
|
distributionBoardId: context.distributionBoardId,
|
|
category: context.category!,
|
|
assignedObjects,
|
|
allLinkedObjects: linkedObjects,
|
|
confirmedConflictObjectIds: confirmedConflicts,
|
|
});
|
|
assertCircuitDeviceRowQuantity({
|
|
quantity: transition.target.quantity,
|
|
manualQuantity: transition.target.manualQuantity ?? transition.target.quantity,
|
|
externalObjects: linkedObjects.map((object) => ({
|
|
effectiveQuantity: object.planningValues.effectiveQuantity,
|
|
})),
|
|
});
|
|
}
|
|
|
|
for (const transition of command.payload.rows) {
|
|
const result = database.update(circuitDeviceRows)
|
|
.set({ quantity: transition.target.quantity })
|
|
.where(and(
|
|
eq(circuitDeviceRows.id, transition.expected.id),
|
|
eq(circuitDeviceRows.quantity, transition.expected.quantity),
|
|
eq(circuitDeviceRows.manualQuantity, transition.expected.manualQuantity ?? transition.expected.quantity)
|
|
)).run();
|
|
if (result.changes !== 1) throw new Error("Circuit device row changed during assignment.");
|
|
}
|
|
applyExternalObjectLinks(
|
|
database,
|
|
command.payload.objects,
|
|
"External object changed during assignment."
|
|
);
|
|
}
|
|
}
|