@@ -707,10 +761,13 @@ export default function ProjectDetailPage() {
| Interner Name |
Anzeigename |
+ Phasenart |
Kategorie |
+ Kostengruppe |
Anzahl |
Leistung je Stück [kW] |
GZF |
+ Gesamtleistung [kW] |
Aktionen |
@@ -739,6 +796,7 @@ export default function ProjectDetailPage() {
}
/>
+
{device.phaseType === "three_phase" ? "3-ph" : "1-ph"} |
|
+
{device.costGroup ?? "-"} |
{device.quantity} |
-
{device.installedPowerPerUnitKw} |
-
{device.demandFactor} |
+
{device.powerPerUnit} |
+
{device.simultaneityFactor} |
+
{device.totalPower} |
))}
diff --git a/src/frontend/types.ts b/src/frontend/types.ts
index dbcb12e..226c577 100644
--- a/src/frontend/types.ts
+++ b/src/frontend/types.ts
@@ -91,8 +91,17 @@ export interface ProjectDeviceDto {
projectId: string;
name: string;
displayName: string;
+ phaseType: "single_phase" | "three_phase";
+ connectionKind: string | null;
+ costGroup: string | null;
category: string | null;
quantity: number;
+ powerPerUnit: number;
+ simultaneityFactor: number;
+ totalPower: number;
+ cosPhi: number | null;
+ remark: string | null;
+ // Legacy aliases retained until the old Consumer editor is removed.
installedPowerPerUnitKw: number;
demandFactor: number;
voltageV: number | null;
@@ -159,14 +168,16 @@ export interface CreateGlobalDeviceInput {
export interface CreateProjectDeviceInput {
name: string;
displayName: string;
+ phaseType: "single_phase" | "three_phase";
+ connectionKind?: string;
+ costGroup?: string;
category?: string;
quantity: number;
- installedPowerPerUnitKw: number;
- demandFactor: number;
+ powerPerUnit: number;
+ simultaneityFactor: number;
+ cosPhi?: number;
+ remark?: string;
voltageV?: number;
- phaseCount?: 1 | 3;
- powerFactor?: number;
- note?: string;
}
export interface CircuitTreeDeviceRowDto {
diff --git a/src/server/controllers/global-device.controller.ts b/src/server/controllers/global-device.controller.ts
index fb00eba..d8f8546 100644
--- a/src/server/controllers/global-device.controller.ts
+++ b/src/server/controllers/global-device.controller.ts
@@ -69,12 +69,12 @@ export async function copyProjectDeviceToGlobal(req: Request, res: Response) {
displayName: source.displayName,
category: source.category ?? undefined,
quantity: source.quantity,
- installedPowerPerUnitKw: source.installedPowerPerUnitKw,
- demandFactor: source.demandFactor,
+ installedPowerPerUnitKw: source.powerPerUnit,
+ demandFactor: source.simultaneityFactor,
voltageV: source.voltageV ?? undefined,
- phaseCount: source.phaseCount === 1 || source.phaseCount === 3 ? source.phaseCount : undefined,
- powerFactor: source.powerFactor ?? undefined,
- note: source.note ?? undefined,
+ phaseCount: source.phaseType === "three_phase" ? 3 : 1,
+ powerFactor: source.cosPhi ?? undefined,
+ note: source.remark ?? undefined,
});
return res.status(201).json(created);
diff --git a/src/server/controllers/project-device.controller.ts b/src/server/controllers/project-device.controller.ts
index b1ce30f..70c7642 100644
--- a/src/server/controllers/project-device.controller.ts
+++ b/src/server/controllers/project-device.controller.ts
@@ -1,6 +1,5 @@
import type { Request, Response } from "express";
import { GlobalDeviceRepository } from "../../db/repositories/global-device.repository.js";
-import { ConsumerRepository } from "../../db/repositories/consumer.repository.js";
import { ProjectDeviceRepository } from "../../db/repositories/project-device.repository.js";
import {
createProjectDeviceSchema,
@@ -8,7 +7,6 @@ import {
} from "../../shared/validation/project-device.schemas.js";
const globalDeviceRepository = new GlobalDeviceRepository();
-const consumerRepository = new ConsumerRepository();
const projectDeviceRepository = new ProjectDeviceRepository();
export async function listProjectDevicesByProject(req: Request, res: Response) {
@@ -45,16 +43,8 @@ export async function updateProjectDevice(req: Request, res: Response) {
}
await projectDeviceRepository.update(projectId, projectDeviceId, parsed.data);
- await consumerRepository.syncLinkedConsumersFromProjectDevice(projectId, projectDeviceId, {
- displayName: parsed.data.displayName,
- category: parsed.data.category,
- quantity: parsed.data.quantity,
- installedPowerPerUnitKw: parsed.data.installedPowerPerUnitKw,
- demandFactor: parsed.data.demandFactor,
- phaseCount: parsed.data.phaseCount,
- powerFactor: parsed.data.powerFactor,
- note: parsed.data.note,
- });
+ // Linked rows are synchronized only through the explicit review flow.
+ // Updating a project device must never silently overwrite local circuit-list values.
const row = await projectDeviceRepository.findById(projectId, projectDeviceId);
if (!row) {
return res.status(404).json({ error: "Project device not found" });
@@ -86,14 +76,14 @@ export async function copyGlobalDeviceToProject(req: Request, res: Response) {
const created = await projectDeviceRepository.create(projectId, {
name: source.name,
displayName: source.displayName,
+ phaseType: source.phaseCount === 3 ? "three_phase" : "single_phase",
category: source.category ?? undefined,
quantity: source.quantity,
- installedPowerPerUnitKw: source.installedPowerPerUnitKw,
- demandFactor: source.demandFactor,
+ powerPerUnit: source.installedPowerPerUnitKw,
+ simultaneityFactor: source.demandFactor,
+ cosPhi: source.powerFactor ?? undefined,
+ remark: source.note ?? undefined,
voltageV: source.voltageV ?? undefined,
- phaseCount: source.phaseCount === 1 || source.phaseCount === 3 ? source.phaseCount : undefined,
- powerFactor: source.powerFactor ?? undefined,
- note: source.note ?? undefined,
});
return res.status(201).json(created);
diff --git a/src/shared/validation/project-device.schemas.ts b/src/shared/validation/project-device.schemas.ts
index 2f85470..3b8bf48 100644
--- a/src/shared/validation/project-device.schemas.ts
+++ b/src/shared/validation/project-device.schemas.ts
@@ -3,14 +3,17 @@ import { z } from "zod";
export const createProjectDeviceSchema = z.object({
name: z.string().min(1),
displayName: z.string().min(1),
+ phaseType: z.enum(["single_phase", "three_phase"]),
+ connectionKind: z.string().optional(),
+ costGroup: z.string().optional(),
category: z.string().optional(),
quantity: z.number().min(0),
- installedPowerPerUnitKw: z.number().min(0),
- demandFactor: z.number().min(0).max(1),
+ powerPerUnit: z.number().min(0),
+ simultaneityFactor: z.number().min(0).max(1),
+ cosPhi: z.number().min(0).max(1).optional(),
+ remark: z.string().optional(),
+ // Transitional metadata used when importing from the legacy global-device library.
voltageV: z.number().positive().optional(),
- phaseCount: z.union([z.literal(1), z.literal(3)]).optional(),
- powerFactor: z.number().min(0).max(1).optional(),
- note: z.string().optional(),
});
export const updateProjectDeviceSchema = createProjectDeviceSchema;
diff --git a/tests/project-device-schema.test.ts b/tests/project-device-schema.test.ts
new file mode 100644
index 0000000..b47eac0
--- /dev/null
+++ b/tests/project-device-schema.test.ts
@@ -0,0 +1,36 @@
+import assert from "node:assert/strict";
+import { describe, it } from "node:test";
+import { createProjectDeviceSchema } from "../src/shared/validation/project-device.schemas.js";
+
+describe("project device circuit-first schema", () => {
+ it("accepts circuit device fields", () => {
+ const result = createProjectDeviceSchema.safeParse({
+ name: "E-Line Pro",
+ displayName: "Office lighting",
+ phaseType: "single_phase",
+ connectionKind: "fixed",
+ costGroup: "440",
+ category: "Lighting",
+ quantity: 6,
+ powerPerUnit: 0.04,
+ simultaneityFactor: 0.8,
+ cosPhi: 0.95,
+ remark: "DALI",
+ });
+
+ assert.equal(result.success, true);
+ });
+
+ it("requires the circuit-first power and phase fields", () => {
+ const result = createProjectDeviceSchema.safeParse({
+ name: "Legacy device",
+ displayName: "Legacy device",
+ quantity: 1,
+ installedPowerPerUnitKw: 0.1,
+ demandFactor: 1,
+ phaseCount: 1,
+ });
+
+ assert.equal(result.success, false);
+ });
+});
|