All first todos completed

This commit is contained in:
Julian Appel 2026-05-01 17:58:14 +02:00
parent 65819900b1
commit 18a4fdd893
29 changed files with 1263 additions and 160 deletions

View file

@ -3,11 +3,14 @@ import { CircuitListRepository } from "../../db/repositories/circuit-list.reposi
import { ConsumerRepository } from "../../db/repositories/consumer.repository.js";
import { DistributionBoardRepository } from "../../db/repositories/distribution-board.repository.js";
import { FloorRepository } from "../../db/repositories/floor.repository.js";
import { ProjectDeviceRepository } from "../../db/repositories/project-device.repository.js";
import { ProjectRepository } from "../../db/repositories/project.repository.js";
import { RoomRepository } from "../../db/repositories/room.repository.js";
import type { Consumer } from "../../domain/models/consumer.model.js";
import { applyLinkedProjectDeviceValues } from "../../domain/services/consumer-linking.service.js";
import { PowerBalanceService } from "../../domain/services/power-balance.service.js";
import {
type CreateConsumerInput,
createConsumerSchema,
updateConsumerSchema,
} from "../../shared/validation/consumer.schemas.js";
@ -16,6 +19,7 @@ const circuitListRepository = new CircuitListRepository();
const consumerRepository = new ConsumerRepository();
const distributionBoardRepository = new DistributionBoardRepository();
const floorRepository = new FloorRepository();
const projectDeviceRepository = new ProjectDeviceRepository();
const projectRepository = new ProjectRepository();
const roomRepository = new RoomRepository();
const powerBalanceService = new PowerBalanceService();
@ -25,6 +29,8 @@ type ConsumerRow = {
projectId: string;
distributionBoardId: string | null;
circuitListId: string | null;
projectDeviceId: string | null;
isLinkedToDevice: number;
roomId: string | null;
circuitNumber: string | null;
description: string | null;
@ -66,6 +72,22 @@ async function validateRoomOwnership(projectId: string, roomId: string | undefin
return roomRepository.existsInProject(projectId, roomId);
}
async function validateProjectDeviceOwnership(projectId: string, projectDeviceId: string | undefined) {
if (!projectDeviceId) {
return true;
}
const row = await projectDeviceRepository.findById(projectId, projectDeviceId);
return Boolean(row);
}
async function applyDeviceLinkIfNeeded(input: CreateConsumerInput): Promise<CreateConsumerInput> {
if (!input.projectDeviceId || !input.isLinkedToDevice) {
return input;
}
const device = await projectDeviceRepository.findById(input.projectId, input.projectDeviceId);
return applyLinkedProjectDeviceValues(input, device);
}
async function resolveCircuitScope(input: {
projectId: string;
distributionBoardId?: string;
@ -125,6 +147,8 @@ function buildConsumerFromRow(
projectId: row.projectId,
distributionBoardId: row.distributionBoardId ?? undefined,
circuitListId: row.circuitListId ?? undefined,
projectDeviceId: row.projectDeviceId ?? undefined,
isLinkedToDevice: Boolean(row.isLinkedToDevice),
roomId: row.roomId ?? undefined,
roomNumber: room?.roomNumber,
roomName: room?.roomName,
@ -195,9 +219,18 @@ export async function createConsumer(req: Request, res: Response) {
return res.status(400).json({ error: parsed.error.flatten() });
}
const [hasValidDistributionBoard, hasValidRoom] = await Promise.all([
validateDistributionBoardOwnership(parsed.data.projectId, parsed.data.distributionBoardId),
validateRoomOwnership(parsed.data.projectId, parsed.data.roomId),
const normalizedData: CreateConsumerInput = {
...parsed.data,
name: parsed.data.name?.trim() || "Unbenannter Eintrag",
quantity: parsed.data.quantity ?? 0,
installedPowerPerUnitKw: parsed.data.installedPowerPerUnitKw ?? 0,
demandFactor: parsed.data.demandFactor ?? 1,
};
const [hasValidDistributionBoard, hasValidRoom, hasValidProjectDevice] = await Promise.all([
validateDistributionBoardOwnership(normalizedData.projectId, normalizedData.distributionBoardId),
validateRoomOwnership(normalizedData.projectId, normalizedData.roomId),
validateProjectDeviceOwnership(normalizedData.projectId, normalizedData.projectDeviceId),
]);
if (!hasValidDistributionBoard) {
return res
@ -207,28 +240,34 @@ export async function createConsumer(req: Request, res: Response) {
if (!hasValidRoom) {
return res.status(400).json({ error: "Room does not belong to the provided project." });
}
if (!hasValidProjectDevice) {
return res.status(400).json({ error: "Project device does not belong to the provided project." });
}
if (normalizedData.isLinkedToDevice && !normalizedData.projectDeviceId) {
return res.status(400).json({ error: "Linked entries require a projectDeviceId." });
}
const resolvedScope = await resolveCircuitScope({
projectId: parsed.data.projectId,
distributionBoardId: parsed.data.distributionBoardId,
circuitListId: parsed.data.circuitListId,
projectId: normalizedData.projectId,
distributionBoardId: normalizedData.distributionBoardId,
circuitListId: normalizedData.circuitListId,
});
if (!resolvedScope.ok) {
return res.status(400).json({ error: resolvedScope.error });
}
const payload = {
...parsed.data,
const payload = await applyDeviceLinkIfNeeded({
...normalizedData,
distributionBoardId: resolvedScope.distributionBoardId,
circuitListId: resolvedScope.circuitListId,
description: parsed.data.description ?? parsed.data.name,
};
description: normalizedData.description ?? normalizedData.name,
});
const created = await consumerRepository.create(payload);
const [project, floors, rooms] = await Promise.all([
projectRepository.findById(parsed.data.projectId),
floorRepository.listByProject(parsed.data.projectId),
roomRepository.listByProject(parsed.data.projectId),
projectRepository.findById(normalizedData.projectId),
floorRepository.listByProject(normalizedData.projectId),
roomRepository.listByProject(normalizedData.projectId),
]);
const roomById = new Map(
rooms.map((room) => [room.id, { floorId: room.floorId, roomName: room.roomName, roomNumber: room.roomNumber }])
@ -269,9 +308,18 @@ export async function updateConsumer(req: Request, res: Response) {
return res.status(400).json({ error: parsed.error.flatten() });
}
const [hasValidDistributionBoard, hasValidRoom] = await Promise.all([
validateDistributionBoardOwnership(parsed.data.projectId, parsed.data.distributionBoardId),
validateRoomOwnership(parsed.data.projectId, parsed.data.roomId),
const normalizedData: CreateConsumerInput = {
...parsed.data,
name: parsed.data.name?.trim() || "Unbenannter Eintrag",
quantity: parsed.data.quantity ?? 0,
installedPowerPerUnitKw: parsed.data.installedPowerPerUnitKw ?? 0,
demandFactor: parsed.data.demandFactor ?? 1,
};
const [hasValidDistributionBoard, hasValidRoom, hasValidProjectDevice] = await Promise.all([
validateDistributionBoardOwnership(normalizedData.projectId, normalizedData.distributionBoardId),
validateRoomOwnership(normalizedData.projectId, normalizedData.roomId),
validateProjectDeviceOwnership(normalizedData.projectId, normalizedData.projectDeviceId),
]);
if (!hasValidDistributionBoard) {
return res
@ -281,23 +329,31 @@ export async function updateConsumer(req: Request, res: Response) {
if (!hasValidRoom) {
return res.status(400).json({ error: "Room does not belong to the provided project." });
}
if (!hasValidProjectDevice) {
return res.status(400).json({ error: "Project device does not belong to the provided project." });
}
if (normalizedData.isLinkedToDevice && !normalizedData.projectDeviceId) {
return res.status(400).json({ error: "Linked entries require a projectDeviceId." });
}
const resolvedScope = await resolveCircuitScope({
projectId: parsed.data.projectId,
distributionBoardId: parsed.data.distributionBoardId,
circuitListId: parsed.data.circuitListId,
projectId: normalizedData.projectId,
distributionBoardId: normalizedData.distributionBoardId,
circuitListId: normalizedData.circuitListId,
});
if (!resolvedScope.ok) {
return res.status(400).json({ error: resolvedScope.error });
}
await consumerRepository.update(consumerId, {
...parsed.data,
const payload = await applyDeviceLinkIfNeeded({
...normalizedData,
distributionBoardId: resolvedScope.distributionBoardId,
circuitListId: resolvedScope.circuitListId,
description: parsed.data.description ?? parsed.data.name,
description: normalizedData.description ?? normalizedData.name,
});
await consumerRepository.update(consumerId, payload);
const row = await consumerRepository.findById(consumerId);
if (!row) {
return res.status(404).json({ error: "Consumer not found" });