Remove legacy consumer API
This commit is contained in:
@@ -148,14 +148,12 @@ Request sketch:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Legacy Endpoints (Temporary)
|
## Removed Legacy Endpoints
|
||||||
|
|
||||||
- `GET /consumers/projects/:projectId`
|
The former `/consumers` read/write endpoints were removed after every retained
|
||||||
- `POST /consumers`
|
consumer had a verified Circuit-First migration mapping. Database upgrade tooling
|
||||||
- `PUT /consumers/:consumerId`
|
reads retained legacy rows directly; application features must use the Circuit-First
|
||||||
- `DELETE /consumers/:consumerId`
|
endpoints above.
|
||||||
|
|
||||||
These remain for migration/legacy views. New circuit-list interactions must use circuit-first endpoints above.
|
|
||||||
|
|
||||||
## Linked Project Device Review
|
## Linked Project Device Review
|
||||||
|
|
||||||
|
|||||||
@@ -10,4 +10,4 @@
|
|||||||
- No full norm-compliant voltage-drop and protection-dimensioning calculation flow yet.
|
- No full norm-compliant voltage-drop and protection-dimensioning calculation flow yet.
|
||||||
- Sorting is view-only until users explicitly apply sorted order.
|
- Sorting is view-only until users explicitly apply sorted order.
|
||||||
- Cross-section circuit drag-reorder is intentionally blocked.
|
- Cross-section circuit drag-reorder is intentionally blocked.
|
||||||
- The legacy consumer UI has been removed after complete migration; its server endpoints and retained database records are still pending final cleanup.
|
- Legacy consumer UI and server endpoints are removed; retained source rows and migration mappings remain available only for upgrade traceability.
|
||||||
|
|||||||
@@ -78,6 +78,6 @@ Actions:
|
|||||||
|
|
||||||
Do not delete legacy consumers yet.
|
Do not delete legacy consumers yet.
|
||||||
|
|
||||||
- legacy write endpoints remain temporarily available for the next server cutover step
|
- legacy read/write endpoints have been removed from the application server
|
||||||
- migration trace tables reference old/new mapping
|
- migration trace tables reference old/new mapping
|
||||||
- keeping legacy rows allows comparison and rollback validation during transition
|
- keeping legacy rows allows comparison and rollback validation during transition
|
||||||
|
|||||||
@@ -342,6 +342,8 @@ Implemented foundation:
|
|||||||
- the legacy consumer migration receives its database dependency at the script entry point
|
- the legacy consumer migration receives its database dependency at the script entry point
|
||||||
- legacy circuit, row, mapping and report writes have real late-failure rollback coverage
|
- legacy circuit, row, mapping and report writes have real late-failure rollback coverage
|
||||||
- circuit and device-row persistence value mapping is separated from the general repositories
|
- circuit and device-row persistence value mapping is separated from the general repositories
|
||||||
|
- the legacy consumer UI and application read/write endpoints are removed after verified data cutover
|
||||||
|
- retained legacy rows are accessible only through explicit database upgrade tooling
|
||||||
|
|
||||||
## Phase 12: Project Revisions and Persistent Undo / Redo
|
## Phase 12: Project Revisions and Persistent Undo / Redo
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -14,8 +14,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/project-device-schema.test.ts tests/project-device-placement.service.test.ts tests/project-device-sync.service.test.ts tests/legacy-consumer-migration-planner.test.ts tests/legacy-consumer-migration.repository.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 tests/circuit-grid-insertion.test.ts tests/circuit-grid-safety.test.ts tests/circuit-grid-model.test.ts tests/circuit-grid-projection.test.ts tests/distribution-board.repository.test.ts tests/circuit-device-row-transaction.repository.test.ts tests/project-device-row-sync.repository.test.ts tests/circuit-section-transaction.repository.test.ts tests/database-backup.test.ts",
|
"test": "tsx --test tests/project-device-schema.test.ts tests/project-device-placement.service.test.ts tests/project-device-sync.service.test.ts tests/legacy-consumer-migration-planner.test.ts tests/legacy-consumer-migration.repository.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 tests/circuit-grid-insertion.test.ts tests/circuit-grid-safety.test.ts tests/circuit-grid-model.test.ts tests/circuit-grid-projection.test.ts tests/distribution-board.repository.test.ts tests/circuit-device-row-transaction.repository.test.ts tests/project-device-row-sync.repository.test.ts tests/circuit-section-transaction.repository.test.ts tests/database-backup.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/project-device-placement.service.test.ts tests/project-device-sync.service.test.ts tests/legacy-consumer-migration-planner.test.ts tests/legacy-consumer-migration.repository.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 tests/circuit-grid-insertion.test.ts tests/circuit-grid-safety.test.ts tests/circuit-grid-model.test.ts tests/circuit-grid-projection.test.ts tests/distribution-board.repository.test.ts tests/circuit-device-row-transaction.repository.test.ts tests/project-device-row-sync.repository.test.ts tests/circuit-section-transaction.repository.test.ts tests/database-backup.test.ts",
|
"test:watch": "tsx --watch --test tests/project-device-schema.test.ts tests/project-device-placement.service.test.ts tests/project-device-sync.service.test.ts tests/legacy-consumer-migration-planner.test.ts tests/legacy-consumer-migration.repository.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 tests/circuit-grid-insertion.test.ts tests/circuit-grid-safety.test.ts tests/circuit-grid-model.test.ts tests/circuit-grid-projection.test.ts tests/distribution-board.repository.test.ts tests/circuit-device-row-transaction.repository.test.ts tests/project-device-row-sync.repository.test.ts tests/circuit-section-transaction.repository.test.ts tests/database-backup.test.ts",
|
||||||
"db:generate": "drizzle-kit generate",
|
"db:generate": "drizzle-kit generate",
|
||||||
"db:migrate": "drizzle-kit migrate",
|
"db:migrate": "drizzle-kit migrate",
|
||||||
"db:backup": "tsx scripts/db-backup.ts",
|
"db:backup": "tsx scripts/db-backup.ts",
|
||||||
|
|||||||
@@ -1,147 +0,0 @@
|
|||||||
import crypto from "node:crypto";
|
|
||||||
import { and, eq } from "drizzle-orm";
|
|
||||||
import { db } from "../client.js";
|
|
||||||
import { consumers } from "../schema/consumers.js";
|
|
||||||
import type {
|
|
||||||
CreateConsumerInput,
|
|
||||||
UpdateConsumerInput,
|
|
||||||
} from "../../shared/validation/consumer.schemas.js";
|
|
||||||
|
|
||||||
export class ConsumerRepository {
|
|
||||||
async listByProject(projectId: string) {
|
|
||||||
return db.select().from(consumers).where(eq(consumers.projectId, projectId));
|
|
||||||
}
|
|
||||||
|
|
||||||
async listByCircuitList(circuitListId: string) {
|
|
||||||
return db.select().from(consumers).where(eq(consumers.circuitListId, circuitListId));
|
|
||||||
}
|
|
||||||
|
|
||||||
async create(input: CreateConsumerInput) {
|
|
||||||
const id = crypto.randomUUID();
|
|
||||||
const normalizedName = input.name?.trim() || "Unbenannter Eintrag";
|
|
||||||
const normalizedQuantity = input.quantity ?? 0;
|
|
||||||
const normalizedInstalledPowerPerUnitKw = input.installedPowerPerUnitKw ?? 0;
|
|
||||||
const normalizedDemandFactor = input.demandFactor ?? 1;
|
|
||||||
await db.insert(consumers).values({
|
|
||||||
id,
|
|
||||||
projectId: input.projectId,
|
|
||||||
distributionBoardId: input.distributionBoardId ?? null,
|
|
||||||
circuitListId: input.circuitListId ?? null,
|
|
||||||
projectDeviceId: input.projectDeviceId ?? null,
|
|
||||||
isLinkedToDevice: input.isLinkedToDevice ? 1 : 0,
|
|
||||||
roomId: input.roomId ?? null,
|
|
||||||
circuitNumber: input.circuitNumber ?? null,
|
|
||||||
description: input.description ?? null,
|
|
||||||
name: normalizedName,
|
|
||||||
category: input.category ?? null,
|
|
||||||
deviceType: input.deviceType ?? null,
|
|
||||||
phaseType: input.phaseType ?? null,
|
|
||||||
tradeOrCostGroup: input.tradeOrCostGroup ?? null,
|
|
||||||
group: input.group ?? null,
|
|
||||||
protectionType: input.protectionType ?? null,
|
|
||||||
protectionRatedCurrent: input.protectionRatedCurrent ?? null,
|
|
||||||
protectionCharacteristic: input.protectionCharacteristic ?? null,
|
|
||||||
cableType: input.cableType ?? null,
|
|
||||||
cableCrossSection: input.cableCrossSection ?? null,
|
|
||||||
comment: input.comment ?? null,
|
|
||||||
quantity: normalizedQuantity,
|
|
||||||
installedPowerPerUnitKw: normalizedInstalledPowerPerUnitKw,
|
|
||||||
demandFactor: normalizedDemandFactor,
|
|
||||||
voltageV: input.voltageV ?? null,
|
|
||||||
phaseCount: input.phaseCount ?? null,
|
|
||||||
powerFactor: input.powerFactor ?? null,
|
|
||||||
note: input.note ?? null,
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
id,
|
|
||||||
...input,
|
|
||||||
name: normalizedName,
|
|
||||||
quantity: normalizedQuantity,
|
|
||||||
installedPowerPerUnitKw: normalizedInstalledPowerPerUnitKw,
|
|
||||||
demandFactor: normalizedDemandFactor,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async update(consumerId: string, input: UpdateConsumerInput) {
|
|
||||||
const normalizedName = input.name?.trim() || "Unbenannter Eintrag";
|
|
||||||
const normalizedQuantity = input.quantity ?? 0;
|
|
||||||
const normalizedInstalledPowerPerUnitKw = input.installedPowerPerUnitKw ?? 0;
|
|
||||||
const normalizedDemandFactor = input.demandFactor ?? 1;
|
|
||||||
await db
|
|
||||||
.update(consumers)
|
|
||||||
.set({
|
|
||||||
projectId: input.projectId,
|
|
||||||
distributionBoardId: input.distributionBoardId ?? null,
|
|
||||||
circuitListId: input.circuitListId ?? null,
|
|
||||||
projectDeviceId: input.projectDeviceId ?? null,
|
|
||||||
isLinkedToDevice: input.isLinkedToDevice ? 1 : 0,
|
|
||||||
roomId: input.roomId ?? null,
|
|
||||||
circuitNumber: input.circuitNumber ?? null,
|
|
||||||
description: input.description ?? null,
|
|
||||||
name: normalizedName,
|
|
||||||
category: input.category ?? null,
|
|
||||||
deviceType: input.deviceType ?? null,
|
|
||||||
phaseType: input.phaseType ?? null,
|
|
||||||
tradeOrCostGroup: input.tradeOrCostGroup ?? null,
|
|
||||||
group: input.group ?? null,
|
|
||||||
protectionType: input.protectionType ?? null,
|
|
||||||
protectionRatedCurrent: input.protectionRatedCurrent ?? null,
|
|
||||||
protectionCharacteristic: input.protectionCharacteristic ?? null,
|
|
||||||
cableType: input.cableType ?? null,
|
|
||||||
cableCrossSection: input.cableCrossSection ?? null,
|
|
||||||
comment: input.comment ?? null,
|
|
||||||
quantity: normalizedQuantity,
|
|
||||||
installedPowerPerUnitKw: normalizedInstalledPowerPerUnitKw,
|
|
||||||
demandFactor: normalizedDemandFactor,
|
|
||||||
voltageV: input.voltageV ?? null,
|
|
||||||
phaseCount: input.phaseCount ?? null,
|
|
||||||
powerFactor: input.powerFactor ?? null,
|
|
||||||
note: input.note ?? null,
|
|
||||||
})
|
|
||||||
.where(eq(consumers.id, consumerId));
|
|
||||||
}
|
|
||||||
|
|
||||||
async findById(consumerId: string) {
|
|
||||||
const [row] = await db.select().from(consumers).where(eq(consumers.id, consumerId)).limit(1);
|
|
||||||
return row ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
async delete(consumerId: string) {
|
|
||||||
await db.delete(consumers).where(eq(consumers.id, consumerId));
|
|
||||||
}
|
|
||||||
|
|
||||||
async syncLinkedConsumersFromProjectDevice(
|
|
||||||
projectId: string,
|
|
||||||
projectDeviceId: string,
|
|
||||||
data: {
|
|
||||||
displayName: string;
|
|
||||||
category?: string;
|
|
||||||
quantity: number;
|
|
||||||
installedPowerPerUnitKw: number;
|
|
||||||
demandFactor: number;
|
|
||||||
phaseCount?: 1 | 3;
|
|
||||||
powerFactor?: number;
|
|
||||||
note?: string;
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
await db
|
|
||||||
.update(consumers)
|
|
||||||
.set({
|
|
||||||
name: data.displayName,
|
|
||||||
category: data.category ?? null,
|
|
||||||
quantity: data.quantity,
|
|
||||||
installedPowerPerUnitKw: data.installedPowerPerUnitKw,
|
|
||||||
demandFactor: data.demandFactor,
|
|
||||||
phaseCount: data.phaseCount ?? null,
|
|
||||||
powerFactor: data.powerFactor ?? null,
|
|
||||||
note: data.note ?? null,
|
|
||||||
})
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(consumers.projectId, projectId),
|
|
||||||
eq(consumers.projectDeviceId, projectDeviceId),
|
|
||||||
eq(consumers.isLinkedToDevice, 1)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -37,6 +37,13 @@ export interface LegacyMigrationReportPersistenceInput {
|
|||||||
export class LegacyConsumerMigrationRepository {
|
export class LegacyConsumerMigrationRepository {
|
||||||
constructor(private readonly database: AppDatabase) {}
|
constructor(private readonly database: AppDatabase) {}
|
||||||
|
|
||||||
|
async listSourceConsumersByCircuitList(circuitListId: string) {
|
||||||
|
return this.database
|
||||||
|
.select()
|
||||||
|
.from(consumers)
|
||||||
|
.where(eq(consumers.circuitListId, circuitListId));
|
||||||
|
}
|
||||||
|
|
||||||
async listUnmigratedConsumers() {
|
async listUnmigratedConsumers() {
|
||||||
return this.database
|
return this.database
|
||||||
.select({
|
.select({
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { projects } from "../schema/projects.js";
|
|||||||
import type {
|
import type {
|
||||||
CreateProjectInput,
|
CreateProjectInput,
|
||||||
UpdateProjectSettingsInput,
|
UpdateProjectSettingsInput,
|
||||||
} from "../../shared/validation/consumer.schemas.js";
|
} from "../../shared/validation/project-structure.schemas.js";
|
||||||
|
|
||||||
export class ProjectRepository {
|
export class ProjectRepository {
|
||||||
async list() {
|
async list() {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import crypto from "node:crypto";
|
|||||||
import { and, asc, eq } from "drizzle-orm";
|
import { and, asc, eq } from "drizzle-orm";
|
||||||
import { db } from "../client.js";
|
import { db } from "../client.js";
|
||||||
import { rooms } from "../schema/rooms.js";
|
import { rooms } from "../schema/rooms.js";
|
||||||
import type { CreateRoomInput } from "../../shared/validation/consumer.schemas.js";
|
import type { CreateRoomInput } from "../../shared/validation/project-structure.schemas.js";
|
||||||
|
|
||||||
export class RoomRepository {
|
export class RoomRepository {
|
||||||
async listByProject(projectId: string) {
|
async listByProject(projectId: string) {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export const projectDevices = sqliteTable("project_devices", {
|
|||||||
simultaneityFactor: real("simultaneity_factor").notNull().default(1),
|
simultaneityFactor: real("simultaneity_factor").notNull().default(1),
|
||||||
cosPhi: real("cos_phi"),
|
cosPhi: real("cos_phi"),
|
||||||
remark: text("remark"),
|
remark: text("remark"),
|
||||||
// Legacy compatibility fields. Keep these synchronized until the Consumer editor is removed.
|
// Transitional mirror columns retained until a dedicated schema cleanup migration removes them.
|
||||||
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"),
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
export interface PowerInput {
|
|
||||||
quantity: number;
|
|
||||||
installedPowerPerUnitKw: number;
|
|
||||||
demandFactor: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CurrentInput {
|
|
||||||
demandPowerKw: number;
|
|
||||||
voltageV: number;
|
|
||||||
phaseCount: 1 | 3;
|
|
||||||
powerFactor: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function calculateInstalledPowerKw(input: PowerInput): number {
|
|
||||||
return input.quantity * input.installedPowerPerUnitKw;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function calculateDemandPowerKw(input: PowerInput): number {
|
|
||||||
return calculateInstalledPowerKw(input) * input.demandFactor;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function calculateCurrentA(input: CurrentInput): number {
|
|
||||||
const powerW = input.demandPowerKw * 1000;
|
|
||||||
if (input.phaseCount === 1) {
|
|
||||||
return powerW / (input.voltageV * input.powerFactor);
|
|
||||||
}
|
|
||||||
return powerW / (Math.sqrt(3) * input.voltageV * input.powerFactor);
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
export interface Consumer {
|
|
||||||
id: string;
|
|
||||||
projectId: string;
|
|
||||||
distributionBoardId?: string;
|
|
||||||
circuitListId?: string;
|
|
||||||
projectDeviceId?: string;
|
|
||||||
isLinkedToDevice?: boolean;
|
|
||||||
roomId?: string;
|
|
||||||
roomNumber?: string;
|
|
||||||
roomName?: string;
|
|
||||||
floorId?: string;
|
|
||||||
floorName?: string;
|
|
||||||
circuitNumber?: string;
|
|
||||||
description?: string;
|
|
||||||
name: string;
|
|
||||||
category?: string;
|
|
||||||
deviceType?: string;
|
|
||||||
phaseType?: string;
|
|
||||||
tradeOrCostGroup?: string;
|
|
||||||
group?: string;
|
|
||||||
protectionType?: string;
|
|
||||||
protectionRatedCurrent?: number;
|
|
||||||
protectionCharacteristic?: string;
|
|
||||||
cableType?: string;
|
|
||||||
cableCrossSection?: string;
|
|
||||||
comment?: string;
|
|
||||||
quantity: number;
|
|
||||||
installedPowerPerUnitKw: number;
|
|
||||||
demandFactor: number;
|
|
||||||
voltageV?: number;
|
|
||||||
phaseCount?: 1 | 3;
|
|
||||||
powerFactor?: number;
|
|
||||||
note?: string;
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
import type { CreateConsumerInput } from "../../shared/validation/consumer.schemas.js";
|
|
||||||
|
|
||||||
type ProjectDeviceForLinking = {
|
|
||||||
displayName: string;
|
|
||||||
category: string | null;
|
|
||||||
quantity: number;
|
|
||||||
installedPowerPerUnitKw: number;
|
|
||||||
demandFactor: number;
|
|
||||||
phaseCount: number | null;
|
|
||||||
powerFactor: number | null;
|
|
||||||
note: string | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function applyLinkedProjectDeviceValues(
|
|
||||||
input: CreateConsumerInput,
|
|
||||||
projectDevice: ProjectDeviceForLinking | null
|
|
||||||
): CreateConsumerInput {
|
|
||||||
if (!input.projectDeviceId || !input.isLinkedToDevice || !projectDevice) {
|
|
||||||
return input;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...input,
|
|
||||||
name: projectDevice.displayName,
|
|
||||||
category: projectDevice.category ?? undefined,
|
|
||||||
quantity: projectDevice.quantity,
|
|
||||||
installedPowerPerUnitKw: projectDevice.installedPowerPerUnitKw,
|
|
||||||
demandFactor: projectDevice.demandFactor,
|
|
||||||
phaseCount:
|
|
||||||
projectDevice.phaseCount === 1 || projectDevice.phaseCount === 3
|
|
||||||
? (projectDevice.phaseCount as 1 | 3)
|
|
||||||
: undefined,
|
|
||||||
powerFactor: projectDevice.powerFactor ?? undefined,
|
|
||||||
note: projectDevice.note ?? undefined,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import { CircuitRepository } from "../../db/repositories/circuit.repository.js";
|
import { CircuitRepository } from "../../db/repositories/circuit.repository.js";
|
||||||
import { CircuitListRepository } from "../../db/repositories/circuit-list.repository.js";
|
import { CircuitListRepository } from "../../db/repositories/circuit-list.repository.js";
|
||||||
import { CircuitSectionRepository } from "../../db/repositories/circuit-section.repository.js";
|
import { CircuitSectionRepository } from "../../db/repositories/circuit-section.repository.js";
|
||||||
import { ConsumerRepository } from "../../db/repositories/consumer.repository.js";
|
|
||||||
import {
|
import {
|
||||||
LegacyConsumerMigrationRepository,
|
LegacyConsumerMigrationRepository,
|
||||||
type LegacyMigrationCircuitPersistenceInput,
|
type LegacyMigrationCircuitPersistenceInput,
|
||||||
@@ -14,7 +13,11 @@ import {
|
|||||||
normalizeCircuitNumber,
|
normalizeCircuitNumber,
|
||||||
} from "./legacy-consumer-migration-planner.js";
|
} from "./legacy-consumer-migration-planner.js";
|
||||||
|
|
||||||
type LegacyConsumerRow = Awaited<ReturnType<ConsumerRepository["listByCircuitList"]>>[number];
|
type LegacyConsumerRow = Awaited<
|
||||||
|
ReturnType<
|
||||||
|
LegacyConsumerMigrationRepository["listSourceConsumersByCircuitList"]
|
||||||
|
>
|
||||||
|
>[number];
|
||||||
|
|
||||||
function parseEquipmentSequence(equipmentIdentifier: string, prefix: string): number | null {
|
function parseEquipmentSequence(equipmentIdentifier: string, prefix: string): number | null {
|
||||||
if (!equipmentIdentifier.startsWith(prefix)) {
|
if (!equipmentIdentifier.startsWith(prefix)) {
|
||||||
@@ -32,7 +35,6 @@ export class LegacyConsumerMigrationService {
|
|||||||
private readonly circuitListRepository = new CircuitListRepository();
|
private readonly circuitListRepository = new CircuitListRepository();
|
||||||
private readonly sectionRepository = new CircuitSectionRepository();
|
private readonly sectionRepository = new CircuitSectionRepository();
|
||||||
private readonly circuitRepository = new CircuitRepository();
|
private readonly circuitRepository = new CircuitRepository();
|
||||||
private readonly consumerRepository = new ConsumerRepository();
|
|
||||||
private readonly roomRepository = new RoomRepository();
|
private readonly roomRepository = new RoomRepository();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@@ -60,7 +62,10 @@ export class LegacyConsumerMigrationService {
|
|||||||
existingCircuits.map((circuit) => circuit.equipmentIdentifier.toUpperCase())
|
existingCircuits.map((circuit) => circuit.equipmentIdentifier.toUpperCase())
|
||||||
);
|
);
|
||||||
|
|
||||||
const legacyConsumers = await this.consumerRepository.listByCircuitList(circuitListId);
|
const legacyConsumers =
|
||||||
|
await this.migrationRepository.listSourceConsumersByCircuitList(
|
||||||
|
circuitListId
|
||||||
|
);
|
||||||
const rooms = await this.roomRepository.listByProject(projectId);
|
const rooms = await this.roomRepository.listByProject(projectId);
|
||||||
const roomById = new Map(rooms.map((room) => [room.id, room]));
|
const roomById = new Map(rooms.map((room) => [room.id, room]));
|
||||||
|
|
||||||
|
|||||||
@@ -1,62 +0,0 @@
|
|||||||
import {
|
|
||||||
calculateCurrentA,
|
|
||||||
calculateDemandPowerKw,
|
|
||||||
calculateInstalledPowerKw,
|
|
||||||
} from "../calculations/power-calculation.js";
|
|
||||||
import type { Consumer } from "../models/consumer.model.js";
|
|
||||||
|
|
||||||
export interface ConsumerWithCalculatedValues extends Consumer {
|
|
||||||
installedPowerKw: number;
|
|
||||||
demandPowerKw: number;
|
|
||||||
effectiveVoltageV?: number;
|
|
||||||
currentA?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ProjectVoltageDefaults {
|
|
||||||
singlePhaseVoltageV: number;
|
|
||||||
threePhaseVoltageV: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class PowerBalanceService {
|
|
||||||
enrichConsumer(
|
|
||||||
consumer: Consumer,
|
|
||||||
projectVoltageDefaults?: ProjectVoltageDefaults
|
|
||||||
): ConsumerWithCalculatedValues {
|
|
||||||
const installedPowerKw = calculateInstalledPowerKw({
|
|
||||||
quantity: consumer.quantity,
|
|
||||||
installedPowerPerUnitKw: consumer.installedPowerPerUnitKw,
|
|
||||||
demandFactor: consumer.demandFactor,
|
|
||||||
});
|
|
||||||
const demandPowerKw = calculateDemandPowerKw({
|
|
||||||
quantity: consumer.quantity,
|
|
||||||
installedPowerPerUnitKw: consumer.installedPowerPerUnitKw,
|
|
||||||
demandFactor: consumer.demandFactor,
|
|
||||||
});
|
|
||||||
|
|
||||||
const effectiveVoltageV =
|
|
||||||
consumer.voltageV ??
|
|
||||||
(consumer.phaseCount === 1
|
|
||||||
? projectVoltageDefaults?.singlePhaseVoltageV
|
|
||||||
: consumer.phaseCount === 3
|
|
||||||
? projectVoltageDefaults?.threePhaseVoltageV
|
|
||||||
: undefined);
|
|
||||||
|
|
||||||
let currentA: number | undefined;
|
|
||||||
if (effectiveVoltageV && consumer.phaseCount && consumer.powerFactor) {
|
|
||||||
currentA = calculateCurrentA({
|
|
||||||
demandPowerKw,
|
|
||||||
voltageV: effectiveVoltageV,
|
|
||||||
phaseCount: consumer.phaseCount,
|
|
||||||
powerFactor: consumer.powerFactor,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...consumer,
|
|
||||||
installedPowerKw,
|
|
||||||
demandPowerKw,
|
|
||||||
effectiveVoltageV,
|
|
||||||
currentA,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,394 +0,0 @@
|
|||||||
import type { Request, Response } from "express";
|
|
||||||
import { db } from "../../db/client.js";
|
|
||||||
import { CircuitListRepository } from "../../db/repositories/circuit-list.repository.js";
|
|
||||||
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";
|
|
||||||
|
|
||||||
const circuitListRepository = new CircuitListRepository();
|
|
||||||
const consumerRepository = new ConsumerRepository();
|
|
||||||
const distributionBoardRepository = new DistributionBoardRepository(db);
|
|
||||||
const floorRepository = new FloorRepository();
|
|
||||||
const projectDeviceRepository = new ProjectDeviceRepository();
|
|
||||||
const projectRepository = new ProjectRepository();
|
|
||||||
const roomRepository = new RoomRepository();
|
|
||||||
const powerBalanceService = new PowerBalanceService();
|
|
||||||
|
|
||||||
type ConsumerRow = {
|
|
||||||
id: string;
|
|
||||||
projectId: string;
|
|
||||||
distributionBoardId: string | null;
|
|
||||||
circuitListId: string | null;
|
|
||||||
projectDeviceId: string | null;
|
|
||||||
isLinkedToDevice: number;
|
|
||||||
roomId: string | null;
|
|
||||||
circuitNumber: string | null;
|
|
||||||
description: string | null;
|
|
||||||
name: string;
|
|
||||||
category: string | null;
|
|
||||||
deviceType: string | null;
|
|
||||||
phaseType: string | null;
|
|
||||||
tradeOrCostGroup: string | null;
|
|
||||||
group: string | null;
|
|
||||||
protectionType: string | null;
|
|
||||||
protectionRatedCurrent: number | null;
|
|
||||||
protectionCharacteristic: string | null;
|
|
||||||
cableType: string | null;
|
|
||||||
cableCrossSection: string | null;
|
|
||||||
comment: string | null;
|
|
||||||
quantity: number;
|
|
||||||
installedPowerPerUnitKw: number;
|
|
||||||
demandFactor: number;
|
|
||||||
voltageV: number | null;
|
|
||||||
phaseCount: number | null;
|
|
||||||
powerFactor: number | null;
|
|
||||||
note: string | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
async function validateDistributionBoardOwnership(
|
|
||||||
projectId: string,
|
|
||||||
distributionBoardId: string | undefined
|
|
||||||
) {
|
|
||||||
if (!distributionBoardId) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return distributionBoardRepository.existsInProject(projectId, distributionBoardId);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function validateRoomOwnership(projectId: string, roomId: string | undefined) {
|
|
||||||
if (!roomId) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
circuitListId?: string;
|
|
||||||
}) {
|
|
||||||
let distributionBoardId = input.distributionBoardId;
|
|
||||||
let circuitListId = input.circuitListId;
|
|
||||||
|
|
||||||
if (distributionBoardId) {
|
|
||||||
const linkedList = await circuitListRepository.findByDistributionBoardId(
|
|
||||||
input.projectId,
|
|
||||||
distributionBoardId
|
|
||||||
);
|
|
||||||
if (!linkedList) {
|
|
||||||
return { ok: false as const, error: "No circuit list found for the provided distribution board." };
|
|
||||||
}
|
|
||||||
if (circuitListId && circuitListId !== linkedList.id) {
|
|
||||||
return {
|
|
||||||
ok: false as const,
|
|
||||||
error: "Circuit list does not match the provided distribution board.",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
circuitListId = linkedList.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (circuitListId) {
|
|
||||||
const list = await circuitListRepository.findById(input.projectId, circuitListId);
|
|
||||||
if (!list) {
|
|
||||||
return { ok: false as const, error: "Circuit list does not belong to the provided project." };
|
|
||||||
}
|
|
||||||
if (distributionBoardId && distributionBoardId !== list.distributionBoardId) {
|
|
||||||
return {
|
|
||||||
ok: false as const,
|
|
||||||
error: "Circuit list does not match the provided distribution board.",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
distributionBoardId = list.distributionBoardId;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
ok: true as const,
|
|
||||||
distributionBoardId,
|
|
||||||
circuitListId,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildConsumerFromRow(
|
|
||||||
row: ConsumerRow,
|
|
||||||
roomById: Map<string, { floorId: string | null; roomName: string; roomNumber: string }>,
|
|
||||||
floorById: Map<string, { name: string }>
|
|
||||||
): Consumer {
|
|
||||||
const room = row.roomId ? roomById.get(row.roomId) : undefined;
|
|
||||||
const floor = room?.floorId ? floorById.get(room.floorId) : undefined;
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: row.id,
|
|
||||||
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,
|
|
||||||
floorId: room?.floorId ?? undefined,
|
|
||||||
floorName: floor?.name,
|
|
||||||
circuitNumber: row.circuitNumber ?? undefined,
|
|
||||||
description: row.description ?? undefined,
|
|
||||||
name: row.name,
|
|
||||||
category: row.category ?? undefined,
|
|
||||||
deviceType: row.deviceType ?? undefined,
|
|
||||||
phaseType: row.phaseType ?? undefined,
|
|
||||||
tradeOrCostGroup: row.tradeOrCostGroup ?? undefined,
|
|
||||||
group: row.group ?? undefined,
|
|
||||||
protectionType: row.protectionType ?? undefined,
|
|
||||||
protectionRatedCurrent: row.protectionRatedCurrent ?? undefined,
|
|
||||||
protectionCharacteristic: row.protectionCharacteristic ?? undefined,
|
|
||||||
cableType: row.cableType ?? undefined,
|
|
||||||
cableCrossSection: row.cableCrossSection ?? undefined,
|
|
||||||
comment: row.comment ?? undefined,
|
|
||||||
quantity: row.quantity,
|
|
||||||
installedPowerPerUnitKw: row.installedPowerPerUnitKw,
|
|
||||||
demandFactor: row.demandFactor,
|
|
||||||
voltageV: row.voltageV ?? undefined,
|
|
||||||
phaseCount: row.phaseCount === 1 || row.phaseCount === 3 ? row.phaseCount : undefined,
|
|
||||||
powerFactor: row.powerFactor ?? undefined,
|
|
||||||
note: row.note ?? undefined,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function listConsumersByProject(req: Request, res: Response) {
|
|
||||||
const { projectId } = req.params;
|
|
||||||
if (typeof projectId !== "string") {
|
|
||||||
return res.status(400).json({ error: "Invalid projectId" });
|
|
||||||
}
|
|
||||||
|
|
||||||
const [rows, project, floors, rooms] = await Promise.all([
|
|
||||||
consumerRepository.listByProject(projectId),
|
|
||||||
projectRepository.findById(projectId),
|
|
||||||
floorRepository.listByProject(projectId),
|
|
||||||
roomRepository.listByProject(projectId),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const roomById = new Map(
|
|
||||||
rooms.map((room) => [room.id, { floorId: room.floorId, roomName: room.roomName, roomNumber: room.roomNumber }])
|
|
||||||
);
|
|
||||||
const floorById = new Map(floors.map((floor) => [floor.id, { name: floor.name }]));
|
|
||||||
|
|
||||||
const projectVoltageDefaults = project
|
|
||||||
? {
|
|
||||||
singlePhaseVoltageV: project.singlePhaseVoltageV,
|
|
||||||
threePhaseVoltageV: project.threePhaseVoltageV,
|
|
||||||
}
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
const enriched = rows.map((row) =>
|
|
||||||
powerBalanceService.enrichConsumer(
|
|
||||||
buildConsumerFromRow(row as ConsumerRow, roomById, floorById),
|
|
||||||
projectVoltageDefaults
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
return res.json(enriched);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function createConsumer(req: Request, res: Response) {
|
|
||||||
const parsed = createConsumerSchema.safeParse(req.body);
|
|
||||||
if (!parsed.success) {
|
|
||||||
return res.status(400).json({ error: parsed.error.flatten() });
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
.status(400)
|
|
||||||
.json({ error: "Distribution board does not belong to the provided project." });
|
|
||||||
}
|
|
||||||
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: normalizedData.projectId,
|
|
||||||
distributionBoardId: normalizedData.distributionBoardId,
|
|
||||||
circuitListId: normalizedData.circuitListId,
|
|
||||||
});
|
|
||||||
if (!resolvedScope.ok) {
|
|
||||||
return res.status(400).json({ error: resolvedScope.error });
|
|
||||||
}
|
|
||||||
|
|
||||||
const payload = await applyDeviceLinkIfNeeded({
|
|
||||||
...normalizedData,
|
|
||||||
distributionBoardId: resolvedScope.distributionBoardId,
|
|
||||||
circuitListId: resolvedScope.circuitListId,
|
|
||||||
description: normalizedData.description ?? normalizedData.name,
|
|
||||||
});
|
|
||||||
|
|
||||||
const created = await consumerRepository.create(payload);
|
|
||||||
const [project, floors, rooms] = await Promise.all([
|
|
||||||
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 }])
|
|
||||||
);
|
|
||||||
const floorById = new Map(floors.map((floor) => [floor.id, { name: floor.name }]));
|
|
||||||
|
|
||||||
const enriched = powerBalanceService.enrichConsumer(
|
|
||||||
{
|
|
||||||
...(created as Consumer),
|
|
||||||
description: created.description ?? created.name,
|
|
||||||
roomNumber: created.roomId ? roomById.get(created.roomId)?.roomNumber : undefined,
|
|
||||||
roomName: created.roomId ? roomById.get(created.roomId)?.roomName : undefined,
|
|
||||||
floorId: created.roomId ? roomById.get(created.roomId)?.floorId ?? undefined : undefined,
|
|
||||||
floorName:
|
|
||||||
created.roomId && roomById.get(created.roomId)?.floorId
|
|
||||||
? floorById.get(roomById.get(created.roomId)!.floorId as string)?.name
|
|
||||||
: undefined,
|
|
||||||
},
|
|
||||||
project
|
|
||||||
? {
|
|
||||||
singlePhaseVoltageV: project.singlePhaseVoltageV,
|
|
||||||
threePhaseVoltageV: project.threePhaseVoltageV,
|
|
||||||
}
|
|
||||||
: undefined
|
|
||||||
);
|
|
||||||
|
|
||||||
return res.status(201).json(enriched);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function updateConsumer(req: Request, res: Response) {
|
|
||||||
const { consumerId } = req.params;
|
|
||||||
if (typeof consumerId !== "string") {
|
|
||||||
return res.status(400).json({ error: "Invalid consumerId" });
|
|
||||||
}
|
|
||||||
|
|
||||||
const parsed = updateConsumerSchema.safeParse(req.body);
|
|
||||||
if (!parsed.success) {
|
|
||||||
return res.status(400).json({ error: parsed.error.flatten() });
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
.status(400)
|
|
||||||
.json({ error: "Distribution board does not belong to the provided project." });
|
|
||||||
}
|
|
||||||
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: normalizedData.projectId,
|
|
||||||
distributionBoardId: normalizedData.distributionBoardId,
|
|
||||||
circuitListId: normalizedData.circuitListId,
|
|
||||||
});
|
|
||||||
if (!resolvedScope.ok) {
|
|
||||||
return res.status(400).json({ error: resolvedScope.error });
|
|
||||||
}
|
|
||||||
|
|
||||||
const payload = await applyDeviceLinkIfNeeded({
|
|
||||||
...normalizedData,
|
|
||||||
distributionBoardId: resolvedScope.distributionBoardId,
|
|
||||||
circuitListId: resolvedScope.circuitListId,
|
|
||||||
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" });
|
|
||||||
}
|
|
||||||
|
|
||||||
const [project, floors, rooms] = await Promise.all([
|
|
||||||
projectRepository.findById(row.projectId),
|
|
||||||
floorRepository.listByProject(row.projectId),
|
|
||||||
roomRepository.listByProject(row.projectId),
|
|
||||||
]);
|
|
||||||
const roomById = new Map(
|
|
||||||
rooms.map((room) => [room.id, { floorId: room.floorId, roomName: room.roomName, roomNumber: room.roomNumber }])
|
|
||||||
);
|
|
||||||
const floorById = new Map(floors.map((floor) => [floor.id, { name: floor.name }]));
|
|
||||||
|
|
||||||
const enriched = powerBalanceService.enrichConsumer(
|
|
||||||
buildConsumerFromRow(row as ConsumerRow, roomById, floorById),
|
|
||||||
project
|
|
||||||
? {
|
|
||||||
singlePhaseVoltageV: project.singlePhaseVoltageV,
|
|
||||||
threePhaseVoltageV: project.threePhaseVoltageV,
|
|
||||||
}
|
|
||||||
: undefined
|
|
||||||
);
|
|
||||||
|
|
||||||
return res.json(enriched);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function deleteConsumer(req: Request, res: Response) {
|
|
||||||
const { consumerId } = req.params;
|
|
||||||
if (typeof consumerId !== "string") {
|
|
||||||
return res.status(400).json({ error: "Invalid consumerId" });
|
|
||||||
}
|
|
||||||
|
|
||||||
await consumerRepository.delete(consumerId);
|
|
||||||
return res.status(204).send();
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from "express";
|
||||||
import { db } from "../../db/client.js";
|
import { db } from "../../db/client.js";
|
||||||
import { DistributionBoardRepository } from "../../db/repositories/distribution-board.repository.js";
|
import { DistributionBoardRepository } from "../../db/repositories/distribution-board.repository.js";
|
||||||
import { createDistributionBoardSchema } from "../../shared/validation/consumer.schemas.js";
|
import { createDistributionBoardSchema } from "../../shared/validation/project-structure.schemas.js";
|
||||||
|
|
||||||
const distributionBoardRepository = new DistributionBoardRepository(db);
|
const distributionBoardRepository = new DistributionBoardRepository(db);
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from "express";
|
||||||
import { FloorRepository } from "../../db/repositories/floor.repository.js";
|
import { FloorRepository } from "../../db/repositories/floor.repository.js";
|
||||||
import { createFloorSchema } from "../../shared/validation/consumer.schemas.js";
|
import { createFloorSchema } from "../../shared/validation/project-structure.schemas.js";
|
||||||
|
|
||||||
const floorRepository = new FloorRepository();
|
const floorRepository = new FloorRepository();
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { ProjectRepository } from "../../db/repositories/project.repository.js";
|
|||||||
import {
|
import {
|
||||||
createProjectSchema,
|
createProjectSchema,
|
||||||
updateProjectSettingsSchema,
|
updateProjectSettingsSchema,
|
||||||
} from "../../shared/validation/consumer.schemas.js";
|
} from "../../shared/validation/project-structure.schemas.js";
|
||||||
|
|
||||||
const projectRepository = new ProjectRepository();
|
const projectRepository = new ProjectRepository();
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from "express";
|
||||||
import { FloorRepository } from "../../db/repositories/floor.repository.js";
|
import { FloorRepository } from "../../db/repositories/floor.repository.js";
|
||||||
import { RoomRepository } from "../../db/repositories/room.repository.js";
|
import { RoomRepository } from "../../db/repositories/room.repository.js";
|
||||||
import { createRoomSchema } from "../../shared/validation/consumer.schemas.js";
|
import { createRoomSchema } from "../../shared/validation/project-structure.schemas.js";
|
||||||
|
|
||||||
const floorRepository = new FloorRepository();
|
const floorRepository = new FloorRepository();
|
||||||
const roomRepository = new RoomRepository();
|
const roomRepository = new RoomRepository();
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import express from "express";
|
|||||||
import { circuitDeviceRowRouter } from "./routes/circuit-device-row.routes.js";
|
import { circuitDeviceRowRouter } from "./routes/circuit-device-row.routes.js";
|
||||||
import { circuitRouter } from "./routes/circuit.routes.js";
|
import { circuitRouter } from "./routes/circuit.routes.js";
|
||||||
import { circuitSectionRouter } from "./routes/circuit-section.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 { globalDeviceRouter } from "./routes/global-device.routes.js";
|
||||||
import { projectDeviceRouter } from "./routes/project-device.routes.js";
|
import { projectDeviceRouter } from "./routes/project-device.routes.js";
|
||||||
import { projectRouter } from "./routes/project.routes.js";
|
import { projectRouter } from "./routes/project.routes.js";
|
||||||
@@ -21,7 +20,6 @@ app.use("/api/projects", projectRouter);
|
|||||||
app.use("/api", circuitRouter);
|
app.use("/api", circuitRouter);
|
||||||
app.use("/api", circuitDeviceRowRouter);
|
app.use("/api", circuitDeviceRowRouter);
|
||||||
app.use("/api", circuitSectionRouter);
|
app.use("/api", circuitSectionRouter);
|
||||||
app.use("/api/consumers", consumerRouter);
|
|
||||||
app.use("/api/global-devices", globalDeviceRouter);
|
app.use("/api/global-devices", globalDeviceRouter);
|
||||||
app.use("/api/project-devices", projectDeviceRouter);
|
app.use("/api/project-devices", projectDeviceRouter);
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
import { Router } from "express";
|
|
||||||
import {
|
|
||||||
createConsumer,
|
|
||||||
deleteConsumer,
|
|
||||||
listConsumersByProject,
|
|
||||||
updateConsumer,
|
|
||||||
} from "../controllers/consumer.controller.js";
|
|
||||||
|
|
||||||
export const consumerRouter = Router();
|
|
||||||
|
|
||||||
consumerRouter.get("/projects/:projectId", listConsumersByProject);
|
|
||||||
consumerRouter.post("/", createConsumer);
|
|
||||||
consumerRouter.put("/:consumerId", updateConsumer);
|
|
||||||
consumerRouter.delete("/:consumerId", deleteConsumer);
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
export const deviceTypeOptions = [
|
|
||||||
"Beleuchtung",
|
|
||||||
"Steckdose",
|
|
||||||
"Heizung",
|
|
||||||
"Kühlung",
|
|
||||||
"Lüftung",
|
|
||||||
"Antrieb",
|
|
||||||
"Sicherheit",
|
|
||||||
"IT",
|
|
||||||
"Sonstiges",
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
export const phaseTypeOptions = ["1-phasig", "3-phasig"] as const;
|
|
||||||
|
|
||||||
export const tradeOrCostGroupOptions = [
|
|
||||||
"KG 440 Starkstromanlagen",
|
|
||||||
"KG 450 Fernmelde- und informationstechnische Anlagen",
|
|
||||||
"KG 460 Förderanlagen",
|
|
||||||
"KG 470 Nutzungsspezifische Anlagen",
|
|
||||||
"KG 480 Gebäude- und Anlagenautomation",
|
|
||||||
"Sonstiges",
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
export const consumerGroupOptions = [
|
|
||||||
"Allgemein",
|
|
||||||
"Notstrom",
|
|
||||||
"Sicherheitsstrom",
|
|
||||||
"USV",
|
|
||||||
"Technik",
|
|
||||||
"Reserve",
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
export const protectionTypeOptions = ["LS", "Schmelzsicherung", "Leistungsschalter", "FI/LS"] as const;
|
|
||||||
|
|
||||||
export const protectionCharacteristicOptions = ["B", "C", "D", "K", "Z"] as const;
|
|
||||||
|
|
||||||
export const cableTypeOptions = ["NYM-J", "NYY-J", "H07RN-F", "NHXMH-J", "Sonstiges"] as const;
|
|
||||||
|
|
||||||
export const cableCrossSectionOptions = [
|
|
||||||
"1,5 mm²",
|
|
||||||
"2,5 mm²",
|
|
||||||
"4 mm²",
|
|
||||||
"6 mm²",
|
|
||||||
"10 mm²",
|
|
||||||
"16 mm²",
|
|
||||||
"25 mm²",
|
|
||||||
"35 mm²",
|
|
||||||
"50 mm²",
|
|
||||||
] as const;
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
import { z } from "zod";
|
|
||||||
import {
|
|
||||||
cableCrossSectionOptions,
|
|
||||||
cableTypeOptions,
|
|
||||||
consumerGroupOptions,
|
|
||||||
deviceTypeOptions,
|
|
||||||
phaseTypeOptions,
|
|
||||||
protectionCharacteristicOptions,
|
|
||||||
protectionTypeOptions,
|
|
||||||
tradeOrCostGroupOptions,
|
|
||||||
} from "../constants/consumer-option-lists.js";
|
|
||||||
|
|
||||||
export const createConsumerSchema = z.object({
|
|
||||||
projectId: z.string().min(1),
|
|
||||||
distributionBoardId: z.string().min(1).optional(),
|
|
||||||
circuitListId: z.string().min(1).optional(),
|
|
||||||
projectDeviceId: z.string().min(1).optional(),
|
|
||||||
isLinkedToDevice: z.boolean().optional(),
|
|
||||||
roomId: z.string().min(1).optional(),
|
|
||||||
circuitNumber: z.string().optional(),
|
|
||||||
description: z.string().optional(),
|
|
||||||
name: z.string().optional(),
|
|
||||||
category: z.string().optional(),
|
|
||||||
deviceType: z.enum(deviceTypeOptions).optional(),
|
|
||||||
phaseType: z.enum(phaseTypeOptions).optional(),
|
|
||||||
tradeOrCostGroup: z.enum(tradeOrCostGroupOptions).optional(),
|
|
||||||
group: z.enum(consumerGroupOptions).optional(),
|
|
||||||
protectionType: z.enum(protectionTypeOptions).optional(),
|
|
||||||
protectionRatedCurrent: z.number().min(0).optional(),
|
|
||||||
protectionCharacteristic: z.enum(protectionCharacteristicOptions).optional(),
|
|
||||||
cableType: z.enum(cableTypeOptions).optional(),
|
|
||||||
cableCrossSection: z.enum(cableCrossSectionOptions).optional(),
|
|
||||||
comment: z.string().optional(),
|
|
||||||
quantity: z.number().min(0).optional(),
|
|
||||||
installedPowerPerUnitKw: z.number().min(0).optional(),
|
|
||||||
demandFactor: z.number().min(0).max(1).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 updateConsumerSchema = createConsumerSchema;
|
|
||||||
|
|
||||||
export const createProjectSchema = z.object({
|
|
||||||
name: z.string().min(1),
|
|
||||||
singlePhaseVoltageV: z.number().positive().optional(),
|
|
||||||
threePhaseVoltageV: z.number().positive().optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const updateProjectSettingsSchema = z.object({
|
|
||||||
singlePhaseVoltageV: z.number().positive(),
|
|
||||||
threePhaseVoltageV: z.number().positive(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const createDistributionBoardSchema = z.object({
|
|
||||||
name: z.string().min(1),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const createFloorSchema = z.object({
|
|
||||||
name: z.string().min(1),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const createRoomSchema = z.object({
|
|
||||||
floorId: z.string().min(1).optional(),
|
|
||||||
roomNumber: z.string().min(1),
|
|
||||||
roomName: z.string().min(1),
|
|
||||||
});
|
|
||||||
|
|
||||||
export type CreateConsumerInput = z.infer<typeof createConsumerSchema>;
|
|
||||||
export type CreateProjectInput = z.infer<typeof createProjectSchema>;
|
|
||||||
export type UpdateProjectSettingsInput = z.infer<typeof updateProjectSettingsSchema>;
|
|
||||||
export type CreateDistributionBoardInput = z.infer<typeof createDistributionBoardSchema>;
|
|
||||||
export type UpdateConsumerInput = z.infer<typeof updateConsumerSchema>;
|
|
||||||
export type CreateFloorInput = z.infer<typeof createFloorSchema>;
|
|
||||||
export type CreateRoomInput = z.infer<typeof createRoomSchema>;
|
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
export const createProjectSchema = z.object({
|
||||||
|
name: z.string().min(1),
|
||||||
|
singlePhaseVoltageV: z.number().positive().optional(),
|
||||||
|
threePhaseVoltageV: z.number().positive().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const updateProjectSettingsSchema = z.object({
|
||||||
|
singlePhaseVoltageV: z.number().positive(),
|
||||||
|
threePhaseVoltageV: z.number().positive(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const createDistributionBoardSchema = z.object({
|
||||||
|
name: z.string().min(1),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const createFloorSchema = z.object({
|
||||||
|
name: z.string().min(1),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const createRoomSchema = z.object({
|
||||||
|
floorId: z.string().min(1).optional(),
|
||||||
|
roomNumber: z.string().min(1),
|
||||||
|
roomName: z.string().min(1),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type CreateProjectInput = z.infer<typeof createProjectSchema>;
|
||||||
|
export type UpdateProjectSettingsInput = z.infer<
|
||||||
|
typeof updateProjectSettingsSchema
|
||||||
|
>;
|
||||||
|
export type CreateDistributionBoardInput = z.infer<
|
||||||
|
typeof createDistributionBoardSchema
|
||||||
|
>;
|
||||||
|
export type CreateFloorInput = z.infer<typeof createFloorSchema>;
|
||||||
|
export type CreateRoomInput = z.infer<typeof createRoomSchema>;
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
import assert from "node:assert/strict";
|
|
||||||
import { describe, it } from "node:test";
|
|
||||||
import { applyLinkedProjectDeviceValues } from "../src/domain/services/consumer-linking.service.js";
|
|
||||||
|
|
||||||
describe("consumer linking service", () => {
|
|
||||||
it("applies project-device values to linked consumers", () => {
|
|
||||||
const input = {
|
|
||||||
projectId: "p1",
|
|
||||||
projectDeviceId: "d1",
|
|
||||||
isLinkedToDevice: true,
|
|
||||||
name: "Alt",
|
|
||||||
quantity: 1,
|
|
||||||
installedPowerPerUnitKw: 0.2,
|
|
||||||
demandFactor: 0.8,
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = applyLinkedProjectDeviceValues(input, {
|
|
||||||
displayName: "Kaffeemaschine",
|
|
||||||
category: "Küche",
|
|
||||||
quantity: 3,
|
|
||||||
installedPowerPerUnitKw: 1.5,
|
|
||||||
demandFactor: 0.6,
|
|
||||||
phaseCount: 3,
|
|
||||||
powerFactor: 0.9,
|
|
||||||
note: "aus Vorlage",
|
|
||||||
});
|
|
||||||
|
|
||||||
assert.equal(result.name, "Kaffeemaschine");
|
|
||||||
assert.equal(result.category, "Küche");
|
|
||||||
assert.equal(result.quantity, 3);
|
|
||||||
assert.equal(result.installedPowerPerUnitKw, 1.5);
|
|
||||||
assert.equal(result.demandFactor, 0.6);
|
|
||||||
assert.equal(result.phaseCount, 3);
|
|
||||||
assert.equal(result.powerFactor, 0.9);
|
|
||||||
assert.equal(result.note, "aus Vorlage");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("keeps values unchanged when entry is not linked", () => {
|
|
||||||
const input = {
|
|
||||||
projectId: "p1",
|
|
||||||
projectDeviceId: "d1",
|
|
||||||
isLinkedToDevice: false,
|
|
||||||
name: "Eigener Name",
|
|
||||||
quantity: 2,
|
|
||||||
installedPowerPerUnitKw: 0.5,
|
|
||||||
demandFactor: 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = applyLinkedProjectDeviceValues(input, {
|
|
||||||
displayName: "Vorlage",
|
|
||||||
category: "Test",
|
|
||||||
quantity: 9,
|
|
||||||
installedPowerPerUnitKw: 9,
|
|
||||||
demandFactor: 0.2,
|
|
||||||
phaseCount: 1,
|
|
||||||
powerFactor: 1,
|
|
||||||
note: null,
|
|
||||||
});
|
|
||||||
|
|
||||||
assert.equal(result.name, "Eigener Name");
|
|
||||||
assert.equal(result.quantity, 2);
|
|
||||||
assert.equal(result.installedPowerPerUnitKw, 0.5);
|
|
||||||
assert.equal(result.demandFactor, 1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
import assert from "node:assert/strict";
|
|
||||||
import { describe, it } from "node:test";
|
|
||||||
import { createConsumerSchema } from "../src/shared/validation/consumer.schemas.js";
|
|
||||||
|
|
||||||
describe("consumer schema fixed option lists", () => {
|
|
||||||
it("accepts valid predefined domain values", () => {
|
|
||||||
const parsed = createConsumerSchema.safeParse({
|
|
||||||
projectId: "p1",
|
|
||||||
name: "Test",
|
|
||||||
quantity: 1,
|
|
||||||
installedPowerPerUnitKw: 1,
|
|
||||||
demandFactor: 1,
|
|
||||||
deviceType: "Beleuchtung",
|
|
||||||
phaseType: "3-phasig",
|
|
||||||
tradeOrCostGroup: "KG 440 Starkstromanlagen",
|
|
||||||
group: "Technik",
|
|
||||||
protectionType: "LS",
|
|
||||||
protectionCharacteristic: "C",
|
|
||||||
cableType: "NYM-J",
|
|
||||||
cableCrossSection: "2,5 mm²",
|
|
||||||
});
|
|
||||||
|
|
||||||
assert.equal(parsed.success, true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("rejects unknown domain values", () => {
|
|
||||||
const parsed = createConsumerSchema.safeParse({
|
|
||||||
projectId: "p1",
|
|
||||||
name: "Test",
|
|
||||||
quantity: 1,
|
|
||||||
installedPowerPerUnitKw: 1,
|
|
||||||
demandFactor: 1,
|
|
||||||
deviceType: "Irgendwas",
|
|
||||||
});
|
|
||||||
|
|
||||||
assert.equal(parsed.success, false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -126,6 +126,12 @@ describe("legacy consumer migration repository", () => {
|
|||||||
.run();
|
.run();
|
||||||
const repository = new LegacyConsumerMigrationRepository(context.db);
|
const repository = new LegacyConsumerMigrationRepository(context.db);
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
(await repository.listSourceConsumersByCircuitList(circuitListId)).map(
|
||||||
|
(consumer) => consumer.id
|
||||||
|
),
|
||||||
|
["consumer-1"]
|
||||||
|
);
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
(await repository.listUnmigratedConsumers()).sort((left, right) =>
|
(await repository.listUnmigratedConsumers()).sort((left, right) =>
|
||||||
left.id.localeCompare(right.id)
|
left.id.localeCompare(right.id)
|
||||||
|
|||||||
@@ -1,56 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
|
||||||
const vitest_1 = require("vitest");
|
|
||||||
const power_calculation_js_1 = require("../src/domain/calculations/power-calculation.js");
|
|
||||||
(0, vitest_1.describe)("power calculation", () => {
|
|
||||||
(0, vitest_1.it)("calculates installed power", () => {
|
|
||||||
const result = (0, power_calculation_js_1.calculateInstalledPowerKw)({
|
|
||||||
quantity: 4,
|
|
||||||
installedPowerPerUnitKw: 1.2,
|
|
||||||
demandFactor: 0.8,
|
|
||||||
});
|
|
||||||
(0, vitest_1.expect)(result).toBeCloseTo(4.8);
|
|
||||||
});
|
|
||||||
(0, vitest_1.it)("calculates demand power", () => {
|
|
||||||
const result = (0, power_calculation_js_1.calculateDemandPowerKw)({
|
|
||||||
quantity: 4,
|
|
||||||
installedPowerPerUnitKw: 1.2,
|
|
||||||
demandFactor: 0.8,
|
|
||||||
});
|
|
||||||
(0, vitest_1.expect)(result).toBeCloseTo(3.84);
|
|
||||||
});
|
|
||||||
(0, vitest_1.it)("handles zero quantity", () => {
|
|
||||||
const result = (0, power_calculation_js_1.calculateInstalledPowerKw)({
|
|
||||||
quantity: 0,
|
|
||||||
installedPowerPerUnitKw: 3,
|
|
||||||
demandFactor: 0.9,
|
|
||||||
});
|
|
||||||
(0, vitest_1.expect)(result).toBe(0);
|
|
||||||
});
|
|
||||||
(0, vitest_1.it)("handles zero demand factor", () => {
|
|
||||||
const result = (0, power_calculation_js_1.calculateDemandPowerKw)({
|
|
||||||
quantity: 5,
|
|
||||||
installedPowerPerUnitKw: 2,
|
|
||||||
demandFactor: 0,
|
|
||||||
});
|
|
||||||
(0, vitest_1.expect)(result).toBe(0);
|
|
||||||
});
|
|
||||||
(0, vitest_1.it)("calculates single-phase current", () => {
|
|
||||||
const current = (0, power_calculation_js_1.calculateCurrentA)({
|
|
||||||
demandPowerKw: 2.3,
|
|
||||||
voltageV: 230,
|
|
||||||
phaseCount: 1,
|
|
||||||
powerFactor: 0.95,
|
|
||||||
});
|
|
||||||
(0, vitest_1.expect)(current).toBeCloseTo(10.53, 2);
|
|
||||||
});
|
|
||||||
(0, vitest_1.it)("calculates three-phase current", () => {
|
|
||||||
const current = (0, power_calculation_js_1.calculateCurrentA)({
|
|
||||||
demandPowerKw: 9.5,
|
|
||||||
voltageV: 400,
|
|
||||||
phaseCount: 3,
|
|
||||||
powerFactor: 0.9,
|
|
||||||
});
|
|
||||||
(0, vitest_1.expect)(current).toBeCloseTo(15.23, 2);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
import { describe, it } from "node:test";
|
|
||||||
import assert from "node:assert/strict";
|
|
||||||
import {
|
|
||||||
calculateCurrentA,
|
|
||||||
calculateDemandPowerKw,
|
|
||||||
calculateInstalledPowerKw,
|
|
||||||
} from "../src/domain/calculations/power-calculation.js";
|
|
||||||
|
|
||||||
describe("power calculation", () => {
|
|
||||||
it("calculates installed power", () => {
|
|
||||||
const result = calculateInstalledPowerKw({
|
|
||||||
quantity: 4,
|
|
||||||
installedPowerPerUnitKw: 1.2,
|
|
||||||
demandFactor: 0.8,
|
|
||||||
});
|
|
||||||
assert.ok(Math.abs(result - 4.8) < 0.00001);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("calculates demand power", () => {
|
|
||||||
const result = calculateDemandPowerKw({
|
|
||||||
quantity: 4,
|
|
||||||
installedPowerPerUnitKw: 1.2,
|
|
||||||
demandFactor: 0.8,
|
|
||||||
});
|
|
||||||
assert.ok(Math.abs(result - 3.84) < 0.00001);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("handles zero quantity", () => {
|
|
||||||
const result = calculateInstalledPowerKw({
|
|
||||||
quantity: 0,
|
|
||||||
installedPowerPerUnitKw: 3,
|
|
||||||
demandFactor: 0.9,
|
|
||||||
});
|
|
||||||
assert.equal(result, 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("handles zero demand factor", () => {
|
|
||||||
const result = calculateDemandPowerKw({
|
|
||||||
quantity: 5,
|
|
||||||
installedPowerPerUnitKw: 2,
|
|
||||||
demandFactor: 0,
|
|
||||||
});
|
|
||||||
assert.equal(result, 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("calculates single-phase current", () => {
|
|
||||||
const current = calculateCurrentA({
|
|
||||||
demandPowerKw: 2.3,
|
|
||||||
voltageV: 230,
|
|
||||||
phaseCount: 1,
|
|
||||||
powerFactor: 0.95,
|
|
||||||
});
|
|
||||||
assert.ok(Math.abs(current - 10.53) < 0.02);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("calculates three-phase current", () => {
|
|
||||||
const current = calculateCurrentA({
|
|
||||||
demandPowerKw: 9.5,
|
|
||||||
voltageV: 400,
|
|
||||||
phaseCount: 3,
|
|
||||||
powerFactor: 0.9,
|
|
||||||
});
|
|
||||||
assert.ok(Math.abs(current - 15.23) < 0.02);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
Reference in New Issue
Block a user