Added 1B, 2 and added bootstrap again for site

This commit is contained in:
2026-05-03 22:04:45 +02:00
parent b8995b3a1b
commit d1ce485572
37 changed files with 1842 additions and 89 deletions
@@ -0,0 +1,61 @@
import type { Request, Response } from "express";
import { CircuitWriteService } from "../../domain/services/circuit-write.service.js";
import {
createCircuitDeviceRowSchema,
updateCircuitDeviceRowSchema,
} from "../../shared/validation/circuit.schemas.js";
const circuitWriteService = new CircuitWriteService();
export async function createCircuitDeviceRow(req: Request, res: Response) {
const { circuitId } = req.params;
if (typeof circuitId !== "string") {
return res.status(400).json({ error: "Invalid circuitId" });
}
const parsed = createCircuitDeviceRowSchema.safeParse(req.body);
if (!parsed.success) {
return res.status(400).json({ error: parsed.error.flatten() });
}
try {
const created = await circuitWriteService.createDeviceRow(circuitId, parsed.data);
return res.status(201).json(created);
} catch (error) {
return res.status(400).json({ error: error instanceof Error ? error.message : "Failed to create device row." });
}
}
export async function updateCircuitDeviceRow(req: Request, res: Response) {
const { rowId } = req.params;
if (typeof rowId !== "string") {
return res.status(400).json({ error: "Invalid rowId" });
}
const parsed = updateCircuitDeviceRowSchema.safeParse(req.body);
if (!parsed.success) {
return res.status(400).json({ error: parsed.error.flatten() });
}
try {
const updated = await circuitWriteService.updateDeviceRow(rowId, parsed.data);
return res.json(updated);
} catch (error) {
return res.status(400).json({ error: error instanceof Error ? error.message : "Failed to update device row." });
}
}
export async function deleteCircuitDeviceRow(req: Request, res: Response) {
const { rowId } = req.params;
if (typeof rowId !== "string") {
return res.status(400).json({ error: "Invalid rowId" });
}
try {
await circuitWriteService.deleteDeviceRow(rowId);
return res.status(204).send();
} catch (error) {
return res.status(400).json({ error: error instanceof Error ? error.message : "Failed to delete device row." });
}
}
@@ -0,0 +1,19 @@
import type { Request, Response } from "express";
import { CircuitWriteService } from "../../domain/services/circuit-write.service.js";
const circuitWriteService = new CircuitWriteService();
export async function renumberCircuitSection(req: Request, res: Response) {
const { sectionId } = req.params;
if (typeof sectionId !== "string") {
return res.status(400).json({ error: "Invalid sectionId" });
}
try {
const updatedCircuits = await circuitWriteService.renumberSection(sectionId);
return res.json({ sectionId, circuits: updatedCircuits });
} catch (error) {
return res.status(400).json({ error: error instanceof Error ? error.message : "Failed to renumber section." });
}
}
@@ -3,17 +3,26 @@ import { CircuitRepository } from "../../db/repositories/circuit.repository.js";
import { CircuitDeviceRowRepository } from "../../db/repositories/circuit-device-row.repository.js";
import { CircuitListRepository } from "../../db/repositories/circuit-list.repository.js";
import { CircuitSectionRepository } from "../../db/repositories/circuit-section.repository.js";
import {
calculateCircuitTotalPower,
calculateRowTotalPower,
} from "../../domain/calculations/circuit-power-calculation.js";
import type { CircuitTreeResponse } from "../../domain/models/circuit-tree.model.js";
import { LegacyConsumerMigrationService } from "../../domain/services/legacy-consumer-migration.service.js";
const circuitListRepository = new CircuitListRepository();
const circuitSectionRepository = new CircuitSectionRepository();
const circuitRepository = new CircuitRepository();
const circuitDeviceRowRepository = new CircuitDeviceRowRepository();
const legacyConsumerMigrationService = new LegacyConsumerMigrationService();
function rowTotalPower(quantity: number, powerPerUnit: number, simultaneityFactor: number): number {
return quantity * powerPerUnit * simultaneityFactor;
export function isMissingCircuitTreeSchemaError(error: unknown): boolean {
if (!(error instanceof Error)) {
return false;
}
return (
error.message.includes("no such table: circuit_sections") ||
error.message.includes("no such table: circuits") ||
error.message.includes("no such table: circuit_device_rows")
);
}
export async function getCircuitTree(req: Request, res: Response) {
@@ -27,87 +36,97 @@ export async function getCircuitTree(req: Request, res: Response) {
return res.status(404).json({ error: "Circuit list not found" });
}
const migrationReport = await legacyConsumerMigrationService.migrateCircuitList(projectId, circuitListId);
const sections = await circuitSectionRepository.listByCircuitList(circuitListId);
const circuits = await circuitRepository.listByCircuitList(circuitListId);
const rows = await circuitDeviceRowRepository.listByCircuitList(circuits.map((entry) => entry.id));
try {
const sections = await circuitSectionRepository.listByCircuitList(circuitListId);
const circuits = await circuitRepository.listByCircuitList(circuitListId);
const rows = await circuitDeviceRowRepository.listByCircuitList(circuits.map((entry) => entry.id));
const sectionById = new Map(sections.map((section) => [section.id, section]));
const rowsByCircuitId = new Map<string, typeof rows>();
for (const row of rows) {
if (!rowsByCircuitId.has(row.circuitId)) {
rowsByCircuitId.set(row.circuitId, []);
const sectionById = new Map(sections.map((section) => [section.id, section]));
const rowsByCircuitId = new Map<string, typeof rows>();
for (const row of rows) {
if (!rowsByCircuitId.has(row.circuitId)) {
rowsByCircuitId.set(row.circuitId, []);
}
rowsByCircuitId.get(row.circuitId)!.push(row);
}
rowsByCircuitId.get(row.circuitId)!.push(row);
}
const tree: CircuitTreeResponse = {
circuitListId,
sections: sections.map((section) => ({
id: section.id,
key: section.key,
displayName: section.displayName,
prefix: section.prefix,
sortOrder: section.sortOrder,
circuits: [],
})),
};
const sectionBlocks = new Map(tree.sections.map((section) => [section.id, section]));
const tree: CircuitTreeResponse = {
circuitListId,
sections: sections.map((section) => ({
id: section.id,
key: section.key,
displayName: section.displayName,
prefix: section.prefix,
sortOrder: section.sortOrder,
circuits: [],
})),
};
const sectionBlocks = new Map(tree.sections.map((section) => [section.id, section]));
for (const circuit of circuits) {
const section = sectionById.get(circuit.sectionId);
if (!section || section.circuitListId !== circuit.circuitListId) {
continue;
for (const circuit of circuits) {
const section = sectionById.get(circuit.sectionId);
if (!section || section.circuitListId !== circuit.circuitListId) {
continue;
}
const deviceRows = (rowsByCircuitId.get(circuit.id) ?? []).map((row) => ({
id: row.id,
linkedProjectDeviceId: row.linkedProjectDeviceId ?? undefined,
legacyConsumerId: row.legacyConsumerId ?? undefined,
sortOrder: row.sortOrder,
name: row.name,
displayName: row.displayName,
phaseType: row.phaseType ?? undefined,
connectionKind: row.connectionKind ?? undefined,
costGroup: row.costGroup ?? undefined,
category: row.category ?? undefined,
level: row.level ?? undefined,
roomId: row.roomId ?? undefined,
roomNumberSnapshot: row.roomNumberSnapshot ?? undefined,
roomNameSnapshot: row.roomNameSnapshot ?? undefined,
quantity: row.quantity,
powerPerUnit: row.powerPerUnit,
simultaneityFactor: row.simultaneityFactor,
cosPhi: row.cosPhi ?? undefined,
remark: row.remark ?? undefined,
overriddenFields: row.overriddenFields ?? undefined,
rowTotalPower: calculateRowTotalPower(row.quantity, row.powerPerUnit, row.simultaneityFactor),
}));
const circuitTotalPower = calculateCircuitTotalPower(deviceRows);
sectionBlocks.get(section.id)?.circuits.push({
id: circuit.id,
circuitListId: circuit.circuitListId,
sectionId: circuit.sectionId,
equipmentIdentifier: circuit.equipmentIdentifier,
displayName: circuit.displayName ?? undefined,
sortOrder: circuit.sortOrder,
protectionType: circuit.protectionType ?? undefined,
protectionRatedCurrent: circuit.protectionRatedCurrent ?? undefined,
protectionCharacteristic: circuit.protectionCharacteristic ?? undefined,
cableType: circuit.cableType ?? undefined,
cableCrossSection: circuit.cableCrossSection ?? undefined,
cableLength: circuit.cableLength ?? undefined,
rcdAssignment: circuit.rcdAssignment ?? undefined,
terminalDesignation: circuit.terminalDesignation ?? undefined,
voltage: circuit.voltage ?? undefined,
status: circuit.status ?? undefined,
isReserve: Boolean(circuit.isReserve),
remark: circuit.remark ?? undefined,
circuitTotalPower,
deviceRows,
});
}
const deviceRows = (rowsByCircuitId.get(circuit.id) ?? []).map((row) => ({
id: row.id,
linkedProjectDeviceId: row.linkedProjectDeviceId ?? undefined,
legacyConsumerId: row.legacyConsumerId ?? undefined,
sortOrder: row.sortOrder,
name: row.name,
displayName: row.displayName,
phaseType: row.phaseType ?? undefined,
connectionKind: row.connectionKind ?? undefined,
costGroup: row.costGroup ?? undefined,
category: row.category ?? undefined,
level: row.level ?? undefined,
roomId: row.roomId ?? undefined,
roomNumberSnapshot: row.roomNumberSnapshot ?? undefined,
roomNameSnapshot: row.roomNameSnapshot ?? undefined,
quantity: row.quantity,
powerPerUnit: row.powerPerUnit,
simultaneityFactor: row.simultaneityFactor,
cosPhi: row.cosPhi ?? undefined,
remark: row.remark ?? undefined,
overriddenFields: row.overriddenFields ?? undefined,
rowTotalPower: rowTotalPower(row.quantity, row.powerPerUnit, row.simultaneityFactor),
}));
const circuitTotalPower = deviceRows.reduce((sum, row) => sum + row.rowTotalPower, 0);
sectionBlocks.get(section.id)?.circuits.push({
id: circuit.id,
circuitListId: circuit.circuitListId,
sectionId: circuit.sectionId,
equipmentIdentifier: circuit.equipmentIdentifier,
displayName: circuit.displayName ?? undefined,
sortOrder: circuit.sortOrder,
protectionType: circuit.protectionType ?? undefined,
protectionRatedCurrent: circuit.protectionRatedCurrent ?? undefined,
protectionCharacteristic: circuit.protectionCharacteristic ?? undefined,
cableType: circuit.cableType ?? undefined,
cableCrossSection: circuit.cableCrossSection ?? undefined,
cableLength: circuit.cableLength ?? undefined,
rcdAssignment: circuit.rcdAssignment ?? undefined,
terminalDesignation: circuit.terminalDesignation ?? undefined,
voltage: circuit.voltage ?? undefined,
status: circuit.status ?? undefined,
isReserve: Boolean(circuit.isReserve),
remark: circuit.remark ?? undefined,
circuitTotalPower,
deviceRows,
});
return res.json(tree);
} catch (error) {
if (isMissingCircuitTreeSchemaError(error)) {
return res.json({
circuitListId,
sections: [],
warning:
"Circuit-first tables are not available yet. Run database migrations (including 0008_circuit_first_model).",
});
}
throw error;
}
return res.json({ ...tree, migrationReport });
}
@@ -0,0 +1,71 @@
import type { Request, Response } from "express";
import { CircuitWriteService } from "../../domain/services/circuit-write.service.js";
import { createCircuitSchema, updateCircuitSchema } from "../../shared/validation/circuit.schemas.js";
const circuitWriteService = new CircuitWriteService();
export async function createCircuit(req: Request, res: Response) {
const { projectId, circuitListId } = req.params;
if (typeof projectId !== "string" || typeof circuitListId !== "string") {
return res.status(400).json({ error: "Invalid parameters" });
}
const parsed = createCircuitSchema.safeParse(req.body);
if (!parsed.success) {
return res.status(400).json({ error: parsed.error.flatten() });
}
try {
const created = await circuitWriteService.createCircuit(projectId, circuitListId, parsed.data);
return res.status(201).json(created);
} catch (error) {
return res.status(400).json({ error: error instanceof Error ? error.message : "Failed to create circuit." });
}
}
export async function updateCircuit(req: Request, res: Response) {
const { circuitId } = req.params;
if (typeof circuitId !== "string") {
return res.status(400).json({ error: "Invalid circuitId" });
}
const parsed = updateCircuitSchema.safeParse(req.body);
if (!parsed.success) {
return res.status(400).json({ error: parsed.error.flatten() });
}
try {
const updated = await circuitWriteService.updateCircuit(circuitId, parsed.data);
return res.json(updated);
} catch (error) {
return res.status(400).json({ error: error instanceof Error ? error.message : "Failed to update circuit." });
}
}
export async function deleteCircuit(req: Request, res: Response) {
const { circuitId } = req.params;
if (typeof circuitId !== "string") {
return res.status(400).json({ error: "Invalid circuitId" });
}
try {
await circuitWriteService.deleteCircuit(circuitId);
return res.status(204).send();
} catch (error) {
return res.status(400).json({ error: error instanceof Error ? error.message : "Failed to delete circuit." });
}
}
export async function getNextCircuitIdentifier(req: Request, res: Response) {
const { sectionId } = req.params;
if (typeof sectionId !== "string") {
return res.status(400).json({ error: "Invalid sectionId" });
}
try {
const nextIdentifier = await circuitWriteService.getNextIdentifier(sectionId);
return res.json({ sectionId, nextIdentifier });
} catch (error) {
return res.status(400).json({ error: error instanceof Error ? error.message : "Failed to get identifier." });
}
}
+6
View File
@@ -1,4 +1,7 @@
import express from "express";
import { circuitDeviceRowRouter } from "./routes/circuit-device-row.routes.js";
import { circuitRouter } from "./routes/circuit.routes.js";
import { circuitSectionRouter } from "./routes/circuit-section.routes.js";
import { consumerRouter } from "./routes/consumer.routes.js";
import { globalDeviceRouter } from "./routes/global-device.routes.js";
import { projectDeviceRouter } from "./routes/project-device.routes.js";
@@ -15,6 +18,9 @@ app.get("/health", (_req, res) => {
});
app.use("/api/projects", projectRouter);
app.use("/api", circuitRouter);
app.use("/api", circuitDeviceRowRouter);
app.use("/api", circuitSectionRouter);
app.use("/api/consumers", consumerRouter);
app.use("/api/global-devices", globalDeviceRouter);
app.use("/api/project-devices", projectDeviceRouter);
@@ -0,0 +1,11 @@
import { Router } from "express";
import {
deleteCircuitDeviceRow,
updateCircuitDeviceRow,
} from "../controllers/circuit-device-row.controller.js";
export const circuitDeviceRowRouter = Router();
circuitDeviceRowRouter.patch("/circuit-device-rows/:rowId", updateCircuitDeviceRow);
circuitDeviceRowRouter.delete("/circuit-device-rows/:rowId", deleteCircuitDeviceRow);
@@ -0,0 +1,7 @@
import { Router } from "express";
import { renumberCircuitSection } from "../controllers/circuit-section.controller.js";
export const circuitSectionRouter = Router();
circuitSectionRouter.post("/circuit-sections/:sectionId/renumber", renumberCircuitSection);
+17
View File
@@ -0,0 +1,17 @@
import { Router } from "express";
import {
createCircuit,
deleteCircuit,
getNextCircuitIdentifier,
updateCircuit,
} from "../controllers/circuit.controller.js";
import { createCircuitDeviceRow } from "../controllers/circuit-device-row.controller.js";
export const circuitRouter = Router();
circuitRouter.post("/projects/:projectId/circuit-lists/:circuitListId/circuits", createCircuit);
circuitRouter.patch("/circuits/:circuitId", updateCircuit);
circuitRouter.delete("/circuits/:circuitId", deleteCircuit);
circuitRouter.get("/circuit-sections/:sectionId/next-identifier", getNextCircuitIdentifier);
circuitRouter.post("/circuits/:circuitId/device-rows", createCircuitDeviceRow);