forked from jappel/leistungsbilanz-ts
Create feeder circuits while linking boards
This commit is contained in:
parent
552362bad1
commit
ade21fe386
11 changed files with 960 additions and 40 deletions
|
|
@ -47,6 +47,7 @@ import {
|
|||
import {
|
||||
copyGlobalDeviceToProject,
|
||||
copyDistributionBoard,
|
||||
createFeederCircuitAndUpdateDistributionBoard,
|
||||
copyProjectDeviceToGlobal,
|
||||
createDistributionBoard,
|
||||
createProjectDevice,
|
||||
|
|
@ -71,6 +72,12 @@ import {
|
|||
updateProjectDevice,
|
||||
updateProjectSettings,
|
||||
} from "../../../frontend/utils/api";
|
||||
import { createClientId } from "../../../frontend/utils/client-id";
|
||||
import {
|
||||
buildFeederCircuitSnapshot,
|
||||
getDefaultFeederCircuitSection,
|
||||
getNextFeederCircuitIdentifier,
|
||||
} from "../../../frontend/utils/distribution-board-feeder-circuit";
|
||||
import type {
|
||||
BuildingDto,
|
||||
CircuitListDto,
|
||||
|
|
@ -146,6 +153,7 @@ const boardFilterCollapsedStorageKey =
|
|||
"leistungsbilanz:overview-filter-collapsed:distribution-boards";
|
||||
const projectDeviceFilterCollapsedStorageKey =
|
||||
"leistungsbilanz:overview-filter-collapsed:project-devices";
|
||||
const createFeederCircuitSelectValue = "__create_feeder_circuit__";
|
||||
|
||||
export default function ProjectDetailPage() {
|
||||
const params = useParams<{ projectId: string }>();
|
||||
|
|
@ -175,6 +183,12 @@ export default function ProjectDetailPage() {
|
|||
const [editingBoardCopyName, setEditingBoardCopyName] = useState("");
|
||||
const [editingFeedingBoardId, setEditingFeedingBoardId] = useState("");
|
||||
const [editingFeedingCircuitId, setEditingFeedingCircuitId] = useState("");
|
||||
const [isCreatingFeederCircuit, setIsCreatingFeederCircuit] =
|
||||
useState(false);
|
||||
const [editingFeederCircuitSectionId, setEditingFeederCircuitSectionId] =
|
||||
useState("");
|
||||
const [editingFeederCircuitDisplayName, setEditingFeederCircuitDisplayName] =
|
||||
useState("");
|
||||
const [editingBoardInfo, setEditingBoardInfo] =
|
||||
useState<DistributionBoardInfoDraft>(
|
||||
toDistributionBoardInfoDraft({
|
||||
|
|
@ -350,6 +364,21 @@ export default function ProjectDetailPage() {
|
|||
const selectedFeedingCircuit = selectedFeedingCircuits.find(
|
||||
({ circuit }) => circuit.id === editingFeedingCircuitId
|
||||
);
|
||||
const feederCircuitSections =
|
||||
selectedFeedingTree?.sections.filter(
|
||||
(section) => section.category !== undefined
|
||||
) ?? [];
|
||||
const selectedNewFeederCircuitSection = feederCircuitSections.find(
|
||||
(section) => section.id === editingFeederCircuitSectionId
|
||||
);
|
||||
const newFeederCircuitIdentifier = selectedNewFeederCircuitSection
|
||||
? getNextFeederCircuitIdentifier(selectedNewFeederCircuitSection)
|
||||
: null;
|
||||
const selectedFeedingCircuitCategory =
|
||||
selectedFeedingCircuit?.section.category ??
|
||||
(isCreatingFeederCircuit
|
||||
? selectedNewFeederCircuitSection?.category
|
||||
: undefined);
|
||||
const selectedFeedingBoard = boards.find(
|
||||
(board) => board.id === editingFeedingBoardId
|
||||
);
|
||||
|
|
@ -556,6 +585,9 @@ export default function ProjectDetailPage() {
|
|||
setEditingBoardInfo(toDistributionBoardInfoDraft(board));
|
||||
setEditingFeedingBoardId(board.feedingDistributionBoardId ?? "");
|
||||
setEditingFeedingCircuitId(board.feedingCircuitId ?? "");
|
||||
setIsCreatingFeederCircuit(false);
|
||||
setEditingFeederCircuitSectionId("");
|
||||
setEditingFeederCircuitDisplayName("");
|
||||
}
|
||||
|
||||
function openBoardCreator() {
|
||||
|
|
@ -584,28 +616,56 @@ export default function ProjectDetailPage() {
|
|||
setIsSaving(true);
|
||||
setError(null);
|
||||
try {
|
||||
const result = await updateDistributionBoard(
|
||||
projectId,
|
||||
editingBoard.id,
|
||||
{
|
||||
const boardInput = {
|
||||
floorId: editingBoardFloorId || null,
|
||||
supplyType: editingBoardSupplyType,
|
||||
simultaneityFactor,
|
||||
feedingDistributionBoardId: editingFeedingBoardId || null,
|
||||
feedingCircuitId: editingFeedingCircuitId || null,
|
||||
...fromDistributionBoardInfoDraft(editingBoardInfo),
|
||||
};
|
||||
let result;
|
||||
if (isCreatingFeederCircuit) {
|
||||
if (!editingFeedingBoardId || !selectedFeedingTree) {
|
||||
throw new Error("Bitte eine speisende Verteilung wählen.");
|
||||
}
|
||||
if (!selectedNewFeederCircuitSection) {
|
||||
throw new Error(
|
||||
"Bitte eine Stromkreisgruppe für den neuen Abgang wählen."
|
||||
);
|
||||
}
|
||||
const circuit = buildFeederCircuitSnapshot({
|
||||
id: createClientId(),
|
||||
tree: selectedFeedingTree,
|
||||
sectionId: selectedNewFeederCircuitSection.id,
|
||||
displayName: editingFeederCircuitDisplayName,
|
||||
});
|
||||
result = await createFeederCircuitAndUpdateDistributionBoard(
|
||||
projectId,
|
||||
editingBoard.id,
|
||||
{
|
||||
...boardInput,
|
||||
feedingDistributionBoardId: editingFeedingBoardId,
|
||||
feedingCircuitId: circuit.id,
|
||||
},
|
||||
circuit,
|
||||
project.currentRevision
|
||||
);
|
||||
setBoards((current) =>
|
||||
current.map((board) =>
|
||||
board.id === result.distributionBoard.id
|
||||
? result.distributionBoard
|
||||
: board
|
||||
)
|
||||
} else {
|
||||
result = await updateDistributionBoard(
|
||||
projectId,
|
||||
editingBoard.id,
|
||||
boardInput,
|
||||
project.currentRevision
|
||||
);
|
||||
}
|
||||
applyProjectRevision(result.history.currentRevision);
|
||||
setCircuitTrees((await getProjectCircuitTrees(projectId)).trees);
|
||||
const [updatedBoards, updatedTrees] = await Promise.all([
|
||||
listDistributionBoards(projectId),
|
||||
getProjectCircuitTrees(projectId),
|
||||
]);
|
||||
setBoards(updatedBoards);
|
||||
setCircuitTrees(updatedTrees.trees);
|
||||
setEditingBoard(null);
|
||||
} catch (err) {
|
||||
setError(
|
||||
|
|
@ -1567,8 +1627,16 @@ export default function ProjectDetailPage() {
|
|||
className="form-select"
|
||||
id="feeding-distribution-board"
|
||||
onChange={(event) => {
|
||||
setEditingFeedingBoardId(event.target.value);
|
||||
const boardId = event.target.value;
|
||||
setEditingFeedingBoardId(boardId);
|
||||
setEditingFeedingCircuitId("");
|
||||
setIsCreatingFeederCircuit(false);
|
||||
setEditingFeederCircuitSectionId(
|
||||
getDefaultFeederCircuitSection(
|
||||
circuitTreeByBoardId.get(boardId)
|
||||
)?.id ?? ""
|
||||
);
|
||||
setEditingFeederCircuitDisplayName("");
|
||||
}}
|
||||
value={editingFeedingBoardId}
|
||||
>
|
||||
|
|
@ -1588,10 +1656,31 @@ export default function ProjectDetailPage() {
|
|||
className="form-select"
|
||||
disabled={!editingFeedingBoardId}
|
||||
id="feeding-circuit"
|
||||
onChange={(event) => setEditingFeedingCircuitId(event.target.value)}
|
||||
value={editingFeedingCircuitId}
|
||||
onChange={(event) => {
|
||||
if (event.target.value === createFeederCircuitSelectValue) {
|
||||
setEditingFeedingCircuitId("");
|
||||
setIsCreatingFeederCircuit(true);
|
||||
setEditingFeederCircuitSectionId(
|
||||
(current) =>
|
||||
current ||
|
||||
getDefaultFeederCircuitSection(selectedFeedingTree)?.id ||
|
||||
""
|
||||
);
|
||||
return;
|
||||
}
|
||||
setIsCreatingFeederCircuit(false);
|
||||
setEditingFeedingCircuitId(event.target.value);
|
||||
}}
|
||||
value={
|
||||
isCreatingFeederCircuit
|
||||
? createFeederCircuitSelectValue
|
||||
: editingFeedingCircuitId
|
||||
}
|
||||
>
|
||||
<option value="">Nur Verteilung bekannt</option>
|
||||
<option value={createFeederCircuitSelectValue}>
|
||||
+ Neuen Einspeiseabgang anlegen …
|
||||
</option>
|
||||
{selectedFeedingCircuits.map(({ circuit }) => (
|
||||
<option key={circuit.id} value={circuit.id}>
|
||||
{circuit.equipmentIdentifier}
|
||||
|
|
@ -1600,7 +1689,62 @@ export default function ProjectDetailPage() {
|
|||
))}
|
||||
</select>
|
||||
</div>
|
||||
{editingFeedingBoardId && !editingFeedingCircuitId ? (
|
||||
{isCreatingFeederCircuit ? (
|
||||
<div className="col-12">
|
||||
<div className="border rounded p-3">
|
||||
<div className="row g-3">
|
||||
<div className="col-12 col-md-6">
|
||||
<label className="form-label" htmlFor="new-feeder-circuit-section">
|
||||
Stromkreisgruppe
|
||||
</label>
|
||||
<select
|
||||
className="form-select"
|
||||
id="new-feeder-circuit-section"
|
||||
onChange={(event) =>
|
||||
setEditingFeederCircuitSectionId(event.target.value)
|
||||
}
|
||||
required
|
||||
value={editingFeederCircuitSectionId}
|
||||
>
|
||||
{feederCircuitSections.map((section) => (
|
||||
<option key={section.id} value={section.id}>
|
||||
{section.displayName}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<div className="form-text">
|
||||
Vorgesehenes BMK: {newFeederCircuitIdentifier ?? "–"}
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-12 col-md-6">
|
||||
<label className="form-label" htmlFor="new-feeder-circuit-name">
|
||||
Abgangsbezeichnung (optional)
|
||||
</label>
|
||||
<input
|
||||
className="form-control"
|
||||
id="new-feeder-circuit-name"
|
||||
onChange={(event) =>
|
||||
setEditingFeederCircuitDisplayName(event.target.value)
|
||||
}
|
||||
placeholder="z. B. Steigetrasse Ost"
|
||||
value={editingFeederCircuitDisplayName}
|
||||
/>
|
||||
<div className="form-text">
|
||||
Ohne Zusatzbezeichnung wird der Name der gespeisten
|
||||
Verteilung angezeigt.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-text mt-2">
|
||||
Der Abgang wird erst beim Speichern angelegt und gemeinsam
|
||||
mit dieser Verteilung verknüpft. Es entsteht keine Gerätezeile.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
{editingFeedingBoardId &&
|
||||
!editingFeedingCircuitId &&
|
||||
!isCreatingFeederCircuit ? (
|
||||
<div className="col-12">
|
||||
<div className="alert alert-warning mb-0 py-2">
|
||||
Ohne speisenden Abgang wird die Hierarchie angezeigt, die
|
||||
|
|
@ -1618,8 +1762,8 @@ export default function ProjectDetailPage() {
|
|||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
{selectedFeedingCircuit &&
|
||||
selectedFeedingCircuit.section.category !== "three_phase" ? (
|
||||
{selectedFeedingCircuitCategory !== undefined &&
|
||||
selectedFeedingCircuitCategory !== "three_phase" ? (
|
||||
<div className="col-12">
|
||||
<div className="alert alert-warning mb-0 py-2">
|
||||
Der gewählte Abgang ist 1-phasig. Bitte prüfen, ob diese
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ export class CircuitStructureProjectCommandRepository
|
|||
this.database,
|
||||
input,
|
||||
(tx) =>
|
||||
this.applyCommand(
|
||||
this.applyInTransaction(
|
||||
tx,
|
||||
input.projectId,
|
||||
input.source,
|
||||
|
|
@ -49,7 +49,11 @@ export class CircuitStructureProjectCommandRepository
|
|||
);
|
||||
}
|
||||
|
||||
private applyCommand(
|
||||
/**
|
||||
* Applies the circuit write inside an already-owned project transaction.
|
||||
* Composite project commands use this without creating a second revision.
|
||||
*/
|
||||
applyInTransaction(
|
||||
database: AppDatabase,
|
||||
projectId: string,
|
||||
source: ExecuteCircuitStructureCommandInput["source"],
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { and, asc, eq, inArray } from "drizzle-orm";
|
||||
import { isDeepStrictEqual } from "node:util";
|
||||
import {
|
||||
assertDistributionBoardDeleteProjectCommand,
|
||||
assertDistributionBoardInsertProjectCommand,
|
||||
|
|
@ -17,6 +18,20 @@ import {
|
|||
type DistributionBoardUpdateProjectCommand,
|
||||
type DistributionBoardUpdateValues,
|
||||
} from "../../domain/models/distribution-board-project-command.model.js";
|
||||
import {
|
||||
assertDistributionBoardFeederCircuitProjectCommand,
|
||||
createDistributionBoardCreateFeederCircuitProjectCommand,
|
||||
createDistributionBoardRemoveFeederCircuitProjectCommand,
|
||||
distributionBoardCreateFeederCircuitCommandType,
|
||||
distributionBoardRemoveFeederCircuitCommandType,
|
||||
type DistributionBoardFeederCircuitProjectCommand,
|
||||
} from "../../domain/models/distribution-board-feeder-circuit-project-command.model.js";
|
||||
import {
|
||||
circuitDeleteCommandType,
|
||||
circuitInsertCommandType,
|
||||
createCircuitDeleteProjectCommand,
|
||||
createCircuitInsertProjectCommand,
|
||||
} from "../../domain/models/circuit-structure-project-command.model.js";
|
||||
import type {
|
||||
DistributionBoardStructureProjectCommandStore,
|
||||
ExecuteDistributionBoardStructureCommandInput,
|
||||
|
|
@ -31,6 +46,7 @@ import { floors } from "../schema/floors.js";
|
|||
import { projects } from "../schema/projects.js";
|
||||
import { executeProjectCommandTransaction } from "./project-command-transaction.persistence.js";
|
||||
import { assertBoardsAreNotReferencedAsFeeders } from "./distribution-board-feeding-deletion-guard.persistence.js";
|
||||
import { CircuitStructureProjectCommandRepository } from "./circuit-structure-project-command.repository.js";
|
||||
|
||||
export class DistributionBoardStructureProjectCommandRepository
|
||||
implements DistributionBoardStructureProjectCommandStore
|
||||
|
|
@ -62,6 +78,30 @@ export class DistributionBoardStructureProjectCommandRepository
|
|||
);
|
||||
}
|
||||
|
||||
executeFeederCircuit(
|
||||
input: Omit<
|
||||
ExecuteDistributionBoardStructureCommandInput,
|
||||
"command"
|
||||
> & {
|
||||
command: DistributionBoardFeederCircuitProjectCommand;
|
||||
}
|
||||
) {
|
||||
assertDistributionBoardFeederCircuitProjectCommand(input.command);
|
||||
if (
|
||||
input.source === "user" &&
|
||||
input.command.type === distributionBoardRemoveFeederCircuitCommandType
|
||||
) {
|
||||
throw new Error(
|
||||
"Generated feeder circuits may only be removed through project history."
|
||||
);
|
||||
}
|
||||
return executeProjectCommandTransaction(
|
||||
this.database,
|
||||
input,
|
||||
(tx) => this.applyFeederCircuitCommand(tx, input)
|
||||
);
|
||||
}
|
||||
|
||||
private applyCommand(
|
||||
database: AppDatabase,
|
||||
projectId: string,
|
||||
|
|
@ -367,6 +407,64 @@ export class DistributionBoardStructureProjectCommandRepository
|
|||
return inverse;
|
||||
}
|
||||
|
||||
private applyFeederCircuitCommand(
|
||||
database: AppDatabase,
|
||||
input: Omit<
|
||||
ExecuteDistributionBoardStructureCommandInput,
|
||||
"command"
|
||||
> & {
|
||||
command: DistributionBoardFeederCircuitProjectCommand;
|
||||
}
|
||||
): DistributionBoardFeederCircuitProjectCommand {
|
||||
const circuitRepository =
|
||||
new CircuitStructureProjectCommandRepository(database);
|
||||
const { circuit, boardUpdate } = input.command.payload;
|
||||
if (
|
||||
input.command.type === distributionBoardCreateFeederCircuitCommandType
|
||||
) {
|
||||
const circuitInverse = circuitRepository.applyInTransaction(
|
||||
database,
|
||||
input.projectId,
|
||||
input.source,
|
||||
createCircuitInsertProjectCommand(circuit)
|
||||
);
|
||||
if (circuitInverse.type !== circuitDeleteCommandType) {
|
||||
throw new Error("Created feeder circuit returned an invalid inverse.");
|
||||
}
|
||||
const boardInverse = this.update(database, {
|
||||
...input,
|
||||
command: boardUpdate,
|
||||
});
|
||||
return createDistributionBoardRemoveFeederCircuitProjectCommand(
|
||||
circuit,
|
||||
boardInverse
|
||||
);
|
||||
}
|
||||
|
||||
const boardInverse = this.update(database, {
|
||||
...input,
|
||||
command: boardUpdate,
|
||||
});
|
||||
const circuitInverse = circuitRepository.applyInTransaction(
|
||||
database,
|
||||
input.projectId,
|
||||
input.source,
|
||||
createCircuitDeleteProjectCommand(circuit.id, circuit.circuitListId)
|
||||
);
|
||||
if (
|
||||
circuitInverse.type !== circuitInsertCommandType ||
|
||||
!isDeepStrictEqual(circuitInverse.payload.circuit, circuit)
|
||||
) {
|
||||
throw new Error(
|
||||
"Generated feeder circuit changed before history removal."
|
||||
);
|
||||
}
|
||||
return createDistributionBoardCreateFeederCircuitProjectCommand(
|
||||
circuit,
|
||||
boardInverse
|
||||
);
|
||||
}
|
||||
|
||||
private assertFeedingReference(
|
||||
database: AppDatabase,
|
||||
projectId: string,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,132 @@
|
|||
import {
|
||||
assertCircuitInsertProjectCommand,
|
||||
circuitInsertCommandType,
|
||||
circuitStructureCommandSchemaVersion,
|
||||
type CircuitSnapshot,
|
||||
} from "./circuit-structure-project-command.model.js";
|
||||
import {
|
||||
assertDistributionBoardUpdateProjectCommand,
|
||||
type DistributionBoardUpdateProjectCommand,
|
||||
} from "./distribution-board-project-command.model.js";
|
||||
import type { SerializedProjectCommand } from "./project-command.model.js";
|
||||
|
||||
export const distributionBoardCreateFeederCircuitCommandType =
|
||||
"distribution-board.create-feeder-circuit" as const;
|
||||
export const distributionBoardRemoveFeederCircuitCommandType =
|
||||
"distribution-board.remove-feeder-circuit" as const;
|
||||
export const distributionBoardFeederCircuitCommandSchemaVersion = 1 as const;
|
||||
|
||||
export interface DistributionBoardFeederCircuitCommandPayload {
|
||||
circuit: CircuitSnapshot;
|
||||
boardUpdate: DistributionBoardUpdateProjectCommand;
|
||||
}
|
||||
|
||||
export interface DistributionBoardCreateFeederCircuitProjectCommand
|
||||
extends SerializedProjectCommand<DistributionBoardFeederCircuitCommandPayload> {
|
||||
schemaVersion: typeof distributionBoardFeederCircuitCommandSchemaVersion;
|
||||
type: typeof distributionBoardCreateFeederCircuitCommandType;
|
||||
}
|
||||
|
||||
export interface DistributionBoardRemoveFeederCircuitProjectCommand
|
||||
extends SerializedProjectCommand<DistributionBoardFeederCircuitCommandPayload> {
|
||||
schemaVersion: typeof distributionBoardFeederCircuitCommandSchemaVersion;
|
||||
type: typeof distributionBoardRemoveFeederCircuitCommandType;
|
||||
}
|
||||
|
||||
export type DistributionBoardFeederCircuitProjectCommand =
|
||||
| DistributionBoardCreateFeederCircuitProjectCommand
|
||||
| DistributionBoardRemoveFeederCircuitProjectCommand;
|
||||
|
||||
export function createDistributionBoardCreateFeederCircuitProjectCommand(
|
||||
circuit: CircuitSnapshot,
|
||||
boardUpdate: DistributionBoardUpdateProjectCommand
|
||||
): DistributionBoardCreateFeederCircuitProjectCommand {
|
||||
const command: DistributionBoardCreateFeederCircuitProjectCommand = {
|
||||
schemaVersion: distributionBoardFeederCircuitCommandSchemaVersion,
|
||||
type: distributionBoardCreateFeederCircuitCommandType,
|
||||
payload: { circuit, boardUpdate },
|
||||
};
|
||||
assertDistributionBoardFeederCircuitProjectCommand(command);
|
||||
return command;
|
||||
}
|
||||
|
||||
export function createDistributionBoardRemoveFeederCircuitProjectCommand(
|
||||
circuit: CircuitSnapshot,
|
||||
boardUpdate: DistributionBoardUpdateProjectCommand
|
||||
): DistributionBoardRemoveFeederCircuitProjectCommand {
|
||||
const command: DistributionBoardRemoveFeederCircuitProjectCommand = {
|
||||
schemaVersion: distributionBoardFeederCircuitCommandSchemaVersion,
|
||||
type: distributionBoardRemoveFeederCircuitCommandType,
|
||||
payload: { circuit, boardUpdate },
|
||||
};
|
||||
assertDistributionBoardFeederCircuitProjectCommand(command);
|
||||
return command;
|
||||
}
|
||||
|
||||
export function assertDistributionBoardFeederCircuitProjectCommand(
|
||||
command: SerializedProjectCommand<unknown>
|
||||
): asserts command is DistributionBoardFeederCircuitProjectCommand {
|
||||
if (
|
||||
command.schemaVersion !==
|
||||
distributionBoardFeederCircuitCommandSchemaVersion ||
|
||||
(command.type !== distributionBoardCreateFeederCircuitCommandType &&
|
||||
command.type !== distributionBoardRemoveFeederCircuitCommandType) ||
|
||||
!isPlainObject(command.payload)
|
||||
) {
|
||||
throw new Error("Unsupported distribution-board feeder-circuit command.");
|
||||
}
|
||||
|
||||
const { circuit, boardUpdate } = command.payload;
|
||||
if (!isPlainObject(boardUpdate)) {
|
||||
throw new Error("Feeder-circuit command requires a board update.");
|
||||
}
|
||||
assertCircuitInsertProjectCommand({
|
||||
schemaVersion: circuitStructureCommandSchemaVersion,
|
||||
type: circuitInsertCommandType,
|
||||
payload: { circuit },
|
||||
});
|
||||
const boardUpdateCommand =
|
||||
boardUpdate as unknown as SerializedProjectCommand<unknown>;
|
||||
assertDistributionBoardUpdateProjectCommand(boardUpdateCommand);
|
||||
|
||||
const typedCircuit = circuit as CircuitSnapshot;
|
||||
const typedBoardUpdate: DistributionBoardUpdateProjectCommand =
|
||||
boardUpdateCommand;
|
||||
if (typedCircuit.deviceRows.length !== 0 || !typedCircuit.isReserve) {
|
||||
throw new Error(
|
||||
"A generated feeder circuit must be empty before the board load is linked."
|
||||
);
|
||||
}
|
||||
const feedingBoard = getChangeValue(
|
||||
typedBoardUpdate,
|
||||
"feedingDistributionBoardId"
|
||||
);
|
||||
const feedingCircuit = getChangeValue(
|
||||
typedBoardUpdate,
|
||||
"feedingCircuitId"
|
||||
);
|
||||
if (feedingBoard === undefined || feedingCircuit === undefined) {
|
||||
throw new Error(
|
||||
"Feeder-circuit commands must update both logical feeding references."
|
||||
);
|
||||
}
|
||||
if (
|
||||
command.type === distributionBoardCreateFeederCircuitCommandType &&
|
||||
(typeof feedingBoard !== "string" || feedingCircuit !== typedCircuit.id)
|
||||
) {
|
||||
throw new Error(
|
||||
"The created feeder circuit must be linked by the distribution-board update."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function getChangeValue(
|
||||
command: DistributionBoardUpdateProjectCommand,
|
||||
field: "feedingDistributionBoardId" | "feedingCircuitId"
|
||||
) {
|
||||
return command.payload.changes.find((change) => change.field === field)?.value;
|
||||
}
|
||||
|
||||
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
||||
return value !== null && typeof value === "object" && !Array.isArray(value);
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import type { DistributionBoardStructureProjectCommand } from "../models/distribution-board-structure-project-command.model.js";
|
||||
import type { DistributionBoardUpdateProjectCommand } from "../models/distribution-board-project-command.model.js";
|
||||
import type { DistributionBoardFeederCircuitProjectCommand } from "../models/distribution-board-feeder-circuit-project-command.model.js";
|
||||
import type {
|
||||
AppendedProjectRevision,
|
||||
ProjectRevisionSource,
|
||||
|
|
@ -35,4 +36,15 @@ export interface DistributionBoardStructureProjectCommandStore {
|
|||
revision: AppendedProjectRevision;
|
||||
inverse: DistributionBoardUpdateProjectCommand;
|
||||
};
|
||||
executeFeederCircuit(
|
||||
input: Omit<
|
||||
ExecuteDistributionBoardStructureCommandInput,
|
||||
"command"
|
||||
> & {
|
||||
command: DistributionBoardFeederCircuitProjectCommand;
|
||||
}
|
||||
): {
|
||||
revision: AppendedProjectRevision;
|
||||
inverse: DistributionBoardFeederCircuitProjectCommand;
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,6 +45,11 @@ import {
|
|||
assertDistributionBoardUpdateProjectCommand,
|
||||
distributionBoardUpdateCommandType,
|
||||
} from "../models/distribution-board-project-command.model.js";
|
||||
import {
|
||||
assertDistributionBoardFeederCircuitProjectCommand,
|
||||
distributionBoardCreateFeederCircuitCommandType,
|
||||
distributionBoardRemoveFeederCircuitCommandType,
|
||||
} from "../models/distribution-board-feeder-circuit-project-command.model.js";
|
||||
import {
|
||||
assertDistributionBoardComponentDeleteProjectCommand,
|
||||
assertDistributionBoardComponentInsertProjectCommand,
|
||||
|
|
@ -418,6 +423,14 @@ export class ProjectCommandService implements ProjectCommandExecutor {
|
|||
command: input.command,
|
||||
}).revision;
|
||||
}
|
||||
case distributionBoardCreateFeederCircuitCommandType:
|
||||
case distributionBoardRemoveFeederCircuitCommandType: {
|
||||
assertDistributionBoardFeederCircuitProjectCommand(input.command);
|
||||
return this.distributionBoardStructureStore.executeFeederCircuit({
|
||||
...input,
|
||||
command: input.command,
|
||||
}).revision;
|
||||
}
|
||||
case distributionBoardComponentInsertCommandType: {
|
||||
assertDistributionBoardComponentInsertProjectCommand(
|
||||
input.command
|
||||
|
|
|
|||
|
|
@ -366,20 +366,22 @@ export function createDistributionBoard(
|
|||
);
|
||||
}
|
||||
|
||||
export function updateDistributionBoard(
|
||||
projectId: string,
|
||||
distributionBoardId: string,
|
||||
input: {
|
||||
export type UpdateDistributionBoardInput = {
|
||||
floorId: string | null;
|
||||
supplyType: NonNullable<DistributionBoardDto["supplyType"]>;
|
||||
simultaneityFactor: number;
|
||||
} & Partial<DistributionBoardInfoDto> &
|
||||
} & Partial<DistributionBoardInfoDto> &
|
||||
Partial<
|
||||
Pick<
|
||||
DistributionBoardDto,
|
||||
"feedingDistributionBoardId" | "feedingCircuitId"
|
||||
>
|
||||
>,
|
||||
>;
|
||||
|
||||
export function updateDistributionBoard(
|
||||
projectId: string,
|
||||
distributionBoardId: string,
|
||||
input: UpdateDistributionBoardInput,
|
||||
expectedRevision: number
|
||||
) {
|
||||
return request<DistributionBoardCommandResultDto>(
|
||||
|
|
@ -391,6 +393,41 @@ export function updateDistributionBoard(
|
|||
);
|
||||
}
|
||||
|
||||
export function createFeederCircuitAndUpdateDistributionBoard(
|
||||
projectId: string,
|
||||
distributionBoardId: string,
|
||||
input: UpdateDistributionBoardInput & {
|
||||
feedingDistributionBoardId: string;
|
||||
feedingCircuitId: string;
|
||||
},
|
||||
circuit: CircuitSnapshot,
|
||||
expectedRevision: number
|
||||
) {
|
||||
return executeProjectCommand(
|
||||
projectId,
|
||||
expectedRevision,
|
||||
{
|
||||
schemaVersion: 1,
|
||||
type: "distribution-board.create-feeder-circuit",
|
||||
payload: {
|
||||
circuit,
|
||||
boardUpdate: {
|
||||
schemaVersion: 2,
|
||||
type: "distribution-board.update",
|
||||
payload: {
|
||||
distributionBoardId,
|
||||
changes: Object.entries(input).map(([field, value]) => ({
|
||||
field,
|
||||
value,
|
||||
})),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"Einspeiseabgang anlegen und Verteilung verknüpfen"
|
||||
);
|
||||
}
|
||||
|
||||
export const documentLogoUrl = "/api/document-branding/logo";
|
||||
|
||||
export function getDocumentBranding() {
|
||||
|
|
|
|||
68
src/frontend/utils/distribution-board-feeder-circuit.ts
Normal file
68
src/frontend/utils/distribution-board-feeder-circuit.ts
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
import type { CircuitSnapshot } from "../../domain/models/circuit-structure-project-command.model.js";
|
||||
import type {
|
||||
CircuitTreeResponseDto,
|
||||
CircuitTreeSectionDto,
|
||||
} from "../types";
|
||||
import { buildCircuitInsertSnapshot } from "./circuit-structure-command";
|
||||
|
||||
export function getDefaultFeederCircuitSection(
|
||||
tree: CircuitTreeResponseDto | undefined
|
||||
) {
|
||||
if (!tree) return undefined;
|
||||
return (
|
||||
tree.sections.find((section) => section.category === "three_phase") ??
|
||||
tree.sections.find((section) => section.category !== undefined)
|
||||
);
|
||||
}
|
||||
|
||||
export function getNextFeederCircuitIdentifier(
|
||||
section: CircuitTreeSectionDto
|
||||
) {
|
||||
let highestSuffix = 0;
|
||||
for (const circuit of section.circuits) {
|
||||
if (!circuit.equipmentIdentifier.startsWith(section.prefix)) continue;
|
||||
const suffix = circuit.equipmentIdentifier.slice(section.prefix.length);
|
||||
if (!/^\d+$/.test(suffix)) continue;
|
||||
highestSuffix = Math.max(highestSuffix, Number(suffix));
|
||||
}
|
||||
return `${section.prefix}${highestSuffix + 1}`;
|
||||
}
|
||||
|
||||
export function buildFeederCircuitSnapshot(input: {
|
||||
id: string;
|
||||
tree: CircuitTreeResponseDto;
|
||||
sectionId: string;
|
||||
displayName: string;
|
||||
}): CircuitSnapshot {
|
||||
const section = input.tree.sections.find(
|
||||
(candidate) => candidate.id === input.sectionId
|
||||
);
|
||||
if (!section || !section.category) {
|
||||
throw new Error("Die gewählte Stromkreisgruppe ist nicht verfügbar.");
|
||||
}
|
||||
const snapshot = buildCircuitInsertSnapshot({
|
||||
id: input.id,
|
||||
circuitListId: input.tree.circuitListId,
|
||||
category: section.category,
|
||||
deviceRows: [],
|
||||
values: {
|
||||
sectionId: section.id,
|
||||
equipmentIdentifier: getNextFeederCircuitIdentifier(section),
|
||||
displayName: input.displayName.trim() || undefined,
|
||||
sortOrder:
|
||||
section.circuits.length === 0
|
||||
? 10
|
||||
: Math.max(...section.circuits.map((circuit) => circuit.sortOrder)) +
|
||||
10,
|
||||
voltage:
|
||||
section.category === "three_phase"
|
||||
? input.tree.threePhaseVoltageV
|
||||
: input.tree.singlePhaseVoltageV,
|
||||
isReserve: true,
|
||||
},
|
||||
});
|
||||
// A distribution-board feeder is not a generic final circuit. Its protection
|
||||
// must be selected from the calculated load and cable, so no 16 A group
|
||||
// default is persisted here.
|
||||
return { ...snapshot, protectionDevice: null };
|
||||
}
|
||||
|
|
@ -32,6 +32,10 @@ const commandTypeLabels: Record<string, string> = {
|
|||
"project.update-settings": "Projekteinstellungen bearbeitet",
|
||||
"distribution-board.insert": "Verteilung angelegt",
|
||||
"distribution-board.update": "Verteilung bearbeitet",
|
||||
"distribution-board.create-feeder-circuit":
|
||||
"Einspeiseabgang angelegt und verknüpft",
|
||||
"distribution-board.remove-feeder-circuit":
|
||||
"Einspeiseabgang und Verknüpfung entfernt",
|
||||
"distribution-board.delete": "Verteilung entfernt",
|
||||
"distribution-board.insert-subtree": "Verteilung vollständig eingefügt",
|
||||
"distribution-board.delete-subtree": "Verteilung vollständig entfernt",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,240 @@
|
|||
import path from "node:path";
|
||||
import assert from "node:assert/strict";
|
||||
import { describe, it } from "node:test";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { migrate } from "drizzle-orm/better-sqlite3/migrator";
|
||||
import {
|
||||
createDatabaseContext,
|
||||
type DatabaseContext,
|
||||
} from "../src/db/database-context.js";
|
||||
import { DistributionBoardStructureProjectCommandRepository } from "../src/db/repositories/distribution-board-structure-project-command.repository.js";
|
||||
import { ProjectHistoryRepository } from "../src/db/repositories/project-history.repository.js";
|
||||
import { circuits } from "../src/db/schema/circuits.js";
|
||||
import { distributionBoards } from "../src/db/schema/distribution-boards.js";
|
||||
import { projects } from "../src/db/schema/projects.js";
|
||||
import {
|
||||
assertDistributionBoardFeederCircuitProjectCommand,
|
||||
createDistributionBoardCreateFeederCircuitProjectCommand,
|
||||
} from "../src/domain/models/distribution-board-feeder-circuit-project-command.model.js";
|
||||
import { createDistributionBoardUpdateProjectCommand } from "../src/domain/models/distribution-board-project-command.model.js";
|
||||
import {
|
||||
createDistributionBoardInsertProjectCommand,
|
||||
createDistributionBoardStructureSnapshot,
|
||||
} from "../src/domain/models/distribution-board-structure-project-command.model.js";
|
||||
import { buildCircuitInsertSnapshot } from "../src/frontend/utils/circuit-structure-command.js";
|
||||
|
||||
function createTestDatabase(): DatabaseContext {
|
||||
const context = createDatabaseContext(":memory:");
|
||||
migrate(context.db, {
|
||||
migrationsFolder: path.resolve("src", "db", "migrations"),
|
||||
});
|
||||
context.db
|
||||
.insert(projects)
|
||||
.values({ id: "project-1", name: "Test project" })
|
||||
.run();
|
||||
return context;
|
||||
}
|
||||
|
||||
function setupBoards(
|
||||
context: DatabaseContext,
|
||||
repository: DistributionBoardStructureProjectCommandRepository
|
||||
) {
|
||||
const source = createDistributionBoardStructureSnapshot(
|
||||
"project-1",
|
||||
"NSHV"
|
||||
);
|
||||
const downstream = createDistributionBoardStructureSnapshot(
|
||||
"project-1",
|
||||
"UV EG"
|
||||
);
|
||||
repository.execute({
|
||||
projectId: "project-1",
|
||||
expectedRevision: 0,
|
||||
source: "user",
|
||||
command: createDistributionBoardInsertProjectCommand(source),
|
||||
});
|
||||
repository.execute({
|
||||
projectId: "project-1",
|
||||
expectedRevision: 1,
|
||||
source: "user",
|
||||
command: createDistributionBoardInsertProjectCommand(downstream),
|
||||
});
|
||||
const section = source.sections.find(
|
||||
(candidate) => candidate.key === "three_phase"
|
||||
)!;
|
||||
const circuit = {
|
||||
...buildCircuitInsertSnapshot({
|
||||
id: "feeder-circuit",
|
||||
circuitListId: source.circuitList.id,
|
||||
category: "three_phase",
|
||||
deviceRows: [],
|
||||
values: {
|
||||
sectionId: section.id,
|
||||
equipmentIdentifier: `${section.prefix}1`,
|
||||
displayName: undefined,
|
||||
sortOrder: 10,
|
||||
voltage: 400,
|
||||
isReserve: true,
|
||||
},
|
||||
}),
|
||||
protectionDevice: null,
|
||||
};
|
||||
const boardUpdate = createDistributionBoardUpdateProjectCommand(
|
||||
downstream.distributionBoard.id,
|
||||
{
|
||||
feedingDistributionBoardId: source.distributionBoard.id,
|
||||
feedingCircuitId: circuit.id,
|
||||
}
|
||||
);
|
||||
const command =
|
||||
createDistributionBoardCreateFeederCircuitProjectCommand(
|
||||
circuit,
|
||||
boardUpdate
|
||||
);
|
||||
return { source, downstream, circuit, command };
|
||||
}
|
||||
|
||||
describe("distribution-board feeder-circuit project command", () => {
|
||||
it("creates and links one feeder atomically and supports undo and redo", () => {
|
||||
const context = createTestDatabase();
|
||||
try {
|
||||
const repository =
|
||||
new DistributionBoardStructureProjectCommandRepository(context.db);
|
||||
const history = new ProjectHistoryRepository(context.db);
|
||||
const { source, downstream, circuit, command } = setupBoards(
|
||||
context,
|
||||
repository
|
||||
);
|
||||
|
||||
repository.executeFeederCircuit({
|
||||
projectId: "project-1",
|
||||
expectedRevision: 2,
|
||||
source: "user",
|
||||
command,
|
||||
});
|
||||
assert.ok(
|
||||
context.db
|
||||
.select()
|
||||
.from(circuits)
|
||||
.where(eq(circuits.id, circuit.id))
|
||||
.get()
|
||||
);
|
||||
assert.deepEqual(
|
||||
context.db
|
||||
.select({
|
||||
boardId: distributionBoards.feedingDistributionBoardId,
|
||||
circuitId: distributionBoards.feedingCircuitId,
|
||||
})
|
||||
.from(distributionBoards)
|
||||
.where(eq(distributionBoards.id, downstream.distributionBoard.id))
|
||||
.get(),
|
||||
{
|
||||
boardId: source.distributionBoard.id,
|
||||
circuitId: circuit.id,
|
||||
}
|
||||
);
|
||||
|
||||
const undo = history.getNextCommand("project-1", "undo");
|
||||
assert.ok(undo);
|
||||
assertDistributionBoardFeederCircuitProjectCommand(undo.command);
|
||||
repository.executeFeederCircuit({
|
||||
projectId: "project-1",
|
||||
expectedRevision: 3,
|
||||
source: "undo",
|
||||
historyTargetChangeSetId: undo.changeSetId,
|
||||
command: undo.command,
|
||||
});
|
||||
assert.equal(
|
||||
context.db
|
||||
.select()
|
||||
.from(circuits)
|
||||
.where(eq(circuits.id, circuit.id))
|
||||
.get(),
|
||||
undefined
|
||||
);
|
||||
assert.deepEqual(
|
||||
context.db
|
||||
.select({
|
||||
boardId: distributionBoards.feedingDistributionBoardId,
|
||||
circuitId: distributionBoards.feedingCircuitId,
|
||||
})
|
||||
.from(distributionBoards)
|
||||
.where(eq(distributionBoards.id, downstream.distributionBoard.id))
|
||||
.get(),
|
||||
{ boardId: null, circuitId: null }
|
||||
);
|
||||
|
||||
const redo = history.getNextCommand("project-1", "redo");
|
||||
assert.ok(redo);
|
||||
assertDistributionBoardFeederCircuitProjectCommand(redo.command);
|
||||
repository.executeFeederCircuit({
|
||||
projectId: "project-1",
|
||||
expectedRevision: 4,
|
||||
source: "redo",
|
||||
historyTargetChangeSetId: redo.changeSetId,
|
||||
command: redo.command,
|
||||
});
|
||||
assert.equal(
|
||||
context.db
|
||||
.select({ equipmentIdentifier: circuits.equipmentIdentifier })
|
||||
.from(circuits)
|
||||
.where(eq(circuits.id, circuit.id))
|
||||
.get()?.equipmentIdentifier,
|
||||
circuit.equipmentIdentifier
|
||||
);
|
||||
} finally {
|
||||
context.close();
|
||||
}
|
||||
});
|
||||
|
||||
it("rolls back unlinking when the generated circuit changed before undo", () => {
|
||||
const context = createTestDatabase();
|
||||
try {
|
||||
const repository =
|
||||
new DistributionBoardStructureProjectCommandRepository(context.db);
|
||||
const history = new ProjectHistoryRepository(context.db);
|
||||
const { downstream, circuit, command } = setupBoards(context, repository);
|
||||
repository.executeFeederCircuit({
|
||||
projectId: "project-1",
|
||||
expectedRevision: 2,
|
||||
source: "user",
|
||||
command,
|
||||
});
|
||||
context.db
|
||||
.update(circuits)
|
||||
.set({ displayName: "Außerhalb der Historie geändert" })
|
||||
.where(eq(circuits.id, circuit.id))
|
||||
.run();
|
||||
|
||||
const undo = history.getNextCommand("project-1", "undo");
|
||||
assert.ok(undo);
|
||||
assertDistributionBoardFeederCircuitProjectCommand(undo.command);
|
||||
assert.throws(
|
||||
() =>
|
||||
repository.executeFeederCircuit({
|
||||
projectId: "project-1",
|
||||
expectedRevision: 3,
|
||||
source: "undo",
|
||||
historyTargetChangeSetId: undo.changeSetId,
|
||||
command: undo.command,
|
||||
}),
|
||||
/changed before history removal/
|
||||
);
|
||||
const board = context.db
|
||||
.select()
|
||||
.from(distributionBoards)
|
||||
.where(eq(distributionBoards.id, downstream.distributionBoard.id))
|
||||
.get();
|
||||
assert.equal(board?.feedingCircuitId, circuit.id);
|
||||
assert.ok(
|
||||
context.db
|
||||
.select()
|
||||
.from(circuits)
|
||||
.where(eq(circuits.id, circuit.id))
|
||||
.get()
|
||||
);
|
||||
} finally {
|
||||
context.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
168
tests/distribution-board-feeder-circuit.test.ts
Normal file
168
tests/distribution-board-feeder-circuit.test.ts
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
import assert from "node:assert/strict";
|
||||
import { describe, it } from "node:test";
|
||||
import {
|
||||
buildFeederCircuitSnapshot,
|
||||
getDefaultFeederCircuitSection,
|
||||
getNextFeederCircuitIdentifier,
|
||||
} from "../src/frontend/utils/distribution-board-feeder-circuit.js";
|
||||
import type { CircuitTreeResponseDto } from "../src/frontend/types.js";
|
||||
import { createFeederCircuitAndUpdateDistributionBoard } from "../src/frontend/utils/api.js";
|
||||
|
||||
function tree(): CircuitTreeResponseDto {
|
||||
return {
|
||||
circuitListId: "list-main",
|
||||
distributionBoardId: "board-main",
|
||||
distributionBoardName: "NSHV",
|
||||
currentRevision: 2,
|
||||
singlePhaseVoltageV: 230,
|
||||
threePhaseVoltageV: 400,
|
||||
distributionBoardSimultaneityFactor: 1,
|
||||
distributionBoardTotalPower: 0,
|
||||
distributionBoardTotalPowerWithSimultaneityFactor: 0,
|
||||
headerComponents: [],
|
||||
footerComponents: [],
|
||||
sections: [
|
||||
{
|
||||
id: "single",
|
||||
key: "single_phase",
|
||||
category: "single_phase",
|
||||
groupNumber: 1,
|
||||
displayName: "1-phasig 1",
|
||||
prefix: "-2F1.",
|
||||
sortOrder: 10,
|
||||
sectionTotalPower: 0,
|
||||
components: [],
|
||||
circuits: [],
|
||||
},
|
||||
{
|
||||
id: "three",
|
||||
key: "three_phase",
|
||||
category: "three_phase",
|
||||
groupNumber: 1,
|
||||
displayName: "3-phasig 1",
|
||||
prefix: "-3F1.",
|
||||
sortOrder: 20,
|
||||
sectionTotalPower: 0,
|
||||
components: [],
|
||||
circuits: [
|
||||
{
|
||||
id: "existing-2",
|
||||
circuitListId: "list-main",
|
||||
sectionId: "three",
|
||||
equipmentIdentifier: "-3F1.2",
|
||||
sortOrder: 10,
|
||||
isReserve: true,
|
||||
circuitTotalPower: 0,
|
||||
circuitApparentPower: 0,
|
||||
circuitCosPhi: 1,
|
||||
deviceRows: [],
|
||||
downstreamBoardLoads: [],
|
||||
},
|
||||
{
|
||||
id: "existing-5",
|
||||
circuitListId: "list-main",
|
||||
sectionId: "three",
|
||||
equipmentIdentifier: "-3F1.5",
|
||||
sortOrder: 30,
|
||||
isReserve: true,
|
||||
circuitTotalPower: 0,
|
||||
circuitApparentPower: 0,
|
||||
circuitCosPhi: 1,
|
||||
deviceRows: [],
|
||||
downstreamBoardLoads: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
describe("distribution-board feeder circuit planning", () => {
|
||||
it("prefers a three-phase section and does not fill BMK gaps", () => {
|
||||
const input = tree();
|
||||
const section = getDefaultFeederCircuitSection(input)!;
|
||||
assert.equal(section.id, "three");
|
||||
assert.equal(getNextFeederCircuitIdentifier(section), "-3F1.6");
|
||||
});
|
||||
|
||||
it("builds an empty circuit with stable id, optional name and defaults", () => {
|
||||
const input = tree();
|
||||
const snapshot = buildFeederCircuitSnapshot({
|
||||
id: "new-feeder",
|
||||
tree: input,
|
||||
sectionId: "three",
|
||||
displayName: " ",
|
||||
});
|
||||
assert.equal(snapshot.id, "new-feeder");
|
||||
assert.equal(snapshot.equipmentIdentifier, "-3F1.6");
|
||||
assert.equal(snapshot.displayName, null);
|
||||
assert.equal(snapshot.sortOrder, 40);
|
||||
assert.equal(snapshot.voltage, 400);
|
||||
assert.equal(snapshot.isReserve, true);
|
||||
assert.deepEqual(snapshot.deviceRows, []);
|
||||
assert.equal(snapshot.protectionDevice, null);
|
||||
});
|
||||
|
||||
it("sends circuit creation and board linking as one project command", async () => {
|
||||
const input = tree();
|
||||
const circuit = buildFeederCircuitSnapshot({
|
||||
id: "new-feeder",
|
||||
tree: input,
|
||||
sectionId: "three",
|
||||
displayName: "",
|
||||
});
|
||||
const requests: Array<{ url: string; method: string; body: any }> = [];
|
||||
const originalFetch = globalThis.fetch;
|
||||
globalThis.fetch = async (request, init) => {
|
||||
requests.push({
|
||||
url: String(request),
|
||||
method: init?.method ?? "GET",
|
||||
body: JSON.parse(String(init?.body)),
|
||||
});
|
||||
return new Response(JSON.stringify({}), {
|
||||
status: 200,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
};
|
||||
try {
|
||||
await createFeederCircuitAndUpdateDistributionBoard(
|
||||
"project-1",
|
||||
"board-child",
|
||||
{
|
||||
floorId: null,
|
||||
supplyType: "AV",
|
||||
simultaneityFactor: 0.8,
|
||||
feedingDistributionBoardId: "board-main",
|
||||
feedingCircuitId: circuit.id,
|
||||
},
|
||||
circuit,
|
||||
7
|
||||
);
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
}
|
||||
assert.equal(requests.length, 1);
|
||||
assert.equal(requests[0].url, "/api/projects/project-1/commands");
|
||||
assert.equal(requests[0].method, "POST");
|
||||
assert.equal(requests[0].body.expectedRevision, 7);
|
||||
assert.equal(
|
||||
requests[0].body.command.type,
|
||||
"distribution-board.create-feeder-circuit"
|
||||
);
|
||||
assert.equal(requests[0].body.command.payload.circuit.id, "new-feeder");
|
||||
assert.deepEqual(
|
||||
requests[0].body.command.payload.boardUpdate.payload.changes
|
||||
.filter((change: { field: string }) =>
|
||||
change.field.startsWith("feeding")
|
||||
)
|
||||
.map((change: { field: string; value: unknown }) => [
|
||||
change.field,
|
||||
change.value,
|
||||
]),
|
||||
[
|
||||
["feedingDistributionBoardId", "board-main"],
|
||||
["feedingCircuitId", "new-feeder"],
|
||||
]
|
||||
);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue