Align project devices with circuit model
This commit is contained in:
@@ -2,3 +2,4 @@ node_modules/
|
|||||||
dist/
|
dist/
|
||||||
.next/
|
.next/
|
||||||
data/*.db
|
data/*.db
|
||||||
|
data/backups/*.db
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ This project uses SQLite at `data/leistungsbilanz.db` and Drizzle migrations in
|
|||||||
npm run db:backup
|
npm run db:backup
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Apply pending schema migrations (includes `0008_circuit_first_model`)
|
2. Apply pending schema migrations (includes `0008_circuit_first_model` and the additive
|
||||||
|
`0009_project_device_circuit_fields` transition)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run db:migrate
|
npm run db:migrate
|
||||||
@@ -52,6 +53,8 @@ WHERE type = 'table'
|
|||||||
ORDER BY name;
|
ORDER BY name;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The verification command also checks the circuit-first project-device columns added by migration `0009`.
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
SELECT circuit_list_id, key, prefix, sort_order
|
SELECT circuit_list_id, key, prefix, sort_order
|
||||||
FROM circuit_sections
|
FROM circuit_sections
|
||||||
|
|||||||
Vendored
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
/// <reference types="next" />
|
/// <reference types="next" />
|
||||||
/// <reference types="next/image-types/global" />
|
/// <reference types="next/image-types/global" />
|
||||||
import "./.next/types/routes.d.ts";
|
import "./.next/dev/types/routes.d.ts";
|
||||||
|
|
||||||
// NOTE: This file should not be edited
|
// NOTE: This file should not be edited
|
||||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||||
|
|||||||
+2
-2
@@ -11,8 +11,8 @@
|
|||||||
"build:api": "tsc -p tsconfig.json",
|
"build:api": "tsc -p tsconfig.json",
|
||||||
"build:web": "next build",
|
"build:web": "next build",
|
||||||
"start": "node dist/server/index.js",
|
"start": "node dist/server/index.js",
|
||||||
"test": "tsx --test tests/power-calculation.test.ts tests/consumer-linking.service.test.ts tests/consumer-schema-options.test.ts tests/legacy-consumer-migration-planner.test.ts tests/circuit-numbering.service.test.ts tests/circuit-write.rules.test.ts tests/circuit-power-calculation.test.ts tests/circuit-tree.controller.test.ts",
|
"test": "tsx --test tests/power-calculation.test.ts tests/consumer-linking.service.test.ts tests/consumer-schema-options.test.ts tests/project-device-schema.test.ts tests/legacy-consumer-migration-planner.test.ts tests/circuit-numbering.service.test.ts tests/circuit-write.rules.test.ts tests/circuit-power-calculation.test.ts tests/circuit-tree.controller.test.ts",
|
||||||
"test:watch": "tsx --watch --test tests/power-calculation.test.ts tests/consumer-linking.service.test.ts tests/consumer-schema-options.test.ts tests/legacy-consumer-migration-planner.test.ts tests/circuit-numbering.service.test.ts tests/circuit-write.rules.test.ts tests/circuit-power-calculation.test.ts tests/circuit-tree.controller.test.ts",
|
"test:watch": "tsx --watch --test tests/power-calculation.test.ts tests/consumer-linking.service.test.ts tests/consumer-schema-options.test.ts tests/project-device-schema.test.ts tests/legacy-consumer-migration-planner.test.ts tests/circuit-numbering.service.test.ts tests/circuit-write.rules.test.ts tests/circuit-power-calculation.test.ts tests/circuit-tree.controller.test.ts",
|
||||||
"db:generate": "drizzle-kit generate",
|
"db:generate": "drizzle-kit generate",
|
||||||
"db:migrate": "drizzle-kit migrate",
|
"db:migrate": "drizzle-kit migrate",
|
||||||
"db:backup": "node scripts/db-backup.js",
|
"db:backup": "node scripts/db-backup.js",
|
||||||
|
|||||||
@@ -18,14 +18,35 @@ const rows = db
|
|||||||
|
|
||||||
const existing = new Set(rows.map((row) => row.name));
|
const existing = new Set(rows.map((row) => row.name));
|
||||||
const missing = requiredTables.filter((name) => !existing.has(name));
|
const missing = requiredTables.filter((name) => !existing.has(name));
|
||||||
|
const requiredProjectDeviceColumns = [
|
||||||
|
"phase_type",
|
||||||
|
"connection_kind",
|
||||||
|
"cost_group",
|
||||||
|
"power_per_unit",
|
||||||
|
"simultaneity_factor",
|
||||||
|
"cos_phi",
|
||||||
|
"remark",
|
||||||
|
];
|
||||||
|
const projectDeviceColumns = new Set(
|
||||||
|
db.prepare("PRAGMA table_info(project_devices)").all().map((column) => column.name)
|
||||||
|
);
|
||||||
|
const missingProjectDeviceColumns = requiredProjectDeviceColumns.filter(
|
||||||
|
(name) => !projectDeviceColumns.has(name)
|
||||||
|
);
|
||||||
|
|
||||||
console.log("Database:", dbPath);
|
console.log("Database:", dbPath);
|
||||||
console.log("Required tables:", requiredTables.join(", "));
|
console.log("Required tables:", requiredTables.join(", "));
|
||||||
console.log("Existing tables:", [...existing].join(", ") || "(none)");
|
console.log("Existing tables:", [...existing].join(", ") || "(none)");
|
||||||
|
console.log("Required project-device columns:", requiredProjectDeviceColumns.join(", "));
|
||||||
|
|
||||||
if (missing.length > 0) {
|
if (missing.length > 0) {
|
||||||
console.error("Missing tables:", missing.join(", "));
|
console.error("Missing tables:", missing.join(", "));
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (missingProjectDeviceColumns.length > 0) {
|
||||||
|
console.error("Missing project-device columns:", missingProjectDeviceColumns.join(", "));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
console.log("Circuit-first schema verification passed.");
|
console.log("Circuit-first schema verification passed.");
|
||||||
|
|||||||
@@ -35,14 +35,15 @@ import type {
|
|||||||
const emptyProjectDevice: CreateProjectDeviceInput = {
|
const emptyProjectDevice: CreateProjectDeviceInput = {
|
||||||
name: "",
|
name: "",
|
||||||
displayName: "",
|
displayName: "",
|
||||||
|
phaseType: "single_phase",
|
||||||
|
connectionKind: "",
|
||||||
|
costGroup: "",
|
||||||
category: "",
|
category: "",
|
||||||
quantity: 1,
|
quantity: 1,
|
||||||
installedPowerPerUnitKw: 0.1,
|
powerPerUnit: 0.1,
|
||||||
demandFactor: 1,
|
simultaneityFactor: 1,
|
||||||
voltageV: 230,
|
cosPhi: 1,
|
||||||
phaseCount: 1,
|
remark: "",
|
||||||
powerFactor: 1,
|
|
||||||
note: "",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function toOptionalNumber(value: string) {
|
function toOptionalNumber(value: string) {
|
||||||
@@ -73,14 +74,15 @@ export default function ProjectDetailPage() {
|
|||||||
const [projectDeviceForm, setProjectDeviceForm] = useState<Record<string, string>>({
|
const [projectDeviceForm, setProjectDeviceForm] = useState<Record<string, string>>({
|
||||||
name: "",
|
name: "",
|
||||||
displayName: "",
|
displayName: "",
|
||||||
|
phaseType: "single_phase",
|
||||||
|
connectionKind: "",
|
||||||
|
costGroup: "",
|
||||||
category: "",
|
category: "",
|
||||||
quantity: "1",
|
quantity: "1",
|
||||||
installedPowerPerUnitKw: "0.1",
|
powerPerUnit: "0.1",
|
||||||
demandFactor: "1",
|
simultaneityFactor: "1",
|
||||||
voltageV: "230",
|
cosPhi: "1",
|
||||||
phaseCount: "1",
|
remark: "",
|
||||||
powerFactor: "1",
|
|
||||||
note: "",
|
|
||||||
});
|
});
|
||||||
const [selectedGlobalDeviceId, setSelectedGlobalDeviceId] = useState("");
|
const [selectedGlobalDeviceId, setSelectedGlobalDeviceId] = useState("");
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
@@ -230,14 +232,15 @@ export default function ProjectDetailPage() {
|
|||||||
const payload: CreateProjectDeviceInput = {
|
const payload: CreateProjectDeviceInput = {
|
||||||
name: projectDeviceForm.name.trim(),
|
name: projectDeviceForm.name.trim(),
|
||||||
displayName: projectDeviceForm.displayName.trim() || 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,
|
category: projectDeviceForm.category.trim() || undefined,
|
||||||
quantity: Number(projectDeviceForm.quantity),
|
quantity: Number(projectDeviceForm.quantity),
|
||||||
installedPowerPerUnitKw: Number(projectDeviceForm.installedPowerPerUnitKw),
|
powerPerUnit: Number(projectDeviceForm.powerPerUnit),
|
||||||
demandFactor: Number(projectDeviceForm.demandFactor),
|
simultaneityFactor: Number(projectDeviceForm.simultaneityFactor),
|
||||||
voltageV: toOptionalNumber(projectDeviceForm.voltageV),
|
cosPhi: toOptionalNumber(projectDeviceForm.cosPhi),
|
||||||
phaseCount: projectDeviceForm.phaseCount === "3" ? 3 : 1,
|
remark: projectDeviceForm.remark.trim() || undefined,
|
||||||
powerFactor: toOptionalNumber(projectDeviceForm.powerFactor),
|
|
||||||
note: projectDeviceForm.note.trim() || undefined,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
setIsSaving(true);
|
setIsSaving(true);
|
||||||
@@ -248,14 +251,15 @@ export default function ProjectDetailPage() {
|
|||||||
setProjectDeviceForm({
|
setProjectDeviceForm({
|
||||||
name: emptyProjectDevice.name,
|
name: emptyProjectDevice.name,
|
||||||
displayName: emptyProjectDevice.displayName,
|
displayName: emptyProjectDevice.displayName,
|
||||||
|
phaseType: emptyProjectDevice.phaseType,
|
||||||
|
connectionKind: emptyProjectDevice.connectionKind ?? "",
|
||||||
|
costGroup: emptyProjectDevice.costGroup ?? "",
|
||||||
category: emptyProjectDevice.category ?? "",
|
category: emptyProjectDevice.category ?? "",
|
||||||
quantity: String(emptyProjectDevice.quantity),
|
quantity: String(emptyProjectDevice.quantity),
|
||||||
installedPowerPerUnitKw: String(emptyProjectDevice.installedPowerPerUnitKw),
|
powerPerUnit: String(emptyProjectDevice.powerPerUnit),
|
||||||
demandFactor: String(emptyProjectDevice.demandFactor),
|
simultaneityFactor: String(emptyProjectDevice.simultaneityFactor),
|
||||||
voltageV: String(emptyProjectDevice.voltageV ?? ""),
|
cosPhi: String(emptyProjectDevice.cosPhi ?? ""),
|
||||||
phaseCount: String(emptyProjectDevice.phaseCount ?? 1),
|
remark: emptyProjectDevice.remark ?? "",
|
||||||
powerFactor: String(emptyProjectDevice.powerFactor ?? ""),
|
|
||||||
note: emptyProjectDevice.note ?? "",
|
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : "Projektgerät konnte nicht erstellt werden.");
|
setError(err instanceof Error ? err.message : "Projektgerät konnte nicht erstellt werden.");
|
||||||
@@ -292,14 +296,16 @@ export default function ProjectDetailPage() {
|
|||||||
const payload: CreateProjectDeviceInput = {
|
const payload: CreateProjectDeviceInput = {
|
||||||
name: key === "name" ? value : device.name,
|
name: key === "name" ? value : device.name,
|
||||||
displayName: key === "displayName" ? value : device.displayName,
|
displayName: key === "displayName" ? value : device.displayName,
|
||||||
|
phaseType: device.phaseType,
|
||||||
|
connectionKind: device.connectionKind ?? undefined,
|
||||||
|
costGroup: device.costGroup ?? undefined,
|
||||||
category: key === "category" ? value : device.category ?? undefined,
|
category: key === "category" ? value : device.category ?? undefined,
|
||||||
quantity: device.quantity,
|
quantity: device.quantity,
|
||||||
installedPowerPerUnitKw: device.installedPowerPerUnitKw,
|
powerPerUnit: device.powerPerUnit,
|
||||||
demandFactor: device.demandFactor,
|
simultaneityFactor: device.simultaneityFactor,
|
||||||
|
cosPhi: device.cosPhi ?? undefined,
|
||||||
|
remark: device.remark ?? undefined,
|
||||||
voltageV: device.voltageV ?? undefined,
|
voltageV: device.voltageV ?? undefined,
|
||||||
phaseCount: device.phaseCount ?? undefined,
|
|
||||||
powerFactor: device.powerFactor ?? undefined,
|
|
||||||
note: device.note ?? undefined,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -591,7 +597,7 @@ export default function ProjectDetailPage() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="card-body border-bottom">
|
<div className="card-body border-bottom">
|
||||||
<form className="row g-2" onSubmit={handleCreateProjectDevice}>
|
<form className="row g-2" onSubmit={handleCreateProjectDevice}>
|
||||||
<div className="col-12 col-md-3">
|
<div className="col-12 col-md-2">
|
||||||
<input
|
<input
|
||||||
className="form-control"
|
className="form-control"
|
||||||
placeholder="Interner Name"
|
placeholder="Interner Name"
|
||||||
@@ -601,7 +607,7 @@ export default function ProjectDetailPage() {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-12 col-md-3">
|
<div className="col-12 col-md-2">
|
||||||
<input
|
<input
|
||||||
className="form-control"
|
className="form-control"
|
||||||
placeholder="Anzeigename"
|
placeholder="Anzeigename"
|
||||||
@@ -621,11 +627,32 @@ export default function ProjectDetailPage() {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</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">
|
<div className="col-6 col-md-1">
|
||||||
<input
|
<input
|
||||||
className="form-control"
|
className="form-control"
|
||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
|
title="Anzahl"
|
||||||
value={projectDeviceForm.quantity}
|
value={projectDeviceForm.quantity}
|
||||||
onChange={(event) =>
|
onChange={(event) =>
|
||||||
setProjectDeviceForm((current) => ({ ...current, quantity: event.target.value }))
|
setProjectDeviceForm((current) => ({ ...current, quantity: event.target.value }))
|
||||||
@@ -638,11 +665,12 @@ export default function ProjectDetailPage() {
|
|||||||
type="number"
|
type="number"
|
||||||
step="0.01"
|
step="0.01"
|
||||||
min="0"
|
min="0"
|
||||||
value={projectDeviceForm.installedPowerPerUnitKw}
|
title="Leistung je Stück [kW]"
|
||||||
|
value={projectDeviceForm.powerPerUnit}
|
||||||
onChange={(event) =>
|
onChange={(event) =>
|
||||||
setProjectDeviceForm((current) => ({
|
setProjectDeviceForm((current) => ({
|
||||||
...current,
|
...current,
|
||||||
installedPowerPerUnitKw: event.target.value,
|
powerPerUnit: event.target.value,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -654,25 +682,51 @@ export default function ProjectDetailPage() {
|
|||||||
step="0.01"
|
step="0.01"
|
||||||
min="0"
|
min="0"
|
||||||
max="1"
|
max="1"
|
||||||
value={projectDeviceForm.demandFactor}
|
title="Gleichzeitigkeitsfaktor"
|
||||||
|
value={projectDeviceForm.simultaneityFactor}
|
||||||
onChange={(event) =>
|
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>
|
||||||
<div className="col-6 col-md-1">
|
<div className="col-6 col-md-1">
|
||||||
<select
|
<select
|
||||||
className="form-select"
|
className="form-select"
|
||||||
value={projectDeviceForm.phaseCount}
|
title="Phasenart"
|
||||||
|
value={projectDeviceForm.phaseType}
|
||||||
onChange={(event) =>
|
onChange={(event) =>
|
||||||
setProjectDeviceForm((current) => ({ ...current, phaseCount: event.target.value }))
|
setProjectDeviceForm((current) => ({ ...current, phaseType: event.target.value }))
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<option value="1">1-ph</option>
|
<option value="single_phase">1-ph</option>
|
||||||
<option value="3">3-ph</option>
|
<option value="three_phase">3-ph</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</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}>
|
<button className="btn btn-primary w-100" type="submit" disabled={isSaving}>
|
||||||
Gerät anlegen
|
Gerät anlegen
|
||||||
</button>
|
</button>
|
||||||
@@ -707,10 +761,13 @@ export default function ProjectDetailPage() {
|
|||||||
<tr>
|
<tr>
|
||||||
<th>Interner Name</th>
|
<th>Interner Name</th>
|
||||||
<th>Anzeigename</th>
|
<th>Anzeigename</th>
|
||||||
|
<th>Phasenart</th>
|
||||||
<th>Kategorie</th>
|
<th>Kategorie</th>
|
||||||
|
<th>Kostengruppe</th>
|
||||||
<th>Anzahl</th>
|
<th>Anzahl</th>
|
||||||
<th>Leistung je Stück [kW]</th>
|
<th>Leistung je Stück [kW]</th>
|
||||||
<th>GZF</th>
|
<th>GZF</th>
|
||||||
|
<th>Gesamtleistung [kW]</th>
|
||||||
<th>Aktionen</th>
|
<th>Aktionen</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -739,6 +796,7 @@ export default function ProjectDetailPage() {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
|
<td>{device.phaseType === "three_phase" ? "3-ph" : "1-ph"}</td>
|
||||||
<td>
|
<td>
|
||||||
<input
|
<input
|
||||||
className="form-control form-control-sm"
|
className="form-control form-control-sm"
|
||||||
@@ -750,9 +808,11 @@ export default function ProjectDetailPage() {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
|
<td>{device.costGroup ?? "-"}</td>
|
||||||
<td>{device.quantity}</td>
|
<td>{device.quantity}</td>
|
||||||
<td>{device.installedPowerPerUnitKw}</td>
|
<td>{device.powerPerUnit}</td>
|
||||||
<td>{device.demandFactor}</td>
|
<td>{device.simultaneityFactor}</td>
|
||||||
|
<td>{device.totalPower}</td>
|
||||||
<td>
|
<td>
|
||||||
<div className="btn-group btn-group-sm">
|
<div className="btn-group btn-group-sm">
|
||||||
<button
|
<button
|
||||||
@@ -777,7 +837,7 @@ export default function ProjectDetailPage() {
|
|||||||
))}
|
))}
|
||||||
{!projectDevices.length ? (
|
{!projectDevices.length ? (
|
||||||
<tr>
|
<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.
|
Noch keine Projektgeräte vorhanden.
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</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,
|
"when": 1777800000000,
|
||||||
"tag": "0008_circuit_first_model",
|
"tag": "0008_circuit_first_model",
|
||||||
"breakpoints": true
|
"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 { and, eq } from "drizzle-orm";
|
||||||
import { db } from "../client.js";
|
import { db } from "../client.js";
|
||||||
import { projectDevices } from "../schema/project-devices.js";
|
import { projectDevices } from "../schema/project-devices.js";
|
||||||
|
import { calculateRowTotalPower } from "../../domain/calculations/circuit-power-calculation.js";
|
||||||
import type {
|
import type {
|
||||||
CreateProjectDeviceInput,
|
CreateProjectDeviceInput,
|
||||||
UpdateProjectDeviceInput,
|
UpdateProjectDeviceInput,
|
||||||
} from "../../shared/validation/project-device.schemas.js";
|
} 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 {
|
export class ProjectDeviceRepository {
|
||||||
async listByProject(projectId: string) {
|
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) {
|
async create(projectId: string, input: CreateProjectDeviceInput) {
|
||||||
@@ -19,16 +28,27 @@ export class ProjectDeviceRepository {
|
|||||||
projectId,
|
projectId,
|
||||||
name: input.name,
|
name: input.name,
|
||||||
displayName: input.displayName,
|
displayName: input.displayName,
|
||||||
|
phaseType: input.phaseType,
|
||||||
|
connectionKind: input.connectionKind ?? null,
|
||||||
|
costGroup: input.costGroup ?? null,
|
||||||
category: input.category ?? null,
|
category: input.category ?? null,
|
||||||
quantity: input.quantity,
|
quantity: input.quantity,
|
||||||
installedPowerPerUnitKw: input.installedPowerPerUnitKw,
|
powerPerUnit: input.powerPerUnit,
|
||||||
demandFactor: input.demandFactor,
|
simultaneityFactor: input.simultaneityFactor,
|
||||||
|
cosPhi: input.cosPhi ?? null,
|
||||||
|
remark: input.remark ?? null,
|
||||||
|
installedPowerPerUnitKw: input.powerPerUnit,
|
||||||
|
demandFactor: input.simultaneityFactor,
|
||||||
voltageV: input.voltageV ?? null,
|
voltageV: input.voltageV ?? null,
|
||||||
phaseCount: input.phaseCount ?? null,
|
phaseCount: input.phaseType === "three_phase" ? 3 : 1,
|
||||||
powerFactor: input.powerFactor ?? null,
|
powerFactor: input.cosPhi ?? null,
|
||||||
note: input.note ?? 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) {
|
async findById(projectId: string, projectDeviceId: string) {
|
||||||
@@ -39,7 +59,7 @@ export class ProjectDeviceRepository {
|
|||||||
and(eq(projectDevices.id, projectDeviceId), eq(projectDevices.projectId, projectId))
|
and(eq(projectDevices.id, projectDeviceId), eq(projectDevices.projectId, projectId))
|
||||||
)
|
)
|
||||||
.limit(1);
|
.limit(1);
|
||||||
return row ?? null;
|
return row ? withTotalPower(row) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async update(projectId: string, projectDeviceId: string, input: UpdateProjectDeviceInput) {
|
async update(projectId: string, projectDeviceId: string, input: UpdateProjectDeviceInput) {
|
||||||
@@ -48,14 +68,21 @@ export class ProjectDeviceRepository {
|
|||||||
.set({
|
.set({
|
||||||
name: input.name,
|
name: input.name,
|
||||||
displayName: input.displayName,
|
displayName: input.displayName,
|
||||||
|
phaseType: input.phaseType,
|
||||||
|
connectionKind: input.connectionKind ?? null,
|
||||||
|
costGroup: input.costGroup ?? null,
|
||||||
category: input.category ?? null,
|
category: input.category ?? null,
|
||||||
quantity: input.quantity,
|
quantity: input.quantity,
|
||||||
installedPowerPerUnitKw: input.installedPowerPerUnitKw,
|
powerPerUnit: input.powerPerUnit,
|
||||||
demandFactor: input.demandFactor,
|
simultaneityFactor: input.simultaneityFactor,
|
||||||
|
cosPhi: input.cosPhi ?? null,
|
||||||
|
remark: input.remark ?? null,
|
||||||
|
installedPowerPerUnitKw: input.powerPerUnit,
|
||||||
|
demandFactor: input.simultaneityFactor,
|
||||||
voltageV: input.voltageV ?? null,
|
voltageV: input.voltageV ?? null,
|
||||||
phaseCount: input.phaseCount ?? null,
|
phaseCount: input.phaseType === "three_phase" ? 3 : 1,
|
||||||
powerFactor: input.powerFactor ?? null,
|
powerFactor: input.cosPhi ?? null,
|
||||||
note: input.note ?? null,
|
note: input.remark ?? null,
|
||||||
})
|
})
|
||||||
.where(
|
.where(
|
||||||
and(eq(projectDevices.id, projectDeviceId), eq(projectDevices.projectId, projectId))
|
and(eq(projectDevices.id, projectDeviceId), eq(projectDevices.projectId, projectId))
|
||||||
|
|||||||
@@ -8,8 +8,16 @@ export const projectDevices = sqliteTable("project_devices", {
|
|||||||
.references(() => projects.id, { onDelete: "cascade" }),
|
.references(() => projects.id, { onDelete: "cascade" }),
|
||||||
name: text("name").notNull(),
|
name: text("name").notNull(),
|
||||||
displayName: text("display_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"),
|
category: text("category"),
|
||||||
quantity: integer("quantity").notNull(),
|
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(),
|
installedPowerPerUnitKw: real("installed_power_per_unit_kw").notNull(),
|
||||||
demandFactor: real("demand_factor").notNull(),
|
demandFactor: real("demand_factor").notNull(),
|
||||||
voltageV: real("voltage_v"),
|
voltageV: real("voltage_v"),
|
||||||
|
|||||||
@@ -1696,10 +1696,7 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
|||||||
}
|
}
|
||||||
|
|
||||||
function resolvePhaseType(device: ProjectDeviceDto): string {
|
function resolvePhaseType(device: ProjectDeviceDto): string {
|
||||||
if (device.phaseCount === 3) {
|
return device.phaseType;
|
||||||
return "three_phase";
|
|
||||||
}
|
|
||||||
return "single_phase";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveSelectedProjectDevice() {
|
function resolveSelectedProjectDevice() {
|
||||||
@@ -1819,11 +1816,14 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
|||||||
name: device.name,
|
name: device.name,
|
||||||
displayName: device.displayName,
|
displayName: device.displayName,
|
||||||
phaseType: resolvePhaseType(device),
|
phaseType: resolvePhaseType(device),
|
||||||
|
connectionKind: device.connectionKind ?? undefined,
|
||||||
|
costGroup: device.costGroup ?? undefined,
|
||||||
quantity: device.quantity,
|
quantity: device.quantity,
|
||||||
powerPerUnit: device.installedPowerPerUnitKw,
|
powerPerUnit: device.powerPerUnit,
|
||||||
simultaneityFactor: device.demandFactor,
|
simultaneityFactor: device.simultaneityFactor,
|
||||||
cosPhi: device.powerFactor ?? undefined,
|
cosPhi: device.cosPhi ?? undefined,
|
||||||
category: device.category ?? undefined,
|
category: device.category ?? undefined,
|
||||||
|
remark: device.remark ?? undefined,
|
||||||
})) as { id: string };
|
})) as { id: string };
|
||||||
|
|
||||||
setActiveSectionId(sectionId);
|
setActiveSectionId(sectionId);
|
||||||
@@ -1837,11 +1837,14 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
|||||||
name: device.name,
|
name: device.name,
|
||||||
displayName: device.displayName,
|
displayName: device.displayName,
|
||||||
phaseType: resolvePhaseType(device),
|
phaseType: resolvePhaseType(device),
|
||||||
|
connectionKind: device.connectionKind ?? undefined,
|
||||||
|
costGroup: device.costGroup ?? undefined,
|
||||||
quantity: device.quantity,
|
quantity: device.quantity,
|
||||||
powerPerUnit: device.installedPowerPerUnitKw,
|
powerPerUnit: device.powerPerUnit,
|
||||||
simultaneityFactor: device.demandFactor,
|
simultaneityFactor: device.simultaneityFactor,
|
||||||
cosPhi: device.powerFactor ?? undefined,
|
cosPhi: device.cosPhi ?? undefined,
|
||||||
category: device.category ?? undefined,
|
category: device.category ?? undefined,
|
||||||
|
remark: device.remark ?? undefined,
|
||||||
})) as { id: string };
|
})) as { id: string };
|
||||||
return { rowId: createdRow.id };
|
return { rowId: createdRow.id };
|
||||||
}
|
}
|
||||||
@@ -2700,11 +2703,12 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
|||||||
>
|
>
|
||||||
<strong>{device.displayName || device.name}</strong>
|
<strong>{device.displayName || device.name}</strong>
|
||||||
<span>Name: {device.name}</span>
|
<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>Qty: {device.quantity}</span>
|
||||||
<span>P/unit: {device.installedPowerPerUnitKw}</span>
|
<span>P/unit: {device.powerPerUnit}</span>
|
||||||
<span>g: {device.demandFactor}</span>
|
<span>g: {device.simultaneityFactor}</span>
|
||||||
<span>Cost group: -</span>
|
<span>Total: {device.totalPower}</span>
|
||||||
|
<span>Cost group: {device.costGroup || "-"}</span>
|
||||||
<span>Category: {device.category || "-"}</span>
|
<span>Category: {device.category || "-"}</span>
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
|
|||||||
+16
-5
@@ -91,8 +91,17 @@ export interface ProjectDeviceDto {
|
|||||||
projectId: string;
|
projectId: string;
|
||||||
name: string;
|
name: string;
|
||||||
displayName: string;
|
displayName: string;
|
||||||
|
phaseType: "single_phase" | "three_phase";
|
||||||
|
connectionKind: string | null;
|
||||||
|
costGroup: string | null;
|
||||||
category: string | null;
|
category: string | null;
|
||||||
quantity: number;
|
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;
|
installedPowerPerUnitKw: number;
|
||||||
demandFactor: number;
|
demandFactor: number;
|
||||||
voltageV: number | null;
|
voltageV: number | null;
|
||||||
@@ -159,14 +168,16 @@ export interface CreateGlobalDeviceInput {
|
|||||||
export interface CreateProjectDeviceInput {
|
export interface CreateProjectDeviceInput {
|
||||||
name: string;
|
name: string;
|
||||||
displayName: string;
|
displayName: string;
|
||||||
|
phaseType: "single_phase" | "three_phase";
|
||||||
|
connectionKind?: string;
|
||||||
|
costGroup?: string;
|
||||||
category?: string;
|
category?: string;
|
||||||
quantity: number;
|
quantity: number;
|
||||||
installedPowerPerUnitKw: number;
|
powerPerUnit: number;
|
||||||
demandFactor: number;
|
simultaneityFactor: number;
|
||||||
|
cosPhi?: number;
|
||||||
|
remark?: string;
|
||||||
voltageV?: number;
|
voltageV?: number;
|
||||||
phaseCount?: 1 | 3;
|
|
||||||
powerFactor?: number;
|
|
||||||
note?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CircuitTreeDeviceRowDto {
|
export interface CircuitTreeDeviceRowDto {
|
||||||
|
|||||||
@@ -69,12 +69,12 @@ export async function copyProjectDeviceToGlobal(req: Request, res: Response) {
|
|||||||
displayName: source.displayName,
|
displayName: source.displayName,
|
||||||
category: source.category ?? undefined,
|
category: source.category ?? undefined,
|
||||||
quantity: source.quantity,
|
quantity: source.quantity,
|
||||||
installedPowerPerUnitKw: source.installedPowerPerUnitKw,
|
installedPowerPerUnitKw: source.powerPerUnit,
|
||||||
demandFactor: source.demandFactor,
|
demandFactor: source.simultaneityFactor,
|
||||||
voltageV: source.voltageV ?? undefined,
|
voltageV: source.voltageV ?? undefined,
|
||||||
phaseCount: source.phaseCount === 1 || source.phaseCount === 3 ? source.phaseCount : undefined,
|
phaseCount: source.phaseType === "three_phase" ? 3 : 1,
|
||||||
powerFactor: source.powerFactor ?? undefined,
|
powerFactor: source.cosPhi ?? undefined,
|
||||||
note: source.note ?? undefined,
|
note: source.remark ?? undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.status(201).json(created);
|
return res.status(201).json(created);
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from "express";
|
||||||
import { GlobalDeviceRepository } from "../../db/repositories/global-device.repository.js";
|
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 { ProjectDeviceRepository } from "../../db/repositories/project-device.repository.js";
|
||||||
import {
|
import {
|
||||||
createProjectDeviceSchema,
|
createProjectDeviceSchema,
|
||||||
@@ -8,7 +7,6 @@ import {
|
|||||||
} from "../../shared/validation/project-device.schemas.js";
|
} from "../../shared/validation/project-device.schemas.js";
|
||||||
|
|
||||||
const globalDeviceRepository = new GlobalDeviceRepository();
|
const globalDeviceRepository = new GlobalDeviceRepository();
|
||||||
const consumerRepository = new ConsumerRepository();
|
|
||||||
const projectDeviceRepository = new ProjectDeviceRepository();
|
const projectDeviceRepository = new ProjectDeviceRepository();
|
||||||
|
|
||||||
export async function listProjectDevicesByProject(req: Request, res: Response) {
|
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 projectDeviceRepository.update(projectId, projectDeviceId, parsed.data);
|
||||||
await consumerRepository.syncLinkedConsumersFromProjectDevice(projectId, projectDeviceId, {
|
// Linked rows are synchronized only through the explicit review flow.
|
||||||
displayName: parsed.data.displayName,
|
// Updating a project device must never silently overwrite local circuit-list values.
|
||||||
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,
|
|
||||||
});
|
|
||||||
const row = await projectDeviceRepository.findById(projectId, projectDeviceId);
|
const row = await projectDeviceRepository.findById(projectId, projectDeviceId);
|
||||||
if (!row) {
|
if (!row) {
|
||||||
return res.status(404).json({ error: "Project device not found" });
|
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, {
|
const created = await projectDeviceRepository.create(projectId, {
|
||||||
name: source.name,
|
name: source.name,
|
||||||
displayName: source.displayName,
|
displayName: source.displayName,
|
||||||
|
phaseType: source.phaseCount === 3 ? "three_phase" : "single_phase",
|
||||||
category: source.category ?? undefined,
|
category: source.category ?? undefined,
|
||||||
quantity: source.quantity,
|
quantity: source.quantity,
|
||||||
installedPowerPerUnitKw: source.installedPowerPerUnitKw,
|
powerPerUnit: source.installedPowerPerUnitKw,
|
||||||
demandFactor: source.demandFactor,
|
simultaneityFactor: source.demandFactor,
|
||||||
|
cosPhi: source.powerFactor ?? undefined,
|
||||||
|
remark: source.note ?? undefined,
|
||||||
voltageV: source.voltageV ?? 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);
|
return res.status(201).json(created);
|
||||||
|
|||||||
@@ -3,14 +3,17 @@ import { z } from "zod";
|
|||||||
export const createProjectDeviceSchema = z.object({
|
export const createProjectDeviceSchema = z.object({
|
||||||
name: z.string().min(1),
|
name: z.string().min(1),
|
||||||
displayName: 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(),
|
category: z.string().optional(),
|
||||||
quantity: z.number().min(0),
|
quantity: z.number().min(0),
|
||||||
installedPowerPerUnitKw: z.number().min(0),
|
powerPerUnit: z.number().min(0),
|
||||||
demandFactor: z.number().min(0).max(1),
|
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(),
|
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;
|
export const updateProjectDeviceSchema = createProjectDeviceSchema;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user