Align project devices with circuit model
This commit is contained in:
@@ -35,14 +35,15 @@ import type {
|
||||
const emptyProjectDevice: CreateProjectDeviceInput = {
|
||||
name: "",
|
||||
displayName: "",
|
||||
phaseType: "single_phase",
|
||||
connectionKind: "",
|
||||
costGroup: "",
|
||||
category: "",
|
||||
quantity: 1,
|
||||
installedPowerPerUnitKw: 0.1,
|
||||
demandFactor: 1,
|
||||
voltageV: 230,
|
||||
phaseCount: 1,
|
||||
powerFactor: 1,
|
||||
note: "",
|
||||
powerPerUnit: 0.1,
|
||||
simultaneityFactor: 1,
|
||||
cosPhi: 1,
|
||||
remark: "",
|
||||
};
|
||||
|
||||
function toOptionalNumber(value: string) {
|
||||
@@ -73,14 +74,15 @@ export default function ProjectDetailPage() {
|
||||
const [projectDeviceForm, setProjectDeviceForm] = useState<Record<string, string>>({
|
||||
name: "",
|
||||
displayName: "",
|
||||
phaseType: "single_phase",
|
||||
connectionKind: "",
|
||||
costGroup: "",
|
||||
category: "",
|
||||
quantity: "1",
|
||||
installedPowerPerUnitKw: "0.1",
|
||||
demandFactor: "1",
|
||||
voltageV: "230",
|
||||
phaseCount: "1",
|
||||
powerFactor: "1",
|
||||
note: "",
|
||||
powerPerUnit: "0.1",
|
||||
simultaneityFactor: "1",
|
||||
cosPhi: "1",
|
||||
remark: "",
|
||||
});
|
||||
const [selectedGlobalDeviceId, setSelectedGlobalDeviceId] = useState("");
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
@@ -230,14 +232,15 @@ export default function ProjectDetailPage() {
|
||||
const payload: CreateProjectDeviceInput = {
|
||||
name: projectDeviceForm.name.trim(),
|
||||
displayName: projectDeviceForm.displayName.trim() || projectDeviceForm.name.trim(),
|
||||
phaseType: projectDeviceForm.phaseType === "three_phase" ? "three_phase" : "single_phase",
|
||||
connectionKind: projectDeviceForm.connectionKind.trim() || undefined,
|
||||
costGroup: projectDeviceForm.costGroup.trim() || undefined,
|
||||
category: projectDeviceForm.category.trim() || undefined,
|
||||
quantity: Number(projectDeviceForm.quantity),
|
||||
installedPowerPerUnitKw: Number(projectDeviceForm.installedPowerPerUnitKw),
|
||||
demandFactor: Number(projectDeviceForm.demandFactor),
|
||||
voltageV: toOptionalNumber(projectDeviceForm.voltageV),
|
||||
phaseCount: projectDeviceForm.phaseCount === "3" ? 3 : 1,
|
||||
powerFactor: toOptionalNumber(projectDeviceForm.powerFactor),
|
||||
note: projectDeviceForm.note.trim() || undefined,
|
||||
powerPerUnit: Number(projectDeviceForm.powerPerUnit),
|
||||
simultaneityFactor: Number(projectDeviceForm.simultaneityFactor),
|
||||
cosPhi: toOptionalNumber(projectDeviceForm.cosPhi),
|
||||
remark: projectDeviceForm.remark.trim() || undefined,
|
||||
};
|
||||
|
||||
setIsSaving(true);
|
||||
@@ -248,14 +251,15 @@ export default function ProjectDetailPage() {
|
||||
setProjectDeviceForm({
|
||||
name: emptyProjectDevice.name,
|
||||
displayName: emptyProjectDevice.displayName,
|
||||
phaseType: emptyProjectDevice.phaseType,
|
||||
connectionKind: emptyProjectDevice.connectionKind ?? "",
|
||||
costGroup: emptyProjectDevice.costGroup ?? "",
|
||||
category: emptyProjectDevice.category ?? "",
|
||||
quantity: String(emptyProjectDevice.quantity),
|
||||
installedPowerPerUnitKw: String(emptyProjectDevice.installedPowerPerUnitKw),
|
||||
demandFactor: String(emptyProjectDevice.demandFactor),
|
||||
voltageV: String(emptyProjectDevice.voltageV ?? ""),
|
||||
phaseCount: String(emptyProjectDevice.phaseCount ?? 1),
|
||||
powerFactor: String(emptyProjectDevice.powerFactor ?? ""),
|
||||
note: emptyProjectDevice.note ?? "",
|
||||
powerPerUnit: String(emptyProjectDevice.powerPerUnit),
|
||||
simultaneityFactor: String(emptyProjectDevice.simultaneityFactor),
|
||||
cosPhi: String(emptyProjectDevice.cosPhi ?? ""),
|
||||
remark: emptyProjectDevice.remark ?? "",
|
||||
});
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : "Projektgerät konnte nicht erstellt werden.");
|
||||
@@ -292,14 +296,16 @@ export default function ProjectDetailPage() {
|
||||
const payload: CreateProjectDeviceInput = {
|
||||
name: key === "name" ? value : device.name,
|
||||
displayName: key === "displayName" ? value : device.displayName,
|
||||
phaseType: device.phaseType,
|
||||
connectionKind: device.connectionKind ?? undefined,
|
||||
costGroup: device.costGroup ?? undefined,
|
||||
category: key === "category" ? value : device.category ?? undefined,
|
||||
quantity: device.quantity,
|
||||
installedPowerPerUnitKw: device.installedPowerPerUnitKw,
|
||||
demandFactor: device.demandFactor,
|
||||
powerPerUnit: device.powerPerUnit,
|
||||
simultaneityFactor: device.simultaneityFactor,
|
||||
cosPhi: device.cosPhi ?? undefined,
|
||||
remark: device.remark ?? undefined,
|
||||
voltageV: device.voltageV ?? undefined,
|
||||
phaseCount: device.phaseCount ?? undefined,
|
||||
powerFactor: device.powerFactor ?? undefined,
|
||||
note: device.note ?? undefined,
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -591,7 +597,7 @@ export default function ProjectDetailPage() {
|
||||
</div>
|
||||
<div className="card-body border-bottom">
|
||||
<form className="row g-2" onSubmit={handleCreateProjectDevice}>
|
||||
<div className="col-12 col-md-3">
|
||||
<div className="col-12 col-md-2">
|
||||
<input
|
||||
className="form-control"
|
||||
placeholder="Interner Name"
|
||||
@@ -601,7 +607,7 @@ export default function ProjectDetailPage() {
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-12 col-md-3">
|
||||
<div className="col-12 col-md-2">
|
||||
<input
|
||||
className="form-control"
|
||||
placeholder="Anzeigename"
|
||||
@@ -621,11 +627,32 @@ export default function ProjectDetailPage() {
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-6 col-md-1">
|
||||
<input
|
||||
className="form-control"
|
||||
placeholder="Anschlussart"
|
||||
value={projectDeviceForm.connectionKind}
|
||||
onChange={(event) =>
|
||||
setProjectDeviceForm((current) => ({ ...current, connectionKind: event.target.value }))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-6 col-md-1">
|
||||
<input
|
||||
className="form-control"
|
||||
placeholder="Kostengruppe"
|
||||
value={projectDeviceForm.costGroup}
|
||||
onChange={(event) =>
|
||||
setProjectDeviceForm((current) => ({ ...current, costGroup: event.target.value }))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-6 col-md-1">
|
||||
<input
|
||||
className="form-control"
|
||||
type="number"
|
||||
min="0"
|
||||
title="Anzahl"
|
||||
value={projectDeviceForm.quantity}
|
||||
onChange={(event) =>
|
||||
setProjectDeviceForm((current) => ({ ...current, quantity: event.target.value }))
|
||||
@@ -638,11 +665,12 @@ export default function ProjectDetailPage() {
|
||||
type="number"
|
||||
step="0.01"
|
||||
min="0"
|
||||
value={projectDeviceForm.installedPowerPerUnitKw}
|
||||
title="Leistung je Stück [kW]"
|
||||
value={projectDeviceForm.powerPerUnit}
|
||||
onChange={(event) =>
|
||||
setProjectDeviceForm((current) => ({
|
||||
...current,
|
||||
installedPowerPerUnitKw: event.target.value,
|
||||
powerPerUnit: event.target.value,
|
||||
}))
|
||||
}
|
||||
/>
|
||||
@@ -654,25 +682,51 @@ export default function ProjectDetailPage() {
|
||||
step="0.01"
|
||||
min="0"
|
||||
max="1"
|
||||
value={projectDeviceForm.demandFactor}
|
||||
title="Gleichzeitigkeitsfaktor"
|
||||
value={projectDeviceForm.simultaneityFactor}
|
||||
onChange={(event) =>
|
||||
setProjectDeviceForm((current) => ({ ...current, demandFactor: event.target.value }))
|
||||
setProjectDeviceForm((current) => ({ ...current, simultaneityFactor: event.target.value }))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-6 col-md-1">
|
||||
<input
|
||||
className="form-control"
|
||||
type="number"
|
||||
step="0.01"
|
||||
min="0"
|
||||
max="1"
|
||||
title="cos Phi"
|
||||
value={projectDeviceForm.cosPhi}
|
||||
onChange={(event) =>
|
||||
setProjectDeviceForm((current) => ({ ...current, cosPhi: event.target.value }))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-6 col-md-1">
|
||||
<select
|
||||
className="form-select"
|
||||
value={projectDeviceForm.phaseCount}
|
||||
title="Phasenart"
|
||||
value={projectDeviceForm.phaseType}
|
||||
onChange={(event) =>
|
||||
setProjectDeviceForm((current) => ({ ...current, phaseCount: event.target.value }))
|
||||
setProjectDeviceForm((current) => ({ ...current, phaseType: event.target.value }))
|
||||
}
|
||||
>
|
||||
<option value="1">1-ph</option>
|
||||
<option value="3">3-ph</option>
|
||||
<option value="single_phase">1-ph</option>
|
||||
<option value="three_phase">3-ph</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="col-6 col-md-2">
|
||||
<div className="col-12 col-md-10">
|
||||
<input
|
||||
className="form-control"
|
||||
placeholder="Bemerkung"
|
||||
value={projectDeviceForm.remark}
|
||||
onChange={(event) =>
|
||||
setProjectDeviceForm((current) => ({ ...current, remark: event.target.value }))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-12 col-md-2">
|
||||
<button className="btn btn-primary w-100" type="submit" disabled={isSaving}>
|
||||
Gerät anlegen
|
||||
</button>
|
||||
@@ -707,10 +761,13 @@ export default function ProjectDetailPage() {
|
||||
<tr>
|
||||
<th>Interner Name</th>
|
||||
<th>Anzeigename</th>
|
||||
<th>Phasenart</th>
|
||||
<th>Kategorie</th>
|
||||
<th>Kostengruppe</th>
|
||||
<th>Anzahl</th>
|
||||
<th>Leistung je Stück [kW]</th>
|
||||
<th>GZF</th>
|
||||
<th>Gesamtleistung [kW]</th>
|
||||
<th>Aktionen</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -739,6 +796,7 @@ export default function ProjectDetailPage() {
|
||||
}
|
||||
/>
|
||||
</td>
|
||||
<td>{device.phaseType === "three_phase" ? "3-ph" : "1-ph"}</td>
|
||||
<td>
|
||||
<input
|
||||
className="form-control form-control-sm"
|
||||
@@ -750,9 +808,11 @@ export default function ProjectDetailPage() {
|
||||
}
|
||||
/>
|
||||
</td>
|
||||
<td>{device.costGroup ?? "-"}</td>
|
||||
<td>{device.quantity}</td>
|
||||
<td>{device.installedPowerPerUnitKw}</td>
|
||||
<td>{device.demandFactor}</td>
|
||||
<td>{device.powerPerUnit}</td>
|
||||
<td>{device.simultaneityFactor}</td>
|
||||
<td>{device.totalPower}</td>
|
||||
<td>
|
||||
<div className="btn-group btn-group-sm">
|
||||
<button
|
||||
@@ -777,7 +837,7 @@ export default function ProjectDetailPage() {
|
||||
))}
|
||||
{!projectDevices.length ? (
|
||||
<tr>
|
||||
<td colSpan={7} className="text-center text-secondary py-4">
|
||||
<td colSpan={10} className="text-center text-secondary py-4">
|
||||
Noch keine Projektgeräte vorhanden.
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
ALTER TABLE `project_devices` ADD COLUMN `phase_type` text NOT NULL DEFAULT 'single_phase';
|
||||
--> statement-breakpoint
|
||||
UPDATE `project_devices`
|
||||
SET `phase_type` = CASE WHEN `phase_count` = 3 THEN 'three_phase' ELSE 'single_phase' END;
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE `project_devices` ADD COLUMN `connection_kind` text;
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE `project_devices` ADD COLUMN `cost_group` text;
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE `project_devices` ADD COLUMN `power_per_unit` real NOT NULL DEFAULT 0;
|
||||
--> statement-breakpoint
|
||||
UPDATE `project_devices` SET `power_per_unit` = `installed_power_per_unit_kw`;
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE `project_devices` ADD COLUMN `simultaneity_factor` real NOT NULL DEFAULT 1;
|
||||
--> statement-breakpoint
|
||||
UPDATE `project_devices` SET `simultaneity_factor` = `demand_factor`;
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE `project_devices` ADD COLUMN `cos_phi` real;
|
||||
--> statement-breakpoint
|
||||
UPDATE `project_devices` SET `cos_phi` = `power_factor`;
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE `project_devices` ADD COLUMN `remark` text;
|
||||
--> statement-breakpoint
|
||||
UPDATE `project_devices` SET `remark` = `note`;
|
||||
@@ -64,6 +64,13 @@
|
||||
"when": 1777800000000,
|
||||
"tag": "0008_circuit_first_model",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 9,
|
||||
"version": "6",
|
||||
"when": 1784743200000,
|
||||
"tag": "0009_project_device_circuit_fields",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -2,14 +2,23 @@ import crypto from "node:crypto";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import { db } from "../client.js";
|
||||
import { projectDevices } from "../schema/project-devices.js";
|
||||
import { calculateRowTotalPower } from "../../domain/calculations/circuit-power-calculation.js";
|
||||
import type {
|
||||
CreateProjectDeviceInput,
|
||||
UpdateProjectDeviceInput,
|
||||
} from "../../shared/validation/project-device.schemas.js";
|
||||
|
||||
function withTotalPower(row: typeof projectDevices.$inferSelect) {
|
||||
return {
|
||||
...row,
|
||||
totalPower: calculateRowTotalPower(row.quantity, row.powerPerUnit, row.simultaneityFactor),
|
||||
};
|
||||
}
|
||||
|
||||
export class ProjectDeviceRepository {
|
||||
async listByProject(projectId: string) {
|
||||
return db.select().from(projectDevices).where(eq(projectDevices.projectId, projectId));
|
||||
const rows = await db.select().from(projectDevices).where(eq(projectDevices.projectId, projectId));
|
||||
return rows.map(withTotalPower);
|
||||
}
|
||||
|
||||
async create(projectId: string, input: CreateProjectDeviceInput) {
|
||||
@@ -19,16 +28,27 @@ export class ProjectDeviceRepository {
|
||||
projectId,
|
||||
name: input.name,
|
||||
displayName: input.displayName,
|
||||
phaseType: input.phaseType,
|
||||
connectionKind: input.connectionKind ?? null,
|
||||
costGroup: input.costGroup ?? null,
|
||||
category: input.category ?? null,
|
||||
quantity: input.quantity,
|
||||
installedPowerPerUnitKw: input.installedPowerPerUnitKw,
|
||||
demandFactor: input.demandFactor,
|
||||
powerPerUnit: input.powerPerUnit,
|
||||
simultaneityFactor: input.simultaneityFactor,
|
||||
cosPhi: input.cosPhi ?? null,
|
||||
remark: input.remark ?? null,
|
||||
installedPowerPerUnitKw: input.powerPerUnit,
|
||||
demandFactor: input.simultaneityFactor,
|
||||
voltageV: input.voltageV ?? null,
|
||||
phaseCount: input.phaseCount ?? null,
|
||||
powerFactor: input.powerFactor ?? null,
|
||||
note: input.note ?? null,
|
||||
phaseCount: input.phaseType === "three_phase" ? 3 : 1,
|
||||
powerFactor: input.cosPhi ?? null,
|
||||
note: input.remark ?? null,
|
||||
});
|
||||
return { id, projectId, ...input };
|
||||
const created = await this.findById(projectId, id);
|
||||
if (!created) {
|
||||
throw new Error("Failed to create project device.");
|
||||
}
|
||||
return created;
|
||||
}
|
||||
|
||||
async findById(projectId: string, projectDeviceId: string) {
|
||||
@@ -39,7 +59,7 @@ export class ProjectDeviceRepository {
|
||||
and(eq(projectDevices.id, projectDeviceId), eq(projectDevices.projectId, projectId))
|
||||
)
|
||||
.limit(1);
|
||||
return row ?? null;
|
||||
return row ? withTotalPower(row) : null;
|
||||
}
|
||||
|
||||
async update(projectId: string, projectDeviceId: string, input: UpdateProjectDeviceInput) {
|
||||
@@ -48,14 +68,21 @@ export class ProjectDeviceRepository {
|
||||
.set({
|
||||
name: input.name,
|
||||
displayName: input.displayName,
|
||||
phaseType: input.phaseType,
|
||||
connectionKind: input.connectionKind ?? null,
|
||||
costGroup: input.costGroup ?? null,
|
||||
category: input.category ?? null,
|
||||
quantity: input.quantity,
|
||||
installedPowerPerUnitKw: input.installedPowerPerUnitKw,
|
||||
demandFactor: input.demandFactor,
|
||||
powerPerUnit: input.powerPerUnit,
|
||||
simultaneityFactor: input.simultaneityFactor,
|
||||
cosPhi: input.cosPhi ?? null,
|
||||
remark: input.remark ?? null,
|
||||
installedPowerPerUnitKw: input.powerPerUnit,
|
||||
demandFactor: input.simultaneityFactor,
|
||||
voltageV: input.voltageV ?? null,
|
||||
phaseCount: input.phaseCount ?? null,
|
||||
powerFactor: input.powerFactor ?? null,
|
||||
note: input.note ?? null,
|
||||
phaseCount: input.phaseType === "three_phase" ? 3 : 1,
|
||||
powerFactor: input.cosPhi ?? null,
|
||||
note: input.remark ?? null,
|
||||
})
|
||||
.where(
|
||||
and(eq(projectDevices.id, projectDeviceId), eq(projectDevices.projectId, projectId))
|
||||
|
||||
@@ -8,8 +8,16 @@ export const projectDevices = sqliteTable("project_devices", {
|
||||
.references(() => projects.id, { onDelete: "cascade" }),
|
||||
name: text("name").notNull(),
|
||||
displayName: text("display_name").notNull(),
|
||||
phaseType: text("phase_type").notNull().default("single_phase"),
|
||||
connectionKind: text("connection_kind"),
|
||||
costGroup: text("cost_group"),
|
||||
category: text("category"),
|
||||
quantity: integer("quantity").notNull(),
|
||||
powerPerUnit: real("power_per_unit").notNull().default(0),
|
||||
simultaneityFactor: real("simultaneity_factor").notNull().default(1),
|
||||
cosPhi: real("cos_phi"),
|
||||
remark: text("remark"),
|
||||
// Legacy compatibility fields. Keep these synchronized until the Consumer editor is removed.
|
||||
installedPowerPerUnitKw: real("installed_power_per_unit_kw").notNull(),
|
||||
demandFactor: real("demand_factor").notNull(),
|
||||
voltageV: real("voltage_v"),
|
||||
|
||||
@@ -1696,10 +1696,7 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
}
|
||||
|
||||
function resolvePhaseType(device: ProjectDeviceDto): string {
|
||||
if (device.phaseCount === 3) {
|
||||
return "three_phase";
|
||||
}
|
||||
return "single_phase";
|
||||
return device.phaseType;
|
||||
}
|
||||
|
||||
function resolveSelectedProjectDevice() {
|
||||
@@ -1819,11 +1816,14 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
name: device.name,
|
||||
displayName: device.displayName,
|
||||
phaseType: resolvePhaseType(device),
|
||||
connectionKind: device.connectionKind ?? undefined,
|
||||
costGroup: device.costGroup ?? undefined,
|
||||
quantity: device.quantity,
|
||||
powerPerUnit: device.installedPowerPerUnitKw,
|
||||
simultaneityFactor: device.demandFactor,
|
||||
cosPhi: device.powerFactor ?? undefined,
|
||||
powerPerUnit: device.powerPerUnit,
|
||||
simultaneityFactor: device.simultaneityFactor,
|
||||
cosPhi: device.cosPhi ?? undefined,
|
||||
category: device.category ?? undefined,
|
||||
remark: device.remark ?? undefined,
|
||||
})) as { id: string };
|
||||
|
||||
setActiveSectionId(sectionId);
|
||||
@@ -1837,11 +1837,14 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
name: device.name,
|
||||
displayName: device.displayName,
|
||||
phaseType: resolvePhaseType(device),
|
||||
connectionKind: device.connectionKind ?? undefined,
|
||||
costGroup: device.costGroup ?? undefined,
|
||||
quantity: device.quantity,
|
||||
powerPerUnit: device.installedPowerPerUnitKw,
|
||||
simultaneityFactor: device.demandFactor,
|
||||
cosPhi: device.powerFactor ?? undefined,
|
||||
powerPerUnit: device.powerPerUnit,
|
||||
simultaneityFactor: device.simultaneityFactor,
|
||||
cosPhi: device.cosPhi ?? undefined,
|
||||
category: device.category ?? undefined,
|
||||
remark: device.remark ?? undefined,
|
||||
})) as { id: string };
|
||||
return { rowId: createdRow.id };
|
||||
}
|
||||
@@ -2700,11 +2703,12 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
>
|
||||
<strong>{device.displayName || device.name}</strong>
|
||||
<span>Name: {device.name}</span>
|
||||
<span>Phase: {device.phaseCount === 3 ? "three_phase" : "single_phase"}</span>
|
||||
<span>Phase: {device.phaseType}</span>
|
||||
<span>Qty: {device.quantity}</span>
|
||||
<span>P/unit: {device.installedPowerPerUnitKw}</span>
|
||||
<span>g: {device.demandFactor}</span>
|
||||
<span>Cost group: -</span>
|
||||
<span>P/unit: {device.powerPerUnit}</span>
|
||||
<span>g: {device.simultaneityFactor}</span>
|
||||
<span>Total: {device.totalPower}</span>
|
||||
<span>Cost group: {device.costGroup || "-"}</span>
|
||||
<span>Category: {device.category || "-"}</span>
|
||||
</button>
|
||||
))}
|
||||
|
||||
+16
-5
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user