Add logical distribution board feed links

This commit is contained in:
Julian Appel 2026-09-27 22:21:58 +02:00
parent b50c00c0ac
commit b893c72534
23 changed files with 3732 additions and 45 deletions

View file

@ -325,6 +325,16 @@ export class DistributionBoardStructureProjectCommandRepository
patch.supplyType
);
}
this.assertFeedingReference(database, input.projectId, current.id, {
feedingDistributionBoardId:
patch.feedingDistributionBoardId === undefined
? current.feedingDistributionBoardId
: patch.feedingDistributionBoardId,
feedingCircuitId:
patch.feedingCircuitId === undefined
? current.feedingCircuitId
: patch.feedingCircuitId,
});
const inversePatch = Object.fromEntries(
input.command.payload.changes.map((change) => [
change.field,
@ -333,7 +343,8 @@ export class DistributionBoardStructureProjectCommandRepository
) as DistributionBoardUpdatePatch;
const inverse = createDistributionBoardUpdateProjectCommand(
current.id,
inversePatch
inversePatch,
input.command.schemaVersion
);
const updated = database
.update(distributionBoards)
@ -352,6 +363,77 @@ export class DistributionBoardStructureProjectCommandRepository
}
return inverse;
}
private assertFeedingReference(
database: AppDatabase,
projectId: string,
distributionBoardId: string,
reference: {
feedingDistributionBoardId: string | null;
feedingCircuitId: string | null;
}
) {
const { feedingDistributionBoardId, feedingCircuitId } = reference;
if (feedingDistributionBoardId === null) {
if (feedingCircuitId !== null) {
throw new Error(
"Ein speisender Stromkreis erfordert eine speisende Verteilung."
);
}
return;
}
if (feedingDistributionBoardId === distributionBoardId) {
throw new Error("Eine Verteilung kann sich nicht selbst speisen.");
}
const projectBoards = database
.select({
id: distributionBoards.id,
feedingDistributionBoardId:
distributionBoards.feedingDistributionBoardId,
})
.from(distributionBoards)
.where(eq(distributionBoards.projectId, projectId))
.all();
const boardById = new Map(projectBoards.map((board) => [board.id, board]));
if (!boardById.has(feedingDistributionBoardId)) {
throw new Error(
"Die speisende Verteilung gehört nicht zum Projekt."
);
}
const visited = new Set<string>([distributionBoardId]);
let currentId: string | null = feedingDistributionBoardId;
while (currentId !== null) {
if (visited.has(currentId)) {
throw new Error("Die Einspeisungsverknüpfung würde einen Zyklus erzeugen.");
}
visited.add(currentId);
currentId = boardById.get(currentId)?.feedingDistributionBoardId ?? null;
}
if (feedingCircuitId === null) {
return;
}
const feedingCircuit = database
.select({
projectId: circuitLists.projectId,
distributionBoardId: circuitLists.distributionBoardId,
})
.from(circuits)
.innerJoin(
circuitLists,
eq(circuitLists.id, circuits.circuitListId)
)
.where(eq(circuits.id, feedingCircuitId))
.get();
if (
!feedingCircuit ||
feedingCircuit.projectId !== projectId ||
feedingCircuit.distributionBoardId !== feedingDistributionBoardId
) {
throw new Error(
"Der speisende Stromkreis gehört nicht zur gewählten Verteilung."
);
}
}
}
function assertSupplyTypeEnabled(
@ -392,11 +474,20 @@ function structureMatches(
location,
fedFrom,
remark,
feedingDistributionBoardId,
feedingCircuitId,
...actualDistributionBoard
} = actual.board;
if (
simultaneityFactor !== 1 ||
[boardNumber, location, fedFrom, remark].some((value) => value !== null) ||
[
boardNumber,
location,
fedFrom,
remark,
feedingDistributionBoardId,
feedingCircuitId,
].some((value) => value !== null) ||
!sameRecord(
expected.distributionBoard,
actualDistributionBoard

View file

@ -300,6 +300,69 @@ export class DistributionBoardSubtreeProjectCommandRepository
);
}
}
const feedingDistributionBoardId =
snapshot.distributionBoard.feedingDistributionBoardId;
const feedingCircuitId = snapshot.distributionBoard.feedingCircuitId;
if (feedingDistributionBoardId === null) {
if (feedingCircuitId !== null) {
throw new Error(
"Ein speisender Stromkreis erfordert eine speisende Verteilung."
);
}
} else {
if (feedingDistributionBoardId === snapshot.distributionBoard.id) {
throw new Error("Eine Verteilung kann sich nicht selbst speisen.");
}
const projectBoards = database
.select({
id: distributionBoards.id,
feedingDistributionBoardId:
distributionBoards.feedingDistributionBoardId,
})
.from(distributionBoards)
.where(eq(distributionBoards.projectId, projectId))
.all();
const boardById = new Map(projectBoards.map((board) => [board.id, board]));
if (!boardById.has(feedingDistributionBoardId)) {
throw new Error(
"Die speisende Verteilung gehört nicht zum Projekt."
);
}
const visited = new Set<string>([snapshot.distributionBoard.id]);
let currentId: string | null = feedingDistributionBoardId;
while (currentId !== null) {
if (visited.has(currentId)) {
throw new Error(
"Die Einspeisungsverknüpfung würde einen Zyklus erzeugen."
);
}
visited.add(currentId);
currentId = boardById.get(currentId)?.feedingDistributionBoardId ?? null;
}
if (feedingCircuitId !== null) {
const feedingCircuit = database
.select({
projectId: circuitLists.projectId,
distributionBoardId: circuitLists.distributionBoardId,
})
.from(circuits)
.innerJoin(
circuitLists,
eq(circuitLists.id, circuits.circuitListId)
)
.where(eq(circuits.id, feedingCircuitId))
.get();
if (
!feedingCircuit ||
feedingCircuit.projectId !== projectId ||
feedingCircuit.distributionBoardId !== feedingDistributionBoardId
) {
throw new Error(
"Der speisende Stromkreis gehört nicht zur gewählten Verteilung."
);
}
}
}
const linkedProjectDeviceIds = uniqueStrings(
snapshot.circuits.flatMap((circuit) =>
circuit.deviceRows.flatMap((row) =>

View file

@ -270,6 +270,14 @@ function replaceProjectState(
database: AppDatabase,
state: ProjectStateSnapshot
) {
database
.update(distributionBoards)
.set({
feedingDistributionBoardId: null,
feedingCircuitId: null,
})
.where(eq(distributionBoards.projectId, state.project.id))
.run();
database
.delete(externalModelSources)
.where(eq(externalModelSources.projectId, state.project.id))
@ -330,7 +338,15 @@ function replaceProjectState(
}
insertMany(database, rooms, state.rooms);
insertMany(database, projectDevices, state.projectDevices);
insertMany(database, distributionBoards, state.distributionBoards);
insertMany(
database,
distributionBoards,
state.distributionBoards.map((board) => ({
...board,
feedingDistributionBoardId: null,
feedingCircuitId: null,
}))
);
insertMany(database, circuitLists, state.circuitLists);
insertMany(database, circuitSections, state.circuitSections);
insertMany(
@ -341,6 +357,7 @@ function replaceProjectState(
isReserve: circuit.isReserve ? 1 : 0,
}))
);
restoreDistributionBoardFeedingReferences(database, state);
insertMany(
database,
distributionBoardComponents,
@ -378,6 +395,31 @@ function replaceProjectState(
insertMany(database, externalModelObjects, state.externalModel.objects);
}
function restoreDistributionBoardFeedingReferences(
database: AppDatabase,
state: ProjectStateSnapshot
) {
for (const board of state.distributionBoards) {
if (
board.feedingDistributionBoardId === null &&
board.feedingCircuitId === null
) {
continue;
}
const updated = database
.update(distributionBoards)
.set({
feedingDistributionBoardId: board.feedingDistributionBoardId,
feedingCircuitId: board.feedingCircuitId,
})
.where(eq(distributionBoards.id, board.id))
.run();
if (updated.changes !== 1) {
throw new Error("Distribution-board feeding reference could not be restored.");
}
}
}
function insertMany<TTable extends Parameters<AppDatabase["insert"]>[0]>(
database: AppDatabase,
table: TTable,

View file

@ -173,7 +173,15 @@ function insertProjectState(
insertMany(database, floors, state.floors);
insertMany(database, rooms, state.rooms);
insertMany(database, projectDevices, state.projectDevices);
insertMany(database, distributionBoards, state.distributionBoards);
insertMany(
database,
distributionBoards,
state.distributionBoards.map((board) => ({
...board,
feedingDistributionBoardId: null,
feedingCircuitId: null,
}))
);
insertMany(database, circuitLists, state.circuitLists);
insertMany(database, circuitSections, state.circuitSections);
insertMany(
@ -184,6 +192,25 @@ function insertProjectState(
isReserve: circuit.isReserve ? 1 : 0,
}))
);
for (const board of state.distributionBoards) {
if (
board.feedingDistributionBoardId === null &&
board.feedingCircuitId === null
) {
continue;
}
const updated = database
.update(distributionBoards)
.set({
feedingDistributionBoardId: board.feedingDistributionBoardId,
feedingCircuitId: board.feedingCircuitId,
})
.where(eq(distributionBoards.id, board.id))
.run();
if (updated.changes !== 1) {
throw new Error("Distribution-board feeding reference could not be imported.");
}
}
insertMany(
database,
distributionBoardComponents,