Expose distribution board copy and delete API
This commit is contained in:
@@ -114,6 +114,11 @@ export interface DistributionBoardCommandResultDto
|
||||
distributionBoard: DistributionBoardDto;
|
||||
}
|
||||
|
||||
export interface DistributionBoardDeleteCommandResultDto
|
||||
extends ProjectCommandResultDto {
|
||||
distributionBoardId: string;
|
||||
}
|
||||
|
||||
export interface CircuitListDto {
|
||||
id: string;
|
||||
projectId: string;
|
||||
|
||||
@@ -6,6 +6,7 @@ import type {
|
||||
CreateGlobalDeviceInput,
|
||||
DistributionBoardDto,
|
||||
DistributionBoardCommandResultDto,
|
||||
DistributionBoardDeleteCommandResultDto,
|
||||
FloorDto,
|
||||
GlobalDeviceDto,
|
||||
ProjectCommandDto,
|
||||
@@ -331,6 +332,35 @@ export function updateDistributionBoard(
|
||||
);
|
||||
}
|
||||
|
||||
export function copyDistributionBoard(
|
||||
projectId: string,
|
||||
distributionBoardId: string,
|
||||
name: string,
|
||||
expectedRevision: number
|
||||
) {
|
||||
return request<DistributionBoardCommandResultDto>(
|
||||
`/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`);
|
||||
}
|
||||
|
||||
@@ -33,6 +33,8 @@ const commandTypeLabels: Record<string, string> = {
|
||||
"distribution-board.insert": "Verteilung angelegt",
|
||||
"distribution-board.update": "Verteilung bearbeitet",
|
||||
"distribution-board.delete": "Verteilung entfernt",
|
||||
"distribution-board.insert-subtree": "Verteilung vollständig eingefügt",
|
||||
"distribution-board.delete-subtree": "Verteilung vollständig entfernt",
|
||||
"distribution-board-component.insert": "Verteilergerät angelegt",
|
||||
"distribution-board-component.delete": "Verteilergerät entfernt",
|
||||
"distribution-board-component.update": "Verteilergerät bearbeitet",
|
||||
|
||||
@@ -5,10 +5,20 @@ import {
|
||||
createDistributionBoardStructureSnapshot,
|
||||
} from "../../domain/models/distribution-board-structure-project-command.model.js";
|
||||
import {
|
||||
createDistributionBoardDeleteSubtreeProjectCommand,
|
||||
createDistributionBoardInsertSubtreeProjectCommand,
|
||||
} from "../../domain/models/distribution-board-subtree-project-command.model.js";
|
||||
import { cloneDistributionBoardSubtree } from "../../domain/models/distribution-board-subtree-snapshot.model.js";
|
||||
import {
|
||||
copyDistributionBoardSchema,
|
||||
createDistributionBoardSchema,
|
||||
deleteDistributionBoardSchema,
|
||||
updateDistributionBoardSchema,
|
||||
} from "../../shared/validation/project-structure.schemas.js";
|
||||
import { projectCommandService } from "../composition/project-command-stores.js";
|
||||
import {
|
||||
distributionBoardSubtreeProjectCommandStore,
|
||||
projectCommandService,
|
||||
} from "../composition/project-command-stores.js";
|
||||
import { distributionBoardRepository } from "../composition/application-repositories.js";
|
||||
import { respondWithProjectCommandError } from "./project-command.controller.js";
|
||||
|
||||
@@ -101,3 +111,85 @@ export async function updateDistributionBoard(
|
||||
return respondWithProjectCommandError(error, res);
|
||||
}
|
||||
}
|
||||
|
||||
export async function copyDistributionBoard(
|
||||
req: Request,
|
||||
res: Response
|
||||
) {
|
||||
const ids = getDistributionBoardIds(req, res);
|
||||
if (!ids) {
|
||||
return;
|
||||
}
|
||||
const parsed = copyDistributionBoardSchema.safeParse(req.body);
|
||||
if (!parsed.success) {
|
||||
return res.status(400).json({ error: parsed.error.flatten() });
|
||||
}
|
||||
try {
|
||||
const source = distributionBoardSubtreeProjectCommandStore.capture(
|
||||
ids.projectId,
|
||||
ids.distributionBoardId
|
||||
);
|
||||
const copy = cloneDistributionBoardSubtree(source, parsed.data.name);
|
||||
const result = projectCommandService.executeUser({
|
||||
projectId: ids.projectId,
|
||||
expectedRevision: parsed.data.expectedRevision,
|
||||
description: "Verteilung kopieren",
|
||||
command:
|
||||
createDistributionBoardInsertSubtreeProjectCommand(copy),
|
||||
});
|
||||
return res.status(201).json({
|
||||
...result,
|
||||
distributionBoard: copy.distributionBoard,
|
||||
});
|
||||
} catch (error) {
|
||||
return respondWithProjectCommandError(error, res);
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteDistributionBoard(
|
||||
req: Request,
|
||||
res: Response
|
||||
) {
|
||||
const ids = getDistributionBoardIds(req, res);
|
||||
if (!ids) {
|
||||
return;
|
||||
}
|
||||
const parsed = deleteDistributionBoardSchema.safeParse(req.body);
|
||||
if (!parsed.success) {
|
||||
return res.status(400).json({ error: parsed.error.flatten() });
|
||||
}
|
||||
try {
|
||||
const snapshot =
|
||||
distributionBoardSubtreeProjectCommandStore.capture(
|
||||
ids.projectId,
|
||||
ids.distributionBoardId
|
||||
);
|
||||
const result = projectCommandService.executeUser({
|
||||
projectId: ids.projectId,
|
||||
expectedRevision: parsed.data.expectedRevision,
|
||||
description: "Verteilung löschen",
|
||||
command:
|
||||
createDistributionBoardDeleteSubtreeProjectCommand(snapshot),
|
||||
});
|
||||
return res.json({
|
||||
...result,
|
||||
distributionBoardId: ids.distributionBoardId,
|
||||
});
|
||||
} catch (error) {
|
||||
return respondWithProjectCommandError(error, res);
|
||||
}
|
||||
}
|
||||
|
||||
function getDistributionBoardIds(req: Request, res: Response) {
|
||||
const { projectId, distributionBoardId } = req.params;
|
||||
if (
|
||||
typeof projectId !== "string" ||
|
||||
typeof distributionBoardId !== "string" ||
|
||||
!projectId.trim() ||
|
||||
!distributionBoardId.trim()
|
||||
) {
|
||||
res.status(400).json({ error: "Invalid distribution board id" });
|
||||
return null;
|
||||
}
|
||||
return { projectId, distributionBoardId };
|
||||
}
|
||||
|
||||
@@ -6,7 +6,9 @@ import {
|
||||
updateProjectSettings,
|
||||
} from "../controllers/project.controller.js";
|
||||
import {
|
||||
copyDistributionBoard,
|
||||
createDistributionBoard,
|
||||
deleteDistributionBoard,
|
||||
listDistributionBoardsByProject,
|
||||
updateDistributionBoard,
|
||||
} from "../controllers/distribution-board.controller.js";
|
||||
@@ -56,10 +58,18 @@ projectRouter.post(
|
||||
projectRouter.put("/:projectId", updateProjectSettings);
|
||||
projectRouter.get("/:projectId/distribution-boards", listDistributionBoardsByProject);
|
||||
projectRouter.post("/:projectId/distribution-boards", createDistributionBoard);
|
||||
projectRouter.post(
|
||||
"/:projectId/distribution-boards/:distributionBoardId/copy",
|
||||
copyDistributionBoard
|
||||
);
|
||||
projectRouter.put(
|
||||
"/:projectId/distribution-boards/:distributionBoardId",
|
||||
updateDistributionBoard
|
||||
);
|
||||
projectRouter.delete(
|
||||
"/:projectId/distribution-boards/:distributionBoardId",
|
||||
deleteDistributionBoard
|
||||
);
|
||||
projectRouter.get("/:projectId/circuit-lists", listCircuitListsByProject);
|
||||
projectRouter.get("/:projectId/circuit-lists/:circuitListId/tree", getCircuitTree);
|
||||
projectRouter.get("/:projectId/floors", listFloorsByProject);
|
||||
|
||||
@@ -49,6 +49,19 @@ export const updateDistributionBoardSchema = z
|
||||
})
|
||||
.strict();
|
||||
|
||||
export const copyDistributionBoardSchema = z
|
||||
.object({
|
||||
expectedRevision: expectedProjectRevisionSchema,
|
||||
name: z.string().trim().min(1).max(200),
|
||||
})
|
||||
.strict();
|
||||
|
||||
export const deleteDistributionBoardSchema = z
|
||||
.object({
|
||||
expectedRevision: expectedProjectRevisionSchema,
|
||||
})
|
||||
.strict();
|
||||
|
||||
export const createFloorSchema = z
|
||||
.object({
|
||||
expectedRevision: expectedProjectRevisionSchema,
|
||||
|
||||
@@ -28,7 +28,9 @@ import {
|
||||
type DistributionBoardUpdateProjectCommand,
|
||||
} from "../src/domain/models/distribution-board-project-command.model.js";
|
||||
import {
|
||||
copyDistributionBoard,
|
||||
createDistributionBoard,
|
||||
deleteDistributionBoard,
|
||||
updateDistributionBoard,
|
||||
} from "../src/frontend/utils/api.js";
|
||||
|
||||
@@ -568,7 +570,7 @@ describe("distribution-board structure project command", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("sends distribution-board updates to the revision-safe API", async () => {
|
||||
it("sends distribution-board writes to the revision-safe API", async () => {
|
||||
const requests: Array<{ url: string; method: string; body: unknown }> = [];
|
||||
const originalFetch = globalThis.fetch;
|
||||
globalThis.fetch = async (input, init) => {
|
||||
@@ -593,6 +595,17 @@ describe("distribution-board structure project command", () => {
|
||||
},
|
||||
9
|
||||
);
|
||||
await copyDistributionBoard(
|
||||
"project-1",
|
||||
"board-1",
|
||||
"UV 1 Kopie",
|
||||
10
|
||||
);
|
||||
await deleteDistributionBoard(
|
||||
"project-1",
|
||||
"board-1",
|
||||
11
|
||||
);
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
}
|
||||
@@ -607,6 +620,21 @@ describe("distribution-board structure project command", () => {
|
||||
expectedRevision: 9,
|
||||
},
|
||||
},
|
||||
{
|
||||
url: "/api/projects/project-1/distribution-boards/board-1/copy",
|
||||
method: "POST",
|
||||
body: {
|
||||
name: "UV 1 Kopie",
|
||||
expectedRevision: 10,
|
||||
},
|
||||
},
|
||||
{
|
||||
url: "/api/projects/project-1/distribution-boards/board-1",
|
||||
method: "DELETE",
|
||||
body: {
|
||||
expectedRevision: 11,
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user