Expose distribution board copy and delete API
This commit is contained in:
@@ -114,6 +114,11 @@ export interface DistributionBoardCommandResultDto
|
|||||||
distributionBoard: DistributionBoardDto;
|
distributionBoard: DistributionBoardDto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DistributionBoardDeleteCommandResultDto
|
||||||
|
extends ProjectCommandResultDto {
|
||||||
|
distributionBoardId: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface CircuitListDto {
|
export interface CircuitListDto {
|
||||||
id: string;
|
id: string;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import type {
|
|||||||
CreateGlobalDeviceInput,
|
CreateGlobalDeviceInput,
|
||||||
DistributionBoardDto,
|
DistributionBoardDto,
|
||||||
DistributionBoardCommandResultDto,
|
DistributionBoardCommandResultDto,
|
||||||
|
DistributionBoardDeleteCommandResultDto,
|
||||||
FloorDto,
|
FloorDto,
|
||||||
GlobalDeviceDto,
|
GlobalDeviceDto,
|
||||||
ProjectCommandDto,
|
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) {
|
export function listCircuitLists(projectId: string) {
|
||||||
return request<CircuitListDto[]>(`/api/projects/${projectId}/circuit-lists`);
|
return request<CircuitListDto[]>(`/api/projects/${projectId}/circuit-lists`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ const commandTypeLabels: Record<string, string> = {
|
|||||||
"distribution-board.insert": "Verteilung angelegt",
|
"distribution-board.insert": "Verteilung angelegt",
|
||||||
"distribution-board.update": "Verteilung bearbeitet",
|
"distribution-board.update": "Verteilung bearbeitet",
|
||||||
"distribution-board.delete": "Verteilung entfernt",
|
"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.insert": "Verteilergerät angelegt",
|
||||||
"distribution-board-component.delete": "Verteilergerät entfernt",
|
"distribution-board-component.delete": "Verteilergerät entfernt",
|
||||||
"distribution-board-component.update": "Verteilergerät bearbeitet",
|
"distribution-board-component.update": "Verteilergerät bearbeitet",
|
||||||
|
|||||||
@@ -5,10 +5,20 @@ import {
|
|||||||
createDistributionBoardStructureSnapshot,
|
createDistributionBoardStructureSnapshot,
|
||||||
} from "../../domain/models/distribution-board-structure-project-command.model.js";
|
} from "../../domain/models/distribution-board-structure-project-command.model.js";
|
||||||
import {
|
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,
|
createDistributionBoardSchema,
|
||||||
|
deleteDistributionBoardSchema,
|
||||||
updateDistributionBoardSchema,
|
updateDistributionBoardSchema,
|
||||||
} from "../../shared/validation/project-structure.schemas.js";
|
} 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 { distributionBoardRepository } from "../composition/application-repositories.js";
|
||||||
import { respondWithProjectCommandError } from "./project-command.controller.js";
|
import { respondWithProjectCommandError } from "./project-command.controller.js";
|
||||||
|
|
||||||
@@ -101,3 +111,85 @@ export async function updateDistributionBoard(
|
|||||||
return respondWithProjectCommandError(error, res);
|
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,
|
updateProjectSettings,
|
||||||
} from "../controllers/project.controller.js";
|
} from "../controllers/project.controller.js";
|
||||||
import {
|
import {
|
||||||
|
copyDistributionBoard,
|
||||||
createDistributionBoard,
|
createDistributionBoard,
|
||||||
|
deleteDistributionBoard,
|
||||||
listDistributionBoardsByProject,
|
listDistributionBoardsByProject,
|
||||||
updateDistributionBoard,
|
updateDistributionBoard,
|
||||||
} from "../controllers/distribution-board.controller.js";
|
} from "../controllers/distribution-board.controller.js";
|
||||||
@@ -56,10 +58,18 @@ projectRouter.post(
|
|||||||
projectRouter.put("/:projectId", updateProjectSettings);
|
projectRouter.put("/:projectId", updateProjectSettings);
|
||||||
projectRouter.get("/:projectId/distribution-boards", listDistributionBoardsByProject);
|
projectRouter.get("/:projectId/distribution-boards", listDistributionBoardsByProject);
|
||||||
projectRouter.post("/:projectId/distribution-boards", createDistributionBoard);
|
projectRouter.post("/:projectId/distribution-boards", createDistributionBoard);
|
||||||
|
projectRouter.post(
|
||||||
|
"/:projectId/distribution-boards/:distributionBoardId/copy",
|
||||||
|
copyDistributionBoard
|
||||||
|
);
|
||||||
projectRouter.put(
|
projectRouter.put(
|
||||||
"/:projectId/distribution-boards/:distributionBoardId",
|
"/:projectId/distribution-boards/:distributionBoardId",
|
||||||
updateDistributionBoard
|
updateDistributionBoard
|
||||||
);
|
);
|
||||||
|
projectRouter.delete(
|
||||||
|
"/:projectId/distribution-boards/:distributionBoardId",
|
||||||
|
deleteDistributionBoard
|
||||||
|
);
|
||||||
projectRouter.get("/:projectId/circuit-lists", listCircuitListsByProject);
|
projectRouter.get("/:projectId/circuit-lists", listCircuitListsByProject);
|
||||||
projectRouter.get("/:projectId/circuit-lists/:circuitListId/tree", getCircuitTree);
|
projectRouter.get("/:projectId/circuit-lists/:circuitListId/tree", getCircuitTree);
|
||||||
projectRouter.get("/:projectId/floors", listFloorsByProject);
|
projectRouter.get("/:projectId/floors", listFloorsByProject);
|
||||||
|
|||||||
@@ -49,6 +49,19 @@ export const updateDistributionBoardSchema = z
|
|||||||
})
|
})
|
||||||
.strict();
|
.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
|
export const createFloorSchema = z
|
||||||
.object({
|
.object({
|
||||||
expectedRevision: expectedProjectRevisionSchema,
|
expectedRevision: expectedProjectRevisionSchema,
|
||||||
|
|||||||
@@ -28,7 +28,9 @@ import {
|
|||||||
type DistributionBoardUpdateProjectCommand,
|
type DistributionBoardUpdateProjectCommand,
|
||||||
} from "../src/domain/models/distribution-board-project-command.model.js";
|
} from "../src/domain/models/distribution-board-project-command.model.js";
|
||||||
import {
|
import {
|
||||||
|
copyDistributionBoard,
|
||||||
createDistributionBoard,
|
createDistributionBoard,
|
||||||
|
deleteDistributionBoard,
|
||||||
updateDistributionBoard,
|
updateDistributionBoard,
|
||||||
} from "../src/frontend/utils/api.js";
|
} 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 requests: Array<{ url: string; method: string; body: unknown }> = [];
|
||||||
const originalFetch = globalThis.fetch;
|
const originalFetch = globalThis.fetch;
|
||||||
globalThis.fetch = async (input, init) => {
|
globalThis.fetch = async (input, init) => {
|
||||||
@@ -593,6 +595,17 @@ describe("distribution-board structure project command", () => {
|
|||||||
},
|
},
|
||||||
9
|
9
|
||||||
);
|
);
|
||||||
|
await copyDistributionBoard(
|
||||||
|
"project-1",
|
||||||
|
"board-1",
|
||||||
|
"UV 1 Kopie",
|
||||||
|
10
|
||||||
|
);
|
||||||
|
await deleteDistributionBoard(
|
||||||
|
"project-1",
|
||||||
|
"board-1",
|
||||||
|
11
|
||||||
|
);
|
||||||
} finally {
|
} finally {
|
||||||
globalThis.fetch = originalFetch;
|
globalThis.fetch = originalFetch;
|
||||||
}
|
}
|
||||||
@@ -607,6 +620,21 @@ describe("distribution-board structure project command", () => {
|
|||||||
expectedRevision: 9,
|
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