1287 lines
32 KiB
TypeScript
1287 lines
32 KiB
TypeScript
import type {
|
|
BuildingDto,
|
|
CircuitListDto,
|
|
CreateBuildingInput,
|
|
CreateFloorInput,
|
|
CreateProjectDeviceInput,
|
|
CreateRoomInput,
|
|
CreateGlobalDeviceInput,
|
|
DistributionBoardDto,
|
|
DistributionBoardCommandResultDto,
|
|
DistributionBoardCopyCommandResultDto,
|
|
DistributionBoardDeleteCommandResultDto,
|
|
DistributionBoardInfoDto,
|
|
DocumentLogoMetadataDto,
|
|
FloorDto,
|
|
OfficeProfileDto,
|
|
GlobalDeviceDto,
|
|
ProjectCommandDto,
|
|
ProjectCommandResultDto,
|
|
ProjectDeviceCommandResultDto,
|
|
ProjectDeviceDto,
|
|
ProjectBuildingCommandResultDto,
|
|
ProjectFloorCommandResultDto,
|
|
ProjectHistoryStateDto,
|
|
ProjectRevisionPageDto,
|
|
ProjectSnapshotMetadataDto,
|
|
ProjectSnapshotRestoreResultDto,
|
|
ProjectSettingsCommandResultDto,
|
|
ProjectRoomCommandResultDto,
|
|
ProjectDeviceSyncCommandResultDto,
|
|
ProjectDeviceSyncPreviewDto,
|
|
ExternalCircuitListObjectsDto,
|
|
ProjectDto,
|
|
RoomDto,
|
|
CircuitTreeResponseDto,
|
|
ProjectCircuitTreesResponseDto,
|
|
ExternalCsvConfigurationSnapshotDto,
|
|
ExternalCsvPreviewDto,
|
|
ExternalInitialImportPlanDto,
|
|
ApplyExternalInitialImportInput,
|
|
ExternalInitialImportResultDto,
|
|
CreateEstimateBalanceInput,
|
|
EstimateBalanceCommandResultDto,
|
|
EstimateBalanceDto,
|
|
EstimateBalanceUpdateResultDto,
|
|
EstimateComparisonDto,
|
|
} from "../types";
|
|
import type { ExternalCsvConfiguration } from "../../external-model/csv/external-csv-contracts";
|
|
import type { ProjectDeviceSyncField } from "../../shared/constants/project-device-sync-fields";
|
|
import {
|
|
createCircuitUpdateProjectCommand,
|
|
type CircuitUpdatePatch,
|
|
} from "../../domain/models/circuit-project-command.model";
|
|
import {
|
|
createCircuitDeviceRowUpdateProjectCommand,
|
|
type CircuitDeviceRowUpdatePatch,
|
|
} from "../../domain/models/circuit-device-row-project-command.model";
|
|
import type {
|
|
CircuitDeviceRowSnapshot,
|
|
} from "../../domain/models/circuit-device-row-structure-project-command.model";
|
|
import type {
|
|
CircuitSnapshot,
|
|
} from "../../domain/models/circuit-structure-project-command.model";
|
|
import type {
|
|
CircuitDeviceRowMoveAssignment,
|
|
} from "../../domain/models/circuit-device-row-move-project-command.model";
|
|
import type {
|
|
CircuitSectionReorderAssignment,
|
|
} from "../../domain/models/circuit-section-reorder-project-command.model";
|
|
import type {
|
|
CircuitSectionsReorderSection,
|
|
} from "../../domain/models/circuit-sections-reorder-project-command.model";
|
|
import type {
|
|
CircuitSectionRenumberAssignment,
|
|
} from "../../domain/models/circuit-section-renumber-project-command.model";
|
|
import type {
|
|
DistributionBoardComponentSnapshot,
|
|
} from "../../domain/models/distribution-board-component-structure-project-command.model";
|
|
import type {
|
|
CircuitGroupSnapshot,
|
|
CircuitGroupReorderAssignment,
|
|
} from "../../domain/models/circuit-group-structure-project-command.model";
|
|
import type {
|
|
CircuitProtectionSnapshot,
|
|
} from "../../domain/models/circuit-protection-project-command.model";
|
|
import type {
|
|
CircuitGroupRenumberPlan,
|
|
} from "../../domain/services/circuit-group-renumbering";
|
|
import type {
|
|
CircuitGroupMovePlan,
|
|
} from "../../domain/services/circuit-group-move-planning";
|
|
import type {
|
|
CircuitGroupSubtreeSnapshot,
|
|
} from "../../domain/models/circuit-group-subtree-snapshot.model";
|
|
|
|
/**
|
|
* Error of a failed API request. The message stays the server's German
|
|
* error text; status and optional error code allow callers to react to
|
|
* specific failures such as a revision conflict (409).
|
|
*/
|
|
export class ApiRequestError extends Error {
|
|
readonly status: number;
|
|
readonly code: string | null;
|
|
|
|
constructor(message: string, status: number, code: string | null = null) {
|
|
super(message);
|
|
this.name = "ApiRequestError";
|
|
this.status = status;
|
|
this.code = code;
|
|
}
|
|
}
|
|
|
|
export function isRevisionConflictError(error: unknown) {
|
|
return (
|
|
error instanceof ApiRequestError &&
|
|
(error.status === 409 || error.code === "PROJECT_REVISION_CONFLICT")
|
|
);
|
|
}
|
|
|
|
async function request<T>(url: string, init?: RequestInit): Promise<T> {
|
|
const response = await fetch(url, {
|
|
...init,
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
...init?.headers,
|
|
},
|
|
cache: "no-store",
|
|
});
|
|
|
|
if (!response.ok) {
|
|
let details = await response.text();
|
|
let code: string | null = null;
|
|
try {
|
|
const parsed = JSON.parse(details) as { error?: unknown; code?: unknown };
|
|
if (typeof parsed.error === "string") {
|
|
details = parsed.error;
|
|
}
|
|
if (typeof parsed.code === "string") {
|
|
code = parsed.code;
|
|
}
|
|
} catch {
|
|
// Keep non-JSON error responses unchanged.
|
|
}
|
|
throw new ApiRequestError(
|
|
details || `Anfrage fehlgeschlagen (Status ${response.status})`,
|
|
response.status,
|
|
code
|
|
);
|
|
}
|
|
|
|
if (response.status === 204) {
|
|
return undefined as T;
|
|
}
|
|
|
|
return response.json() as Promise<T>;
|
|
}
|
|
|
|
export function listProjects() {
|
|
return request<ProjectDto[]>("/api/projects");
|
|
}
|
|
|
|
export function getProject(projectId: string) {
|
|
return request<ProjectDto>(`/api/projects/${projectId}`);
|
|
}
|
|
|
|
export function getProjectHistory(projectId: string) {
|
|
return request<ProjectHistoryStateDto>(
|
|
`/api/projects/${projectId}/history`
|
|
);
|
|
}
|
|
|
|
export function listProjectRevisions(
|
|
projectId: string,
|
|
input: { limit?: number; beforeRevision?: number } = {}
|
|
) {
|
|
const query = new URLSearchParams();
|
|
if (input.limit !== undefined) {
|
|
query.set("limit", String(input.limit));
|
|
}
|
|
if (input.beforeRevision !== undefined) {
|
|
query.set("beforeRevision", String(input.beforeRevision));
|
|
}
|
|
const suffix = query.size ? `?${query.toString()}` : "";
|
|
return request<ProjectRevisionPageDto>(
|
|
`/api/projects/${projectId}/history/revisions${suffix}`
|
|
);
|
|
}
|
|
|
|
export function listProjectSnapshots(projectId: string) {
|
|
return request<ProjectSnapshotMetadataDto[]>(
|
|
`/api/projects/${projectId}/snapshots`
|
|
);
|
|
}
|
|
|
|
export function createNamedProjectSnapshot(
|
|
projectId: string,
|
|
expectedRevision: number,
|
|
name: string,
|
|
description?: string
|
|
) {
|
|
return request<ProjectSnapshotMetadataDto>(
|
|
`/api/projects/${projectId}/snapshots`,
|
|
{
|
|
method: "POST",
|
|
body: JSON.stringify({
|
|
expectedRevision,
|
|
name,
|
|
...(description ? { description } : {}),
|
|
}),
|
|
}
|
|
);
|
|
}
|
|
|
|
export function restoreProjectSnapshot(
|
|
projectId: string,
|
|
snapshotId: string,
|
|
expectedRevision: number
|
|
) {
|
|
return request<ProjectSnapshotRestoreResultDto>(
|
|
`/api/projects/${projectId}/snapshots/${snapshotId}/restore`,
|
|
{
|
|
method: "POST",
|
|
body: JSON.stringify({ expectedRevision }),
|
|
}
|
|
);
|
|
}
|
|
|
|
export function executeProjectCommand(
|
|
projectId: string,
|
|
expectedRevision: number,
|
|
command: ProjectCommandDto,
|
|
description?: string
|
|
) {
|
|
return request<ProjectCommandResultDto>(
|
|
`/api/projects/${projectId}/commands`,
|
|
{
|
|
method: "POST",
|
|
body: JSON.stringify({
|
|
expectedRevision,
|
|
command,
|
|
...(description ? { description } : {}),
|
|
}),
|
|
}
|
|
);
|
|
}
|
|
|
|
export function undoProjectCommand(
|
|
projectId: string,
|
|
expectedRevision: number
|
|
) {
|
|
return executeProjectHistoryCommand(
|
|
projectId,
|
|
expectedRevision,
|
|
"undo"
|
|
);
|
|
}
|
|
|
|
export function redoProjectCommand(
|
|
projectId: string,
|
|
expectedRevision: number
|
|
) {
|
|
return executeProjectHistoryCommand(
|
|
projectId,
|
|
expectedRevision,
|
|
"redo"
|
|
);
|
|
}
|
|
|
|
function executeProjectHistoryCommand(
|
|
projectId: string,
|
|
expectedRevision: number,
|
|
direction: "undo" | "redo"
|
|
) {
|
|
return request<ProjectCommandResultDto>(
|
|
`/api/projects/${projectId}/history/${direction}`,
|
|
{
|
|
method: "POST",
|
|
body: JSON.stringify({ expectedRevision }),
|
|
}
|
|
);
|
|
}
|
|
|
|
export function createProject(name: string) {
|
|
return request<ProjectDto>("/api/projects", {
|
|
method: "POST",
|
|
body: JSON.stringify({ name }),
|
|
});
|
|
}
|
|
|
|
export function updateProjectSettings(
|
|
projectId: string,
|
|
expectedRevision: number,
|
|
input: {
|
|
name: string;
|
|
internalProjectNumber: string | null;
|
|
externalProjectNumber: string | null;
|
|
buildingOwner: string | null;
|
|
description: string | null;
|
|
isPublicBuilding: boolean;
|
|
singlePhaseVoltageV: number;
|
|
threePhaseVoltageV: number;
|
|
enabledDistributionBoardSupplyTypes: ProjectDto["enabledDistributionBoardSupplyTypes"];
|
|
} & Partial<Pick<ProjectDto, "siteAddress" | "workStage" | "projectLead" | "projectEditor">>
|
|
) {
|
|
return request<ProjectSettingsCommandResultDto>(
|
|
`/api/projects/${projectId}`,
|
|
{
|
|
method: "PUT",
|
|
body: JSON.stringify({ expectedRevision, ...input }),
|
|
}
|
|
);
|
|
}
|
|
|
|
export function exportProjectTransfer(projectId: string) {
|
|
return request<unknown>(`/api/projects/${projectId}/export`);
|
|
}
|
|
|
|
export function importProjectTransferAsNew(transfer: unknown) {
|
|
return request<{ projectId: string; name: string }>(
|
|
"/api/projects/import",
|
|
{
|
|
method: "POST",
|
|
body: JSON.stringify({ transfer }),
|
|
}
|
|
);
|
|
}
|
|
|
|
export function importProjectTransfer(
|
|
projectId: string,
|
|
mode: "replace" | "duplicate",
|
|
expectedRevision: number,
|
|
transfer: unknown
|
|
) {
|
|
return request<
|
|
| { projectId: string; name: string }
|
|
| ProjectSettingsCommandResultDto
|
|
>(`/api/projects/${projectId}/import`, {
|
|
method: "POST",
|
|
body: JSON.stringify(
|
|
mode === "replace"
|
|
? { mode, expectedRevision, transfer }
|
|
: { mode, transfer }
|
|
),
|
|
});
|
|
}
|
|
|
|
export function listDistributionBoards(projectId: string) {
|
|
return request<DistributionBoardDto[]>(`/api/projects/${projectId}/distribution-boards`);
|
|
}
|
|
|
|
export function createDistributionBoard(
|
|
projectId: string,
|
|
input: {
|
|
name: string;
|
|
floorId: string | null;
|
|
supplyType: NonNullable<DistributionBoardDto["supplyType"]>;
|
|
},
|
|
expectedRevision: number
|
|
) {
|
|
return request<DistributionBoardCommandResultDto>(
|
|
`/api/projects/${projectId}/distribution-boards`,
|
|
{
|
|
method: "POST",
|
|
body: JSON.stringify({ ...input, expectedRevision }),
|
|
}
|
|
);
|
|
}
|
|
|
|
export type UpdateDistributionBoardInput = {
|
|
floorId: string | null;
|
|
supplyType: NonNullable<DistributionBoardDto["supplyType"]>;
|
|
simultaneityFactor: number;
|
|
} & Partial<DistributionBoardInfoDto> &
|
|
Partial<
|
|
Pick<
|
|
DistributionBoardDto,
|
|
"feedingDistributionBoardId" | "feedingCircuitId"
|
|
>
|
|
>;
|
|
|
|
export function updateDistributionBoard(
|
|
projectId: string,
|
|
distributionBoardId: string,
|
|
input: UpdateDistributionBoardInput,
|
|
expectedRevision: number
|
|
) {
|
|
return request<DistributionBoardCommandResultDto>(
|
|
`/api/projects/${projectId}/distribution-boards/${distributionBoardId}`,
|
|
{
|
|
method: "PUT",
|
|
body: JSON.stringify({ ...input, expectedRevision }),
|
|
}
|
|
);
|
|
}
|
|
|
|
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() {
|
|
return request<{
|
|
logo: DocumentLogoMetadataDto | null;
|
|
office: OfficeProfileDto;
|
|
}>("/api/document-branding");
|
|
}
|
|
|
|
export function updateOfficeProfile(office: OfficeProfileDto) {
|
|
return request<{ office: OfficeProfileDto }>("/api/document-branding/office", {
|
|
method: "PUT",
|
|
body: JSON.stringify(office),
|
|
});
|
|
}
|
|
|
|
export async function uploadDocumentLogo(file: File) {
|
|
const bytes = new Uint8Array(await file.arrayBuffer());
|
|
let binary = "";
|
|
for (let index = 0; index < bytes.length; index += 0x8000) {
|
|
binary += String.fromCharCode(...bytes.subarray(index, index + 0x8000));
|
|
}
|
|
return request<{ logo: DocumentLogoMetadataDto }>(documentLogoUrl, {
|
|
method: "PUT",
|
|
body: JSON.stringify({
|
|
fileName: file.name,
|
|
mimeType: file.type,
|
|
contentBase64: btoa(binary),
|
|
}),
|
|
});
|
|
}
|
|
|
|
export function deleteDocumentLogo() {
|
|
return request<void>(documentLogoUrl, { method: "DELETE" });
|
|
}
|
|
|
|
export function getExternalCsvConfiguration(projectId: string) {
|
|
return request<{ configuration: ExternalCsvConfigurationSnapshotDto | null }>(
|
|
`/api/projects/${projectId}/external-csv/configuration`
|
|
);
|
|
}
|
|
|
|
export function getExternalCircuitListObjects(
|
|
projectId: string,
|
|
circuitListId: string
|
|
) {
|
|
return request<ExternalCircuitListObjectsDto>(
|
|
`/api/projects/${projectId}/circuit-lists/${circuitListId}/external-objects`
|
|
);
|
|
}
|
|
|
|
export function updateExternalCsvConfiguration(
|
|
projectId: string,
|
|
expectedRevision: number,
|
|
configuration: ExternalCsvConfiguration
|
|
) {
|
|
return request<
|
|
ProjectCommandResultDto & { configuration: ExternalCsvConfigurationSnapshotDto }
|
|
>(`/api/projects/${projectId}/external-csv/configuration`, {
|
|
method: "PUT",
|
|
body: JSON.stringify({ expectedRevision, configuration }),
|
|
});
|
|
}
|
|
|
|
export function previewExternalCsv(
|
|
projectId: string,
|
|
fileName: string,
|
|
contentBase64: string
|
|
) {
|
|
return request<ExternalCsvPreviewDto>(
|
|
`/api/projects/${projectId}/external-csv/preview`,
|
|
{
|
|
method: "POST",
|
|
body: JSON.stringify({ fileName, contentBase64 }),
|
|
}
|
|
);
|
|
}
|
|
|
|
export function planExternalInitialImport(
|
|
projectId: string,
|
|
fileName: string,
|
|
contentBase64: string
|
|
) {
|
|
return request<ExternalInitialImportPlanDto>(
|
|
`/api/projects/${projectId}/external-csv/initial-import/plan`,
|
|
{
|
|
method: "POST",
|
|
body: JSON.stringify({ fileName, contentBase64 }),
|
|
}
|
|
);
|
|
}
|
|
|
|
export function applyExternalInitialImport(
|
|
projectId: string,
|
|
input: ApplyExternalInitialImportInput
|
|
) {
|
|
return request<ExternalInitialImportResultDto>(
|
|
`/api/projects/${projectId}/external-csv/initial-import/apply`,
|
|
{ method: "POST", body: JSON.stringify(input) }
|
|
);
|
|
}
|
|
|
|
export function copyDistributionBoard(
|
|
projectId: string,
|
|
distributionBoardId: string,
|
|
name: string,
|
|
expectedRevision: number
|
|
) {
|
|
return request<DistributionBoardCopyCommandResultDto>(
|
|
`/api/projects/${projectId}/distribution-boards/${distributionBoardId}/copy`,
|
|
{
|
|
method: "POST",
|
|
body: JSON.stringify({ name, expectedRevision }),
|
|
}
|
|
);
|
|
}
|
|
|
|
export function deleteDistributionBoard(
|
|
projectId: string,
|
|
distributionBoardId: string,
|
|
expectedRevision: number
|
|
) {
|
|
return request<DistributionBoardDeleteCommandResultDto>(
|
|
`/api/projects/${projectId}/distribution-boards/${distributionBoardId}`,
|
|
{
|
|
method: "DELETE",
|
|
body: JSON.stringify({ expectedRevision }),
|
|
}
|
|
);
|
|
}
|
|
|
|
export function listCircuitLists(projectId: string) {
|
|
return request<CircuitListDto[]>(`/api/projects/${projectId}/circuit-lists`);
|
|
}
|
|
|
|
export function getCircuitTree(projectId: string, circuitListId: string) {
|
|
return request<CircuitTreeResponseDto>(`/api/projects/${projectId}/circuit-lists/${circuitListId}/tree`);
|
|
}
|
|
|
|
export function getProjectCircuitTrees(projectId: string) {
|
|
return request<ProjectCircuitTreesResponseDto>(
|
|
`/api/projects/${projectId}/circuit-trees`
|
|
);
|
|
}
|
|
|
|
export function insertDistributionBoardComponentCommand(
|
|
projectId: string,
|
|
expectedRevision: number,
|
|
snapshot: DistributionBoardComponentSnapshot
|
|
) {
|
|
return executeProjectCommand(
|
|
projectId,
|
|
expectedRevision,
|
|
{
|
|
schemaVersion: 1,
|
|
type: "distribution-board-component.insert",
|
|
payload: { snapshot },
|
|
},
|
|
"Verteilerkomponente hinzufügen"
|
|
);
|
|
}
|
|
|
|
export function updateDistributionBoardComponentCommand(
|
|
projectId: string,
|
|
expectedRevision: number,
|
|
expected: DistributionBoardComponentSnapshot,
|
|
target: DistributionBoardComponentSnapshot
|
|
) {
|
|
return executeProjectCommand(
|
|
projectId,
|
|
expectedRevision,
|
|
{
|
|
schemaVersion: 1,
|
|
type: "distribution-board-component.update",
|
|
payload: { expected, target },
|
|
},
|
|
"Verteilerkomponente bearbeiten"
|
|
);
|
|
}
|
|
|
|
export function deleteDistributionBoardComponentCommand(
|
|
projectId: string,
|
|
expectedRevision: number,
|
|
snapshot: DistributionBoardComponentSnapshot
|
|
) {
|
|
return executeProjectCommand(
|
|
projectId,
|
|
expectedRevision,
|
|
{
|
|
schemaVersion: 1,
|
|
type: "distribution-board-component.delete",
|
|
payload: { snapshot },
|
|
},
|
|
"Verteilerkomponente entfernen"
|
|
);
|
|
}
|
|
|
|
export function insertCircuitGroupCommand(
|
|
projectId: string,
|
|
expectedRevision: number,
|
|
snapshot: CircuitGroupSnapshot
|
|
) {
|
|
return executeProjectCommand(
|
|
projectId,
|
|
expectedRevision,
|
|
{
|
|
schemaVersion: 1,
|
|
type: "circuit-group.insert",
|
|
payload: { snapshot },
|
|
},
|
|
"Stromkreisgruppe hinzufügen"
|
|
);
|
|
}
|
|
|
|
export function updateCircuitGroupCommand(
|
|
projectId: string,
|
|
expectedRevision: number,
|
|
expected: CircuitGroupSnapshot,
|
|
target: CircuitGroupSnapshot
|
|
) {
|
|
return executeProjectCommand(
|
|
projectId,
|
|
expectedRevision,
|
|
{
|
|
schemaVersion: 1,
|
|
type: "circuit-group.update",
|
|
payload: { expected, target },
|
|
},
|
|
"Stromkreisgruppe bearbeiten"
|
|
);
|
|
}
|
|
|
|
export function deleteCircuitGroupCommand(
|
|
projectId: string,
|
|
expectedRevision: number,
|
|
snapshot: CircuitGroupSnapshot
|
|
) {
|
|
return executeProjectCommand(
|
|
projectId,
|
|
expectedRevision,
|
|
{
|
|
schemaVersion: 1,
|
|
type: "circuit-group.delete",
|
|
payload: { snapshot },
|
|
},
|
|
"Leere Stromkreisgruppe entfernen"
|
|
);
|
|
}
|
|
|
|
export function deleteCircuitGroupSubtreeCommand(
|
|
projectId: string,
|
|
expectedRevision: number,
|
|
snapshot: CircuitGroupSubtreeSnapshot
|
|
) {
|
|
return executeProjectCommand(
|
|
projectId,
|
|
expectedRevision,
|
|
{
|
|
schemaVersion: 1,
|
|
type: "circuit-group.delete-subtree",
|
|
payload: { snapshot },
|
|
},
|
|
"Befüllte Stromkreisgruppe entfernen"
|
|
);
|
|
}
|
|
|
|
export function reorderCircuitGroupsCommand(
|
|
projectId: string,
|
|
expectedRevision: number,
|
|
circuitListId: string,
|
|
assignments: CircuitGroupReorderAssignment[]
|
|
) {
|
|
return executeProjectCommand(
|
|
projectId,
|
|
expectedRevision,
|
|
{
|
|
schemaVersion: 1,
|
|
type: "circuit-group.reorder",
|
|
payload: { circuitListId, assignments },
|
|
},
|
|
"Stromkreisgruppen sortieren"
|
|
);
|
|
}
|
|
|
|
export function renumberCircuitGroupsCommand(
|
|
projectId: string,
|
|
expectedRevision: number,
|
|
circuitListId: string,
|
|
plan: CircuitGroupRenumberPlan
|
|
) {
|
|
return executeProjectCommand(
|
|
projectId,
|
|
expectedRevision,
|
|
{
|
|
schemaVersion: 1,
|
|
type: "circuit-group.renumber",
|
|
payload: { circuitListId, groups: plan.groups },
|
|
},
|
|
"Stromkreisgruppen neu nummerieren"
|
|
);
|
|
}
|
|
|
|
export function moveCircuitToGroupCommand(
|
|
projectId: string,
|
|
expectedRevision: number,
|
|
plan: CircuitGroupMovePlan
|
|
) {
|
|
return executeProjectCommand(
|
|
projectId,
|
|
expectedRevision,
|
|
{
|
|
schemaVersion: 1,
|
|
type: "circuit.move-group",
|
|
payload: plan,
|
|
},
|
|
"Stromkreis in andere Gruppe verschieben"
|
|
);
|
|
}
|
|
|
|
export function updateCircuitProtectionCommand(
|
|
projectId: string,
|
|
expectedRevision: number,
|
|
circuitId: string,
|
|
expected: CircuitProtectionSnapshot | null,
|
|
target: CircuitProtectionSnapshot
|
|
) {
|
|
return executeProjectCommand(
|
|
projectId,
|
|
expectedRevision,
|
|
{
|
|
schemaVersion: 1,
|
|
type: "circuit-protection.update",
|
|
payload: { circuitId, expected, target },
|
|
},
|
|
"Stromkreisschutz bearbeiten"
|
|
);
|
|
}
|
|
|
|
export function updateCircuitById(
|
|
projectId: string,
|
|
expectedRevision: number,
|
|
circuitId: string,
|
|
input: CircuitUpdatePatch
|
|
) {
|
|
return executeProjectCommand(
|
|
projectId,
|
|
expectedRevision,
|
|
createCircuitUpdateProjectCommand(circuitId, input),
|
|
"Stromkreisfeld bearbeiten"
|
|
);
|
|
}
|
|
|
|
export function insertCircuitCommand(
|
|
projectId: string,
|
|
expectedRevision: number,
|
|
circuit: CircuitSnapshot,
|
|
description: string
|
|
) {
|
|
return executeProjectCommand(
|
|
projectId,
|
|
expectedRevision,
|
|
{
|
|
schemaVersion: 1,
|
|
type: "circuit.insert",
|
|
payload: { circuit },
|
|
},
|
|
description
|
|
);
|
|
}
|
|
|
|
export function deleteCircuitCommand(
|
|
projectId: string,
|
|
expectedRevision: number,
|
|
circuitId: string,
|
|
expectedCircuitListId: string,
|
|
description: string
|
|
) {
|
|
return executeProjectCommand(
|
|
projectId,
|
|
expectedRevision,
|
|
{
|
|
schemaVersion: 1,
|
|
type: "circuit.delete",
|
|
payload: { circuitId, expectedCircuitListId },
|
|
},
|
|
description
|
|
);
|
|
}
|
|
|
|
export function getNextCircuitIdentifier(projectId: string, sectionId: string) {
|
|
return request<{ sectionId: string; nextIdentifier: string }>(
|
|
`/api/projects/${projectId}/circuit-sections/${sectionId}/next-identifier`
|
|
);
|
|
}
|
|
|
|
export function updateCircuitDeviceRowById(
|
|
projectId: string,
|
|
expectedRevision: number,
|
|
rowId: string,
|
|
input: CircuitDeviceRowUpdatePatch
|
|
) {
|
|
return executeProjectCommand(
|
|
projectId,
|
|
expectedRevision,
|
|
createCircuitDeviceRowUpdateProjectCommand(rowId, input),
|
|
"Gerätezeilenfeld bearbeiten"
|
|
);
|
|
}
|
|
|
|
export function insertCircuitDeviceRowCommand(
|
|
projectId: string,
|
|
expectedRevision: number,
|
|
row: CircuitDeviceRowSnapshot,
|
|
description: string
|
|
) {
|
|
return executeProjectCommand(
|
|
projectId,
|
|
expectedRevision,
|
|
{
|
|
schemaVersion: 1,
|
|
type: "circuit-device-row.insert",
|
|
payload: { row },
|
|
},
|
|
description
|
|
);
|
|
}
|
|
|
|
export function deleteCircuitDeviceRowCommand(
|
|
projectId: string,
|
|
expectedRevision: number,
|
|
rowId: string,
|
|
expectedCircuitId: string,
|
|
description: string
|
|
) {
|
|
return executeProjectCommand(
|
|
projectId,
|
|
expectedRevision,
|
|
{
|
|
schemaVersion: 1,
|
|
type: "circuit-device-row.delete",
|
|
payload: { rowId, expectedCircuitId },
|
|
},
|
|
description
|
|
);
|
|
}
|
|
|
|
export function moveCircuitDeviceRowsCommand(
|
|
projectId: string,
|
|
expectedRevision: number,
|
|
moves: CircuitDeviceRowMoveAssignment[],
|
|
description: string
|
|
) {
|
|
return executeProjectCommand(
|
|
projectId,
|
|
expectedRevision,
|
|
{
|
|
schemaVersion: 1,
|
|
type: "circuit-device-row.move",
|
|
payload: { moves },
|
|
},
|
|
description
|
|
);
|
|
}
|
|
|
|
export function moveCircuitDeviceRowsToNewCircuitCommand(
|
|
projectId: string,
|
|
expectedRevision: number,
|
|
targetCircuit: CircuitSnapshot,
|
|
moves: CircuitDeviceRowMoveAssignment[],
|
|
description: string
|
|
) {
|
|
return executeProjectCommand(
|
|
projectId,
|
|
expectedRevision,
|
|
{
|
|
schemaVersion: 1,
|
|
type: "circuit-device-row.move-with-new-circuit",
|
|
payload: {
|
|
targetCircuitAction: "create",
|
|
targetCircuit,
|
|
moves,
|
|
},
|
|
},
|
|
description
|
|
);
|
|
}
|
|
|
|
export function reorderCircuitSectionCommand(
|
|
projectId: string,
|
|
expectedRevision: number,
|
|
sectionId: string,
|
|
assignments: CircuitSectionReorderAssignment[],
|
|
description: string
|
|
) {
|
|
return executeProjectCommand(
|
|
projectId,
|
|
expectedRevision,
|
|
{
|
|
schemaVersion: 1,
|
|
type: "circuit.reorder-section",
|
|
payload: { sectionId, assignments },
|
|
},
|
|
description
|
|
);
|
|
}
|
|
|
|
export function reorderCircuitSectionsCommand(
|
|
projectId: string,
|
|
expectedRevision: number,
|
|
sections: CircuitSectionsReorderSection[],
|
|
description: string
|
|
) {
|
|
return executeProjectCommand(
|
|
projectId,
|
|
expectedRevision,
|
|
{
|
|
schemaVersion: 1,
|
|
type: "circuit.reorder-sections",
|
|
payload: { sections },
|
|
},
|
|
description
|
|
);
|
|
}
|
|
|
|
export function renumberCircuitSectionCommand(
|
|
projectId: string,
|
|
expectedRevision: number,
|
|
sectionId: string,
|
|
assignments: CircuitSectionRenumberAssignment[],
|
|
description: string
|
|
) {
|
|
return executeProjectCommand(
|
|
projectId,
|
|
expectedRevision,
|
|
{
|
|
schemaVersion: 1,
|
|
type: "circuit.renumber-section",
|
|
payload: { sectionId, assignments },
|
|
},
|
|
description
|
|
);
|
|
}
|
|
|
|
export function listBuildings(projectId: string) {
|
|
return request<BuildingDto[]>(`/api/projects/${projectId}/buildings`);
|
|
}
|
|
|
|
export function createBuilding(
|
|
projectId: string,
|
|
input: CreateBuildingInput,
|
|
expectedRevision: number
|
|
) {
|
|
return request<ProjectBuildingCommandResultDto>(
|
|
`/api/projects/${projectId}/buildings`,
|
|
{
|
|
method: "POST",
|
|
body: JSON.stringify({ name: input.name, expectedRevision }),
|
|
}
|
|
);
|
|
}
|
|
|
|
export function updateBuilding(
|
|
projectId: string,
|
|
buildingId: string,
|
|
input: CreateBuildingInput,
|
|
expectedRevision: number
|
|
) {
|
|
return request<ProjectBuildingCommandResultDto>(
|
|
`/api/projects/${projectId}/buildings/${buildingId}`,
|
|
{
|
|
method: "PUT",
|
|
body: JSON.stringify({ name: input.name, expectedRevision }),
|
|
}
|
|
);
|
|
}
|
|
|
|
export function deleteBuilding(
|
|
projectId: string,
|
|
buildingId: string,
|
|
expectedRevision: number
|
|
) {
|
|
return request<ProjectCommandResultDto>(
|
|
`/api/projects/${projectId}/buildings/${buildingId}`,
|
|
{
|
|
method: "DELETE",
|
|
body: JSON.stringify({ expectedRevision }),
|
|
}
|
|
);
|
|
}
|
|
|
|
// buildingId is only sent when the caller passes it (string or null);
|
|
// an omitted buildingId keeps the current assignment on update.
|
|
function toFloorRequestBody(input: CreateFloorInput, expectedRevision: number) {
|
|
return input.buildingId === undefined
|
|
? { name: input.name, expectedRevision }
|
|
: { name: input.name, buildingId: input.buildingId, expectedRevision };
|
|
}
|
|
|
|
export function listFloors(projectId: string) {
|
|
return request<FloorDto[]>(`/api/projects/${projectId}/floors`);
|
|
}
|
|
|
|
export function createFloor(
|
|
projectId: string,
|
|
input: CreateFloorInput,
|
|
expectedRevision: number
|
|
) {
|
|
return request<ProjectFloorCommandResultDto>(
|
|
`/api/projects/${projectId}/floors`,
|
|
{
|
|
method: "POST",
|
|
body: JSON.stringify(toFloorRequestBody(input, expectedRevision)),
|
|
}
|
|
);
|
|
}
|
|
|
|
export function updateFloor(
|
|
projectId: string,
|
|
floorId: string,
|
|
input: CreateFloorInput,
|
|
expectedRevision: number
|
|
) {
|
|
return request<ProjectFloorCommandResultDto>(`/api/projects/${projectId}/floors/${floorId}`, {
|
|
method: "PUT",
|
|
body: JSON.stringify(toFloorRequestBody(input, expectedRevision)),
|
|
});
|
|
}
|
|
|
|
export function deleteFloor(projectId: string, floorId: string, expectedRevision: number) {
|
|
return request<ProjectCommandResultDto>(`/api/projects/${projectId}/floors/${floorId}`, {
|
|
method: "DELETE",
|
|
body: JSON.stringify({ expectedRevision }),
|
|
});
|
|
}
|
|
|
|
export function listRooms(projectId: string) {
|
|
return request<RoomDto[]>(`/api/projects/${projectId}/rooms`);
|
|
}
|
|
|
|
export function createRoom(
|
|
projectId: string,
|
|
input: CreateRoomInput,
|
|
expectedRevision: number
|
|
) {
|
|
return request<ProjectRoomCommandResultDto>(
|
|
`/api/projects/${projectId}/rooms`,
|
|
{
|
|
method: "POST",
|
|
body: JSON.stringify({ ...input, expectedRevision }),
|
|
}
|
|
);
|
|
}
|
|
|
|
export function updateRoom(
|
|
projectId: string,
|
|
roomId: string,
|
|
input: CreateRoomInput,
|
|
expectedRevision: number
|
|
) {
|
|
return request<ProjectRoomCommandResultDto>(`/api/projects/${projectId}/rooms/${roomId}`, {
|
|
method: "PUT",
|
|
body: JSON.stringify({ ...input, floorId: input.floorId ?? null, expectedRevision }),
|
|
});
|
|
}
|
|
|
|
export function deleteRoom(projectId: string, roomId: string, expectedRevision: number) {
|
|
return request<ProjectCommandResultDto>(`/api/projects/${projectId}/rooms/${roomId}`, {
|
|
method: "DELETE",
|
|
body: JSON.stringify({ expectedRevision }),
|
|
});
|
|
}
|
|
|
|
export function listGlobalDevices() {
|
|
return request<GlobalDeviceDto[]>("/api/global-devices");
|
|
}
|
|
|
|
export function createGlobalDevice(input: CreateGlobalDeviceInput) {
|
|
return request<GlobalDeviceDto>("/api/global-devices", {
|
|
method: "POST",
|
|
body: JSON.stringify(input),
|
|
});
|
|
}
|
|
|
|
export function updateGlobalDevice(globalDeviceId: string, input: CreateGlobalDeviceInput) {
|
|
return request<GlobalDeviceDto>(`/api/global-devices/${globalDeviceId}`, {
|
|
method: "PUT",
|
|
body: JSON.stringify(input),
|
|
});
|
|
}
|
|
|
|
export function deleteGlobalDevice(globalDeviceId: string) {
|
|
return request<void>(`/api/global-devices/${globalDeviceId}`, { method: "DELETE" });
|
|
}
|
|
|
|
export function copyProjectDeviceToGlobal(projectId: string, projectDeviceId: string) {
|
|
return request<GlobalDeviceDto>(`/api/global-devices/import-project/${projectId}/${projectDeviceId}`, {
|
|
method: "POST",
|
|
});
|
|
}
|
|
|
|
export function listProjectDevices(projectId: string) {
|
|
return request<ProjectDeviceDto[]>(`/api/project-devices/projects/${projectId}`);
|
|
}
|
|
|
|
export function createProjectDevice(
|
|
projectId: string,
|
|
input: CreateProjectDeviceInput,
|
|
expectedRevision: number
|
|
) {
|
|
return request<ProjectDeviceCommandResultDto>(`/api/project-devices/projects/${projectId}`, {
|
|
method: "POST",
|
|
body: JSON.stringify({ ...input, expectedRevision }),
|
|
});
|
|
}
|
|
|
|
export function updateProjectDevice(
|
|
projectId: string,
|
|
projectDeviceId: string,
|
|
input: CreateProjectDeviceInput,
|
|
expectedRevision: number
|
|
) {
|
|
return request<ProjectDeviceCommandResultDto>(
|
|
`/api/project-devices/projects/${projectId}/${projectDeviceId}`,
|
|
{
|
|
method: "PUT",
|
|
body: JSON.stringify({ ...input, expectedRevision }),
|
|
}
|
|
);
|
|
}
|
|
|
|
export function deleteProjectDevice(
|
|
projectId: string,
|
|
projectDeviceId: string,
|
|
expectedRevision: number
|
|
) {
|
|
return request<ProjectCommandResultDto>(
|
|
`/api/project-devices/projects/${projectId}/${projectDeviceId}`,
|
|
{
|
|
method: "DELETE",
|
|
body: JSON.stringify({ expectedRevision }),
|
|
}
|
|
);
|
|
}
|
|
|
|
export function copyGlobalDeviceToProject(
|
|
projectId: string,
|
|
globalDeviceId: string,
|
|
expectedRevision: number
|
|
) {
|
|
return request<ProjectDeviceCommandResultDto>(
|
|
`/api/project-devices/projects/${projectId}/import-global/${globalDeviceId}`,
|
|
{
|
|
method: "POST",
|
|
body: JSON.stringify({ expectedRevision }),
|
|
}
|
|
);
|
|
}
|
|
|
|
export function getProjectDeviceSyncPreview(projectId: string, projectDeviceId: string) {
|
|
return request<ProjectDeviceSyncPreviewDto>(
|
|
`/api/project-devices/projects/${projectId}/${projectDeviceId}/links`
|
|
);
|
|
}
|
|
|
|
export function synchronizeProjectDeviceRows(
|
|
projectId: string,
|
|
projectDeviceId: string,
|
|
rowIds: string[],
|
|
fields: ProjectDeviceSyncField[],
|
|
expectedRevision: number
|
|
) {
|
|
return request<ProjectDeviceSyncCommandResultDto>(
|
|
`/api/project-devices/projects/${projectId}/${projectDeviceId}/synchronize`,
|
|
{
|
|
method: "POST",
|
|
body: JSON.stringify({ rowIds, fields, expectedRevision }),
|
|
}
|
|
);
|
|
}
|
|
|
|
export function disconnectProjectDeviceRows(
|
|
projectId: string,
|
|
projectDeviceId: string,
|
|
rowIds: string[],
|
|
expectedRevision: number
|
|
) {
|
|
return request<ProjectDeviceSyncCommandResultDto>(
|
|
`/api/project-devices/projects/${projectId}/${projectDeviceId}/disconnect`,
|
|
{
|
|
method: "POST",
|
|
body: JSON.stringify({ rowIds, expectedRevision }),
|
|
}
|
|
);
|
|
}
|
|
|
|
// ── Grobe Leistungsbilanz (LPH 2/3) ─────────────────────────────────────
|
|
|
|
function estimateBalancesUrl(projectId: string, balanceId?: string) {
|
|
const base = `/api/projects/${projectId}/estimate-balances`;
|
|
return balanceId ? `${base}/${balanceId}` : base;
|
|
}
|
|
|
|
export function listEstimateBalances(projectId: string) {
|
|
return request<EstimateBalanceDto[]>(estimateBalancesUrl(projectId));
|
|
}
|
|
|
|
export function getEstimateBalance(projectId: string, balanceId: string) {
|
|
return request<EstimateBalanceDto>(estimateBalancesUrl(projectId, balanceId));
|
|
}
|
|
|
|
export function createEstimateBalance(
|
|
projectId: string,
|
|
input: CreateEstimateBalanceInput,
|
|
expectedRevision: number
|
|
) {
|
|
return request<EstimateBalanceCommandResultDto>(estimateBalancesUrl(projectId), {
|
|
method: "POST",
|
|
body: JSON.stringify({ ...input, expectedRevision }),
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Saves the complete target document. `keepalive` lets the request outlive
|
|
* the page, e.g. for the last autosave while the tab is being closed.
|
|
*/
|
|
export function updateEstimateBalance(
|
|
projectId: string,
|
|
balance: EstimateBalanceDto,
|
|
expectedRevision: number,
|
|
options: { keepalive?: boolean } = {}
|
|
) {
|
|
return request<EstimateBalanceUpdateResultDto>(
|
|
estimateBalancesUrl(projectId, balance.id),
|
|
{
|
|
method: "PUT",
|
|
body: JSON.stringify({ expectedRevision, balance }),
|
|
...(options.keepalive ? { keepalive: true } : {}),
|
|
}
|
|
);
|
|
}
|
|
|
|
export function deleteEstimateBalance(
|
|
projectId: string,
|
|
balanceId: string,
|
|
expectedRevision: number
|
|
) {
|
|
return request<ProjectCommandResultDto>(estimateBalancesUrl(projectId, balanceId), {
|
|
method: "DELETE",
|
|
body: JSON.stringify({ expectedRevision }),
|
|
});
|
|
}
|
|
|
|
export function getEstimateComparison(projectId: string, balanceId: string) {
|
|
return request<EstimateComparisonDto>(
|
|
`${estimateBalancesUrl(projectId, balanceId)}/comparison`
|
|
);
|
|
}
|