leistungsbilanz-ts/src/frontend/utils/api.ts

1303 lines
33 KiB
TypeScript

import {
WRITE_REQUEST_HEADER_NAME,
WRITE_REQUEST_HEADER_VALUE,
} from "../../shared/constants/request-security";
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,
ExternalModelConfigurationSnapshotDto,
ExternalInitialImportPlanDto,
ExternalInitialImportResultDto,
ExternalInitialImportStatusDto,
ApplyExternalInitialImportInput,
CreateEstimateBalanceInput,
EstimateBalanceCommandResultDto,
EstimateBalanceDto,
EstimateBalanceUpdateResultDto,
EstimateComparisonDto,
} from "../types";
import type { ExternalModelConfiguration } from "../../external-model/configuration/external-model-configuration";
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",
[WRITE_REQUEST_HEADER_NAME]: WRITE_REQUEST_HEADER_VALUE,
...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 getExternalModelConfiguration(projectId: string) {
return request<{ configuration: ExternalModelConfigurationSnapshotDto | null }>(
`/api/projects/${projectId}/external-model/configuration`
);
}
export function updateExternalModelConfiguration(
projectId: string,
expectedRevision: number,
configuration: ExternalModelConfiguration
) {
return request<{
configuration: ExternalModelConfigurationSnapshotDto;
history: ProjectHistoryStateDto;
}>(`/api/projects/${projectId}/external-model/configuration`, {
method: "PUT",
body: JSON.stringify({ expectedRevision, configuration }),
});
}
export async function detectExternalModelWorkbook(
projectId: string,
file: File,
configuration: ExternalModelConfiguration
) {
const bytes = new Uint8Array(await file.arrayBuffer());
let binary = "";
for (const byte of bytes) binary += String.fromCharCode(byte);
return request<{ sheetName: string; columnCount: number; objectCount: number }>(
`/api/projects/${projectId}/external-model/configuration/detect`,
{ method: "POST", body: JSON.stringify({ fileName: file.name, contentBase64: btoa(binary), configuration }) }
);
}
export function getExternalInitialImportStatus(projectId: string) {
return request<ExternalInitialImportStatusDto>(`/api/projects/${projectId}/external-model/import-status`);
}
export async function planExternalInitialImport(
projectId: string,
file: File,
configuration: ExternalModelConfiguration,
expectedRevision: number
) {
return request<ExternalInitialImportPlanDto>(`/api/projects/${projectId}/external-model/initial-import/plan`, {
method: "POST",
body: JSON.stringify({ fileName: file.name, contentBase64: await fileToBase64(file), configuration, expectedRevision }),
});
}
export async function applyExternalInitialImport(
projectId: string,
file: File,
input: Omit<ApplyExternalInitialImportInput, "fileName" | "contentBase64">
) {
return request<ExternalInitialImportResultDto>(`/api/projects/${projectId}/external-model/initial-import/apply`, {
method: "POST",
body: JSON.stringify({ ...input, fileName: file.name, contentBase64: await fileToBase64(file) }),
});
}
async function fileToBase64(file: File) {
const bytes = new Uint8Array(await file.arrayBuffer());
let binary = "";
for (const byte of bytes) binary += String.fromCharCode(byte);
return btoa(binary);
}
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 getExternalCircuitListObjects(
projectId: string,
circuitListId: string
) {
return request<ExternalCircuitListObjectsDto>(
`/api/projects/${projectId}/circuit-lists/${circuitListId}/external-objects`
);
}
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`
);
}