Keep single-device circuit names equal to their device row
A circuit with exactly one device row and no fed distribution board is shown as that row, so its own name was hidden and could drift (e.g. "Neuer Stromkreis" in the print view). Commands that create or leave such a circuit, rename its row or unlink its downstream board now set the circuit name to the row name and store the change as optional circuitDisplayNames, so Undo restores an overwritten free name exactly. Migration 0016 aligns existing data. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
58cec53713
commit
01312b89fe
21 changed files with 4330 additions and 111 deletions
|
|
@ -10,6 +10,7 @@ import {
|
|||
type CircuitDeviceRowMoveHistoryProjectCommand,
|
||||
} from "../../domain/models/circuit-device-row-move-project-command.model.js";
|
||||
import type { CircuitSnapshot } from "../../domain/models/circuit-structure-project-command.model.js";
|
||||
import { invertCircuitDisplayNameTransitions } from "../../domain/models/circuit-display-name-sync.model.js";
|
||||
import type {
|
||||
CircuitDeviceRowMoveProjectCommandStore,
|
||||
ExecuteCircuitDeviceRowMoveCommandInput,
|
||||
|
|
@ -20,7 +21,11 @@ import { circuitLists } from "../schema/circuit-lists.js";
|
|||
import { circuitProtectionDevices } from "../schema/circuit-protection-devices.js";
|
||||
import { circuitSections } from "../schema/circuit-sections.js";
|
||||
import { circuits } from "../schema/circuits.js";
|
||||
import { executeProjectCommandTransaction } from "./project-command-transaction.persistence.js";
|
||||
import { syncSingleDeviceCircuitDisplayNames } from "./circuit-display-name-sync.persistence.js";
|
||||
import {
|
||||
executeProjectCommandTransactionWithAppliedForward,
|
||||
type AppliedProjectCommand,
|
||||
} from "./project-command-transaction.persistence.js";
|
||||
import { updateDerivedCircuitVoltage } from "./project-voltage.persistence.js";
|
||||
import { resolveCircuitVoltage } from "./project-voltage.persistence.js";
|
||||
import { assertCircuitsAreNotReferencedAsFeeders } from "./distribution-board-feeding-deletion-guard.persistence.js";
|
||||
|
|
@ -30,6 +35,7 @@ interface PersistedMoveRow {
|
|||
circuitId: string;
|
||||
sortOrder: number;
|
||||
phaseType: string | null;
|
||||
displayName: string;
|
||||
}
|
||||
|
||||
export class CircuitDeviceRowMoveProjectCommandRepository
|
||||
|
|
@ -40,11 +46,16 @@ export class CircuitDeviceRowMoveProjectCommandRepository
|
|||
execute(input: ExecuteCircuitDeviceRowMoveCommandInput) {
|
||||
this.assertSupportedCommand(input.command);
|
||||
|
||||
return executeProjectCommandTransaction(
|
||||
return executeProjectCommandTransactionWithAppliedForward(
|
||||
this.database,
|
||||
input,
|
||||
(tx) =>
|
||||
this.applyCommand(tx, input.projectId, input.command)
|
||||
this.applyCommand(
|
||||
tx,
|
||||
input.projectId,
|
||||
input.source,
|
||||
input.command
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -70,8 +81,12 @@ export class CircuitDeviceRowMoveProjectCommandRepository
|
|||
private applyCommand(
|
||||
database: AppDatabase,
|
||||
projectId: string,
|
||||
source: ExecuteCircuitDeviceRowMoveCommandInput["source"],
|
||||
command: CircuitDeviceRowMoveHistoryProjectCommand
|
||||
): CircuitDeviceRowMoveHistoryProjectCommand {
|
||||
): AppliedProjectCommand<
|
||||
CircuitDeviceRowMoveHistoryProjectCommand,
|
||||
CircuitDeviceRowMoveHistoryProjectCommand
|
||||
> {
|
||||
if (command.type === circuitDeviceRowMoveCommandType) {
|
||||
const rowsById = this.loadExpectedRows(
|
||||
database,
|
||||
|
|
@ -85,21 +100,43 @@ export class CircuitDeviceRowMoveProjectCommandRepository
|
|||
projectId,
|
||||
circuitIds
|
||||
);
|
||||
const inverse = createCircuitDeviceRowMoveProjectCommand(
|
||||
this.reverseMoves(command.payload.moves, rowsById)
|
||||
);
|
||||
this.applyMoves(database, command.payload.moves);
|
||||
this.updateReserveStates(database, circuitIds);
|
||||
this.updateVoltages(database, projectId, circuitIds);
|
||||
return inverse;
|
||||
const circuitDisplayNames = syncSingleDeviceCircuitDisplayNames(
|
||||
database,
|
||||
source,
|
||||
circuitIds,
|
||||
command.payload.circuitDisplayNames
|
||||
);
|
||||
return {
|
||||
forward: createCircuitDeviceRowMoveProjectCommand(
|
||||
command.payload.moves,
|
||||
circuitDisplayNames
|
||||
),
|
||||
inverse: createCircuitDeviceRowMoveProjectCommand(
|
||||
this.reverseMoves(command.payload.moves, rowsById),
|
||||
invertCircuitDisplayNameTransitions(circuitDisplayNames)
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
assertCircuitDeviceRowMoveWithNewCircuitProjectCommand(
|
||||
command
|
||||
);
|
||||
const { targetCircuit, targetCircuitAction, moves } =
|
||||
command.payload;
|
||||
const { targetCircuitAction, moves } = command.payload;
|
||||
const rowsById = this.loadExpectedRows(database, moves);
|
||||
// A new circuit receiving exactly one row is shown as that row, so it
|
||||
// takes the row's name instead of the client's placeholder name.
|
||||
const targetCircuit =
|
||||
source === "user" &&
|
||||
targetCircuitAction === "create" &&
|
||||
moves.length === 1
|
||||
? {
|
||||
...command.payload.targetCircuit,
|
||||
displayName: rowsById.get(moves[0]!.rowId)!.displayName,
|
||||
}
|
||||
: command.payload.targetCircuit;
|
||||
const appliedTargetCircuit = {
|
||||
...targetCircuit,
|
||||
voltage: resolveCircuitVoltage(
|
||||
|
|
@ -124,12 +161,6 @@ export class CircuitDeviceRowMoveProjectCommandRepository
|
|||
this.assertTargetCircuitAvailable(database, appliedTargetCircuit);
|
||||
this.insertTargetCircuit(database, appliedTargetCircuit);
|
||||
|
||||
const inverse =
|
||||
createCircuitDeviceRowMoveWithNewCircuitProjectCommand(
|
||||
"delete",
|
||||
appliedTargetCircuit,
|
||||
this.reverseMoves(moves, rowsById)
|
||||
);
|
||||
this.applyMoves(database, moves);
|
||||
this.updateReserveStates(database, [
|
||||
...sourceCircuitIds,
|
||||
|
|
@ -139,7 +170,26 @@ export class CircuitDeviceRowMoveProjectCommandRepository
|
|||
...sourceCircuitIds,
|
||||
targetCircuit.id,
|
||||
]);
|
||||
return inverse;
|
||||
const circuitDisplayNames = syncSingleDeviceCircuitDisplayNames(
|
||||
database,
|
||||
source,
|
||||
[...sourceCircuitIds, targetCircuit.id],
|
||||
command.payload.circuitDisplayNames
|
||||
);
|
||||
return {
|
||||
forward: createCircuitDeviceRowMoveWithNewCircuitProjectCommand(
|
||||
"create",
|
||||
targetCircuit,
|
||||
moves,
|
||||
circuitDisplayNames
|
||||
),
|
||||
inverse: createCircuitDeviceRowMoveWithNewCircuitProjectCommand(
|
||||
"delete",
|
||||
appliedTargetCircuit,
|
||||
this.reverseMoves(moves, rowsById),
|
||||
invertCircuitDisplayNameTransitions(circuitDisplayNames)
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
this.assertTargetCircuitUnchanged(
|
||||
|
|
@ -156,12 +206,6 @@ export class CircuitDeviceRowMoveProjectCommandRepository
|
|||
destinationCircuitIds,
|
||||
targetCircuit.circuitListId
|
||||
);
|
||||
const inverse =
|
||||
createCircuitDeviceRowMoveWithNewCircuitProjectCommand(
|
||||
"create",
|
||||
appliedTargetCircuit,
|
||||
this.reverseMoves(moves, rowsById)
|
||||
);
|
||||
this.applyMoves(database, moves);
|
||||
this.updateReserveStates(database, [
|
||||
targetCircuit.id,
|
||||
|
|
@ -172,6 +216,12 @@ export class CircuitDeviceRowMoveProjectCommandRepository
|
|||
projectId,
|
||||
destinationCircuitIds
|
||||
);
|
||||
const circuitDisplayNames = syncSingleDeviceCircuitDisplayNames(
|
||||
database,
|
||||
source,
|
||||
destinationCircuitIds,
|
||||
command.payload.circuitDisplayNames
|
||||
);
|
||||
assertCircuitsAreNotReferencedAsFeeders(database, projectId, [
|
||||
targetCircuit.id,
|
||||
]);
|
||||
|
|
@ -193,7 +243,20 @@ export class CircuitDeviceRowMoveProjectCommandRepository
|
|||
"Created target circuit changed before deletion."
|
||||
);
|
||||
}
|
||||
return inverse;
|
||||
return {
|
||||
forward: createCircuitDeviceRowMoveWithNewCircuitProjectCommand(
|
||||
"delete",
|
||||
targetCircuit,
|
||||
moves,
|
||||
circuitDisplayNames
|
||||
),
|
||||
inverse: createCircuitDeviceRowMoveWithNewCircuitProjectCommand(
|
||||
"create",
|
||||
appliedTargetCircuit,
|
||||
this.reverseMoves(moves, rowsById),
|
||||
invertCircuitDisplayNameTransitions(circuitDisplayNames)
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
private updateVoltages(
|
||||
|
|
@ -217,6 +280,7 @@ export class CircuitDeviceRowMoveProjectCommandRepository
|
|||
circuitId: circuitDeviceRows.circuitId,
|
||||
sortOrder: circuitDeviceRows.sortOrder,
|
||||
phaseType: circuitDeviceRows.phaseType,
|
||||
displayName: circuitDeviceRows.displayName,
|
||||
})
|
||||
.from(circuitDeviceRows)
|
||||
.where(inArray(circuitDeviceRows.id, rowIds))
|
||||
|
|
|
|||
|
|
@ -6,6 +6,10 @@ import {
|
|||
type CircuitDeviceRowUpdatePatch,
|
||||
type CircuitDeviceRowUpdateValues,
|
||||
} from "../../domain/models/circuit-device-row-project-command.model.js";
|
||||
import {
|
||||
assertCircuitDisplayNameTransitions,
|
||||
invertCircuitDisplayNameTransitions,
|
||||
} from "../../domain/models/circuit-display-name-sync.model.js";
|
||||
import type {
|
||||
CircuitDeviceRowProjectCommandStore,
|
||||
ExecuteCircuitDeviceRowUpdateCommandInput,
|
||||
|
|
@ -22,6 +26,7 @@ import {
|
|||
toCircuitDeviceRowPatchValues,
|
||||
type CircuitDeviceRowPatchInput,
|
||||
} from "./circuit-device-row.persistence.js";
|
||||
import { syncSingleDeviceCircuitDisplayNames } from "./circuit-display-name-sync.persistence.js";
|
||||
import { executeProjectCommandTransactionWithAppliedForward } from "./project-command-transaction.persistence.js";
|
||||
import { updateDerivedCircuitVoltage } from "./project-voltage.persistence.js";
|
||||
|
||||
|
|
@ -34,6 +39,9 @@ export class CircuitDeviceRowProjectCommandRepository
|
|||
|
||||
executeUpdate(input: ExecuteCircuitDeviceRowUpdateCommandInput) {
|
||||
assertCircuitDeviceRowUpdateProjectCommand(input.command);
|
||||
assertCircuitDisplayNameTransitions(
|
||||
input.command.payload.circuitDisplayNames
|
||||
);
|
||||
|
||||
return executeProjectCommandTransactionWithAppliedForward(
|
||||
this.database,
|
||||
|
|
@ -111,20 +119,16 @@ export class CircuitDeviceRowProjectCommandRepository
|
|||
patch.overriddenFields = overriddenFields;
|
||||
}
|
||||
|
||||
const appliedForward = createCircuitDeviceRowUpdateProjectCommand(
|
||||
const appliedPatch = createCircuitDeviceRowUpdateProjectCommand(
|
||||
current.id,
|
||||
patch as CircuitDeviceRowUpdatePatch
|
||||
);
|
||||
const inversePatch = Object.fromEntries(
|
||||
appliedForward.payload.changes.map((change) => [
|
||||
appliedPatch.payload.changes.map((change) => [
|
||||
change.field,
|
||||
getCircuitDeviceRowFieldValue(current, change.field),
|
||||
])
|
||||
) as CircuitDeviceRowUpdatePatch;
|
||||
const inverse = createCircuitDeviceRowUpdateProjectCommand(
|
||||
current.id,
|
||||
inversePatch
|
||||
);
|
||||
|
||||
const update = tx
|
||||
.update(circuitDeviceRows)
|
||||
|
|
@ -141,10 +145,24 @@ export class CircuitDeviceRowProjectCommandRepository
|
|||
current.circuitId
|
||||
);
|
||||
}
|
||||
const circuitDisplayNames = syncSingleDeviceCircuitDisplayNames(
|
||||
tx,
|
||||
input.source,
|
||||
patch.displayName === undefined ? [] : [current.circuitId],
|
||||
input.command.payload.circuitDisplayNames
|
||||
);
|
||||
|
||||
return {
|
||||
forward: appliedForward,
|
||||
inverse,
|
||||
forward: createCircuitDeviceRowUpdateProjectCommand(
|
||||
current.id,
|
||||
patch as CircuitDeviceRowUpdatePatch,
|
||||
circuitDisplayNames
|
||||
),
|
||||
inverse: createCircuitDeviceRowUpdateProjectCommand(
|
||||
current.id,
|
||||
inversePatch,
|
||||
invertCircuitDisplayNameTransitions(circuitDisplayNames)
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,10 @@ import {
|
|||
type CircuitDeviceRowSnapshot,
|
||||
type CircuitDeviceRowStructureProjectCommand,
|
||||
} from "../../domain/models/circuit-device-row-structure-project-command.model.js";
|
||||
import {
|
||||
invertCircuitDisplayNameTransitions,
|
||||
type CircuitDisplayNameTransition,
|
||||
} from "../../domain/models/circuit-display-name-sync.model.js";
|
||||
import type {
|
||||
CircuitDeviceRowStructureProjectCommandStore,
|
||||
ExecuteCircuitDeviceRowStructureCommandInput,
|
||||
|
|
@ -23,6 +27,7 @@ import {
|
|||
toCircuitDeviceRowInsertValues,
|
||||
toCircuitDeviceRowSnapshot,
|
||||
} from "./circuit-device-row-structure.persistence.js";
|
||||
import { syncSingleDeviceCircuitDisplayNames } from "./circuit-display-name-sync.persistence.js";
|
||||
import { executeProjectCommandTransactionWithAppliedForward, type AppliedProjectCommand } from "./project-command-transaction.persistence.js";
|
||||
import { updateDerivedCircuitVoltage } from "./project-voltage.persistence.js";
|
||||
import {
|
||||
|
|
@ -69,7 +74,8 @@ export class CircuitDeviceRowStructureProjectCommandRepository
|
|||
projectId,
|
||||
source,
|
||||
command.payload.row,
|
||||
command.payload.externalObjects ?? []
|
||||
command.payload.externalObjects ?? [],
|
||||
command.payload.circuitDisplayNames
|
||||
);
|
||||
}
|
||||
if (command.type === circuitDeviceRowDeleteCommandType) {
|
||||
|
|
@ -77,9 +83,11 @@ export class CircuitDeviceRowStructureProjectCommandRepository
|
|||
return this.delete(
|
||||
database,
|
||||
projectId,
|
||||
source,
|
||||
command.payload.rowId,
|
||||
command.payload.expectedCircuitId,
|
||||
command.payload.externalObjects
|
||||
command.payload.externalObjects,
|
||||
command.payload.circuitDisplayNames
|
||||
);
|
||||
}
|
||||
throw new Error("Unsupported circuit device-row structure command.");
|
||||
|
|
@ -90,7 +98,8 @@ export class CircuitDeviceRowStructureProjectCommandRepository
|
|||
projectId: string,
|
||||
source: ExecuteCircuitDeviceRowStructureCommandInput["source"],
|
||||
row: CircuitDeviceRowSnapshot,
|
||||
externalObjects: ExternalObjectLinkTransition[]
|
||||
externalObjects: ExternalObjectLinkTransition[],
|
||||
storedCircuitDisplayNames: CircuitDisplayNameTransition[] | undefined
|
||||
) {
|
||||
if (source === "user" && !isElectricalPhaseType(row.phaseType)) {
|
||||
throw new Error("Device-row phase type is invalid.");
|
||||
|
|
@ -125,19 +134,32 @@ export class CircuitDeviceRowStructureProjectCommandRepository
|
|||
loadExpectedExternalObjectTransitions(database, projectId, externalObjects, "Revit-Objekt wurde vor dem Wiederherstellen geändert.");
|
||||
applyExternalObjectLinks(database, externalObjects, "Revit-Objekt wurde beim Wiederherstellen geändert.");
|
||||
}
|
||||
const circuitDisplayNames = syncSingleDeviceCircuitDisplayNames(
|
||||
database,
|
||||
source,
|
||||
[row.circuitId],
|
||||
storedCircuitDisplayNames
|
||||
);
|
||||
|
||||
return {
|
||||
forward: createCircuitDeviceRowInsertProjectCommand(row, externalObjects),
|
||||
inverse: createCircuitDeviceRowDeleteProjectCommand(row.id, row.circuitId, invertExternalObjectLinkTransitions(externalObjects)),
|
||||
forward: createCircuitDeviceRowInsertProjectCommand(row, externalObjects, circuitDisplayNames),
|
||||
inverse: createCircuitDeviceRowDeleteProjectCommand(
|
||||
row.id,
|
||||
row.circuitId,
|
||||
invertExternalObjectLinkTransitions(externalObjects),
|
||||
invertCircuitDisplayNameTransitions(circuitDisplayNames)
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
private delete(
|
||||
database: AppDatabase,
|
||||
projectId: string,
|
||||
source: ExecuteCircuitDeviceRowStructureCommandInput["source"],
|
||||
rowId: string,
|
||||
expectedCircuitId: string,
|
||||
suppliedExternalObjects?: ExternalObjectLinkTransition[]
|
||||
suppliedExternalObjects: ExternalObjectLinkTransition[] | undefined,
|
||||
storedCircuitDisplayNames: CircuitDisplayNameTransition[] | undefined
|
||||
) {
|
||||
const row = database
|
||||
.select()
|
||||
|
|
@ -187,10 +209,20 @@ export class CircuitDeviceRowStructureProjectCommandRepository
|
|||
projectId,
|
||||
expectedCircuitId
|
||||
);
|
||||
const circuitDisplayNames = syncSingleDeviceCircuitDisplayNames(
|
||||
database,
|
||||
source,
|
||||
[expectedCircuitId],
|
||||
storedCircuitDisplayNames
|
||||
);
|
||||
|
||||
return {
|
||||
forward: createCircuitDeviceRowDeleteProjectCommand(rowId, expectedCircuitId, externalObjects),
|
||||
inverse: createCircuitDeviceRowInsertProjectCommand(toCircuitDeviceRowSnapshot(row), invertExternalObjectLinkTransitions(externalObjects)),
|
||||
forward: createCircuitDeviceRowDeleteProjectCommand(rowId, expectedCircuitId, externalObjects, circuitDisplayNames),
|
||||
inverse: createCircuitDeviceRowInsertProjectCommand(
|
||||
toCircuitDeviceRowSnapshot(row),
|
||||
invertExternalObjectLinkTransitions(externalObjects),
|
||||
invertCircuitDisplayNameTransitions(circuitDisplayNames)
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
89
src/db/repositories/circuit-display-name-sync.persistence.ts
Normal file
89
src/db/repositories/circuit-display-name-sync.persistence.ts
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
import { and, eq, isNull } from "drizzle-orm";
|
||||
import {
|
||||
resolveSingleDeviceCircuitDisplayName,
|
||||
type CircuitDisplayNameTransition,
|
||||
} from "../../domain/models/circuit-display-name-sync.model.js";
|
||||
import type { ProjectRevisionSource } from "../../domain/ports/project-revision.store.js";
|
||||
import type { AppDatabase } from "../database-context.js";
|
||||
import { circuitDeviceRows } from "../schema/circuit-device-rows.js";
|
||||
import { circuits } from "../schema/circuits.js";
|
||||
import { distributionBoards } from "../schema/distribution-boards.js";
|
||||
|
||||
/**
|
||||
* Keeps the name of single-device circuits equal to their device row name.
|
||||
* A user command derives the changes for the affected circuits after its own
|
||||
* write; history replays apply exactly the stored changes. Commands stored
|
||||
* before this rule carry no changes and keep their original behavior.
|
||||
*/
|
||||
export function syncSingleDeviceCircuitDisplayNames(
|
||||
database: AppDatabase,
|
||||
source: ProjectRevisionSource,
|
||||
circuitIds: Iterable<string>,
|
||||
storedTransitions: readonly CircuitDisplayNameTransition[] | undefined
|
||||
): CircuitDisplayNameTransition[] {
|
||||
if (source !== "user") {
|
||||
const transitions = [...(storedTransitions ?? [])];
|
||||
for (const transition of transitions) {
|
||||
applyCircuitDisplayNameTransition(database, transition);
|
||||
}
|
||||
return transitions;
|
||||
}
|
||||
|
||||
const transitions: CircuitDisplayNameTransition[] = [];
|
||||
for (const circuitId of new Set(circuitIds)) {
|
||||
const circuit = database
|
||||
.select({ displayName: circuits.displayName })
|
||||
.from(circuits)
|
||||
.where(eq(circuits.id, circuitId))
|
||||
.get();
|
||||
if (!circuit) continue;
|
||||
const deviceRowDisplayNames = database
|
||||
.select({ displayName: circuitDeviceRows.displayName })
|
||||
.from(circuitDeviceRows)
|
||||
.where(eq(circuitDeviceRows.circuitId, circuitId))
|
||||
.limit(2)
|
||||
.all()
|
||||
.map((row) => row.displayName);
|
||||
const fedBoard = database
|
||||
.select({ id: distributionBoards.id })
|
||||
.from(distributionBoards)
|
||||
.where(eq(distributionBoards.feedingCircuitId, circuitId))
|
||||
.limit(1)
|
||||
.get();
|
||||
const target = resolveSingleDeviceCircuitDisplayName({
|
||||
displayName: circuit.displayName,
|
||||
deviceRowDisplayNames,
|
||||
feedsDistributionBoard: fedBoard !== undefined,
|
||||
});
|
||||
if (target === null) continue;
|
||||
const transition = {
|
||||
circuitId,
|
||||
expected: circuit.displayName,
|
||||
target,
|
||||
};
|
||||
applyCircuitDisplayNameTransition(database, transition);
|
||||
transitions.push(transition);
|
||||
}
|
||||
return transitions;
|
||||
}
|
||||
|
||||
function applyCircuitDisplayNameTransition(
|
||||
database: AppDatabase,
|
||||
transition: CircuitDisplayNameTransition
|
||||
) {
|
||||
const updated = database
|
||||
.update(circuits)
|
||||
.set({ displayName: transition.target })
|
||||
.where(
|
||||
and(
|
||||
eq(circuits.id, transition.circuitId),
|
||||
transition.expected === null
|
||||
? isNull(circuits.displayName)
|
||||
: eq(circuits.displayName, transition.expected)
|
||||
)
|
||||
)
|
||||
.run();
|
||||
if (updated.changes !== 1) {
|
||||
throw new Error("Circuit name changed before command execution.");
|
||||
}
|
||||
}
|
||||
|
|
@ -106,9 +106,17 @@ export class CircuitStructureProjectCommandRepository
|
|||
database: AppDatabase,
|
||||
projectId: string,
|
||||
source: ExecuteCircuitStructureCommandInput["source"],
|
||||
snapshot: CircuitSnapshot,
|
||||
requestedSnapshot: CircuitSnapshot,
|
||||
externalObjects: ExternalObjectLinkTransition[]
|
||||
) {
|
||||
// A single-device circuit is shown as its device row and carries its name.
|
||||
const snapshot =
|
||||
source === "user" && requestedSnapshot.deviceRows.length === 1
|
||||
? {
|
||||
...requestedSnapshot,
|
||||
displayName: requestedSnapshot.deviceRows[0]!.displayName,
|
||||
}
|
||||
: requestedSnapshot;
|
||||
this.assertCircuitLocation(database, projectId, snapshot);
|
||||
if (source === "user") {
|
||||
const derivedVoltage = resolveCircuitVoltage(
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import {
|
|||
createCircuitDeleteProjectCommand,
|
||||
createCircuitInsertProjectCommand,
|
||||
} from "../../domain/models/circuit-structure-project-command.model.js";
|
||||
import { invertCircuitDisplayNameTransitions } from "../../domain/models/circuit-display-name-sync.model.js";
|
||||
import type {
|
||||
DistributionBoardStructureProjectCommandStore,
|
||||
ExecuteDistributionBoardStructureCommandInput,
|
||||
|
|
@ -42,7 +43,12 @@ import { distributionBoardComponents } from "../schema/distribution-board-compon
|
|||
import { distributionBoards } from "../schema/distribution-boards.js";
|
||||
import { floors } from "../schema/floors.js";
|
||||
import { projects } from "../schema/projects.js";
|
||||
import { executeProjectCommandTransaction } from "./project-command-transaction.persistence.js";
|
||||
import { syncSingleDeviceCircuitDisplayNames } from "./circuit-display-name-sync.persistence.js";
|
||||
import {
|
||||
executeProjectCommandTransaction,
|
||||
executeProjectCommandTransactionWithAppliedForward,
|
||||
type AppliedProjectCommand,
|
||||
} from "./project-command-transaction.persistence.js";
|
||||
import { assertBoardsAreNotReferencedAsFeeders } from "./distribution-board-feeding-deletion-guard.persistence.js";
|
||||
import { assertDistributionBoardHasNoExternalObjects } from "./external-object-deletion-guard.persistence.js";
|
||||
import { CircuitStructureProjectCommandRepository } from "./circuit-structure-project-command.repository.js";
|
||||
|
|
@ -70,7 +76,7 @@ export class DistributionBoardStructureProjectCommandRepository
|
|||
}
|
||||
) {
|
||||
assertDistributionBoardUpdateProjectCommand(input.command);
|
||||
return executeProjectCommandTransaction(
|
||||
return executeProjectCommandTransactionWithAppliedForward(
|
||||
this.database,
|
||||
input,
|
||||
(tx) => this.update(tx, input)
|
||||
|
|
@ -94,7 +100,7 @@ export class DistributionBoardStructureProjectCommandRepository
|
|||
"Generated feeder circuits may only be removed through project history."
|
||||
);
|
||||
}
|
||||
return executeProjectCommandTransaction(
|
||||
return executeProjectCommandTransactionWithAppliedForward(
|
||||
this.database,
|
||||
input,
|
||||
(tx) => this.applyFeederCircuitCommand(tx, input)
|
||||
|
|
@ -384,11 +390,6 @@ export class DistributionBoardStructureProjectCommandRepository
|
|||
getDistributionBoardFieldValue(current, change.field),
|
||||
])
|
||||
) as DistributionBoardUpdatePatch;
|
||||
const inverse = createDistributionBoardUpdateProjectCommand(
|
||||
current.id,
|
||||
inversePatch,
|
||||
input.command.schemaVersion
|
||||
);
|
||||
const updated = database
|
||||
.update(distributionBoards)
|
||||
.set(patch)
|
||||
|
|
@ -404,7 +405,26 @@ export class DistributionBoardStructureProjectCommandRepository
|
|||
"Distribution board changed before command execution."
|
||||
);
|
||||
}
|
||||
return inverse;
|
||||
const circuitDisplayNames = syncSingleDeviceCircuitDisplayNames(
|
||||
database,
|
||||
input.source,
|
||||
current.feedingCircuitId === null ? [] : [current.feedingCircuitId],
|
||||
input.command.payload.circuitDisplayNames
|
||||
);
|
||||
return {
|
||||
forward: createDistributionBoardUpdateProjectCommand(
|
||||
current.id,
|
||||
patch,
|
||||
input.command.schemaVersion,
|
||||
circuitDisplayNames
|
||||
),
|
||||
inverse: createDistributionBoardUpdateProjectCommand(
|
||||
current.id,
|
||||
inversePatch,
|
||||
input.command.schemaVersion,
|
||||
invertCircuitDisplayNameTransitions(circuitDisplayNames)
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
private applyFeederCircuitCommand(
|
||||
|
|
@ -415,7 +435,10 @@ export class DistributionBoardStructureProjectCommandRepository
|
|||
> & {
|
||||
command: DistributionBoardFeederCircuitProjectCommand;
|
||||
}
|
||||
): DistributionBoardFeederCircuitProjectCommand {
|
||||
): AppliedProjectCommand<
|
||||
DistributionBoardFeederCircuitProjectCommand,
|
||||
DistributionBoardFeederCircuitProjectCommand
|
||||
> {
|
||||
const circuitRepository =
|
||||
new CircuitStructureProjectCommandRepository(database);
|
||||
const { circuit, boardUpdate } = input.command.payload;
|
||||
|
|
@ -431,17 +454,23 @@ export class DistributionBoardStructureProjectCommandRepository
|
|||
if (circuitInverse.type !== circuitDeleteCommandType) {
|
||||
throw new Error("Created feeder circuit returned an invalid inverse.");
|
||||
}
|
||||
const boardInverse = this.update(database, {
|
||||
const board = this.update(database, {
|
||||
...input,
|
||||
command: boardUpdate,
|
||||
});
|
||||
return createDistributionBoardRemoveFeederCircuitProjectCommand(
|
||||
circuit,
|
||||
boardInverse
|
||||
);
|
||||
return {
|
||||
forward: createDistributionBoardCreateFeederCircuitProjectCommand(
|
||||
circuit,
|
||||
board.forward
|
||||
),
|
||||
inverse: createDistributionBoardRemoveFeederCircuitProjectCommand(
|
||||
circuit,
|
||||
board.inverse
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
const boardInverse = this.update(database, {
|
||||
const board = this.update(database, {
|
||||
...input,
|
||||
command: boardUpdate,
|
||||
});
|
||||
|
|
@ -459,10 +488,16 @@ export class DistributionBoardStructureProjectCommandRepository
|
|||
"Generated feeder circuit changed before history removal."
|
||||
);
|
||||
}
|
||||
return createDistributionBoardCreateFeederCircuitProjectCommand(
|
||||
circuit,
|
||||
boardInverse
|
||||
);
|
||||
return {
|
||||
forward: createDistributionBoardRemoveFeederCircuitProjectCommand(
|
||||
circuit,
|
||||
board.forward
|
||||
),
|
||||
inverse: createDistributionBoardCreateFeederCircuitProjectCommand(
|
||||
circuit,
|
||||
board.inverse
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
private assertFeedingReference(
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { eq } from "drizzle-orm";
|
|||
import { assertCircuitDeviceRowQuantity } from "../../domain/calculations/circuit-device-row-quantity.js";
|
||||
import {
|
||||
assertExternalObjectNewRowProjectCommand,
|
||||
createExternalObjectNewRowProjectCommand,
|
||||
externalObjectAssignToNewRowCommandType,
|
||||
externalObjectDeleteCreatedRowCommandType,
|
||||
invertExternalObjectNewRowProjectCommand,
|
||||
|
|
@ -20,7 +21,8 @@ import {
|
|||
toCircuitDeviceRowInsertValues,
|
||||
toCircuitDeviceRowSnapshot,
|
||||
} from "./circuit-device-row-structure.persistence.js";
|
||||
import { executeProjectCommandTransaction } from "./project-command-transaction.persistence.js";
|
||||
import { syncSingleDeviceCircuitDisplayNames } from "./circuit-display-name-sync.persistence.js";
|
||||
import { executeProjectCommandTransactionWithAppliedForward } from "./project-command-transaction.persistence.js";
|
||||
import { updateDerivedCircuitVoltage } from "./project-voltage.persistence.js";
|
||||
import {
|
||||
applyExternalObjectLinks,
|
||||
|
|
@ -42,13 +44,26 @@ export class ExternalObjectNewRowProjectCommandRepository
|
|||
) {
|
||||
throw new Error("Created external rows may only be removed through project history.");
|
||||
}
|
||||
return executeProjectCommandTransaction(this.database, input, (tx) => {
|
||||
return executeProjectCommandTransactionWithAppliedForward(this.database, input, (tx) => {
|
||||
if (input.command.type === externalObjectAssignToNewRowCommandType) {
|
||||
this.insert(tx, input.projectId, input.command);
|
||||
} else {
|
||||
this.remove(tx, input.projectId, input.command);
|
||||
}
|
||||
return invertExternalObjectNewRowProjectCommand(input.command);
|
||||
const circuitDisplayNames = syncSingleDeviceCircuitDisplayNames(
|
||||
tx,
|
||||
input.source,
|
||||
[input.command.payload.row.circuitId],
|
||||
input.command.payload.circuitDisplayNames
|
||||
);
|
||||
const forward = createExternalObjectNewRowProjectCommand(
|
||||
input.command.type,
|
||||
{ ...input.command.payload, circuitDisplayNames }
|
||||
);
|
||||
return {
|
||||
forward,
|
||||
inverse: invertExternalObjectNewRowProjectCommand(forward),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import {
|
|||
projectDeviceSyncRowSnapshotFields,
|
||||
type ProjectDeviceSyncRowSnapshot,
|
||||
} from "../../domain/models/project-device-row-sync-project-command.model.js";
|
||||
import { invertCircuitDisplayNameTransitions } from "../../domain/models/circuit-display-name-sync.model.js";
|
||||
import type {
|
||||
ExecuteProjectDeviceRowSyncCommandInput,
|
||||
ProjectDeviceRowSyncProjectCommandStore,
|
||||
|
|
@ -16,7 +17,8 @@ import { circuitLists } from "../schema/circuit-lists.js";
|
|||
import { circuits } from "../schema/circuits.js";
|
||||
import { externalModelObjects } from "../schema/external-model-objects.js";
|
||||
import { projectDevices } from "../schema/project-devices.js";
|
||||
import { executeProjectCommandTransaction } from "./project-command-transaction.persistence.js";
|
||||
import { syncSingleDeviceCircuitDisplayNames } from "./circuit-display-name-sync.persistence.js";
|
||||
import { executeProjectCommandTransactionWithAppliedForward } from "./project-command-transaction.persistence.js";
|
||||
import { updateDerivedCircuitVoltage } from "./project-voltage.persistence.js";
|
||||
|
||||
type CircuitDeviceRow = typeof circuitDeviceRows.$inferSelect;
|
||||
|
|
@ -29,7 +31,7 @@ export class ProjectDeviceRowSyncProjectCommandRepository
|
|||
execute(input: ExecuteProjectDeviceRowSyncCommandInput) {
|
||||
assertProjectDeviceRowSyncProjectCommand(input.command);
|
||||
|
||||
return executeProjectCommandTransaction(
|
||||
return executeProjectCommandTransactionWithAppliedForward(
|
||||
this.database,
|
||||
input,
|
||||
(tx) => this.applyCommand(tx, input)
|
||||
|
|
@ -107,18 +109,6 @@ export class ProjectDeviceRowSyncProjectCommandRepository
|
|||
}
|
||||
}
|
||||
|
||||
const inverse = createProjectDeviceRowSyncProjectCommand(
|
||||
projectDevice.id,
|
||||
invertProjectDeviceRowSyncOperation(
|
||||
input.command.payload.operation
|
||||
),
|
||||
input.command.payload.rows.map((row) => ({
|
||||
rowId: row.rowId,
|
||||
expected: row.target,
|
||||
target: row.expected,
|
||||
}))
|
||||
);
|
||||
|
||||
for (const assignment of input.command.payload.rows) {
|
||||
const values: typeof assignment.target & {
|
||||
manualQuantity?: number;
|
||||
|
|
@ -173,8 +163,40 @@ export class ProjectDeviceRowSyncProjectCommandRepository
|
|||
circuitId
|
||||
);
|
||||
}
|
||||
const circuitDisplayNames = syncSingleDeviceCircuitDisplayNames(
|
||||
tx,
|
||||
input.source,
|
||||
input.command.payload.rows
|
||||
.filter(
|
||||
(assignment) =>
|
||||
assignment.expected.displayName !==
|
||||
assignment.target.displayName
|
||||
)
|
||||
.map(
|
||||
(assignment) => persistedById.get(assignment.rowId)!.circuitId
|
||||
),
|
||||
input.command.payload.circuitDisplayNames
|
||||
);
|
||||
|
||||
return inverse;
|
||||
const { projectDeviceId, operation, rows } = input.command.payload;
|
||||
return {
|
||||
forward: createProjectDeviceRowSyncProjectCommand(
|
||||
projectDeviceId,
|
||||
operation,
|
||||
rows,
|
||||
circuitDisplayNames
|
||||
),
|
||||
inverse: createProjectDeviceRowSyncProjectCommand(
|
||||
projectDevice.id,
|
||||
invertProjectDeviceRowSyncOperation(operation),
|
||||
rows.map((row) => ({
|
||||
rowId: row.rowId,
|
||||
expected: row.target,
|
||||
target: row.expected,
|
||||
})),
|
||||
invertCircuitDisplayNameTransitions(circuitDisplayNames)
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue