Add logical distribution board feed links
This commit is contained in:
parent
b50c00c0ac
commit
b893c72534
23 changed files with 3732 additions and 45 deletions
4
src/db/migrations/0012_good_gateway.sql
Normal file
4
src/db/migrations/0012_good_gateway.sql
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
ALTER TABLE `distribution_boards` ADD `feeding_distribution_board_id` text REFERENCES distribution_boards(id);--> statement-breakpoint
|
||||
ALTER TABLE `distribution_boards` ADD `feeding_circuit_id` text REFERENCES circuits(id);--> statement-breakpoint
|
||||
CREATE INDEX `distribution_boards_feeding_board_idx` ON `distribution_boards` (`feeding_distribution_board_id`);--> statement-breakpoint
|
||||
CREATE INDEX `distribution_boards_feeding_circuit_idx` ON `distribution_boards` (`feeding_circuit_id`);
|
||||
2986
src/db/migrations/meta/0012_snapshot.json
Normal file
2986
src/db/migrations/meta/0012_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -85,6 +85,13 @@
|
|||
"when": 1790531351492,
|
||||
"tag": "0011_glossy_norman_osborn",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 12,
|
||||
"version": "6",
|
||||
"when": 1790540142918,
|
||||
"tag": "0012_good_gateway",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) =>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { sqliteTable, text, unique } from "drizzle-orm/sqlite-core";
|
||||
import { sqliteTable, text, unique, type AnySQLiteColumn } from "drizzle-orm/sqlite-core";
|
||||
import { distributionBoards } from "./distribution-boards.js";
|
||||
import { projects } from "./projects.js";
|
||||
|
||||
|
|
@ -11,7 +11,7 @@ export const circuitLists = sqliteTable(
|
|||
.references(() => projects.id, { onDelete: "cascade" }),
|
||||
distributionBoardId: text("distribution_board_id")
|
||||
.notNull()
|
||||
.references(() => distributionBoards.id, { onDelete: "cascade" }),
|
||||
.references((): AnySQLiteColumn => distributionBoards.id, { onDelete: "cascade" }),
|
||||
name: text("name").notNull(),
|
||||
},
|
||||
(table) => [unique("circuit_lists_distribution_board_id_unique").on(table.distributionBoardId)]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { index, integer, real, sqliteTable, text, unique } from "drizzle-orm/sqlite-core";
|
||||
import { index, integer, real, sqliteTable, text, unique, type AnySQLiteColumn } from "drizzle-orm/sqlite-core";
|
||||
import { circuitLists } from "./circuit-lists.js";
|
||||
import { circuitSections } from "./circuit-sections.js";
|
||||
|
||||
|
|
@ -8,7 +8,7 @@ export const circuits = sqliteTable(
|
|||
id: text("id").primaryKey(),
|
||||
circuitListId: text("circuit_list_id")
|
||||
.notNull()
|
||||
.references(() => circuitLists.id, { onDelete: "cascade" }),
|
||||
.references((): AnySQLiteColumn => circuitLists.id, { onDelete: "cascade" }),
|
||||
sectionId: text("section_id")
|
||||
.notNull()
|
||||
.references(() => circuitSections.id, { onDelete: "cascade" }),
|
||||
|
|
|
|||
|
|
@ -1,24 +1,44 @@
|
|||
import { real, sqliteTable, text } from "drizzle-orm/sqlite-core";
|
||||
import { index, real, sqliteTable, text, type AnySQLiteColumn } from "drizzle-orm/sqlite-core";
|
||||
import { circuits } from "./circuits.js";
|
||||
import { floors } from "./floors.js";
|
||||
import { projects } from "./projects.js";
|
||||
import type { DistributionBoardSupplyType } from "../../shared/constants/distribution-board.js";
|
||||
|
||||
export const distributionBoards = sqliteTable("distribution_boards", {
|
||||
id: text("id").primaryKey(),
|
||||
projectId: text("project_id")
|
||||
.notNull()
|
||||
.references(() => projects.id, { onDelete: "cascade" }),
|
||||
name: text("name").notNull(),
|
||||
floorId: text("floor_id").references(() => floors.id, {
|
||||
onDelete: "set null",
|
||||
}),
|
||||
supplyType: text("supply_type").$type<DistributionBoardSupplyType>(),
|
||||
simultaneityFactor: real("simultaneity_factor")
|
||||
.notNull()
|
||||
.default(1),
|
||||
boardNumber: text("board_number"),
|
||||
location: text("location"),
|
||||
fedFrom: text("fed_from"),
|
||||
remark: text("remark"),
|
||||
});
|
||||
export const distributionBoards = sqliteTable(
|
||||
"distribution_boards",
|
||||
{
|
||||
id: text("id").primaryKey(),
|
||||
projectId: text("project_id")
|
||||
.notNull()
|
||||
.references(() => projects.id, { onDelete: "cascade" }),
|
||||
name: text("name").notNull(),
|
||||
floorId: text("floor_id").references(() => floors.id, {
|
||||
onDelete: "set null",
|
||||
}),
|
||||
supplyType: text("supply_type").$type<DistributionBoardSupplyType>(),
|
||||
simultaneityFactor: real("simultaneity_factor")
|
||||
.notNull()
|
||||
.default(1),
|
||||
boardNumber: text("board_number"),
|
||||
location: text("location"),
|
||||
fedFrom: text("fed_from"),
|
||||
remark: text("remark"),
|
||||
feedingDistributionBoardId: text("feeding_distribution_board_id").references(
|
||||
(): AnySQLiteColumn => distributionBoards.id,
|
||||
{ onDelete: "restrict" }
|
||||
),
|
||||
feedingCircuitId: text("feeding_circuit_id").references(
|
||||
(): AnySQLiteColumn => circuits.id,
|
||||
{ onDelete: "restrict" }
|
||||
),
|
||||
},
|
||||
(table) => [
|
||||
index("distribution_boards_feeding_board_idx").on(
|
||||
table.feedingDistributionBoardId
|
||||
),
|
||||
index("distribution_boards_feeding_circuit_idx").on(
|
||||
table.feedingCircuitId
|
||||
),
|
||||
]
|
||||
);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue