forked from jappel/leistungsbilanz-ts
Merge remote-tracking branch 'origin/main' into feature/cable-sizing
This commit is contained in:
commit
598003554a
136 changed files with 27182 additions and 1669 deletions
|
|
@ -6,6 +6,8 @@ import { CircuitRepository } from "../../db/repositories/circuit.repository.js";
|
|||
import { CircuitProtectionDeviceRepository } from "../../db/repositories/circuit-protection-device.repository.js";
|
||||
import { DistributionBoardComponentRepository } from "../../db/repositories/distribution-board-component.repository.js";
|
||||
import { DistributionBoardRepository } from "../../db/repositories/distribution-board.repository.js";
|
||||
import { BuildingRepository } from "../../db/repositories/building.repository.js";
|
||||
import { EstimateBalanceRepository } from "../../db/repositories/estimate-balance.repository.js";
|
||||
import { FloorRepository } from "../../db/repositories/floor.repository.js";
|
||||
import { GlobalDeviceRepository } from "../../db/repositories/global-device.repository.js";
|
||||
import { ProjectDeviceRepository } from "../../db/repositories/project-device.repository.js";
|
||||
|
|
@ -13,6 +15,7 @@ import { ProjectRepository } from "../../db/repositories/project.repository.js";
|
|||
import { RoomRepository } from "../../db/repositories/room.repository.js";
|
||||
import { ExternalCsvConfigurationRepository } from "../../db/repositories/external-csv-configuration.repository.js";
|
||||
import { ExternalModelStateRepository } from "../../db/repositories/external-model-state.repository.js";
|
||||
import { DocumentBrandingRepository } from "../../db/repositories/document-branding.repository.js";
|
||||
|
||||
export const circuitDeviceRowRepository =
|
||||
new CircuitDeviceRowRepository(db);
|
||||
|
|
@ -25,6 +28,8 @@ export const distributionBoardComponentRepository =
|
|||
new DistributionBoardComponentRepository(db);
|
||||
export const distributionBoardRepository =
|
||||
new DistributionBoardRepository(db);
|
||||
export const buildingRepository = new BuildingRepository(db);
|
||||
export const estimateBalanceRepository = new EstimateBalanceRepository(db);
|
||||
export const floorRepository = new FloorRepository(db);
|
||||
export const globalDeviceRepository = new GlobalDeviceRepository(db);
|
||||
export const projectDeviceRepository = new ProjectDeviceRepository(db);
|
||||
|
|
@ -33,3 +38,4 @@ export const roomRepository = new RoomRepository(db);
|
|||
export const externalCsvConfigurationRepository =
|
||||
new ExternalCsvConfigurationRepository(db);
|
||||
export const externalModelStateRepository = new ExternalModelStateRepository(db);
|
||||
export const documentBrandingRepository = new DocumentBrandingRepository(db);
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import { ExternalInitialImportProjectCommandRepository } from "../../db/reposito
|
|||
import { ExternalObjectRowAssignmentProjectCommandRepository } from "../../db/repositories/external-object-row-assignment-project-command.repository.js";
|
||||
import { ExternalObjectNewRowProjectCommandRepository } from "../../db/repositories/external-object-new-row-project-command.repository.js";
|
||||
import { ExternalObjectNewCircuitProjectCommandRepository } from "../../db/repositories/external-object-new-circuit-project-command.repository.js";
|
||||
import { EstimateBalanceProjectCommandRepository } from "../../db/repositories/estimate-balance-project-command.repository.js";
|
||||
|
||||
export const circuitProjectCommandStore = new CircuitProjectCommandRepository(db);
|
||||
export const circuitDeviceRowProjectCommandStore =
|
||||
|
|
@ -80,6 +81,8 @@ export const externalObjectNewRowProjectCommandStore =
|
|||
new ExternalObjectNewRowProjectCommandRepository(db);
|
||||
export const externalObjectNewCircuitProjectCommandStore =
|
||||
new ExternalObjectNewCircuitProjectCommandRepository(db);
|
||||
export const estimateBalanceProjectCommandStore =
|
||||
new EstimateBalanceProjectCommandRepository(db);
|
||||
export const projectCommandService = new ProjectCommandService(
|
||||
circuitProjectCommandStore,
|
||||
circuitDeviceRowProjectCommandStore,
|
||||
|
|
@ -107,5 +110,6 @@ export const projectCommandService = new ProjectCommandService(
|
|||
externalInitialImportProjectCommandStore,
|
||||
externalObjectRowAssignmentProjectCommandStore,
|
||||
externalObjectNewRowProjectCommandStore,
|
||||
externalObjectNewCircuitProjectCommandStore
|
||||
externalObjectNewCircuitProjectCommandStore,
|
||||
estimateBalanceProjectCommandStore
|
||||
);
|
||||
|
|
|
|||
111
src/server/controllers/building.controller.ts
Normal file
111
src/server/controllers/building.controller.ts
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
import type { Request, Response } from "express";
|
||||
import {
|
||||
createProjectBuildingDeleteProjectCommand,
|
||||
createProjectBuildingInsertProjectCommand,
|
||||
createProjectBuildingSnapshot,
|
||||
createProjectBuildingUpdateProjectCommand,
|
||||
} from "../../domain/models/project-location-structure-project-command.model.js";
|
||||
import {
|
||||
createBuildingSchema,
|
||||
deleteProjectLocationSchema,
|
||||
updateBuildingSchema,
|
||||
} from "../../shared/validation/project-structure.schemas.js";
|
||||
import { projectCommandService } from "../composition/project-command-stores.js";
|
||||
import { buildingRepository } from "../composition/application-repositories.js";
|
||||
import { respondWithProjectCommandError } from "./project-command.controller.js";
|
||||
|
||||
export async function listBuildingsByProject(req: Request, res: Response) {
|
||||
const { projectId } = req.params;
|
||||
if (typeof projectId !== "string") {
|
||||
return res.status(400).json({ error: "Invalid projectId" });
|
||||
}
|
||||
|
||||
const result = await buildingRepository.listByProject(projectId);
|
||||
return res.json(result);
|
||||
}
|
||||
|
||||
export async function createBuilding(req: Request, res: Response) {
|
||||
const { projectId } = req.params;
|
||||
if (typeof projectId !== "string") {
|
||||
return res.status(400).json({ error: "Invalid projectId" });
|
||||
}
|
||||
|
||||
const parsed = createBuildingSchema.safeParse(req.body);
|
||||
if (!parsed.success) {
|
||||
return res.status(400).json({ error: parsed.error.flatten() });
|
||||
}
|
||||
|
||||
const existingBuildings = await buildingRepository.listByProject(projectId);
|
||||
const nextSortOrder = existingBuildings.length
|
||||
? Math.max(...existingBuildings.map((building) => building.sortOrder)) + 1
|
||||
: 0;
|
||||
const building = createProjectBuildingSnapshot(
|
||||
projectId,
|
||||
parsed.data.name,
|
||||
nextSortOrder
|
||||
);
|
||||
try {
|
||||
const result = projectCommandService.executeUser({
|
||||
projectId,
|
||||
expectedRevision: parsed.data.expectedRevision,
|
||||
description: "Gebäude anlegen",
|
||||
command: createProjectBuildingInsertProjectCommand(building),
|
||||
});
|
||||
return res.status(201).json({ ...result, building });
|
||||
} catch (error) {
|
||||
return respondWithProjectCommandError(error, res);
|
||||
}
|
||||
}
|
||||
|
||||
export async function updateBuilding(req: Request, res: Response) {
|
||||
const { projectId, buildingId } = req.params;
|
||||
if (typeof projectId !== "string" || typeof buildingId !== "string") {
|
||||
return res.status(400).json({ error: "Invalid parameters" });
|
||||
}
|
||||
const parsed = updateBuildingSchema.safeParse(req.body);
|
||||
if (!parsed.success) {
|
||||
return res.status(400).json({ error: parsed.error.flatten() });
|
||||
}
|
||||
const current = await buildingRepository.findById(projectId, buildingId);
|
||||
if (!current) {
|
||||
return res.status(404).json({ error: "Building not found" });
|
||||
}
|
||||
const target = { ...current, name: parsed.data.name };
|
||||
try {
|
||||
const result = projectCommandService.executeUser({
|
||||
projectId,
|
||||
expectedRevision: parsed.data.expectedRevision,
|
||||
description: "Gebäude bearbeiten",
|
||||
command: createProjectBuildingUpdateProjectCommand(current, target),
|
||||
});
|
||||
return res.json({ ...result, building: target });
|
||||
} catch (error) {
|
||||
return respondWithProjectCommandError(error, res);
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteBuilding(req: Request, res: Response) {
|
||||
const { projectId, buildingId } = req.params;
|
||||
if (typeof projectId !== "string" || typeof buildingId !== "string") {
|
||||
return res.status(400).json({ error: "Invalid parameters" });
|
||||
}
|
||||
const parsed = deleteProjectLocationSchema.safeParse(req.body);
|
||||
if (!parsed.success) {
|
||||
return res.status(400).json({ error: parsed.error.flatten() });
|
||||
}
|
||||
const current = await buildingRepository.findById(projectId, buildingId);
|
||||
if (!current) {
|
||||
return res.status(404).json({ error: "Building not found" });
|
||||
}
|
||||
try {
|
||||
const result = projectCommandService.executeUser({
|
||||
projectId,
|
||||
expectedRevision: parsed.data.expectedRevision,
|
||||
description: "Gebäude löschen",
|
||||
command: createProjectBuildingDeleteProjectCommand(current),
|
||||
});
|
||||
return res.json(result);
|
||||
} catch (error) {
|
||||
return respondWithProjectCommandError(error, res);
|
||||
}
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@ import {
|
|||
createDistributionBoardInsertSubtreeProjectCommand,
|
||||
} from "../../domain/models/distribution-board-subtree-project-command.model.js";
|
||||
import { cloneDistributionBoardSubtree } from "../../domain/models/distribution-board-subtree-snapshot.model.js";
|
||||
import { distributionBoardInfoFields } from "../../shared/constants/distribution-board.js";
|
||||
import {
|
||||
copyDistributionBoardSchema,
|
||||
createDistributionBoardSchema,
|
||||
|
|
@ -64,6 +65,10 @@ export async function createDistributionBoard(req: Request, res: Response) {
|
|||
distributionBoard: {
|
||||
...structure.distributionBoard,
|
||||
simultaneityFactor: 1,
|
||||
boardNumber: null,
|
||||
location: null,
|
||||
fedFrom: null,
|
||||
remark: null,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
|
|
@ -86,6 +91,13 @@ export async function updateDistributionBoard(
|
|||
if (!parsed.success) {
|
||||
return res.status(400).json({ error: parsed.error.flatten() });
|
||||
}
|
||||
// Info fields are optional in the request; only submitted fields become
|
||||
// part of the command and therefore of its inverse.
|
||||
const infoPatch = Object.fromEntries(
|
||||
distributionBoardInfoFields
|
||||
.filter((field) => parsed.data[field] !== undefined)
|
||||
.map((field) => [field, parsed.data[field]])
|
||||
);
|
||||
try {
|
||||
const result = projectCommandService.executeUser({
|
||||
projectId,
|
||||
|
|
@ -97,6 +109,7 @@ export async function updateDistributionBoard(
|
|||
floorId: parsed.data.floorId,
|
||||
supplyType: parsed.data.supplyType,
|
||||
simultaneityFactor: parsed.data.simultaneityFactor,
|
||||
...infoPatch,
|
||||
}
|
||||
),
|
||||
});
|
||||
|
|
|
|||
100
src/server/controllers/document-branding.controller.ts
Normal file
100
src/server/controllers/document-branding.controller.ts
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
import type { Request, Response } from "express";
|
||||
import type { DocumentBrandingAsset } from "../../db/schema/document-branding-assets.js";
|
||||
import { officeProfileFields } from "../../shared/constants/project-cover.js";
|
||||
import {
|
||||
maxDocumentLogoBytes,
|
||||
updateOfficeProfileSchema,
|
||||
uploadDocumentLogoSchema,
|
||||
type DocumentLogoMimeType,
|
||||
} from "../../shared/validation/document-branding.schemas.js";
|
||||
import { documentBrandingRepository } from "../composition/application-repositories.js";
|
||||
|
||||
const logoKey = "logo";
|
||||
|
||||
export function getDocumentBrandingHandler(_req: Request, res: Response) {
|
||||
const logo = documentBrandingRepository.get(logoKey);
|
||||
return res.json({
|
||||
logo: logo ? toLogoMetadata(logo) : null,
|
||||
office: documentBrandingRepository.getSettings(officeProfileFields),
|
||||
});
|
||||
}
|
||||
|
||||
export function updateOfficeProfileHandler(req: Request, res: Response) {
|
||||
const parsed = updateOfficeProfileSchema.safeParse(req.body);
|
||||
if (!parsed.success) {
|
||||
return res.status(400).json({ error: parsed.error.flatten() });
|
||||
}
|
||||
documentBrandingRepository.setSettings(parsed.data);
|
||||
return res.json({
|
||||
office: documentBrandingRepository.getSettings(officeProfileFields),
|
||||
});
|
||||
}
|
||||
|
||||
export function getDocumentLogoHandler(_req: Request, res: Response) {
|
||||
const logo = documentBrandingRepository.get(logoKey);
|
||||
if (!logo) {
|
||||
return res.status(404).json({ error: "No logo uploaded" });
|
||||
}
|
||||
res.setHeader("Content-Type", logo.mimeType);
|
||||
res.setHeader("Cache-Control", "no-cache");
|
||||
res.setHeader("X-Content-Type-Options", "nosniff");
|
||||
// SVG uploads are rendered through <img> only; forbid script execution if
|
||||
// the file is ever opened directly.
|
||||
res.setHeader(
|
||||
"Content-Security-Policy",
|
||||
"default-src 'none'; style-src 'unsafe-inline'; img-src data:"
|
||||
);
|
||||
return res.send(logo.data);
|
||||
}
|
||||
|
||||
export function uploadDocumentLogoHandler(req: Request, res: Response) {
|
||||
const parsed = uploadDocumentLogoSchema.safeParse(req.body);
|
||||
if (!parsed.success) {
|
||||
return res.status(400).json({ error: parsed.error.flatten() });
|
||||
}
|
||||
const data = Buffer.from(parsed.data.contentBase64, "base64");
|
||||
if (data.length === 0 || data.length > maxDocumentLogoBytes) {
|
||||
return res
|
||||
.status(400)
|
||||
.json({ error: "Das Logo darf höchstens 2 MB groß sein." });
|
||||
}
|
||||
if (!matchesMimeType(data, parsed.data.mimeType)) {
|
||||
return res.status(400).json({
|
||||
error: "Der Dateiinhalt passt nicht zum angegebenen Bildformat.",
|
||||
});
|
||||
}
|
||||
const logo = documentBrandingRepository.upsert({
|
||||
key: logoKey,
|
||||
mimeType: parsed.data.mimeType,
|
||||
fileName: parsed.data.fileName,
|
||||
data,
|
||||
});
|
||||
return res.json({ logo: toLogoMetadata(logo) });
|
||||
}
|
||||
|
||||
export function deleteDocumentLogoHandler(_req: Request, res: Response) {
|
||||
documentBrandingRepository.delete(logoKey);
|
||||
return res.status(204).end();
|
||||
}
|
||||
|
||||
function toLogoMetadata(logo: DocumentBrandingAsset) {
|
||||
return {
|
||||
mimeType: logo.mimeType,
|
||||
fileName: logo.fileName,
|
||||
sizeBytes: logo.data.length,
|
||||
updatedAtIso: logo.updatedAt.toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
function matchesMimeType(data: Buffer, mimeType: DocumentLogoMimeType) {
|
||||
if (mimeType === "image/png") {
|
||||
return data
|
||||
.subarray(0, 8)
|
||||
.equals(Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]));
|
||||
}
|
||||
if (mimeType === "image/jpeg") {
|
||||
return data[0] === 0xff && data[1] === 0xd8 && data[2] === 0xff;
|
||||
}
|
||||
const head = data.subarray(0, 2048).toString("utf8").toLowerCase();
|
||||
return head.includes("<svg");
|
||||
}
|
||||
180
src/server/controllers/estimate-balance.controller.ts
Normal file
180
src/server/controllers/estimate-balance.controller.ts
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
import type { Request, Response } from "express";
|
||||
import { ZodError } from "zod";
|
||||
import {
|
||||
createEstimateBalanceDeleteProjectCommand,
|
||||
createEstimateBalanceInsertProjectCommand,
|
||||
createEstimateBalanceSnapshot,
|
||||
createEstimateBalanceUpdateProjectCommand,
|
||||
parseEstimateBalanceSnapshot,
|
||||
sameEstimateBalance,
|
||||
} from "../../estimate-balance/domain/estimate-balance-command.model.js";
|
||||
import {
|
||||
createEstimateBalanceRequestSchema,
|
||||
deleteEstimateBalanceRequestSchema,
|
||||
updateEstimateBalanceRequestSchema,
|
||||
} from "../../estimate-balance/api/estimate-balance-request.schemas.js";
|
||||
import { compareEstimateWithDetailed } from "../../shared/estimate-balance/estimate-balance.js";
|
||||
import { estimateBalanceRepository } from "../composition/application-repositories.js";
|
||||
import { projectCommandService } from "../composition/project-command-stores.js";
|
||||
import { respondWithProjectCommandError } from "./project-command.controller.js";
|
||||
|
||||
function params(req: Request) {
|
||||
const { projectId, balanceId } = req.params;
|
||||
return {
|
||||
projectId: typeof projectId === "string" ? projectId : null,
|
||||
balanceId: typeof balanceId === "string" ? balanceId : null,
|
||||
};
|
||||
}
|
||||
|
||||
export function listEstimateBalances(req: Request, res: Response) {
|
||||
const { projectId } = params(req);
|
||||
if (!projectId) {
|
||||
return res.status(400).json({ error: "Ungültige Projekt-ID." });
|
||||
}
|
||||
return res.json(estimateBalanceRepository.listByProject(projectId));
|
||||
}
|
||||
|
||||
export function getEstimateBalance(req: Request, res: Response) {
|
||||
const { projectId, balanceId } = params(req);
|
||||
if (!projectId || !balanceId) {
|
||||
return res.status(400).json({ error: "Ungültige Parameter." });
|
||||
}
|
||||
const balance = estimateBalanceRepository.findById(projectId, balanceId);
|
||||
if (!balance) {
|
||||
return res.status(404).json({ error: "Grobe Leistungsbilanz nicht gefunden." });
|
||||
}
|
||||
return res.json(balance);
|
||||
}
|
||||
|
||||
export function createEstimateBalance(req: Request, res: Response) {
|
||||
const { projectId } = params(req);
|
||||
if (!projectId) {
|
||||
return res.status(400).json({ error: "Ungültige Projekt-ID." });
|
||||
}
|
||||
const parsed = createEstimateBalanceRequestSchema.safeParse(req.body);
|
||||
if (!parsed.success) {
|
||||
return res.status(400).json({ error: parsed.error.flatten() });
|
||||
}
|
||||
let template: Parameters<typeof createEstimateBalanceSnapshot>[0]["template"] =
|
||||
parsed.data.template === "copy" ? "empty" : parsed.data.template;
|
||||
if (parsed.data.template === "copy") {
|
||||
const source = estimateBalanceRepository.findById(
|
||||
projectId,
|
||||
parsed.data.copyFromBalanceId!
|
||||
);
|
||||
if (!source) {
|
||||
return res
|
||||
.status(404)
|
||||
.json({ error: "Der zu kopierende Stand wurde nicht gefunden." });
|
||||
}
|
||||
template = { copyFrom: source };
|
||||
}
|
||||
try {
|
||||
const balance = createEstimateBalanceSnapshot({
|
||||
projectId,
|
||||
name: parsed.data.name,
|
||||
workStage: parsed.data.workStage ?? null,
|
||||
dateIso: parsed.data.dateIso ?? null,
|
||||
sortOrder: estimateBalanceRepository.nextSortOrder(projectId),
|
||||
template,
|
||||
});
|
||||
const result = projectCommandService.executeUser({
|
||||
projectId,
|
||||
expectedRevision: parsed.data.expectedRevision,
|
||||
description: "Grobe Leistungsbilanz anlegen",
|
||||
command: createEstimateBalanceInsertProjectCommand(balance),
|
||||
});
|
||||
return res.status(201).json({ ...result, balance });
|
||||
} catch (error) {
|
||||
return respondWithProjectCommandError(error, res);
|
||||
}
|
||||
}
|
||||
|
||||
export function updateEstimateBalance(req: Request, res: Response) {
|
||||
const { projectId, balanceId } = params(req);
|
||||
if (!projectId || !balanceId) {
|
||||
return res.status(400).json({ error: "Ungültige Parameter." });
|
||||
}
|
||||
const parsed = updateEstimateBalanceRequestSchema.safeParse(req.body);
|
||||
if (!parsed.success) {
|
||||
return res.status(400).json({ error: parsed.error.flatten() });
|
||||
}
|
||||
let target;
|
||||
try {
|
||||
target = parseEstimateBalanceSnapshot(parsed.data.balance);
|
||||
} catch (error) {
|
||||
return res.status(400).json({
|
||||
error: "Die grobe Leistungsbilanz enthält ungültige Werte.",
|
||||
issues: error instanceof ZodError ? error.issues : undefined,
|
||||
});
|
||||
}
|
||||
if (target.id !== balanceId || target.projectId !== projectId) {
|
||||
return res
|
||||
.status(400)
|
||||
.json({ error: "Die Bilanz gehört nicht zu diesem Projekt oder dieser Adresse." });
|
||||
}
|
||||
const current = estimateBalanceRepository.findById(projectId, balanceId);
|
||||
if (!current) {
|
||||
return res.status(404).json({ error: "Grobe Leistungsbilanz nicht gefunden." });
|
||||
}
|
||||
if (sameEstimateBalance(current, target)) {
|
||||
return res.json({ unchanged: true, balance: current });
|
||||
}
|
||||
try {
|
||||
const result = projectCommandService.executeUser({
|
||||
projectId,
|
||||
expectedRevision: parsed.data.expectedRevision,
|
||||
description: "Grobe Leistungsbilanz bearbeiten",
|
||||
command: createEstimateBalanceUpdateProjectCommand(current, target),
|
||||
});
|
||||
return res.json({
|
||||
...result,
|
||||
balance: estimateBalanceRepository.findById(projectId, balanceId),
|
||||
});
|
||||
} catch (error) {
|
||||
return respondWithProjectCommandError(error, res);
|
||||
}
|
||||
}
|
||||
|
||||
export function deleteEstimateBalance(req: Request, res: Response) {
|
||||
const { projectId, balanceId } = params(req);
|
||||
if (!projectId || !balanceId) {
|
||||
return res.status(400).json({ error: "Ungültige Parameter." });
|
||||
}
|
||||
const parsed = deleteEstimateBalanceRequestSchema.safeParse(req.body);
|
||||
if (!parsed.success) {
|
||||
return res.status(400).json({ error: parsed.error.flatten() });
|
||||
}
|
||||
const current = estimateBalanceRepository.findById(projectId, balanceId);
|
||||
if (!current) {
|
||||
return res.status(404).json({ error: "Grobe Leistungsbilanz nicht gefunden." });
|
||||
}
|
||||
try {
|
||||
const result = projectCommandService.executeUser({
|
||||
projectId,
|
||||
expectedRevision: parsed.data.expectedRevision,
|
||||
description: "Grobe Leistungsbilanz löschen",
|
||||
command: createEstimateBalanceDeleteProjectCommand(current),
|
||||
});
|
||||
return res.json(result);
|
||||
} catch (error) {
|
||||
return respondWithProjectCommandError(error, res);
|
||||
}
|
||||
}
|
||||
|
||||
export function getEstimateComparison(req: Request, res: Response) {
|
||||
const { projectId, balanceId } = params(req);
|
||||
if (!projectId || !balanceId) {
|
||||
return res.status(400).json({ error: "Ungültige Parameter." });
|
||||
}
|
||||
const balance = estimateBalanceRepository.findById(projectId, balanceId);
|
||||
if (!balance) {
|
||||
return res.status(404).json({ error: "Grobe Leistungsbilanz nicht gefunden." });
|
||||
}
|
||||
return res.json(
|
||||
compareEstimateWithDetailed(
|
||||
balance,
|
||||
estimateBalanceRepository.listDetailedDeviceRowPower(projectId)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
@ -42,7 +42,8 @@ export async function createFloor(req: Request, res: Response) {
|
|||
const floor = createProjectFloorSnapshot(
|
||||
projectId,
|
||||
parsed.data.name,
|
||||
nextSortOrder
|
||||
nextSortOrder,
|
||||
parsed.data.buildingId ?? null
|
||||
);
|
||||
try {
|
||||
const result = projectCommandService.executeUser({
|
||||
|
|
@ -70,7 +71,14 @@ export async function updateFloor(req: Request, res: Response) {
|
|||
if (!current) {
|
||||
return res.status(404).json({ error: "Floor not found" });
|
||||
}
|
||||
const target = { ...current, name: parsed.data.name };
|
||||
const target = {
|
||||
...current,
|
||||
name: parsed.data.name,
|
||||
buildingId:
|
||||
parsed.data.buildingId === undefined
|
||||
? current.buildingId
|
||||
: parsed.data.buildingId,
|
||||
};
|
||||
try {
|
||||
const result = projectCommandService.executeUser({
|
||||
projectId,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,10 @@ import {
|
|||
updateProjectSettingsSchema,
|
||||
} from "../../shared/validation/project-structure.schemas.js";
|
||||
import { createProjectSettingsUpdateProjectCommand } from "../../domain/models/project-settings-project-command.model.js";
|
||||
import {
|
||||
projectCoverFields,
|
||||
type ProjectCoverField,
|
||||
} from "../../shared/constants/project-cover.js";
|
||||
import { projectCommandService } from "../composition/project-command-stores.js";
|
||||
import { projectRepository } from "../composition/application-repositories.js";
|
||||
import { respondWithProjectCommandError } from "./project-command.controller.js";
|
||||
|
|
@ -46,12 +50,27 @@ export async function updateProjectSettings(req: Request, res: Response) {
|
|||
}
|
||||
|
||||
const { expectedRevision, ...settings } = parsed.data;
|
||||
const current = await projectRepository.findById(projectId);
|
||||
if (!current) {
|
||||
return res.status(404).json({ error: "Project not found" });
|
||||
}
|
||||
// Cover fields are optional in the request and keep their current value
|
||||
// when omitted. `expectedRevision` rejects a project changed meanwhile.
|
||||
const coverValues = Object.fromEntries(
|
||||
projectCoverFields.map((field) => [
|
||||
field,
|
||||
settings[field] === undefined ? current[field] : settings[field],
|
||||
])
|
||||
) as Record<ProjectCoverField, string | null>;
|
||||
try {
|
||||
const result = projectCommandService.executeUser({
|
||||
projectId,
|
||||
expectedRevision,
|
||||
description: "Projekteinstellungen bearbeiten",
|
||||
command: createProjectSettingsUpdateProjectCommand(settings),
|
||||
command: createProjectSettingsUpdateProjectCommand({
|
||||
...settings,
|
||||
...coverValues,
|
||||
}),
|
||||
});
|
||||
const project = await projectRepository.findById(projectId);
|
||||
if (!project) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import express from "express";
|
||||
import { globalDeviceRouter } from "./routes/global-device.routes.js";
|
||||
import { projectDeviceRouter } from "./routes/project-device.routes.js";
|
||||
import { documentBrandingRouter } from "./routes/document-branding.routes.js";
|
||||
import { projectRouter } from "./routes/project.routes.js";
|
||||
import { cableSizingRouter } from "./routes/cable-sizing.routes.js";
|
||||
import { errorMiddleware } from "./middleware/error.middleware.js";
|
||||
|
|
@ -53,6 +54,7 @@ app.use("/api/cable-sizing", cableSizingRouter);
|
|||
app.use("/api/projects", projectRouter);
|
||||
app.use("/api/global-devices", globalDeviceRouter);
|
||||
app.use("/api/project-devices", projectDeviceRouter);
|
||||
app.use("/api/document-branding", documentBrandingRouter);
|
||||
|
||||
app.use(errorMiddleware);
|
||||
|
||||
|
|
|
|||
10
src/server/routes/document-branding.routes.ts
Normal file
10
src/server/routes/document-branding.routes.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import express from "express";
|
||||
import * as documentBrandingController from "../controllers/document-branding.controller.js";
|
||||
|
||||
export const documentBrandingRouter = express.Router();
|
||||
|
||||
documentBrandingRouter.get("/", documentBrandingController.getDocumentBrandingHandler);
|
||||
documentBrandingRouter.put("/office", documentBrandingController.updateOfficeProfileHandler);
|
||||
documentBrandingRouter.get("/logo", documentBrandingController.getDocumentLogoHandler);
|
||||
documentBrandingRouter.put("/logo", documentBrandingController.uploadDocumentLogoHandler);
|
||||
documentBrandingRouter.delete("/logo", documentBrandingController.deleteDocumentLogoHandler);
|
||||
|
|
@ -13,8 +13,22 @@ import {
|
|||
updateDistributionBoard,
|
||||
} from "../controllers/distribution-board.controller.js";
|
||||
import { listCircuitListsByProject } from "../controllers/circuit-list.controller.js";
|
||||
import {
|
||||
createBuilding,
|
||||
deleteBuilding,
|
||||
listBuildingsByProject,
|
||||
updateBuilding,
|
||||
} from "../controllers/building.controller.js";
|
||||
import { createFloor, deleteFloor, listFloorsByProject, updateFloor } from "../controllers/floor.controller.js";
|
||||
import { createRoom, deleteRoom, listRoomsByProject, updateRoom } from "../controllers/room.controller.js";
|
||||
import {
|
||||
createEstimateBalance,
|
||||
deleteEstimateBalance,
|
||||
getEstimateBalance,
|
||||
getEstimateComparison,
|
||||
listEstimateBalances,
|
||||
updateEstimateBalance,
|
||||
} from "../controllers/estimate-balance.controller.js";
|
||||
import { getCircuitTree } from "../controllers/circuit-tree.controller.js";
|
||||
import { getNextCircuitIdentifier } from "../controllers/circuit.controller.js";
|
||||
import {
|
||||
|
|
@ -100,6 +114,10 @@ projectRouter.get(
|
|||
"/:projectId/circuit-sections/:sectionId/next-identifier",
|
||||
getNextCircuitIdentifier
|
||||
);
|
||||
projectRouter.get("/:projectId/buildings", listBuildingsByProject);
|
||||
projectRouter.post("/:projectId/buildings", createBuilding);
|
||||
projectRouter.put("/:projectId/buildings/:buildingId", updateBuilding);
|
||||
projectRouter.delete("/:projectId/buildings/:buildingId", deleteBuilding);
|
||||
projectRouter.get("/:projectId/floors", listFloorsByProject);
|
||||
projectRouter.post("/:projectId/floors", createFloor);
|
||||
projectRouter.put("/:projectId/floors/:floorId", updateFloor);
|
||||
|
|
@ -108,3 +126,12 @@ projectRouter.get("/:projectId/rooms", listRoomsByProject);
|
|||
projectRouter.post("/:projectId/rooms", createRoom);
|
||||
projectRouter.put("/:projectId/rooms/:roomId", updateRoom);
|
||||
projectRouter.delete("/:projectId/rooms/:roomId", deleteRoom);
|
||||
projectRouter.get("/:projectId/estimate-balances", listEstimateBalances);
|
||||
projectRouter.post("/:projectId/estimate-balances", createEstimateBalance);
|
||||
projectRouter.get("/:projectId/estimate-balances/:balanceId", getEstimateBalance);
|
||||
projectRouter.put("/:projectId/estimate-balances/:balanceId", updateEstimateBalance);
|
||||
projectRouter.delete("/:projectId/estimate-balances/:balanceId", deleteEstimateBalance);
|
||||
projectRouter.get(
|
||||
"/:projectId/estimate-balances/:balanceId/comparison",
|
||||
getEstimateComparison
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue