Derive device voltages from project settings
This commit is contained in:
@@ -150,7 +150,11 @@ synchronizes linked circuit rows implicitly.
|
|||||||
- response:
|
- response:
|
||||||
`{ "project": { ... }, "revision": { ... }, "history": { ... } }`
|
`{ "project": { ... }, "revision": { ... }, "history": { ... } }`
|
||||||
- persistent Undo/Redo restores metadata, voltage values and the enabled
|
- persistent Undo/Redo restores metadata, voltage values and the enabled
|
||||||
distribution-board supply types together
|
distribution-board supply types together; project-device and circuit
|
||||||
|
voltages are recalculated from the restored project settings in the same
|
||||||
|
transaction
|
||||||
|
- project-device and circuit voltage are derived values and are not accepted
|
||||||
|
as editable frontend fields
|
||||||
- at least one of `AV`, `SV`, `EV`, `USV`, `MSR`, `SiBe` must be enabled;
|
- at least one of `AV`, `SV`, `EV`, `USV`, `MSR`, `SiBe` must be enabled;
|
||||||
a supply type currently used by a distribution board cannot be disabled
|
a supply type currently used by a distribution board cannot be disabled
|
||||||
- unchanged values are rejected without creating a revision; a stale
|
- unchanged values are rejected without creating a revision; a stale
|
||||||
|
|||||||
@@ -265,7 +265,9 @@ Completed foundation:
|
|||||||
snapshots in the same transaction
|
snapshots in the same transaction
|
||||||
- `project.update-settings` persists both project voltage defaults, its exact
|
- `project.update-settings` persists both project voltage defaults, its exact
|
||||||
inverse, revision and history transition atomically; the project page and
|
inverse, revision and history transition atomically; the project page and
|
||||||
existing PUT route require the current expected revision
|
existing PUT route require the current expected revision; the same
|
||||||
|
transaction derives every project-device and circuit voltage again, so
|
||||||
|
Undo/Redo cannot leave mixed voltage states
|
||||||
- `distribution-board.insert` persists the complete generated board, circuit
|
- `distribution-board.insert` persists the complete generated board, circuit
|
||||||
list and default-section structure with stable ids; its delete inverse
|
list and default-section structure with stable ids; its delete inverse
|
||||||
refuses changed or populated structures and the project-page POST tracks the
|
refuses changed or populated structures and the project-page POST tracks the
|
||||||
|
|||||||
@@ -35,21 +35,27 @@ requirements and intended sequencing, not proof of implementation.
|
|||||||
- Persist changes through project commands so they remain undoable after a
|
- Persist changes through project commands so they remain undoable after a
|
||||||
restart.
|
restart.
|
||||||
|
|
||||||
## Device Voltage Defaults
|
## Device Voltage Derivation
|
||||||
|
|
||||||
- Derive voltage from the selected phase type and the project settings:
|
- Derive voltage from the selected phase type and the project settings:
|
||||||
- single-phase uses the project's single-phase voltage;
|
- single-phase uses the project's single-phase voltage;
|
||||||
- three-phase uses the project's three-phase voltage.
|
- three-phase uses the project's three-phase voltage.
|
||||||
- Apply this default consistently when creating global devices, project
|
- Voltage is not editable on global devices, project devices or circuits.
|
||||||
devices and manual circuit-list entries.
|
Global devices store only their phase; copying one into a project derives
|
||||||
- Define explicit override behavior before implementation so a user-entered
|
the target project's voltage.
|
||||||
exceptional voltage is not silently overwritten when phase type or project
|
- Project-device voltage follows its phase. Circuit voltage follows the
|
||||||
settings change.
|
section phase; an unassigned circuit is three-phase only when all assigned
|
||||||
|
device rows with a valid phase are three-phase.
|
||||||
|
- Changing project voltage settings updates all project devices and circuits
|
||||||
|
atomically in the same persistent Undo/Redo step.
|
||||||
|
- Database migration `0019` and snapshot schema version `5` normalize older
|
||||||
|
stored values. Version-four and older imports remain supported and are
|
||||||
|
normalized while being upgraded.
|
||||||
|
|
||||||
## Recommended Sequence
|
## Recommended Sequence
|
||||||
|
|
||||||
1. Distribution-board floor and supply fields.
|
1. Distribution-board floor and supply fields.
|
||||||
2. Device voltage default and override semantics.
|
2. Device voltage derivation without overrides.
|
||||||
3. Snapshot-to-revision descriptions and history presentation.
|
3. Snapshot-to-revision descriptions and history presentation.
|
||||||
|
|
||||||
The Revit/CSV/IFCGUID round-trip remains a separate jointly planned phase and
|
The Revit/CSV/IFCGUID round-trip remains a separate jointly planned phase and
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ const emptyGlobalDevice: CreateGlobalDeviceInput = {
|
|||||||
quantity: 1,
|
quantity: 1,
|
||||||
installedPowerPerUnitKw: 0.1,
|
installedPowerPerUnitKw: 0.1,
|
||||||
demandFactor: 1,
|
demandFactor: 1,
|
||||||
voltageV: 230,
|
|
||||||
phaseCount: 1,
|
phaseCount: 1,
|
||||||
powerFactor: 1,
|
powerFactor: 1,
|
||||||
note: "",
|
note: "",
|
||||||
@@ -49,7 +48,6 @@ export default function ProjectsPage() {
|
|||||||
quantity: "1",
|
quantity: "1",
|
||||||
installedPowerPerUnitKw: "0.1",
|
installedPowerPerUnitKw: "0.1",
|
||||||
demandFactor: "1",
|
demandFactor: "1",
|
||||||
voltageV: "230",
|
|
||||||
phaseCount: "1",
|
phaseCount: "1",
|
||||||
powerFactor: "1",
|
powerFactor: "1",
|
||||||
note: "",
|
note: "",
|
||||||
@@ -153,7 +151,6 @@ export default function ProjectsPage() {
|
|||||||
quantity: Number(globalDeviceForm.quantity),
|
quantity: Number(globalDeviceForm.quantity),
|
||||||
installedPowerPerUnitKw: Number(globalDeviceForm.installedPowerPerUnitKw),
|
installedPowerPerUnitKw: Number(globalDeviceForm.installedPowerPerUnitKw),
|
||||||
demandFactor: Number(globalDeviceForm.demandFactor),
|
demandFactor: Number(globalDeviceForm.demandFactor),
|
||||||
voltageV: toOptionalNumber(globalDeviceForm.voltageV),
|
|
||||||
phaseCount: globalDeviceForm.phaseCount === "3" ? 3 : 1,
|
phaseCount: globalDeviceForm.phaseCount === "3" ? 3 : 1,
|
||||||
powerFactor: toOptionalNumber(globalDeviceForm.powerFactor),
|
powerFactor: toOptionalNumber(globalDeviceForm.powerFactor),
|
||||||
note: globalDeviceForm.note.trim() || undefined,
|
note: globalDeviceForm.note.trim() || undefined,
|
||||||
@@ -170,7 +167,6 @@ export default function ProjectsPage() {
|
|||||||
quantity: String(emptyGlobalDevice.quantity),
|
quantity: String(emptyGlobalDevice.quantity),
|
||||||
installedPowerPerUnitKw: String(emptyGlobalDevice.installedPowerPerUnitKw),
|
installedPowerPerUnitKw: String(emptyGlobalDevice.installedPowerPerUnitKw),
|
||||||
demandFactor: String(emptyGlobalDevice.demandFactor),
|
demandFactor: String(emptyGlobalDevice.demandFactor),
|
||||||
voltageV: String(emptyGlobalDevice.voltageV ?? ""),
|
|
||||||
phaseCount: String(emptyGlobalDevice.phaseCount ?? 1),
|
phaseCount: String(emptyGlobalDevice.phaseCount ?? 1),
|
||||||
powerFactor: String(emptyGlobalDevice.powerFactor ?? ""),
|
powerFactor: String(emptyGlobalDevice.powerFactor ?? ""),
|
||||||
note: emptyGlobalDevice.note ?? "",
|
note: emptyGlobalDevice.note ?? "",
|
||||||
@@ -207,7 +203,6 @@ export default function ProjectsPage() {
|
|||||||
quantity: device.quantity,
|
quantity: device.quantity,
|
||||||
installedPowerPerUnitKw: device.installedPowerPerUnitKw,
|
installedPowerPerUnitKw: device.installedPowerPerUnitKw,
|
||||||
demandFactor: device.demandFactor,
|
demandFactor: device.demandFactor,
|
||||||
voltageV: device.voltageV ?? undefined,
|
|
||||||
phaseCount: device.phaseCount ?? undefined,
|
phaseCount: device.phaseCount ?? undefined,
|
||||||
powerFactor: device.powerFactor ?? undefined,
|
powerFactor: device.powerFactor ?? undefined,
|
||||||
note: device.note ?? undefined,
|
note: device.note ?? undefined,
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
UPDATE `global_devices`
|
||||||
|
SET `voltage_v` = NULL;
|
||||||
|
--> statement-breakpoint
|
||||||
|
UPDATE `project_devices` AS `pd`
|
||||||
|
SET `voltage_v` = CASE
|
||||||
|
WHEN `pd`.`phase_type` = 'three_phase' THEN `p`.`three_phase_voltage_v`
|
||||||
|
ELSE `p`.`single_phase_voltage_v`
|
||||||
|
END
|
||||||
|
FROM `projects` AS `p`
|
||||||
|
WHERE `p`.`id` = `pd`.`project_id`;
|
||||||
|
--> statement-breakpoint
|
||||||
|
WITH `derived_circuit_voltages` AS (
|
||||||
|
SELECT
|
||||||
|
`c`.`id` AS `circuit_id`,
|
||||||
|
CASE
|
||||||
|
WHEN `cs`.`key` = 'three_phase' THEN `p`.`three_phase_voltage_v`
|
||||||
|
WHEN `cs`.`key` IN ('lighting', 'single_phase') THEN `p`.`single_phase_voltage_v`
|
||||||
|
WHEN EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM `circuit_device_rows` AS `three_phase_row`
|
||||||
|
WHERE `three_phase_row`.`circuit_id` = `c`.`id`
|
||||||
|
AND `three_phase_row`.`phase_type` = 'three_phase'
|
||||||
|
) AND NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM `circuit_device_rows` AS `other_row`
|
||||||
|
WHERE `other_row`.`circuit_id` = `c`.`id`
|
||||||
|
AND `other_row`.`phase_type` = 'single_phase'
|
||||||
|
) THEN `p`.`three_phase_voltage_v`
|
||||||
|
ELSE `p`.`single_phase_voltage_v`
|
||||||
|
END AS `derived_voltage`
|
||||||
|
FROM `circuits` AS `c`
|
||||||
|
INNER JOIN `circuit_lists` AS `cl`
|
||||||
|
ON `cl`.`id` = `c`.`circuit_list_id`
|
||||||
|
INNER JOIN `projects` AS `p`
|
||||||
|
ON `p`.`id` = `cl`.`project_id`
|
||||||
|
INNER JOIN `circuit_sections` AS `cs`
|
||||||
|
ON `cs`.`id` = `c`.`section_id`
|
||||||
|
)
|
||||||
|
UPDATE `circuits`
|
||||||
|
SET `voltage` = (
|
||||||
|
SELECT `derived_voltage`
|
||||||
|
FROM `derived_circuit_voltages`
|
||||||
|
WHERE `circuit_id` = `circuits`.`id`
|
||||||
|
)
|
||||||
|
WHERE `id` IN (
|
||||||
|
SELECT `circuit_id`
|
||||||
|
FROM `derived_circuit_voltages`
|
||||||
|
);
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -134,6 +134,13 @@
|
|||||||
"when": 1785308458789,
|
"when": 1785308458789,
|
||||||
"tag": "0018_fancy_argent",
|
"tag": "0018_fancy_argent",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 19,
|
||||||
|
"version": "6",
|
||||||
|
"when": 1785310907349,
|
||||||
|
"tag": "0019_normalize_project_voltages",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -20,11 +20,14 @@ import { circuitLists } from "../schema/circuit-lists.js";
|
|||||||
import { circuitSections } from "../schema/circuit-sections.js";
|
import { circuitSections } from "../schema/circuit-sections.js";
|
||||||
import { circuits } from "../schema/circuits.js";
|
import { circuits } from "../schema/circuits.js";
|
||||||
import { executeProjectCommandTransaction } from "./project-command-transaction.persistence.js";
|
import { executeProjectCommandTransaction } from "./project-command-transaction.persistence.js";
|
||||||
|
import { updateDerivedCircuitVoltage } from "./project-voltage.persistence.js";
|
||||||
|
import { resolveCircuitVoltage } from "./project-voltage.persistence.js";
|
||||||
|
|
||||||
interface PersistedMoveRow {
|
interface PersistedMoveRow {
|
||||||
id: string;
|
id: string;
|
||||||
circuitId: string;
|
circuitId: string;
|
||||||
sortOrder: number;
|
sortOrder: number;
|
||||||
|
phaseType: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CircuitDeviceRowMoveProjectCommandRepository
|
export class CircuitDeviceRowMoveProjectCommandRepository
|
||||||
@@ -85,6 +88,7 @@ export class CircuitDeviceRowMoveProjectCommandRepository
|
|||||||
);
|
);
|
||||||
this.applyMoves(database, command.payload.moves);
|
this.applyMoves(database, command.payload.moves);
|
||||||
this.updateReserveStates(database, circuitIds);
|
this.updateReserveStates(database, circuitIds);
|
||||||
|
this.updateVoltages(database, projectId, circuitIds);
|
||||||
return inverse;
|
return inverse;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,7 +98,16 @@ export class CircuitDeviceRowMoveProjectCommandRepository
|
|||||||
const { targetCircuit, targetCircuitAction, moves } =
|
const { targetCircuit, targetCircuitAction, moves } =
|
||||||
command.payload;
|
command.payload;
|
||||||
const rowsById = this.loadExpectedRows(database, moves);
|
const rowsById = this.loadExpectedRows(database, moves);
|
||||||
this.assertCircuitLocation(database, projectId, targetCircuit);
|
const appliedTargetCircuit = {
|
||||||
|
...targetCircuit,
|
||||||
|
voltage: resolveCircuitVoltage(
|
||||||
|
database,
|
||||||
|
projectId,
|
||||||
|
targetCircuit.sectionId,
|
||||||
|
moves.map((move) => rowsById.get(move.rowId)?.phaseType)
|
||||||
|
),
|
||||||
|
};
|
||||||
|
this.assertCircuitLocation(database, projectId, appliedTargetCircuit);
|
||||||
|
|
||||||
if (targetCircuitAction === "create") {
|
if (targetCircuitAction === "create") {
|
||||||
const sourceCircuitIds = [
|
const sourceCircuitIds = [
|
||||||
@@ -106,13 +119,13 @@ export class CircuitDeviceRowMoveProjectCommandRepository
|
|||||||
sourceCircuitIds,
|
sourceCircuitIds,
|
||||||
targetCircuit.circuitListId
|
targetCircuit.circuitListId
|
||||||
);
|
);
|
||||||
this.assertTargetCircuitAvailable(database, targetCircuit);
|
this.assertTargetCircuitAvailable(database, appliedTargetCircuit);
|
||||||
this.insertTargetCircuit(database, targetCircuit);
|
this.insertTargetCircuit(database, appliedTargetCircuit);
|
||||||
|
|
||||||
const inverse =
|
const inverse =
|
||||||
createCircuitDeviceRowMoveWithNewCircuitProjectCommand(
|
createCircuitDeviceRowMoveWithNewCircuitProjectCommand(
|
||||||
"delete",
|
"delete",
|
||||||
targetCircuit,
|
appliedTargetCircuit,
|
||||||
this.reverseMoves(moves, rowsById)
|
this.reverseMoves(moves, rowsById)
|
||||||
);
|
);
|
||||||
this.applyMoves(database, moves);
|
this.applyMoves(database, moves);
|
||||||
@@ -120,12 +133,16 @@ export class CircuitDeviceRowMoveProjectCommandRepository
|
|||||||
...sourceCircuitIds,
|
...sourceCircuitIds,
|
||||||
targetCircuit.id,
|
targetCircuit.id,
|
||||||
]);
|
]);
|
||||||
|
this.updateVoltages(database, projectId, [
|
||||||
|
...sourceCircuitIds,
|
||||||
|
targetCircuit.id,
|
||||||
|
]);
|
||||||
return inverse;
|
return inverse;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.assertTargetCircuitUnchanged(
|
this.assertTargetCircuitUnchanged(
|
||||||
database,
|
database,
|
||||||
targetCircuit,
|
appliedTargetCircuit,
|
||||||
moves.map((move) => move.rowId)
|
moves.map((move) => move.rowId)
|
||||||
);
|
);
|
||||||
const destinationCircuitIds = [
|
const destinationCircuitIds = [
|
||||||
@@ -140,7 +157,7 @@ export class CircuitDeviceRowMoveProjectCommandRepository
|
|||||||
const inverse =
|
const inverse =
|
||||||
createCircuitDeviceRowMoveWithNewCircuitProjectCommand(
|
createCircuitDeviceRowMoveWithNewCircuitProjectCommand(
|
||||||
"create",
|
"create",
|
||||||
targetCircuit,
|
appliedTargetCircuit,
|
||||||
this.reverseMoves(moves, rowsById)
|
this.reverseMoves(moves, rowsById)
|
||||||
);
|
);
|
||||||
this.applyMoves(database, moves);
|
this.applyMoves(database, moves);
|
||||||
@@ -148,6 +165,11 @@ export class CircuitDeviceRowMoveProjectCommandRepository
|
|||||||
targetCircuit.id,
|
targetCircuit.id,
|
||||||
...destinationCircuitIds,
|
...destinationCircuitIds,
|
||||||
]);
|
]);
|
||||||
|
this.updateVoltages(
|
||||||
|
database,
|
||||||
|
projectId,
|
||||||
|
destinationCircuitIds
|
||||||
|
);
|
||||||
const deleted = database
|
const deleted = database
|
||||||
.delete(circuits)
|
.delete(circuits)
|
||||||
.where(
|
.where(
|
||||||
@@ -169,6 +191,16 @@ export class CircuitDeviceRowMoveProjectCommandRepository
|
|||||||
return inverse;
|
return inverse;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private updateVoltages(
|
||||||
|
database: AppDatabase,
|
||||||
|
projectId: string,
|
||||||
|
circuitIds: string[]
|
||||||
|
) {
|
||||||
|
for (const circuitId of new Set(circuitIds)) {
|
||||||
|
updateDerivedCircuitVoltage(database, projectId, circuitId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private loadExpectedRows(
|
private loadExpectedRows(
|
||||||
database: AppDatabase,
|
database: AppDatabase,
|
||||||
moves: CircuitDeviceRowMoveAssignment[]
|
moves: CircuitDeviceRowMoveAssignment[]
|
||||||
@@ -179,6 +211,7 @@ export class CircuitDeviceRowMoveProjectCommandRepository
|
|||||||
id: circuitDeviceRows.id,
|
id: circuitDeviceRows.id,
|
||||||
circuitId: circuitDeviceRows.circuitId,
|
circuitId: circuitDeviceRows.circuitId,
|
||||||
sortOrder: circuitDeviceRows.sortOrder,
|
sortOrder: circuitDeviceRows.sortOrder,
|
||||||
|
phaseType: circuitDeviceRows.phaseType,
|
||||||
})
|
})
|
||||||
.from(circuitDeviceRows)
|
.from(circuitDeviceRows)
|
||||||
.where(inArray(circuitDeviceRows.id, rowIds))
|
.where(inArray(circuitDeviceRows.id, rowIds))
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import {
|
|||||||
type CircuitDeviceRowPatchInput,
|
type CircuitDeviceRowPatchInput,
|
||||||
} from "./circuit-device-row.persistence.js";
|
} from "./circuit-device-row.persistence.js";
|
||||||
import { executeProjectCommandTransactionWithAppliedForward } from "./project-command-transaction.persistence.js";
|
import { executeProjectCommandTransactionWithAppliedForward } from "./project-command-transaction.persistence.js";
|
||||||
|
import { updateDerivedCircuitVoltage } from "./project-voltage.persistence.js";
|
||||||
|
|
||||||
type CircuitDeviceRow = typeof circuitDeviceRows.$inferSelect;
|
type CircuitDeviceRow = typeof circuitDeviceRows.$inferSelect;
|
||||||
|
|
||||||
@@ -117,6 +118,13 @@ export class CircuitDeviceRowProjectCommandRepository
|
|||||||
if (update.changes !== 1) {
|
if (update.changes !== 1) {
|
||||||
throw new Error("Circuit device row changed before command execution.");
|
throw new Error("Circuit device row changed before command execution.");
|
||||||
}
|
}
|
||||||
|
if (patch.phaseType !== undefined) {
|
||||||
|
updateDerivedCircuitVoltage(
|
||||||
|
tx,
|
||||||
|
input.projectId,
|
||||||
|
current.circuitId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
forward: appliedForward,
|
forward: appliedForward,
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import {
|
|||||||
toCircuitDeviceRowSnapshot,
|
toCircuitDeviceRowSnapshot,
|
||||||
} from "./circuit-device-row-structure.persistence.js";
|
} from "./circuit-device-row-structure.persistence.js";
|
||||||
import { executeProjectCommandTransaction } from "./project-command-transaction.persistence.js";
|
import { executeProjectCommandTransaction } from "./project-command-transaction.persistence.js";
|
||||||
|
import { updateDerivedCircuitVoltage } from "./project-voltage.persistence.js";
|
||||||
|
|
||||||
export class CircuitDeviceRowStructureProjectCommandRepository
|
export class CircuitDeviceRowStructureProjectCommandRepository
|
||||||
implements CircuitDeviceRowStructureProjectCommandStore
|
implements CircuitDeviceRowStructureProjectCommandStore
|
||||||
@@ -101,6 +102,7 @@ export class CircuitDeviceRowStructureProjectCommandRepository
|
|||||||
if (circuitUpdate.changes !== 1) {
|
if (circuitUpdate.changes !== 1) {
|
||||||
throw new Error("Circuit changed before device-row insertion.");
|
throw new Error("Circuit changed before device-row insertion.");
|
||||||
}
|
}
|
||||||
|
updateDerivedCircuitVoltage(database, projectId, row.circuitId);
|
||||||
|
|
||||||
return createCircuitDeviceRowDeleteProjectCommand(
|
return createCircuitDeviceRowDeleteProjectCommand(
|
||||||
row.id,
|
row.id,
|
||||||
@@ -153,6 +155,11 @@ export class CircuitDeviceRowStructureProjectCommandRepository
|
|||||||
if (circuitUpdate.changes !== 1) {
|
if (circuitUpdate.changes !== 1) {
|
||||||
throw new Error("Circuit changed before device-row deletion.");
|
throw new Error("Circuit changed before device-row deletion.");
|
||||||
}
|
}
|
||||||
|
updateDerivedCircuitVoltage(
|
||||||
|
database,
|
||||||
|
projectId,
|
||||||
|
expectedCircuitId
|
||||||
|
);
|
||||||
|
|
||||||
return createCircuitDeviceRowInsertProjectCommand(
|
return createCircuitDeviceRowInsertProjectCommand(
|
||||||
toCircuitDeviceRowSnapshot(row)
|
toCircuitDeviceRowSnapshot(row)
|
||||||
|
|||||||
@@ -18,7 +18,9 @@ import {
|
|||||||
toCircuitPatchValues,
|
toCircuitPatchValues,
|
||||||
type CircuitPatchPersistenceInput,
|
type CircuitPatchPersistenceInput,
|
||||||
} from "./circuit.persistence.js";
|
} from "./circuit.persistence.js";
|
||||||
import { executeProjectCommandTransaction } from "./project-command-transaction.persistence.js";
|
import { executeProjectCommandTransactionWithAppliedForward } from "./project-command-transaction.persistence.js";
|
||||||
|
import { resolveCircuitVoltage } from "./project-voltage.persistence.js";
|
||||||
|
import { circuitDeviceRows } from "../schema/circuit-device-rows.js";
|
||||||
|
|
||||||
type CircuitRow = typeof circuits.$inferSelect;
|
type CircuitRow = typeof circuits.$inferSelect;
|
||||||
|
|
||||||
@@ -30,7 +32,7 @@ export class CircuitProjectCommandRepository
|
|||||||
executeUpdate(input: ExecuteCircuitUpdateCommandInput) {
|
executeUpdate(input: ExecuteCircuitUpdateCommandInput) {
|
||||||
assertCircuitUpdateProjectCommand(input.command);
|
assertCircuitUpdateProjectCommand(input.command);
|
||||||
|
|
||||||
return executeProjectCommandTransaction(
|
return executeProjectCommandTransactionWithAppliedForward(
|
||||||
this.database,
|
this.database,
|
||||||
input,
|
input,
|
||||||
(tx) => this.applyCommand(tx, input)
|
(tx) => this.applyCommand(tx, input)
|
||||||
@@ -71,14 +73,44 @@ export class CircuitProjectCommandRepository
|
|||||||
])
|
])
|
||||||
) as CircuitPatchPersistenceInput;
|
) as CircuitPatchPersistenceInput;
|
||||||
this.assertSectionInCircuitList(tx, current, patch.sectionId);
|
this.assertSectionInCircuitList(tx, current, patch.sectionId);
|
||||||
|
if (input.source === "user") {
|
||||||
|
if (
|
||||||
|
input.command.payload.changes.every(
|
||||||
|
(change) => change.field === "voltage"
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
throw new Error("Circuit voltage is derived and cannot be edited.");
|
||||||
|
}
|
||||||
|
const devicePhaseTypes = tx
|
||||||
|
.select({ phaseType: circuitDeviceRows.phaseType })
|
||||||
|
.from(circuitDeviceRows)
|
||||||
|
.where(eq(circuitDeviceRows.circuitId, current.id))
|
||||||
|
.all()
|
||||||
|
.map((row) => row.phaseType);
|
||||||
|
const derivedVoltage = resolveCircuitVoltage(
|
||||||
|
tx,
|
||||||
|
input.projectId,
|
||||||
|
patch.sectionId ?? current.sectionId,
|
||||||
|
devicePhaseTypes
|
||||||
|
);
|
||||||
|
if (derivedVoltage === current.voltage) {
|
||||||
|
delete patch.voltage;
|
||||||
|
} else {
|
||||||
|
patch.voltage = derivedVoltage;
|
||||||
|
}
|
||||||
|
}
|
||||||
this.assertUniqueEquipmentIdentifier(
|
this.assertUniqueEquipmentIdentifier(
|
||||||
tx,
|
tx,
|
||||||
current,
|
current,
|
||||||
patch.equipmentIdentifier
|
patch.equipmentIdentifier
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const appliedForward = createCircuitUpdateProjectCommand(
|
||||||
|
current.id,
|
||||||
|
patch as CircuitUpdatePatch
|
||||||
|
);
|
||||||
const inversePatch = Object.fromEntries(
|
const inversePatch = Object.fromEntries(
|
||||||
input.command.payload.changes.map((change) => [
|
appliedForward.payload.changes.map((change) => [
|
||||||
change.field,
|
change.field,
|
||||||
getCircuitFieldValue(current, change.field),
|
getCircuitFieldValue(current, change.field),
|
||||||
])
|
])
|
||||||
@@ -97,7 +129,7 @@ export class CircuitProjectCommandRepository
|
|||||||
throw new Error("Circuit changed before command execution.");
|
throw new Error("Circuit changed before command execution.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return inverse;
|
return { forward: appliedForward, inverse };
|
||||||
}
|
}
|
||||||
|
|
||||||
private assertSectionInCircuitList(
|
private assertSectionInCircuitList(
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import {
|
|||||||
toCircuitDeviceRowSnapshot,
|
toCircuitDeviceRowSnapshot,
|
||||||
} from "./circuit-device-row-structure.persistence.js";
|
} from "./circuit-device-row-structure.persistence.js";
|
||||||
import { executeProjectCommandTransaction } from "./project-command-transaction.persistence.js";
|
import { executeProjectCommandTransaction } from "./project-command-transaction.persistence.js";
|
||||||
|
import { resolveCircuitVoltage } from "./project-voltage.persistence.js";
|
||||||
|
|
||||||
export class CircuitStructureProjectCommandRepository
|
export class CircuitStructureProjectCommandRepository
|
||||||
implements CircuitStructureProjectCommandStore
|
implements CircuitStructureProjectCommandStore
|
||||||
@@ -77,6 +78,19 @@ export class CircuitStructureProjectCommandRepository
|
|||||||
snapshot: CircuitSnapshot
|
snapshot: CircuitSnapshot
|
||||||
) {
|
) {
|
||||||
this.assertCircuitLocation(database, projectId, snapshot);
|
this.assertCircuitLocation(database, projectId, snapshot);
|
||||||
|
if (source === "user") {
|
||||||
|
const derivedVoltage = resolveCircuitVoltage(
|
||||||
|
database,
|
||||||
|
projectId,
|
||||||
|
snapshot.sectionId,
|
||||||
|
snapshot.deviceRows.map((row) => row.phaseType)
|
||||||
|
);
|
||||||
|
if (snapshot.voltage !== derivedVoltage) {
|
||||||
|
throw new Error(
|
||||||
|
"Circuit voltage must match the project phase voltage."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const existingCircuit = database
|
const existingCircuit = database
|
||||||
.select({ id: circuits.id })
|
.select({ id: circuits.id })
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export class GlobalDeviceRepository {
|
|||||||
quantity: input.quantity,
|
quantity: input.quantity,
|
||||||
installedPowerPerUnitKw: input.installedPowerPerUnitKw,
|
installedPowerPerUnitKw: input.installedPowerPerUnitKw,
|
||||||
demandFactor: input.demandFactor,
|
demandFactor: input.demandFactor,
|
||||||
voltageV: input.voltageV ?? null,
|
voltageV: null,
|
||||||
phaseCount: input.phaseCount ?? null,
|
phaseCount: input.phaseCount ?? null,
|
||||||
powerFactor: input.powerFactor ?? null,
|
powerFactor: input.powerFactor ?? null,
|
||||||
note: input.note ?? null,
|
note: input.note ?? null,
|
||||||
@@ -45,7 +45,7 @@ export class GlobalDeviceRepository {
|
|||||||
quantity: input.quantity,
|
quantity: input.quantity,
|
||||||
installedPowerPerUnitKw: input.installedPowerPerUnitKw,
|
installedPowerPerUnitKw: input.installedPowerPerUnitKw,
|
||||||
demandFactor: input.demandFactor,
|
demandFactor: input.demandFactor,
|
||||||
voltageV: input.voltageV ?? null,
|
voltageV: null,
|
||||||
phaseCount: input.phaseCount ?? null,
|
phaseCount: input.phaseCount ?? null,
|
||||||
powerFactor: input.powerFactor ?? null,
|
powerFactor: input.powerFactor ?? null,
|
||||||
note: input.note ?? null,
|
note: input.note ?? null,
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ import type {
|
|||||||
} from "../../domain/ports/project-device-project-command.store.js";
|
} from "../../domain/ports/project-device-project-command.store.js";
|
||||||
import type { AppDatabase } from "../database-context.js";
|
import type { AppDatabase } from "../database-context.js";
|
||||||
import { projectDevices } from "../schema/project-devices.js";
|
import { projectDevices } from "../schema/project-devices.js";
|
||||||
import { executeProjectCommandTransaction } from "./project-command-transaction.persistence.js";
|
import { executeProjectCommandTransactionWithAppliedForward } from "./project-command-transaction.persistence.js";
|
||||||
|
import { resolveProjectDeviceVoltage } from "./project-voltage.persistence.js";
|
||||||
|
|
||||||
type ProjectDeviceRow = typeof projectDevices.$inferSelect;
|
type ProjectDeviceRow = typeof projectDevices.$inferSelect;
|
||||||
|
|
||||||
@@ -24,7 +25,7 @@ export class ProjectDeviceProjectCommandRepository
|
|||||||
executeUpdate(input: ExecuteProjectDeviceUpdateCommandInput) {
|
executeUpdate(input: ExecuteProjectDeviceUpdateCommandInput) {
|
||||||
assertProjectDeviceUpdateProjectCommand(input.command);
|
assertProjectDeviceUpdateProjectCommand(input.command);
|
||||||
|
|
||||||
return executeProjectCommandTransaction(
|
return executeProjectCommandTransactionWithAppliedForward(
|
||||||
this.database,
|
this.database,
|
||||||
input,
|
input,
|
||||||
(tx) => this.applyCommand(tx, input)
|
(tx) => this.applyCommand(tx, input)
|
||||||
@@ -60,8 +61,40 @@ export class ProjectDeviceProjectCommandRepository
|
|||||||
change.value,
|
change.value,
|
||||||
])
|
])
|
||||||
) as ProjectDeviceUpdatePatch;
|
) as ProjectDeviceUpdatePatch;
|
||||||
|
if (input.source === "user") {
|
||||||
|
if (
|
||||||
|
input.command.payload.changes.every(
|
||||||
|
(change) => change.field === "voltageV"
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
"Project-device voltage is derived and cannot be edited."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const targetPhaseType = patch.phaseType ?? current.phaseType;
|
||||||
|
if (
|
||||||
|
targetPhaseType !== "single_phase" &&
|
||||||
|
targetPhaseType !== "three_phase"
|
||||||
|
) {
|
||||||
|
throw new Error("Project-device phase type is invalid.");
|
||||||
|
}
|
||||||
|
const derivedVoltage = resolveProjectDeviceVoltage(
|
||||||
|
tx,
|
||||||
|
input.projectId,
|
||||||
|
targetPhaseType
|
||||||
|
);
|
||||||
|
if (derivedVoltage === current.voltageV) {
|
||||||
|
delete patch.voltageV;
|
||||||
|
} else {
|
||||||
|
patch.voltageV = derivedVoltage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const appliedForward = createProjectDeviceUpdateProjectCommand(
|
||||||
|
current.id,
|
||||||
|
patch
|
||||||
|
);
|
||||||
const inversePatch = Object.fromEntries(
|
const inversePatch = Object.fromEntries(
|
||||||
input.command.payload.changes.map((change) => [
|
appliedForward.payload.changes.map((change) => [
|
||||||
change.field,
|
change.field,
|
||||||
getProjectDeviceFieldValue(current, change.field),
|
getProjectDeviceFieldValue(current, change.field),
|
||||||
])
|
])
|
||||||
@@ -87,7 +120,7 @@ export class ProjectDeviceProjectCommandRepository
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return inverse;
|
return { forward: appliedForward, inverse };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { circuitLists } from "../schema/circuit-lists.js";
|
|||||||
import { circuits } from "../schema/circuits.js";
|
import { circuits } from "../schema/circuits.js";
|
||||||
import { projectDevices } from "../schema/project-devices.js";
|
import { projectDevices } from "../schema/project-devices.js";
|
||||||
import { executeProjectCommandTransaction } from "./project-command-transaction.persistence.js";
|
import { executeProjectCommandTransaction } from "./project-command-transaction.persistence.js";
|
||||||
|
import { updateDerivedCircuitVoltage } from "./project-voltage.persistence.js";
|
||||||
|
|
||||||
type CircuitDeviceRow = typeof circuitDeviceRows.$inferSelect;
|
type CircuitDeviceRow = typeof circuitDeviceRows.$inferSelect;
|
||||||
|
|
||||||
@@ -129,6 +130,25 @@ export class ProjectDeviceRowSyncProjectCommandRepository
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const affectedCircuitIds = new Set(
|
||||||
|
input.command.payload.rows
|
||||||
|
.filter(
|
||||||
|
(assignment) =>
|
||||||
|
assignment.expected.phaseType !==
|
||||||
|
assignment.target.phaseType
|
||||||
|
)
|
||||||
|
.map(
|
||||||
|
(assignment) =>
|
||||||
|
persistedById.get(assignment.rowId)!.circuitId
|
||||||
|
)
|
||||||
|
);
|
||||||
|
for (const circuitId of affectedCircuitIds) {
|
||||||
|
updateDerivedCircuitVoltage(
|
||||||
|
tx,
|
||||||
|
input.projectId,
|
||||||
|
circuitId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return inverse;
|
return inverse;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import { projectDevices } from "../schema/project-devices.js";
|
|||||||
import { projects } from "../schema/projects.js";
|
import { projects } from "../schema/projects.js";
|
||||||
import { toCircuitDeviceRowSnapshot } from "./circuit-device-row-structure.persistence.js";
|
import { toCircuitDeviceRowSnapshot } from "./circuit-device-row-structure.persistence.js";
|
||||||
import { executeProjectCommandTransaction } from "./project-command-transaction.persistence.js";
|
import { executeProjectCommandTransaction } from "./project-command-transaction.persistence.js";
|
||||||
|
import { resolveProjectDeviceVoltage } from "./project-voltage.persistence.js";
|
||||||
|
|
||||||
const circuitDeviceRowSnapshotFields = [
|
const circuitDeviceRowSnapshotFields = [
|
||||||
"id",
|
"id",
|
||||||
@@ -125,6 +126,19 @@ export class ProjectDeviceStructureProjectCommandRepository
|
|||||||
if (!project) {
|
if (!project) {
|
||||||
throw new Error("Project does not exist.");
|
throw new Error("Project does not exist.");
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
source === "user" &&
|
||||||
|
snapshot.voltageV !==
|
||||||
|
resolveProjectDeviceVoltage(
|
||||||
|
database,
|
||||||
|
projectId,
|
||||||
|
snapshot.phaseType
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
"Project-device voltage must match the project phase voltage."
|
||||||
|
);
|
||||||
|
}
|
||||||
const existing = database
|
const existing = database
|
||||||
.select({ id: projectDevices.id })
|
.select({ id: projectDevices.id })
|
||||||
.from(projectDevices)
|
.from(projectDevices)
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import type { AppDatabase } from "../database-context.js";
|
|||||||
import { projects } from "../schema/projects.js";
|
import { projects } from "../schema/projects.js";
|
||||||
import { distributionBoards } from "../schema/distribution-boards.js";
|
import { distributionBoards } from "../schema/distribution-boards.js";
|
||||||
import { executeProjectCommandTransaction } from "./project-command-transaction.persistence.js";
|
import { executeProjectCommandTransaction } from "./project-command-transaction.persistence.js";
|
||||||
|
import { updateAllDerivedProjectVoltages } from "./project-voltage.persistence.js";
|
||||||
|
|
||||||
export class ProjectSettingsProjectCommandRepository
|
export class ProjectSettingsProjectCommandRepository
|
||||||
implements ProjectSettingsProjectCommandStore
|
implements ProjectSettingsProjectCommandStore
|
||||||
@@ -94,6 +95,7 @@ export class ProjectSettingsProjectCommandRepository
|
|||||||
if (updated.changes !== 1) {
|
if (updated.changes !== 1) {
|
||||||
throw new Error("Project changed before settings update.");
|
throw new Error("Project changed before settings update.");
|
||||||
}
|
}
|
||||||
|
updateAllDerivedProjectVoltages(tx, input.projectId);
|
||||||
|
|
||||||
return inverse;
|
return inverse;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
legacyProjectStateSnapshotSchemaVersion,
|
legacyProjectStateSnapshotSchemaVersion,
|
||||||
previousProjectStateSnapshotSchemaVersion,
|
previousProjectStateSnapshotSchemaVersion,
|
||||||
projectStateSnapshotSchemaVersion,
|
projectStateSnapshotSchemaVersion,
|
||||||
|
supplyTypesProjectStateSnapshotSchemaVersion,
|
||||||
} from "../../domain/models/project-state-snapshot.model.js";
|
} from "../../domain/models/project-state-snapshot.model.js";
|
||||||
import type {
|
import type {
|
||||||
CreateNamedProjectSnapshotInput,
|
CreateNamedProjectSnapshotInput,
|
||||||
@@ -146,6 +147,8 @@ export class ProjectSnapshotRepository implements ProjectSnapshotStore {
|
|||||||
stored.schemaVersion !== previousProjectStateSnapshotSchemaVersion &&
|
stored.schemaVersion !== previousProjectStateSnapshotSchemaVersion &&
|
||||||
stored.schemaVersion !==
|
stored.schemaVersion !==
|
||||||
distributionBoardProjectStateSnapshotSchemaVersion &&
|
distributionBoardProjectStateSnapshotSchemaVersion &&
|
||||||
|
stored.schemaVersion !==
|
||||||
|
supplyTypesProjectStateSnapshotSchemaVersion &&
|
||||||
stored.schemaVersion !== projectStateSnapshotSchemaVersion
|
stored.schemaVersion !== projectStateSnapshotSchemaVersion
|
||||||
) {
|
) {
|
||||||
throw new Error("Project snapshot schema version is not supported.");
|
throw new Error("Project snapshot schema version is not supported.");
|
||||||
|
|||||||
@@ -0,0 +1,154 @@
|
|||||||
|
import { and, eq } from "drizzle-orm";
|
||||||
|
import {
|
||||||
|
resolveCircuitPhaseType,
|
||||||
|
resolveProjectVoltage,
|
||||||
|
type ElectricalPhaseType,
|
||||||
|
type ProjectVoltageSettings,
|
||||||
|
} from "../../domain/services/project-voltage.service.js";
|
||||||
|
import type { AppDatabase } from "../database-context.js";
|
||||||
|
import { circuitDeviceRows } from "../schema/circuit-device-rows.js";
|
||||||
|
import { circuitLists } from "../schema/circuit-lists.js";
|
||||||
|
import { circuitSections } from "../schema/circuit-sections.js";
|
||||||
|
import { circuits } from "../schema/circuits.js";
|
||||||
|
import { projectDevices } from "../schema/project-devices.js";
|
||||||
|
import { projects } from "../schema/projects.js";
|
||||||
|
|
||||||
|
export function readProjectVoltageSettings(
|
||||||
|
database: AppDatabase,
|
||||||
|
projectId: string
|
||||||
|
): ProjectVoltageSettings {
|
||||||
|
const project = database
|
||||||
|
.select({
|
||||||
|
singlePhaseVoltageV: projects.singlePhaseVoltageV,
|
||||||
|
threePhaseVoltageV: projects.threePhaseVoltageV,
|
||||||
|
})
|
||||||
|
.from(projects)
|
||||||
|
.where(eq(projects.id, projectId))
|
||||||
|
.get();
|
||||||
|
if (!project) {
|
||||||
|
throw new Error("Project not found.");
|
||||||
|
}
|
||||||
|
return project;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveProjectDeviceVoltage(
|
||||||
|
database: AppDatabase,
|
||||||
|
projectId: string,
|
||||||
|
phaseType: ElectricalPhaseType
|
||||||
|
) {
|
||||||
|
return resolveProjectVoltage(
|
||||||
|
phaseType,
|
||||||
|
readProjectVoltageSettings(database, projectId)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveCircuitVoltage(
|
||||||
|
database: AppDatabase,
|
||||||
|
projectId: string,
|
||||||
|
sectionId: string,
|
||||||
|
devicePhaseTypes: ReadonlyArray<string | null | undefined> = []
|
||||||
|
) {
|
||||||
|
const section = database
|
||||||
|
.select({ key: circuitSections.key })
|
||||||
|
.from(circuitSections)
|
||||||
|
.innerJoin(
|
||||||
|
circuitLists,
|
||||||
|
eq(circuitLists.id, circuitSections.circuitListId)
|
||||||
|
)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(circuitSections.id, sectionId),
|
||||||
|
eq(circuitLists.projectId, projectId)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.get();
|
||||||
|
if (!section) {
|
||||||
|
throw new Error("Circuit section does not belong to project.");
|
||||||
|
}
|
||||||
|
return resolveProjectVoltage(
|
||||||
|
resolveCircuitPhaseType(section.key, devicePhaseTypes),
|
||||||
|
readProjectVoltageSettings(database, projectId)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateDerivedCircuitVoltage(
|
||||||
|
database: AppDatabase,
|
||||||
|
projectId: string,
|
||||||
|
circuitId: string
|
||||||
|
) {
|
||||||
|
const circuit = database
|
||||||
|
.select({
|
||||||
|
id: circuits.id,
|
||||||
|
sectionId: circuits.sectionId,
|
||||||
|
})
|
||||||
|
.from(circuits)
|
||||||
|
.innerJoin(circuitLists, eq(circuitLists.id, circuits.circuitListId))
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(circuits.id, circuitId),
|
||||||
|
eq(circuitLists.projectId, projectId)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.get();
|
||||||
|
if (!circuit) {
|
||||||
|
throw new Error("Circuit does not belong to project.");
|
||||||
|
}
|
||||||
|
const devicePhaseTypes = database
|
||||||
|
.select({ phaseType: circuitDeviceRows.phaseType })
|
||||||
|
.from(circuitDeviceRows)
|
||||||
|
.where(eq(circuitDeviceRows.circuitId, circuitId))
|
||||||
|
.all()
|
||||||
|
.map((row) => row.phaseType);
|
||||||
|
const voltage = resolveCircuitVoltage(
|
||||||
|
database,
|
||||||
|
projectId,
|
||||||
|
circuit.sectionId,
|
||||||
|
devicePhaseTypes
|
||||||
|
);
|
||||||
|
database
|
||||||
|
.update(circuits)
|
||||||
|
.set({ voltage })
|
||||||
|
.where(eq(circuits.id, circuitId))
|
||||||
|
.run();
|
||||||
|
return voltage;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateAllDerivedProjectVoltages(
|
||||||
|
database: AppDatabase,
|
||||||
|
projectId: string
|
||||||
|
) {
|
||||||
|
const settings = readProjectVoltageSettings(database, projectId);
|
||||||
|
const devices = database
|
||||||
|
.select({
|
||||||
|
id: projectDevices.id,
|
||||||
|
phaseType: projectDevices.phaseType,
|
||||||
|
})
|
||||||
|
.from(projectDevices)
|
||||||
|
.where(eq(projectDevices.projectId, projectId))
|
||||||
|
.all();
|
||||||
|
for (const device of devices) {
|
||||||
|
if (
|
||||||
|
device.phaseType !== "single_phase" &&
|
||||||
|
device.phaseType !== "three_phase"
|
||||||
|
) {
|
||||||
|
throw new Error("Persisted project-device phase type is invalid.");
|
||||||
|
}
|
||||||
|
database
|
||||||
|
.update(projectDevices)
|
||||||
|
.set({
|
||||||
|
voltageV: resolveProjectVoltage(device.phaseType, settings),
|
||||||
|
})
|
||||||
|
.where(eq(projectDevices.id, device.id))
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
const projectCircuits = database
|
||||||
|
.select({ id: circuits.id })
|
||||||
|
.from(circuits)
|
||||||
|
.innerJoin(circuitLists, eq(circuitLists.id, circuits.circuitListId))
|
||||||
|
.where(eq(circuitLists.projectId, projectId))
|
||||||
|
.all();
|
||||||
|
for (const circuit of projectCircuits) {
|
||||||
|
updateDerivedCircuitVoltage(database, projectId, circuit.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -58,6 +58,8 @@ export interface CircuitTreeSectionBlock {
|
|||||||
export interface CircuitTreeResponse {
|
export interface CircuitTreeResponse {
|
||||||
circuitListId: string;
|
circuitListId: string;
|
||||||
currentRevision: number;
|
currentRevision: number;
|
||||||
|
singlePhaseVoltageV: number;
|
||||||
|
threePhaseVoltageV: number;
|
||||||
sections: CircuitTreeSectionBlock[];
|
sections: CircuitTreeSectionBlock[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,16 @@ import {
|
|||||||
defaultDistributionBoardSupplyTypes,
|
defaultDistributionBoardSupplyTypes,
|
||||||
distributionBoardSupplyTypes,
|
distributionBoardSupplyTypes,
|
||||||
} from "../../shared/constants/distribution-board.js";
|
} from "../../shared/constants/distribution-board.js";
|
||||||
|
import {
|
||||||
|
resolveCircuitPhaseType,
|
||||||
|
resolveProjectVoltage,
|
||||||
|
} from "../services/project-voltage.service.js";
|
||||||
|
|
||||||
export const legacyProjectStateSnapshotSchemaVersion = 1 as const;
|
export const legacyProjectStateSnapshotSchemaVersion = 1 as const;
|
||||||
export const previousProjectStateSnapshotSchemaVersion = 2 as const;
|
export const previousProjectStateSnapshotSchemaVersion = 2 as const;
|
||||||
export const distributionBoardProjectStateSnapshotSchemaVersion = 3 as const;
|
export const distributionBoardProjectStateSnapshotSchemaVersion = 3 as const;
|
||||||
export const projectStateSnapshotSchemaVersion = 4 as const;
|
export const supplyTypesProjectStateSnapshotSchemaVersion = 4 as const;
|
||||||
|
export const projectStateSnapshotSchemaVersion = 5 as const;
|
||||||
|
|
||||||
const idSchema = z.string().trim().min(1);
|
const idSchema = z.string().trim().min(1);
|
||||||
const nullableStringSchema = z.string().nullable();
|
const nullableStringSchema = z.string().nullable();
|
||||||
@@ -198,15 +203,20 @@ const distributionBoardProjectStateSnapshotSchema = z
|
|||||||
})
|
})
|
||||||
.strict();
|
.strict();
|
||||||
|
|
||||||
export const projectStateSnapshotSchema = z
|
const supplyTypesProjectStateSnapshotSchema = z
|
||||||
.object({
|
.object({
|
||||||
schemaVersion: z.literal(projectStateSnapshotSchemaVersion),
|
schemaVersion: z.literal(supplyTypesProjectStateSnapshotSchemaVersion),
|
||||||
project: currentProjectSchema,
|
project: currentProjectSchema,
|
||||||
distributionBoards: z.array(distributionBoardSchema),
|
distributionBoards: z.array(distributionBoardSchema),
|
||||||
...commonProjectStateSnapshotContents,
|
...commonProjectStateSnapshotContents,
|
||||||
})
|
})
|
||||||
.strict();
|
.strict();
|
||||||
|
|
||||||
|
export const projectStateSnapshotSchema =
|
||||||
|
supplyTypesProjectStateSnapshotSchema.extend({
|
||||||
|
schemaVersion: z.literal(projectStateSnapshotSchemaVersion),
|
||||||
|
});
|
||||||
|
|
||||||
export type ProjectStateSnapshot = z.infer<
|
export type ProjectStateSnapshot = z.infer<
|
||||||
typeof projectStateSnapshotSchema
|
typeof projectStateSnapshotSchema
|
||||||
>;
|
>;
|
||||||
@@ -217,23 +227,33 @@ export function parseProjectStateSnapshot(
|
|||||||
const version = isPlainObject(value) ? value.schemaVersion : undefined;
|
const version = isPlainObject(value) ? value.schemaVersion : undefined;
|
||||||
const snapshot =
|
const snapshot =
|
||||||
version === legacyProjectStateSnapshotSchemaVersion
|
version === legacyProjectStateSnapshotSchemaVersion
|
||||||
? upgradeDistributionBoardProjectStateSnapshot(
|
? upgradeSupplyTypesProjectStateSnapshot(
|
||||||
upgradePreviousProjectStateSnapshot(
|
upgradeDistributionBoardProjectStateSnapshot(
|
||||||
upgradeLegacyProjectStateSnapshot(
|
upgradePreviousProjectStateSnapshot(
|
||||||
legacyProjectStateSnapshotSchema.parse(value)
|
upgradeLegacyProjectStateSnapshot(
|
||||||
|
legacyProjectStateSnapshotSchema.parse(value)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
: version === previousProjectStateSnapshotSchemaVersion
|
: version === previousProjectStateSnapshotSchemaVersion
|
||||||
? upgradeDistributionBoardProjectStateSnapshot(
|
? upgradeSupplyTypesProjectStateSnapshot(
|
||||||
upgradePreviousProjectStateSnapshot(
|
upgradeDistributionBoardProjectStateSnapshot(
|
||||||
previousProjectStateSnapshotSchema.parse(value)
|
upgradePreviousProjectStateSnapshot(
|
||||||
|
previousProjectStateSnapshotSchema.parse(value)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
: version === distributionBoardProjectStateSnapshotSchemaVersion
|
: version === distributionBoardProjectStateSnapshotSchemaVersion
|
||||||
? upgradeDistributionBoardProjectStateSnapshot(
|
? upgradeSupplyTypesProjectStateSnapshot(
|
||||||
distributionBoardProjectStateSnapshotSchema.parse(value)
|
upgradeDistributionBoardProjectStateSnapshot(
|
||||||
|
distributionBoardProjectStateSnapshotSchema.parse(value)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
: version === supplyTypesProjectStateSnapshotSchemaVersion
|
||||||
|
? upgradeSupplyTypesProjectStateSnapshot(
|
||||||
|
supplyTypesProjectStateSnapshotSchema.parse(value)
|
||||||
|
)
|
||||||
: projectStateSnapshotSchema.parse(value);
|
: projectStateSnapshotSchema.parse(value);
|
||||||
assertProjectStateSnapshotRelations(snapshot);
|
assertProjectStateSnapshotRelations(snapshot);
|
||||||
return snapshot;
|
return snapshot;
|
||||||
@@ -271,10 +291,10 @@ function upgradePreviousProjectStateSnapshot(
|
|||||||
|
|
||||||
function upgradeDistributionBoardProjectStateSnapshot(
|
function upgradeDistributionBoardProjectStateSnapshot(
|
||||||
snapshot: z.infer<typeof distributionBoardProjectStateSnapshotSchema>
|
snapshot: z.infer<typeof distributionBoardProjectStateSnapshotSchema>
|
||||||
): ProjectStateSnapshot {
|
): z.infer<typeof supplyTypesProjectStateSnapshotSchema> {
|
||||||
return {
|
return {
|
||||||
...snapshot,
|
...snapshot,
|
||||||
schemaVersion: projectStateSnapshotSchemaVersion,
|
schemaVersion: supplyTypesProjectStateSnapshotSchemaVersion,
|
||||||
project: {
|
project: {
|
||||||
...snapshot.project,
|
...snapshot.project,
|
||||||
enabledDistributionBoardSupplyTypes: [
|
enabledDistributionBoardSupplyTypes: [
|
||||||
@@ -284,6 +304,37 @@ function upgradeDistributionBoardProjectStateSnapshot(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function upgradeSupplyTypesProjectStateSnapshot(
|
||||||
|
snapshot: z.infer<typeof supplyTypesProjectStateSnapshotSchema>
|
||||||
|
): ProjectStateSnapshot {
|
||||||
|
const settings = snapshot.project;
|
||||||
|
const sectionById = new Map(
|
||||||
|
snapshot.circuitSections.map((section) => [section.id, section])
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
...snapshot,
|
||||||
|
schemaVersion: projectStateSnapshotSchemaVersion,
|
||||||
|
projectDevices: snapshot.projectDevices.map((device) => ({
|
||||||
|
...device,
|
||||||
|
voltageV: resolveProjectVoltage(device.phaseType, settings),
|
||||||
|
})),
|
||||||
|
circuits: snapshot.circuits.map((circuit) => {
|
||||||
|
const section = sectionById.get(circuit.sectionId);
|
||||||
|
if (!section) {
|
||||||
|
return circuit;
|
||||||
|
}
|
||||||
|
const phaseType = resolveCircuitPhaseType(
|
||||||
|
section.key,
|
||||||
|
circuit.deviceRows.map((row) => row.phaseType)
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
...circuit,
|
||||||
|
voltage: resolveProjectVoltage(phaseType, settings),
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
||||||
return value !== null && typeof value === "object" && !Array.isArray(value);
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
||||||
}
|
}
|
||||||
@@ -364,6 +415,14 @@ function assertProjectStateSnapshotRelations(
|
|||||||
}
|
}
|
||||||
for (const device of snapshot.projectDevices) {
|
for (const device of snapshot.projectDevices) {
|
||||||
assertProjectOwnership(device.projectId, projectId, "project device");
|
assertProjectOwnership(device.projectId, projectId, "project device");
|
||||||
|
if (
|
||||||
|
device.voltageV !==
|
||||||
|
resolveProjectVoltage(device.phaseType, snapshot.project)
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
"Snapshot project device voltage must match project settings."
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for (const floor of snapshot.floors) {
|
for (const floor of snapshot.floors) {
|
||||||
assertProjectOwnership(floor.projectId, projectId, "floor");
|
assertProjectOwnership(floor.projectId, projectId, "floor");
|
||||||
@@ -394,6 +453,18 @@ function assertProjectStateSnapshotRelations(
|
|||||||
"Snapshot circuit reserve state must match its device rows."
|
"Snapshot circuit reserve state must match its device rows."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
const expectedVoltage = resolveProjectVoltage(
|
||||||
|
resolveCircuitPhaseType(
|
||||||
|
section.key,
|
||||||
|
circuit.deviceRows.map((row) => row.phaseType)
|
||||||
|
),
|
||||||
|
snapshot.project
|
||||||
|
);
|
||||||
|
if (circuit.voltage !== expectedVoltage) {
|
||||||
|
throw new Error(
|
||||||
|
"Snapshot circuit voltage must match project settings."
|
||||||
|
);
|
||||||
|
}
|
||||||
for (const row of circuit.deviceRows) {
|
for (const row of circuit.deviceRows) {
|
||||||
if (row.circuitId !== circuit.id) {
|
if (row.circuitId !== circuit.id) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
export type ElectricalPhaseType = "single_phase" | "three_phase";
|
||||||
|
|
||||||
|
export interface ProjectVoltageSettings {
|
||||||
|
singlePhaseVoltageV: number;
|
||||||
|
threePhaseVoltageV: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveProjectVoltage(
|
||||||
|
phaseType: ElectricalPhaseType,
|
||||||
|
settings: ProjectVoltageSettings
|
||||||
|
) {
|
||||||
|
return phaseType === "three_phase"
|
||||||
|
? settings.threePhaseVoltageV
|
||||||
|
: settings.singlePhaseVoltageV;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveCircuitPhaseType(
|
||||||
|
sectionKey: string,
|
||||||
|
devicePhaseTypes: ReadonlyArray<string | null | undefined> = []
|
||||||
|
): ElectricalPhaseType {
|
||||||
|
if (sectionKey === "three_phase") {
|
||||||
|
return "three_phase";
|
||||||
|
}
|
||||||
|
if (sectionKey === "lighting" || sectionKey === "single_phase") {
|
||||||
|
return "single_phase";
|
||||||
|
}
|
||||||
|
const assignedPhaseTypes = devicePhaseTypes.filter(
|
||||||
|
(phaseType): phaseType is ElectricalPhaseType =>
|
||||||
|
phaseType === "single_phase" || phaseType === "three_phase"
|
||||||
|
);
|
||||||
|
return assignedPhaseTypes.length > 0 &&
|
||||||
|
assignedPhaseTypes.every((phaseType) => phaseType === "three_phase")
|
||||||
|
? "three_phase"
|
||||||
|
: "single_phase";
|
||||||
|
}
|
||||||
@@ -5,6 +5,10 @@ import {
|
|||||||
inferProjectDeviceSectionKey,
|
inferProjectDeviceSectionKey,
|
||||||
isProjectDevicePlacementValid,
|
isProjectDevicePlacementValid,
|
||||||
} from "../../domain/services/project-device-placement.service";
|
} from "../../domain/services/project-device-placement.service";
|
||||||
|
import {
|
||||||
|
resolveCircuitPhaseType,
|
||||||
|
resolveProjectVoltage,
|
||||||
|
} from "../../domain/services/project-voltage.service";
|
||||||
import {
|
import {
|
||||||
getInsertionSortOrder,
|
getInsertionSortOrder,
|
||||||
resolveGridInsertionIntent,
|
resolveGridInsertionIntent,
|
||||||
@@ -819,6 +823,19 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
|||||||
values: CreateCircuitInputDto,
|
values: CreateCircuitInputDto,
|
||||||
deviceRowValues: CreateCircuitDeviceRowInputDto[] = []
|
deviceRowValues: CreateCircuitDeviceRowInputDto[] = []
|
||||||
) {
|
) {
|
||||||
|
const section = data?.sections.find(
|
||||||
|
(entry) => entry.id === values.sectionId
|
||||||
|
);
|
||||||
|
if (!section || !data) {
|
||||||
|
throw new Error("Bereich wurde nicht gefunden.");
|
||||||
|
}
|
||||||
|
const voltage = resolveProjectVoltage(
|
||||||
|
resolveCircuitPhaseType(
|
||||||
|
section.key,
|
||||||
|
deviceRowValues.map((row) => row.phaseType)
|
||||||
|
),
|
||||||
|
data
|
||||||
|
);
|
||||||
const circuitId = crypto.randomUUID();
|
const circuitId = crypto.randomUUID();
|
||||||
const deviceRows = deviceRowValues.map((row, index) =>
|
const deviceRows = deviceRowValues.map((row, index) =>
|
||||||
createDeviceRowSnapshot(circuitId, row, (index + 1) * 10)
|
createDeviceRowSnapshot(circuitId, row, (index + 1) * 10)
|
||||||
@@ -826,7 +843,7 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
|||||||
return buildCircuitInsertSnapshot({
|
return buildCircuitInsertSnapshot({
|
||||||
id: circuitId,
|
id: circuitId,
|
||||||
circuitListId,
|
circuitListId,
|
||||||
values,
|
values: { ...values, voltage },
|
||||||
deviceRows,
|
deviceRows,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,7 +47,6 @@ export function ProjectDeviceModal({
|
|||||||
simultaneityFactor: Number(values.simultaneityFactor),
|
simultaneityFactor: Number(values.simultaneityFactor),
|
||||||
cosPhi: optionalNumber(values.cosPhi),
|
cosPhi: optionalNumber(values.cosPhi),
|
||||||
remark: optionalString(values.remark),
|
remark: optionalString(values.remark),
|
||||||
voltageV: optionalNumber(values.voltageV),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,13 +181,6 @@ export function ProjectDeviceModal({
|
|||||||
step="0.01"
|
step="0.01"
|
||||||
value={values.cosPhi}
|
value={values.cosPhi}
|
||||||
/>
|
/>
|
||||||
<NumberField
|
|
||||||
id="device-voltage"
|
|
||||||
label="Spannung [V]"
|
|
||||||
min="1"
|
|
||||||
onChange={(value) => update("voltageV", value)}
|
|
||||||
value={values.voltageV}
|
|
||||||
/>
|
|
||||||
<div className="col-12">
|
<div className="col-12">
|
||||||
<label className="form-label" htmlFor="device-remark">
|
<label className="form-label" htmlFor="device-remark">
|
||||||
Bemerkung
|
Bemerkung
|
||||||
@@ -272,7 +264,6 @@ function toFormValues(device?: ProjectDeviceDto) {
|
|||||||
simultaneityFactor: String(device?.simultaneityFactor ?? 1),
|
simultaneityFactor: String(device?.simultaneityFactor ?? 1),
|
||||||
cosPhi: String(device?.cosPhi ?? 1),
|
cosPhi: String(device?.cosPhi ?? 1),
|
||||||
remark: device?.remark ?? "",
|
remark: device?.remark ?? "",
|
||||||
voltageV: String(device?.voltageV ?? ""),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -187,7 +187,6 @@ export interface CreateGlobalDeviceInput {
|
|||||||
quantity: number;
|
quantity: number;
|
||||||
installedPowerPerUnitKw: number;
|
installedPowerPerUnitKw: number;
|
||||||
demandFactor: number;
|
demandFactor: number;
|
||||||
voltageV?: number;
|
|
||||||
phaseCount?: 1 | 3;
|
phaseCount?: 1 | 3;
|
||||||
powerFactor?: number;
|
powerFactor?: number;
|
||||||
note?: string;
|
note?: string;
|
||||||
@@ -205,7 +204,6 @@ export interface CreateProjectDeviceInput {
|
|||||||
simultaneityFactor: number;
|
simultaneityFactor: number;
|
||||||
cosPhi?: number;
|
cosPhi?: number;
|
||||||
remark?: string;
|
remark?: string;
|
||||||
voltageV?: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ProjectDeviceSyncDifferenceDto {
|
export interface ProjectDeviceSyncDifferenceDto {
|
||||||
@@ -314,6 +312,8 @@ export interface CircuitTreeMigrationReportDto {
|
|||||||
export interface CircuitTreeResponseDto {
|
export interface CircuitTreeResponseDto {
|
||||||
circuitListId: string;
|
circuitListId: string;
|
||||||
currentRevision: number;
|
currentRevision: number;
|
||||||
|
singlePhaseVoltageV: number;
|
||||||
|
threePhaseVoltageV: number;
|
||||||
sections: CircuitTreeSectionDto[];
|
sections: CircuitTreeSectionDto[];
|
||||||
migrationReport?: CircuitTreeMigrationReportDto;
|
migrationReport?: CircuitTreeMigrationReportDto;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -161,7 +161,6 @@ const circuitFieldKeys = new Set<CellKey>([
|
|||||||
"cableSummary",
|
"cableSummary",
|
||||||
"rcdAssignment",
|
"rcdAssignment",
|
||||||
"terminalDesignation",
|
"terminalDesignation",
|
||||||
"voltage",
|
|
||||||
"controlRequirement",
|
"controlRequirement",
|
||||||
"status",
|
"status",
|
||||||
"isReserve",
|
"isReserve",
|
||||||
@@ -393,6 +392,7 @@ export function compareSortValues(left: GridValue, right: GridValue): number {
|
|||||||
|
|
||||||
export function getCellKind(rowType: RowType, key: CellKey): CellKind {
|
export function getCellKind(rowType: RowType, key: CellKey): CellKind {
|
||||||
if (key === "rowTotalPower" || key === "circuitTotalPower") return "computed";
|
if (key === "rowTotalPower" || key === "circuitTotalPower") return "computed";
|
||||||
|
if (key === "voltage") return "readonly";
|
||||||
if (rowType === "section") return "readonly";
|
if (rowType === "section") return "readonly";
|
||||||
if (rowType === "placeholder") {
|
if (rowType === "placeholder") {
|
||||||
if (deviceFieldKeys.has(key)) return "deviceField";
|
if (deviceFieldKeys.has(key)) return "deviceField";
|
||||||
|
|||||||
@@ -149,6 +149,8 @@ function makeVisibleGridRow(
|
|||||||
value = circuit.circuitTotalPower;
|
value = circuit.circuitTotalPower;
|
||||||
} else if (column.key === "circuitTotalPower" && circuit) {
|
} else if (column.key === "circuitTotalPower" && circuit) {
|
||||||
value = circuit.circuitTotalPower;
|
value = circuit.circuitTotalPower;
|
||||||
|
} else if (column.key === "voltage" && circuit) {
|
||||||
|
value = circuit.voltage;
|
||||||
}
|
}
|
||||||
|
|
||||||
return { cellKey: column.key, editable, kind, value };
|
return { cellKey: column.key, editable, kind, value };
|
||||||
|
|||||||
@@ -55,6 +55,8 @@ export async function getCircuitTree(req: Request, res: Response) {
|
|||||||
const tree: CircuitTreeResponse = {
|
const tree: CircuitTreeResponse = {
|
||||||
circuitListId,
|
circuitListId,
|
||||||
currentRevision: project.currentRevision,
|
currentRevision: project.currentRevision,
|
||||||
|
singlePhaseVoltageV: project.singlePhaseVoltageV,
|
||||||
|
threePhaseVoltageV: project.threePhaseVoltageV,
|
||||||
sections: sections.map((section) => ({
|
sections: sections.map((section) => ({
|
||||||
id: section.id,
|
id: section.id,
|
||||||
key: section.key,
|
key: section.key,
|
||||||
@@ -127,6 +129,8 @@ export async function getCircuitTree(req: Request, res: Response) {
|
|||||||
return res.json({
|
return res.json({
|
||||||
circuitListId,
|
circuitListId,
|
||||||
currentRevision: project.currentRevision,
|
currentRevision: project.currentRevision,
|
||||||
|
singlePhaseVoltageV: project.singlePhaseVoltageV,
|
||||||
|
threePhaseVoltageV: project.threePhaseVoltageV,
|
||||||
sections: [],
|
sections: [],
|
||||||
warning:
|
warning:
|
||||||
"Circuit-first tables are not available yet. Run database migrations (including 0008_circuit_first_model).",
|
"Circuit-first tables are not available yet. Run database migrations (including 0008_circuit_first_model).",
|
||||||
|
|||||||
@@ -70,7 +70,6 @@ export async function copyProjectDeviceToGlobal(req: Request, res: Response) {
|
|||||||
quantity: source.quantity,
|
quantity: source.quantity,
|
||||||
installedPowerPerUnitKw: source.powerPerUnit,
|
installedPowerPerUnitKw: source.powerPerUnit,
|
||||||
demandFactor: source.simultaneityFactor,
|
demandFactor: source.simultaneityFactor,
|
||||||
voltageV: source.voltageV ?? undefined,
|
|
||||||
phaseCount: source.phaseType === "three_phase" ? 3 : 1,
|
phaseCount: source.phaseType === "three_phase" ? 3 : 1,
|
||||||
powerFactor: source.cosPhi ?? undefined,
|
powerFactor: source.cosPhi ?? undefined,
|
||||||
note: source.remark ?? undefined,
|
note: source.remark ?? undefined,
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { projectCommandService } from "../composition/project-command-stores.js"
|
|||||||
import {
|
import {
|
||||||
globalDeviceRepository,
|
globalDeviceRepository,
|
||||||
projectDeviceRepository,
|
projectDeviceRepository,
|
||||||
|
projectRepository,
|
||||||
} from "../composition/application-repositories.js";
|
} from "../composition/application-repositories.js";
|
||||||
import {
|
import {
|
||||||
createProjectDeviceCommandSchema,
|
createProjectDeviceCommandSchema,
|
||||||
@@ -21,6 +22,7 @@ import {
|
|||||||
} from "../../shared/validation/project-device.schemas.js";
|
} from "../../shared/validation/project-device.schemas.js";
|
||||||
import type { CreateProjectDeviceInput } from "../../shared/validation/project-device.schemas.js";
|
import type { CreateProjectDeviceInput } from "../../shared/validation/project-device.schemas.js";
|
||||||
import { respondWithProjectCommandError } from "./project-command.controller.js";
|
import { respondWithProjectCommandError } from "./project-command.controller.js";
|
||||||
|
import { resolveProjectVoltage } from "../../domain/services/project-voltage.service.js";
|
||||||
|
|
||||||
export async function listProjectDevicesByProject(req: Request, res: Response) {
|
export async function listProjectDevicesByProject(req: Request, res: Response) {
|
||||||
const { projectId } = req.params;
|
const { projectId } = req.params;
|
||||||
@@ -41,9 +43,18 @@ export async function createProjectDevice(req: Request, res: Response) {
|
|||||||
return res.status(400).json({ error: parsed.error.flatten() });
|
return res.status(400).json({ error: parsed.error.flatten() });
|
||||||
}
|
}
|
||||||
const { expectedRevision, ...input } = parsed.data;
|
const { expectedRevision, ...input } = parsed.data;
|
||||||
const projectDevice = toProjectDeviceSnapshot(projectId, randomUUID(), input);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const project = await projectRepository.findById(projectId);
|
||||||
|
if (!project) {
|
||||||
|
return res.status(404).json({ error: "Project not found" });
|
||||||
|
}
|
||||||
|
const projectDevice = toProjectDeviceSnapshot(
|
||||||
|
projectId,
|
||||||
|
randomUUID(),
|
||||||
|
input,
|
||||||
|
project
|
||||||
|
);
|
||||||
const result = projectCommandService.executeUser({
|
const result = projectCommandService.executeUser({
|
||||||
projectId,
|
projectId,
|
||||||
expectedRevision,
|
expectedRevision,
|
||||||
@@ -73,13 +84,17 @@ export async function updateProjectDevice(req: Request, res: Response) {
|
|||||||
const { expectedRevision, ...input } = parsed.data;
|
const { expectedRevision, ...input } = parsed.data;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const project = await projectRepository.findById(projectId);
|
||||||
|
if (!project) {
|
||||||
|
return res.status(404).json({ error: "Project not found" });
|
||||||
|
}
|
||||||
const result = projectCommandService.executeUser({
|
const result = projectCommandService.executeUser({
|
||||||
projectId,
|
projectId,
|
||||||
expectedRevision,
|
expectedRevision,
|
||||||
description: "Projektgerät bearbeiten",
|
description: "Projektgerät bearbeiten",
|
||||||
command: createProjectDeviceUpdateProjectCommand(
|
command: createProjectDeviceUpdateProjectCommand(
|
||||||
projectDeviceId,
|
projectDeviceId,
|
||||||
toProjectDeviceValues(input)
|
toProjectDeviceValues(input, project)
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
// Linked rows are synchronized only through the explicit review flow.
|
// Linked rows are synchronized only through the explicit review flow.
|
||||||
@@ -133,6 +148,10 @@ export async function copyGlobalDeviceToProject(req: Request, res: Response) {
|
|||||||
if (!source) {
|
if (!source) {
|
||||||
return res.status(404).json({ error: "Global device not found" });
|
return res.status(404).json({ error: "Global device not found" });
|
||||||
}
|
}
|
||||||
|
const project = await projectRepository.findById(projectId);
|
||||||
|
if (!project) {
|
||||||
|
return res.status(404).json({ error: "Project not found" });
|
||||||
|
}
|
||||||
|
|
||||||
const projectDevice = toProjectDeviceSnapshot(projectId, randomUUID(), {
|
const projectDevice = toProjectDeviceSnapshot(projectId, randomUUID(), {
|
||||||
name: source.name,
|
name: source.name,
|
||||||
@@ -144,8 +163,7 @@ export async function copyGlobalDeviceToProject(req: Request, res: Response) {
|
|||||||
simultaneityFactor: source.demandFactor,
|
simultaneityFactor: source.demandFactor,
|
||||||
cosPhi: source.powerFactor ?? undefined,
|
cosPhi: source.powerFactor ?? undefined,
|
||||||
remark: source.note ?? undefined,
|
remark: source.note ?? undefined,
|
||||||
voltageV: source.voltageV ?? undefined,
|
}, project);
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = projectCommandService.executeUser({
|
const result = projectCommandService.executeUser({
|
||||||
@@ -167,16 +185,26 @@ export async function copyGlobalDeviceToProject(req: Request, res: Response) {
|
|||||||
function toProjectDeviceSnapshot(
|
function toProjectDeviceSnapshot(
|
||||||
projectId: string,
|
projectId: string,
|
||||||
id: string,
|
id: string,
|
||||||
input: CreateProjectDeviceInput
|
input: CreateProjectDeviceInput,
|
||||||
|
project: {
|
||||||
|
singlePhaseVoltageV: number;
|
||||||
|
threePhaseVoltageV: number;
|
||||||
|
}
|
||||||
): ProjectDeviceSnapshot {
|
): ProjectDeviceSnapshot {
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
projectId,
|
projectId,
|
||||||
...toProjectDeviceValues(input),
|
...toProjectDeviceValues(input, project),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function toProjectDeviceValues(input: CreateProjectDeviceInput) {
|
function toProjectDeviceValues(
|
||||||
|
input: CreateProjectDeviceInput,
|
||||||
|
project: {
|
||||||
|
singlePhaseVoltageV: number;
|
||||||
|
threePhaseVoltageV: number;
|
||||||
|
}
|
||||||
|
) {
|
||||||
return {
|
return {
|
||||||
name: input.name,
|
name: input.name,
|
||||||
displayName: input.displayName,
|
displayName: input.displayName,
|
||||||
@@ -189,7 +217,7 @@ function toProjectDeviceValues(input: CreateProjectDeviceInput) {
|
|||||||
simultaneityFactor: input.simultaneityFactor,
|
simultaneityFactor: input.simultaneityFactor,
|
||||||
cosPhi: input.cosPhi ?? null,
|
cosPhi: input.cosPhi ?? null,
|
||||||
remark: input.remark ?? null,
|
remark: input.remark ?? null,
|
||||||
voltageV: input.voltageV ?? null,
|
voltageV: resolveProjectVoltage(input.phaseType, project),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,11 +7,10 @@ export const createGlobalDeviceSchema = z.object({
|
|||||||
quantity: z.number().min(0),
|
quantity: z.number().min(0),
|
||||||
installedPowerPerUnitKw: z.number().min(0),
|
installedPowerPerUnitKw: z.number().min(0),
|
||||||
demandFactor: z.number().min(0).max(1),
|
demandFactor: z.number().min(0).max(1),
|
||||||
voltageV: z.number().positive().optional(),
|
|
||||||
phaseCount: z.union([z.literal(1), z.literal(3)]).optional(),
|
phaseCount: z.union([z.literal(1), z.literal(3)]).optional(),
|
||||||
powerFactor: z.number().min(0).max(1).optional(),
|
powerFactor: z.number().min(0).max(1).optional(),
|
||||||
note: z.string().optional(),
|
note: z.string().optional(),
|
||||||
});
|
}).strict();
|
||||||
|
|
||||||
export const updateGlobalDeviceSchema = createGlobalDeviceSchema;
|
export const updateGlobalDeviceSchema = createGlobalDeviceSchema;
|
||||||
|
|
||||||
|
|||||||
@@ -14,9 +14,7 @@ export const createProjectDeviceSchema = z.object({
|
|||||||
simultaneityFactor: z.number().min(0).max(1),
|
simultaneityFactor: z.number().min(0).max(1),
|
||||||
cosPhi: z.number().min(0).max(1).optional(),
|
cosPhi: z.number().min(0).max(1).optional(),
|
||||||
remark: z.string().optional(),
|
remark: z.string().optional(),
|
||||||
// Transitional metadata used when importing from the legacy global-device library.
|
}).strict();
|
||||||
voltageV: z.number().positive().optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const updateProjectDeviceSchema = createProjectDeviceSchema;
|
export const updateProjectDeviceSchema = createProjectDeviceSchema;
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ import {
|
|||||||
calculateCircuitTotalPower,
|
calculateCircuitTotalPower,
|
||||||
calculateRowTotalPower,
|
calculateRowTotalPower,
|
||||||
} from "../src/domain/calculations/circuit-power-calculation.js";
|
} from "../src/domain/calculations/circuit-power-calculation.js";
|
||||||
|
import {
|
||||||
|
resolveCircuitPhaseType,
|
||||||
|
resolveProjectVoltage,
|
||||||
|
} from "../src/domain/services/project-voltage.service.js";
|
||||||
|
|
||||||
describe("circuit power calculation", () => {
|
describe("circuit power calculation", () => {
|
||||||
it("calculates row and circuit totals from device rows", () => {
|
it("calculates row and circuit totals from device rows", () => {
|
||||||
@@ -16,3 +20,28 @@ describe("circuit power calculation", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("project voltage derivation", () => {
|
||||||
|
const settings = {
|
||||||
|
singlePhaseVoltageV: 240,
|
||||||
|
threePhaseVoltageV: 415,
|
||||||
|
};
|
||||||
|
|
||||||
|
it("derives device voltage exclusively from phase and project settings", () => {
|
||||||
|
assert.equal(resolveProjectVoltage("single_phase", settings), 240);
|
||||||
|
assert.equal(resolveProjectVoltage("three_phase", settings), 415);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses section phase and handles unassigned circuits conservatively", () => {
|
||||||
|
assert.equal(resolveCircuitPhaseType("lighting", ["three_phase"]), "single_phase");
|
||||||
|
assert.equal(resolveCircuitPhaseType("single_phase", ["three_phase"]), "single_phase");
|
||||||
|
assert.equal(resolveCircuitPhaseType("three_phase", ["single_phase"]), "three_phase");
|
||||||
|
assert.equal(resolveCircuitPhaseType("unassigned", ["three_phase"]), "three_phase");
|
||||||
|
assert.equal(
|
||||||
|
resolveCircuitPhaseType("unassigned", ["three_phase", "single_phase"]),
|
||||||
|
"single_phase"
|
||||||
|
);
|
||||||
|
assert.equal(resolveCircuitPhaseType("unassigned", []), "single_phase");
|
||||||
|
assert.equal(resolveCircuitPhaseType("unassigned", [null]), "single_phase");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ describe("circuit project-command repository", () => {
|
|||||||
const circuit = getCircuit(fixture.context);
|
const circuit = getCircuit(fixture.context);
|
||||||
assert.equal(circuit.displayName, null);
|
assert.equal(circuit.displayName, null);
|
||||||
assert.equal(circuit.protectionRatedCurrent, 16);
|
assert.equal(circuit.protectionRatedCurrent, 16);
|
||||||
assert.equal(circuit.voltage, 400);
|
assert.equal(circuit.voltage, 230);
|
||||||
assert.equal(circuit.controlRequirement, "DALI");
|
assert.equal(circuit.controlRequirement, "DALI");
|
||||||
assert.equal(circuit.isReserve, 1);
|
assert.equal(circuit.isReserve, 1);
|
||||||
assert.equal(executed.revision.revisionNumber, 1);
|
assert.equal(executed.revision.revisionNumber, 1);
|
||||||
@@ -138,7 +138,6 @@ describe("circuit project-command repository", () => {
|
|||||||
changes: [
|
changes: [
|
||||||
{ field: "displayName", value: "Licht Bestand" },
|
{ field: "displayName", value: "Licht Bestand" },
|
||||||
{ field: "protectionRatedCurrent", value: 10 },
|
{ field: "protectionRatedCurrent", value: 10 },
|
||||||
{ field: "voltage", value: 230 },
|
|
||||||
{ field: "controlRequirement", value: "none" },
|
{ field: "controlRequirement", value: "none" },
|
||||||
{ field: "isReserve", value: false },
|
{ field: "isReserve", value: false },
|
||||||
],
|
],
|
||||||
@@ -149,7 +148,15 @@ describe("circuit project-command repository", () => {
|
|||||||
.from(projectChangeSets)
|
.from(projectChangeSets)
|
||||||
.get();
|
.get();
|
||||||
assert.ok(changeSet);
|
assert.ok(changeSet);
|
||||||
assert.deepEqual(deserializeProjectCommand(changeSet.forwardPayloadJson), command);
|
assert.deepEqual(
|
||||||
|
deserializeProjectCommand(changeSet.forwardPayloadJson),
|
||||||
|
createCircuitUpdateProjectCommand("circuit-1", {
|
||||||
|
displayName: null,
|
||||||
|
protectionRatedCurrent: 16,
|
||||||
|
controlRequirement: "DALI",
|
||||||
|
isReserve: true,
|
||||||
|
})
|
||||||
|
);
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
deserializeProjectCommand(changeSet.inversePayloadJson),
|
deserializeProjectCommand(changeSet.inversePayloadJson),
|
||||||
executed.inverse
|
executed.inverse
|
||||||
|
|||||||
@@ -41,6 +41,20 @@ describe("project device circuit-first schema", () => {
|
|||||||
assert.equal(result.success, false);
|
assert.equal(result.success, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("rejects manually supplied device voltages", () => {
|
||||||
|
const result = createProjectDeviceSchema.safeParse({
|
||||||
|
name: "Pumpe",
|
||||||
|
displayName: "Pumpe",
|
||||||
|
phaseType: "three_phase",
|
||||||
|
quantity: 1,
|
||||||
|
powerPerUnit: 2,
|
||||||
|
simultaneityFactor: 1,
|
||||||
|
voltageV: 500,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(result.success, false);
|
||||||
|
});
|
||||||
|
|
||||||
it("requires a project revision for create and update commands", () => {
|
it("requires a project revision for create and update commands", () => {
|
||||||
const device = {
|
const device = {
|
||||||
name: "E-Line Pro",
|
name: "E-Line Pro",
|
||||||
|
|||||||
@@ -12,6 +12,11 @@ import { ProjectSettingsProjectCommandRepository } from "../src/db/repositories/
|
|||||||
import { projectRevisions } from "../src/db/schema/project-revisions.js";
|
import { projectRevisions } from "../src/db/schema/project-revisions.js";
|
||||||
import { projects } from "../src/db/schema/projects.js";
|
import { projects } from "../src/db/schema/projects.js";
|
||||||
import { distributionBoards } from "../src/db/schema/distribution-boards.js";
|
import { distributionBoards } from "../src/db/schema/distribution-boards.js";
|
||||||
|
import { circuitLists } from "../src/db/schema/circuit-lists.js";
|
||||||
|
import { circuitSections } from "../src/db/schema/circuit-sections.js";
|
||||||
|
import { circuits } from "../src/db/schema/circuits.js";
|
||||||
|
import { projectDevices } from "../src/db/schema/project-devices.js";
|
||||||
|
import { DistributionBoardFixtureRepository } from "./support/distribution-board-fixture.js";
|
||||||
import { ProjectRevisionConflictError } from "../src/domain/errors/project-revision-conflict.error.js";
|
import { ProjectRevisionConflictError } from "../src/domain/errors/project-revision-conflict.error.js";
|
||||||
import { createProjectSettingsUpdateProjectCommand } from "../src/domain/models/project-settings-project-command.model.js";
|
import { createProjectSettingsUpdateProjectCommand } from "../src/domain/models/project-settings-project-command.model.js";
|
||||||
import { updateProjectSettings } from "../src/frontend/utils/api.js";
|
import { updateProjectSettings } from "../src/frontend/utils/api.js";
|
||||||
@@ -78,6 +83,86 @@ function settings(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function seedDerivedVoltageRecords(context: DatabaseContext) {
|
||||||
|
context.db.insert(projectDevices).values([
|
||||||
|
{
|
||||||
|
id: "device-single",
|
||||||
|
projectId: "project-1",
|
||||||
|
name: "Leuchte",
|
||||||
|
displayName: "Leuchte",
|
||||||
|
phaseType: "single_phase",
|
||||||
|
quantity: 1,
|
||||||
|
powerPerUnit: 0.1,
|
||||||
|
simultaneityFactor: 1,
|
||||||
|
voltageV: 230,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "device-three",
|
||||||
|
projectId: "project-1",
|
||||||
|
name: "Pumpe",
|
||||||
|
displayName: "Pumpe",
|
||||||
|
phaseType: "three_phase",
|
||||||
|
quantity: 1,
|
||||||
|
powerPerUnit: 2,
|
||||||
|
simultaneityFactor: 1,
|
||||||
|
voltageV: 400,
|
||||||
|
},
|
||||||
|
]).run();
|
||||||
|
const board = new DistributionBoardFixtureRepository(
|
||||||
|
context.db
|
||||||
|
).createWithCircuitListAndDefaultSections("project-1", "UV-01");
|
||||||
|
const list = context.db
|
||||||
|
.select()
|
||||||
|
.from(circuitLists)
|
||||||
|
.where(eq(circuitLists.distributionBoardId, board.id))
|
||||||
|
.get();
|
||||||
|
assert.ok(list);
|
||||||
|
const sections = context.db
|
||||||
|
.select()
|
||||||
|
.from(circuitSections)
|
||||||
|
.where(eq(circuitSections.circuitListId, list.id))
|
||||||
|
.all();
|
||||||
|
const singleSection = sections.find((section) => section.key === "single_phase");
|
||||||
|
const threeSection = sections.find((section) => section.key === "three_phase");
|
||||||
|
assert.ok(singleSection);
|
||||||
|
assert.ok(threeSection);
|
||||||
|
context.db.insert(circuits).values([
|
||||||
|
{
|
||||||
|
id: "circuit-single",
|
||||||
|
circuitListId: list.id,
|
||||||
|
sectionId: singleSection.id,
|
||||||
|
equipmentIdentifier: "-2F1",
|
||||||
|
sortOrder: 10,
|
||||||
|
voltage: 230,
|
||||||
|
isReserve: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "circuit-three",
|
||||||
|
circuitListId: list.id,
|
||||||
|
sectionId: threeSection.id,
|
||||||
|
equipmentIdentifier: "-3F1",
|
||||||
|
sortOrder: 10,
|
||||||
|
voltage: 400,
|
||||||
|
isReserve: 1,
|
||||||
|
},
|
||||||
|
]).run();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDerivedVoltages(context: DatabaseContext) {
|
||||||
|
return {
|
||||||
|
devices: context.db
|
||||||
|
.select({ id: projectDevices.id, voltageV: projectDevices.voltageV })
|
||||||
|
.from(projectDevices)
|
||||||
|
.all()
|
||||||
|
.sort((left, right) => left.id.localeCompare(right.id)),
|
||||||
|
circuits: context.db
|
||||||
|
.select({ id: circuits.id, voltage: circuits.voltage })
|
||||||
|
.from(circuits)
|
||||||
|
.all()
|
||||||
|
.sort((left, right) => left.id.localeCompare(right.id)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
describe("project settings project-command repository", () => {
|
describe("project settings project-command repository", () => {
|
||||||
it("validates settings commands and sends the expected revision from the frontend", async () => {
|
it("validates settings commands and sends the expected revision from the frontend", async () => {
|
||||||
assert.throws(
|
assert.throws(
|
||||||
@@ -137,6 +222,7 @@ describe("project settings project-command repository", () => {
|
|||||||
it("updates both voltages and supports persisted undo and redo", () => {
|
it("updates both voltages and supports persisted undo and redo", () => {
|
||||||
const context = createTestDatabase();
|
const context = createTestDatabase();
|
||||||
try {
|
try {
|
||||||
|
seedDerivedVoltageRecords(context);
|
||||||
const repository = new ProjectSettingsProjectCommandRepository(
|
const repository = new ProjectSettingsProjectCommandRepository(
|
||||||
context.db
|
context.db
|
||||||
);
|
);
|
||||||
@@ -171,6 +257,16 @@ describe("project settings project-command repository", () => {
|
|||||||
}),
|
}),
|
||||||
currentRevision: 1,
|
currentRevision: 1,
|
||||||
});
|
});
|
||||||
|
assert.deepEqual(getDerivedVoltages(context), {
|
||||||
|
devices: [
|
||||||
|
{ id: "device-single", voltageV: 240 },
|
||||||
|
{ id: "device-three", voltageV: 415 },
|
||||||
|
],
|
||||||
|
circuits: [
|
||||||
|
{ id: "circuit-single", voltage: 240 },
|
||||||
|
{ id: "circuit-three", voltage: 415 },
|
||||||
|
],
|
||||||
|
});
|
||||||
assert.deepEqual(forward.inverse.payload, {
|
assert.deepEqual(forward.inverse.payload, {
|
||||||
name: "Testprojekt",
|
name: "Testprojekt",
|
||||||
internalProjectNumber: "INT-1",
|
internalProjectNumber: "INT-1",
|
||||||
@@ -216,6 +312,16 @@ describe("project settings project-command repository", () => {
|
|||||||
],
|
],
|
||||||
currentRevision: 2,
|
currentRevision: 2,
|
||||||
});
|
});
|
||||||
|
assert.deepEqual(getDerivedVoltages(context), {
|
||||||
|
devices: [
|
||||||
|
{ id: "device-single", voltageV: 230 },
|
||||||
|
{ id: "device-three", voltageV: 400 },
|
||||||
|
],
|
||||||
|
circuits: [
|
||||||
|
{ id: "circuit-single", voltage: 230 },
|
||||||
|
{ id: "circuit-three", voltage: 400 },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
const redoStep = history.getNextCommand("project-1", "redo");
|
const redoStep = history.getNextCommand("project-1", "redo");
|
||||||
assert.ok(redoStep);
|
assert.ok(redoStep);
|
||||||
@@ -244,6 +350,16 @@ describe("project settings project-command repository", () => {
|
|||||||
],
|
],
|
||||||
currentRevision: 3,
|
currentRevision: 3,
|
||||||
});
|
});
|
||||||
|
assert.deepEqual(getDerivedVoltages(context), {
|
||||||
|
devices: [
|
||||||
|
{ id: "device-single", voltageV: 240 },
|
||||||
|
{ id: "device-three", voltageV: 415 },
|
||||||
|
],
|
||||||
|
circuits: [
|
||||||
|
{ id: "circuit-single", voltage: 240 },
|
||||||
|
{ id: "circuit-three", voltage: 415 },
|
||||||
|
],
|
||||||
|
});
|
||||||
} finally {
|
} finally {
|
||||||
context.close();
|
context.close();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ function createTestDatabase(): DatabaseContext {
|
|||||||
quantity: 1,
|
quantity: 1,
|
||||||
powerPerUnit: 2.5,
|
powerPerUnit: 2.5,
|
||||||
simultaneityFactor: 0.8,
|
simultaneityFactor: 0.8,
|
||||||
|
voltageV: 400,
|
||||||
})
|
})
|
||||||
.run();
|
.run();
|
||||||
context.db
|
context.db
|
||||||
@@ -112,6 +113,7 @@ function createTestDatabase(): DatabaseContext {
|
|||||||
equipmentIdentifier: "-1F1",
|
equipmentIdentifier: "-1F1",
|
||||||
displayName: "Ausgang",
|
displayName: "Ausgang",
|
||||||
sortOrder: 10,
|
sortOrder: 10,
|
||||||
|
voltage: 230,
|
||||||
isReserve: 0,
|
isReserve: 0,
|
||||||
})
|
})
|
||||||
.run();
|
.run();
|
||||||
@@ -165,7 +167,10 @@ function changeCompleteProjectState(context: DatabaseContext) {
|
|||||||
.run();
|
.run();
|
||||||
context.db
|
context.db
|
||||||
.update(circuits)
|
.update(circuits)
|
||||||
.set({ displayName: "Geänderter Stromkreis" })
|
.set({
|
||||||
|
displayName: "Geänderter Stromkreis",
|
||||||
|
voltage: 240,
|
||||||
|
})
|
||||||
.where(eq(circuits.id, "circuit-1"))
|
.where(eq(circuits.id, "circuit-1"))
|
||||||
.run();
|
.run();
|
||||||
context.db
|
context.db
|
||||||
@@ -198,8 +203,14 @@ function changeCompleteProjectState(context: DatabaseContext) {
|
|||||||
quantity: 2,
|
quantity: 2,
|
||||||
powerPerUnit: 0.05,
|
powerPerUnit: 0.05,
|
||||||
simultaneityFactor: 1,
|
simultaneityFactor: 1,
|
||||||
|
voltageV: 240,
|
||||||
})
|
})
|
||||||
.run();
|
.run();
|
||||||
|
context.db
|
||||||
|
.update(projectDevices)
|
||||||
|
.set({ voltageV: 415 })
|
||||||
|
.where(eq(projectDevices.id, "device-1"))
|
||||||
|
.run();
|
||||||
new DistributionBoardFixtureRepository(
|
new DistributionBoardFixtureRepository(
|
||||||
context.db
|
context.db
|
||||||
).createWithCircuitListAndDefaultSections("project-1", "UV-02");
|
).createWithCircuitListAndDefaultSections("project-1", "UV-02");
|
||||||
|
|||||||
@@ -128,6 +128,36 @@ describe("project state snapshot model", () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("normalizes version-four device voltages to project settings", () => {
|
||||||
|
const previous = {
|
||||||
|
...minimalSnapshot(),
|
||||||
|
schemaVersion: 4 as const,
|
||||||
|
projectDevices: [
|
||||||
|
{
|
||||||
|
id: "device-1",
|
||||||
|
projectId: "project-1",
|
||||||
|
name: "Pumpe",
|
||||||
|
displayName: "Pumpe",
|
||||||
|
phaseType: "three_phase" as const,
|
||||||
|
connectionKind: null,
|
||||||
|
costGroup: null,
|
||||||
|
category: null,
|
||||||
|
quantity: 1,
|
||||||
|
powerPerUnit: 2,
|
||||||
|
simultaneityFactor: 1,
|
||||||
|
cosPhi: null,
|
||||||
|
remark: null,
|
||||||
|
voltageV: 500,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const snapshot = parseProjectStateSnapshot(previous);
|
||||||
|
|
||||||
|
assert.equal(snapshot.schemaVersion, projectStateSnapshotSchemaVersion);
|
||||||
|
assert.equal(snapshot.projectDevices[0]?.voltageV, 400);
|
||||||
|
});
|
||||||
|
|
||||||
it("rejects duplicate ids and cross-project ownership", () => {
|
it("rejects duplicate ids and cross-project ownership", () => {
|
||||||
assert.throws(
|
assert.throws(
|
||||||
() =>
|
() =>
|
||||||
|
|||||||
@@ -156,11 +156,11 @@ describe("project device modal presentation", () => {
|
|||||||
"Anzahl",
|
"Anzahl",
|
||||||
"Leistung je Stück [kW]",
|
"Leistung je Stück [kW]",
|
||||||
"Gleichzeitigkeitsfaktor",
|
"Gleichzeitigkeitsfaktor",
|
||||||
"Spannung [V]",
|
|
||||||
"Bemerkung",
|
"Bemerkung",
|
||||||
]) {
|
]) {
|
||||||
assert.match(markup, new RegExp(label.replace("[", "\\[").replace("]", "\\]")));
|
assert.match(markup, new RegExp(label.replace("[", "\\[").replace("]", "\\]")));
|
||||||
}
|
}
|
||||||
|
assert.doesNotMatch(markup, /Spannung \[V\]/);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user