Add public building project setting

This commit is contained in:
2026-07-31 17:41:30 +02:00
parent 43a3bb0b69
commit 44b6fde940
29 changed files with 2057 additions and 54 deletions
+10 -6
View File
@@ -250,9 +250,11 @@ not change the project revision or undo/redo stacks. Restoring a server-stored
snapshot verifies its checksum and the current-state hash, replaces supported snapshot verifies its checksum and the current-state hash, replaces supported
project data atomically and records a new `restore` revision with a complete project data atomically and records a new `restore` revision with a complete
inverse command. Restore can therefore be undone and redone after a restart. inverse command. Restore can therefore be undone and redone after a restart.
Baseline snapshot schema version 1 contains circuit-group identity, Current snapshot schema version 2 additionally stores whether the project is a
distribution-board components and the separate circuit/component protection public building. Supported baseline version 1 snapshots are upgraded with
records. Pre-baseline snapshots are unsupported. Portable duplicate imports `isPublicBuilding = false`; pre-baseline snapshots remain unsupported. Both
versions contain circuit-group identity, distribution-board components and the
separate circuit/component protection records. Portable duplicate imports
remap component ids and protection-owner references together with the existing remap component ids and protection-owner references together with the existing
project graph. project graph.
The central revision boundary creates an automatic logical snapshot after each The central revision boundary creates an automatic logical snapshot after each
@@ -284,9 +286,11 @@ global-to-project copy API/UI paths use these persistent commands and track the
returned project revision. ProjectDevice synchronization/disconnect API and UI returned project revision. ProjectDevice synchronization/disconnect API and UI
paths do the same; their undo action uses the project-wide history endpoint. paths do the same; their undo action uses the project-wide history endpoint.
Project settings use the persistent `project.update-settings` command. Project Project settings use the persistent `project.update-settings` command. Project
metadata, both voltage defaults and the enabled distribution-board supply types metadata, the public-building classification, both voltage defaults and the
change in one revision and Undo/Redo restores them together; the project PUT enabled distribution-board supply types change in one revision and Undo/Redo
route requires `expectedRevision`. The system catalog is `AV`, `SV`, `EV`, restores them together; the project PUT route requires `expectedRevision`.
The public-building flag is the future sizing input for requiring halogen-free
cables and lines. The system catalog is `AV`, `SV`, `EV`,
`USV`, `MSR`, `SiBe`; at least one must be enabled and a type used by a board `USV`, `MSR`, `SiBe`; at least one must be enabled and a type used by a board
cannot be disabled. cannot be disabled.
Distribution-board setup uses `distribution-board.insert` schema version 1 Distribution-board setup uses `distribution-board.insert` schema version 1
+4 -3
View File
@@ -108,9 +108,10 @@ npm run db:generate
``` ```
`db:backup` verwendet die SQLite-Online-Backup-API und prüft das Ergebnis auf `db:backup` verwendet die SQLite-Online-Backup-API und prüft das Ergebnis auf
Integrität und Fremdschlüsselverletzungen. Die Migration `0000` ist die Integrität und Fremdschlüsselverletzungen. Migration `0000` ist die
Release-Baseline und richtet ausschließlich eine neue, leere Datenbank ein. Release-Baseline für eine leere Datenbank; nachfolgende nummerierte Migrationen
Datenbanken aus Entwicklungsständen vor dieser Baseline werden nicht unterstützt. entwickeln diese Baseline additiv weiter. Datenbanken aus Entwicklungsständen
vor der Baseline werden nicht unterstützt.
## Dokumentation ## Dokumentation
+8 -6
View File
@@ -73,9 +73,11 @@ Protected-board structure uses
`circuit-protection.update`. A subtree restore is normally emitted only as the `circuit-protection.update`. A subtree restore is normally emitted only as the
persisted inverse of a delete. The dispatcher also supports the documented persisted inverse of a delete. The dispatcher also supports the documented
project-device, project settings, distribution-board and project-location project-device, project settings, distribution-board and project-location
commands. The generic editor commands listed here use schema version `1`; commands. The generic editor commands listed here use schema version `1`.
the clean pre-release baseline does not accept older development formats. `project.update-settings` uses version `2` and still accepts supported baseline
Unsupported types or schema versions are rejected. Insert commands contain the complete entity or circuit block version `1` commands without changing the newer building classification.
Pre-baseline formats remain unsupported. Unsupported types or schema versions
are rejected. Insert commands contain the complete entity or circuit block
with stable ids; delete commands include the expected parent identity. Circuit with stable ids; delete commands include the expected parent identity. Circuit
snapshots contain zero, one or multiple complete device rows. Move commands snapshots contain zero, one or multiple complete device rows. Move commands
contain each row's expected and target circuit plus its exact expected and contain each row's expected and target circuit plus its exact expected and
@@ -170,12 +172,12 @@ synchronizes linked circuit rows implicitly.
- `PUT /projects/:projectId` - `PUT /projects/:projectId`
- request: - request:
`{ "expectedRevision": 12, "singlePhaseVoltageV": 230, "threePhaseVoltageV": 400, "enabledDistributionBoardSupplyTypes": ["AV", "MSR", "SiBe"], ...projectMetadata }` `{ "expectedRevision": 12, "isPublicBuilding": true, "singlePhaseVoltageV": 230, "threePhaseVoltageV": 400, "enabledDistributionBoardSupplyTypes": ["AV", "MSR", "SiBe"], ...projectMetadata }`
- executes `project.update-settings` as one atomic revision - executes `project.update-settings` as one atomic revision
- response: - response:
`{ "project": { ... }, "revision": { ... }, "history": { ... } }` `{ "project": { ... }, "revision": { ... }, "history": { ... } }`
- persistent Undo/Redo restores metadata, voltage values and the enabled - persistent Undo/Redo restores metadata, the public-building flag, voltage
distribution-board supply types together; project-device and circuit values and enabled distribution-board supply types together; project-device and circuit
voltages are recalculated from the restored project settings in the same voltages are recalculated from the restored project settings in the same
transaction transaction
- project-device and circuit voltage are derived values and are not accepted - project-device and circuit voltage are derived values and are not accepted
+12 -9
View File
@@ -232,8 +232,10 @@ und Historienstapel teilen dieselbe Transaktion. Create, Import aus der globalen
Gerätebibliothek und Delete laufen über dieselbe Command-Grenze; ihre Antworten Gerätebibliothek und Delete laufen über dieselbe Command-Grenze; ihre Antworten
liefern Gerät und aktualisierten Historienstand an die Projektseite zurück. liefern Gerät und aktualisierten Historienstand an die Projektseite zurück.
`project.update-settings` versioniert Projektname, interne und externe `project.update-settings` versioniert Projektname, interne und externe
Projektnummer, Bauherr, Beschreibung, beide Standardspannungen sowie die im Projektnummer, Bauherr, Beschreibung, die Kennzeichnung als öffentliches
Projekt freigeschalteten Verteiler-Netzarten als eine atomare Änderung. Der Gebäude, beide Standardspannungen sowie die im Projekt freigeschalteten
Verteiler-Netzarten als eine atomare Änderung. Die Gebäudekennzeichnung ist
der spätere Eingang für die Auswahl halogenfreier Kabel und Leitungen. Der
Systemkatalog besteht aus `AV`, `SV`, `EV`, `USV`, `MSR` und `SiBe`; mindestens Systemkatalog besteht aus `AV`, `SV`, `EV`, `USV`, `MSR` und `SiBe`; mindestens
eine Netzart muss aktiv bleiben und eine bereits von einer Verteilung eine Netzart muss aktiv bleiben und eine bereits von einer Verteilung
verwendete Netzart kann nicht deaktiviert werden. Die Projektseite bearbeitet diese Angaben in einem verwendete Netzart kann nicht deaktiviert werden. Die Projektseite bearbeitet diese Angaben in einem
@@ -414,12 +416,13 @@ wieder her.
verteilerweiten Gleichzeitigkeitsfaktor gemeinsam und stellt alle Werte über verteilerweiten Gleichzeitigkeitsfaktor gemeinsam und stellt alle Werte über
dauerhaftes Undo/Redo wieder her. Der Faktor liegt zwischen `0` und `1` und dauerhaftes Undo/Redo wieder her. Der Faktor liegt zwischen `0` und `1` und
ist für bestehende sowie neu angelegte Verteilungen standardmäßig `1`. ist für bestehende sowie neu angelegte Verteilungen standardmäßig `1`.
Das aktuelle Baseline-Snapshot-Schema enthält den Gleichzeitigkeitsfaktor, Snapshot-Schema 2 enthält zusätzlich die Gebäudekennzeichnung sowie den Gleichzeitigkeitsfaktor,
Gruppenkategorie und -nummer, Verteilerkomponenten sowie die getrennten Gruppenkategorie und -nummer, Verteilerkomponenten sowie die getrennten
1:1-Schutzgerätedaten. Capture, 1:1-Schutzgerätedaten. Capture,
benannte und automatische Snapshots, Wiederherstellung, Undo/Redo und beide benannte und automatische Snapshots, Wiederherstellung, Undo/Redo und beide
JSON-Importmodi verwenden denselben vollständigen Zustand. Vorherige JSON-Importmodi verwenden denselben vollständigen Zustand. Die unterstützte
Entwicklungsschemas werden nicht mehr eingelesen. Baseline-Version 1 wird mit `isPublicBuilding = false` hochgestuft; Formate vor
dieser Baseline werden nicht eingelesen.
Persistente Insert-, Update- und Delete-Commands versionieren Anlage, Persistente Insert-, Update- und Delete-Commands versionieren Anlage,
Bearbeitung und Löschung von Geschossen und Räumen mit stabilen UUIDs und Bearbeitung und Löschung von Geschossen und Räumen mit stabilen UUIDs und
exakten Vorher-/Nachher-Snapshots. Ein Geschoss wird nur exakten Vorher-/Nachher-Snapshots. Ein Geschoss wird nur
@@ -447,8 +450,8 @@ Kopieren in ein Projekt erzeugt ein eigenständiges Projektgerät.
- SQLite ist die aktuell unterstützte Datenbank. - SQLite ist die aktuell unterstützte Datenbank.
- Fremdschlüssel werden für jeden Datenbankkontext aktiviert. - Fremdschlüssel werden für jeden Datenbankkontext aktiviert.
- `npm run db:migrate` richtet mit der einzelnen Migration `0000` das komplette - `npm run db:migrate` richtet mit Baseline-Migration `0000` und den folgenden
aktuelle Schema einer leeren Datenbank ein. additiven Migrationen das aktuelle Schema ein.
- `npm run db:verify:circuit-schema` prüft erforderliche und entfernte Spalten. - `npm run db:verify:circuit-schema` prüft erforderliche und entfernte Spalten.
- `npm run db:backup` erzeugt ein konsistentes und verifiziertes Online-Backup. - `npm run db:backup` erzeugt ein konsistentes und verifiziertes Online-Backup.
- `npm run typecheck:scripts` prüft die TypeScript-Wartungsskripte, ohne Code zu - `npm run typecheck:scripts` prüft die TypeScript-Wartungsskripte, ohne Code zu
@@ -469,8 +472,8 @@ Kopieren in ein Projekt erzeugt ein eigenständiges Projektgerät.
flachen Stromkreis-Schutzfelder sind aus der Baseline entfernt. flachen Stromkreis-Schutzfelder sind aus der Baseline entfernt.
Ein triggergeführtes Register erzwingt bereits eine normalisierte, Ein triggergeführtes Register erzwingt bereits eine normalisierte,
stromkreislistenweite BMK-Eindeutigkeit über Stromkreise und stromkreislistenweite BMK-Eindeutigkeit über Stromkreise und
Verteilerkomponenten. Snapshot- und Transfer-Integration verwenden Verteilerkomponenten. Snapshot- und Transfer-Integration verwenden aktuell
Snapshot-Schema 1 der Baseline. Persistente Insert/Delete/Update-Commands für Snapshot-Schema 2. Persistente Insert/Delete/Update-Commands für
veränderliche Verteilerkomponenten, Gruppen einschließlich befüllter veränderliche Verteilerkomponenten, Gruppen einschließlich befüllter
Unterbäume sowie vollständige Gruppensortierung sind integriert. Der Editor Unterbäume sowie vollständige Gruppensortierung sind integriert. Der Editor
zeigt die geschützte Struktur an und bearbeitet veränderliche Gruppen- und zeigt die geschützte Struktur an und bearbeitet veränderliche Gruppen- und
@@ -262,8 +262,9 @@ Completed foundation:
- `project-device.insert` and `project-device.delete` preserve stable device - `project-device.insert` and `project-device.delete` preserve stable device
ids; delete undo restores links only from complete unchanged disconnected-row ids; delete undo restores links only from complete unchanged disconnected-row
snapshots in the same transaction snapshots in the same transaction
- `project.update-settings` persists both project voltage defaults, its exact - `project.update-settings` persists the public-building classification and
inverse, revision and history transition atomically; the project page and both project voltage defaults with their exact inverse, revision and history
transition atomically; the project page and
existing PUT route require the current expected revision; the same existing PUT route require the current expected revision; the same
transaction derives every project-device and circuit voltage again, so transaction derives every project-device and circuit voltage again, so
Undo/Redo cannot leave mixed voltage states Undo/Redo cannot leave mixed voltage states
+1 -1
View File
@@ -52,7 +52,7 @@ Wichtige fachliche Grenzen:
Für einen späteren Revit-Austausch besonders relevante Daten sind: Für einen späteren Revit-Austausch besonders relevante Daten sind:
- Projekt: Name, interne/externe Projektnummer, Bauherr, Beschreibung, - Projekt: Name, interne/externe Projektnummer, Bauherr, Beschreibung,
Projektspannungen. Kennzeichnung als öffentliches Gebäude und Projektspannungen.
- Geschoss und Raum: interne UUID, Name beziehungsweise Raumnummer und - Geschoss und Raum: interne UUID, Name beziehungsweise Raumnummer und
Raumname. Raumname.
- Verteilung: interne UUID, Name, Etage, Netzart und Gleichzeitigkeitsfaktor. - Verteilung: interne UUID, Name, Etage, Netzart und Gleichzeitigkeitsfaktor.
@@ -14,12 +14,18 @@ Future suggestions or checks may include:
- cable type - cable type
- number of cores - number of cores
- cable cross-section - cable cross-section
- halogen-free cable or line requirement for public buildings
- minimum cable cross-section based on current - minimum cable cross-section based on current
- maximum permissible cable length based on voltage drop - maximum permissible cable length based on voltage drop
- warning messages if cable length, cross-section or protection device do not match - warning messages if cable length, cross-section or protection device do not match
Users must remain able to manually override suggestions. Users must remain able to manually override suggestions.
The project-owned `isPublicBuilding` setting is the authoritative input for
the later halogen-free selection rule. The setting is persisted now; this
document does not yet define or implement the complete cable catalog or sizing
algorithm.
## Initial Design Rule Examples ## Initial Design Rule Examples
These are common planning defaults, not a replacement for full norm-compliant calculation. These are common planning defaults, not a replacement for full norm-compliant calculation.
+3 -3
View File
@@ -465,9 +465,9 @@ Implemented foundation:
- ProjectDevice synchronization and disconnect endpoints also require - ProjectDevice synchronization and disconnect endpoints also require
`expectedRevision`; their UI undo uses project-wide persistent history and `expectedRevision`; their UI undo uses project-wide persistent history and
the obsolete direct restore/reconnect write paths are removed the obsolete direct restore/reconnect write paths are removed
- project voltage settings use `project.update-settings`; both values, inverse, - project settings use `project.update-settings`; metadata, public-building
revision and history transition commit atomically and the former direct classification, voltage values, inverse, revision and history transition
settings write is removed commit atomically and the former direct settings write is removed
- distribution-board setup uses `distribution-board.insert` with a complete - distribution-board setup uses `distribution-board.insert` with a complete
stable board/list/default-section snapshot; Undo removes only an unchanged stable board/list/default-section snapshot; Undo removes only an unchanged
empty setup and the former direct controller write is removed empty setup and the former direct controller write is removed
+11 -4
View File
@@ -54,7 +54,7 @@ requirements and intended sequencing, not proof of implementation.
The pre-release cleanup established the following compatibility boundary: The pre-release cleanup established the following compatibility boundary:
- [x] Pre-release databases, portable JSON exports, logical snapshots and - [x] Pre-baseline databases, portable JSON exports, logical snapshots and
persisted command histories are explicitly unsupported. persisted command histories are explicitly unsupported.
- [x] Migration `0000` builds the complete supported schema on an empty - [x] Migration `0000` builds the complete supported schema on an empty
database. database.
@@ -109,9 +109,16 @@ The pre-release cleanup established the following compatibility boundary:
device rows with a valid phase are three-phase. device rows with a valid phase are three-phase.
- [x] Changing project voltage settings updates all project devices and circuits - [x] Changing project voltage settings updates all project devices and circuits
atomically in the same persistent Undo/Redo step. atomically in the same persistent Undo/Redo step.
- [x] Baseline migration `0000` and project-state schema version `1` store only - [x] Baseline migration `0000` stores only canonical derived values.
the canonical derived values. Pre-release imports are intentionally Pre-baseline imports are intentionally unsupported.
unsupported.
## Building Classification
- [x] Store whether a project represents a public building.
- [x] Edit the value in the project settings and preserve it through persistent
Undo/Redo, snapshots and portable project transfers.
- [x] Provide the value as an explicit future sizing input for selecting
halogen-free cables and lines; automatic sizing remains deferred.
## Circuit List Power Summary ## Circuit List Power Summary
@@ -467,11 +467,11 @@ The supported logical project snapshot and portable JSON transfer must include:
- circuit protection-device state - circuit protection-device state
- all new component relationships and sort positions - all new component relationships and sort positions
The clean pre-release baseline uses project-state schema version `1`. It Current project-state schema version `2` contains the complete supported
contains the complete supported structure above and is shared by logical structure above and the project building classification. It is shared by
snapshots, restore, Undo/Redo and portable JSON transfer. Payloads from earlier logical snapshots, restore, Undo/Redo and portable JSON transfer. Supported
development schemas are intentionally unsupported and rejected before any baseline version `1` is upgraded with the non-public default; payloads from
write. before that baseline are rejected before any write.
Migration `0000` creates the complete relational model on an empty database. Migration `0000` creates the complete relational model on an empty database.
New defaults apply when boards, groups or circuits are created; they never New defaults apply when boards, groups or circuits are created; they never
@@ -545,7 +545,7 @@ Implemented persistence:
one-to-one ownership boundaries one-to-one ownership boundaries
- a trigger-maintained circuit-list registry rejects normalized BMK collisions - a trigger-maintained circuit-list registry rejects normalized BMK collisions
across circuits and distribution-board components across circuits and distribution-board components
- snapshot schema version `1` includes group identity, distribution-board - snapshot schema version `2` includes group identity, distribution-board
components and both one-to-one protection collections components and both one-to-one protection collections
- capture, named and automatic snapshots, restore, Undo/Redo and portable JSON - capture, named and automatic snapshots, restore, Undo/Redo and portable JSON
transfers preserve the complete new relational state transfers preserve the complete new relational state
+1
View File
@@ -78,6 +78,7 @@ const requiredProjectColumns = [
"external_project_number", "external_project_number",
"building_owner", "building_owner",
"description", "description",
"is_public_building",
"enabled_distribution_board_supply_types", "enabled_distribution_board_supply_types",
]; ];
const projectColumns = new Set( const projectColumns = new Set(
@@ -0,0 +1 @@
ALTER TABLE `projects` ADD `is_public_building` integer DEFAULT false NOT NULL;
File diff suppressed because it is too large Load Diff
+7
View File
@@ -8,6 +8,13 @@
"when": 1785499119431, "when": 1785499119431,
"tag": "0000_whole_surge", "tag": "0000_whole_surge",
"breakpoints": true "breakpoints": true
},
{
"idx": 1,
"version": "6",
"when": 1785511894897,
"tag": "0001_add_public_building_setting",
"breakpoints": true
} }
] ]
} }
@@ -40,22 +40,33 @@ export class ProjectSettingsProjectCommandRepository
if (!current) { if (!current) {
throw new Error("Project not found."); throw new Error("Project not found.");
} }
const target = input.command.payload; const target: ProjectSettingsValues =
input.command.schemaVersion === 1
? {
...input.command.payload,
isPublicBuilding: current.isPublicBuilding,
}
: input.command.payload;
if (projectSettingsEqual(current, target)) { if (projectSettingsEqual(current, target)) {
throw new Error("Project settings did not change."); throw new Error("Project settings did not change.");
} }
const inverse = createProjectSettingsUpdateProjectCommand({ const inverseValues: ProjectSettingsValues = {
name: current.name, name: current.name,
internalProjectNumber: current.internalProjectNumber, internalProjectNumber: current.internalProjectNumber,
externalProjectNumber: current.externalProjectNumber, externalProjectNumber: current.externalProjectNumber,
buildingOwner: current.buildingOwner, buildingOwner: current.buildingOwner,
description: current.description, description: current.description,
isPublicBuilding: current.isPublicBuilding,
singlePhaseVoltageV: current.singlePhaseVoltageV, singlePhaseVoltageV: current.singlePhaseVoltageV,
threePhaseVoltageV: current.threePhaseVoltageV, threePhaseVoltageV: current.threePhaseVoltageV,
enabledDistributionBoardSupplyTypes: enabledDistributionBoardSupplyTypes:
current.enabledDistributionBoardSupplyTypes, current.enabledDistributionBoardSupplyTypes,
}); };
const inverse =
input.command.schemaVersion === 1
? createBaselineProjectSettingsUpdateCommand(inverseValues)
: createProjectSettingsUpdateProjectCommand(inverseValues);
assertDisabledSupplyTypesAreUnused(tx, input.projectId, target); assertDisabledSupplyTypesAreUnused(tx, input.projectId, target);
const updated = tx const updated = tx
.update(projects) .update(projects)
@@ -71,6 +82,26 @@ export class ProjectSettingsProjectCommandRepository
} }
} }
function createBaselineProjectSettingsUpdateCommand(
values: ProjectSettingsValues
) {
return {
schemaVersion: 1 as const,
type: "project.update-settings" as const,
payload: {
name: values.name,
internalProjectNumber: values.internalProjectNumber,
externalProjectNumber: values.externalProjectNumber,
buildingOwner: values.buildingOwner,
description: values.description,
singlePhaseVoltageV: values.singlePhaseVoltageV,
threePhaseVoltageV: values.threePhaseVoltageV,
enabledDistributionBoardSupplyTypes:
values.enabledDistributionBoardSupplyTypes,
},
};
}
function projectSettingsEqual( function projectSettingsEqual(
current: ProjectSettingsValues, current: ProjectSettingsValues,
target: ProjectSettingsValues target: ProjectSettingsValues
@@ -81,6 +112,7 @@ function projectSettingsEqual(
current.externalProjectNumber === target.externalProjectNumber && current.externalProjectNumber === target.externalProjectNumber &&
current.buildingOwner === target.buildingOwner && current.buildingOwner === target.buildingOwner &&
current.description === target.description && current.description === target.description &&
current.isPublicBuilding === target.isPublicBuilding &&
current.singlePhaseVoltageV === target.singlePhaseVoltageV && current.singlePhaseVoltageV === target.singlePhaseVoltageV &&
current.threePhaseVoltageV === target.threePhaseVoltageV current.threePhaseVoltageV === target.threePhaseVoltageV
&& sameSupplyTypes( && sameSupplyTypes(
@@ -208,6 +208,7 @@ function replaceProjectState(
externalProjectNumber: state.project.externalProjectNumber, externalProjectNumber: state.project.externalProjectNumber,
buildingOwner: state.project.buildingOwner, buildingOwner: state.project.buildingOwner,
description: state.project.description, description: state.project.description,
isPublicBuilding: state.project.isPublicBuilding,
singlePhaseVoltageV: state.project.singlePhaseVoltageV, singlePhaseVoltageV: state.project.singlePhaseVoltageV,
threePhaseVoltageV: state.project.threePhaseVoltageV, threePhaseVoltageV: state.project.threePhaseVoltageV,
enabledDistributionBoardSupplyTypes: enabledDistributionBoardSupplyTypes:
@@ -39,6 +39,7 @@ export function readProjectStateSnapshot(
externalProjectNumber: projects.externalProjectNumber, externalProjectNumber: projects.externalProjectNumber,
buildingOwner: projects.buildingOwner, buildingOwner: projects.buildingOwner,
description: projects.description, description: projects.description,
isPublicBuilding: projects.isPublicBuilding,
singlePhaseVoltageV: projects.singlePhaseVoltageV, singlePhaseVoltageV: projects.singlePhaseVoltageV,
threePhaseVoltageV: projects.threePhaseVoltageV, threePhaseVoltageV: projects.threePhaseVoltageV,
enabledDistributionBoardSupplyTypes: enabledDistributionBoardSupplyTypes:
@@ -233,6 +234,7 @@ export function readProjectStateSnapshot(
externalProjectNumber: project.externalProjectNumber, externalProjectNumber: project.externalProjectNumber,
buildingOwner: project.buildingOwner, buildingOwner: project.buildingOwner,
description: project.description, description: project.description,
isPublicBuilding: project.isPublicBuilding,
singlePhaseVoltageV: project.singlePhaseVoltageV, singlePhaseVoltageV: project.singlePhaseVoltageV,
threePhaseVoltageV: project.threePhaseVoltageV, threePhaseVoltageV: project.threePhaseVoltageV,
enabledDistributionBoardSupplyTypes: enabledDistributionBoardSupplyTypes:
@@ -25,6 +25,7 @@ export class ProjectRepository {
externalProjectNumber: input.externalProjectNumber ?? null, externalProjectNumber: input.externalProjectNumber ?? null,
buildingOwner: input.buildingOwner ?? null, buildingOwner: input.buildingOwner ?? null,
description: input.description ?? null, description: input.description ?? null,
isPublicBuilding: input.isPublicBuilding ?? false,
singlePhaseVoltageV: input.singlePhaseVoltageV ?? 230, singlePhaseVoltageV: input.singlePhaseVoltageV ?? 230,
threePhaseVoltageV: input.threePhaseVoltageV ?? 400, threePhaseVoltageV: input.threePhaseVoltageV ?? 400,
enabledDistributionBoardSupplyTypes: [ enabledDistributionBoardSupplyTypes: [
+3
View File
@@ -9,6 +9,9 @@ export const projects = sqliteTable("projects", {
externalProjectNumber: text("external_project_number"), externalProjectNumber: text("external_project_number"),
buildingOwner: text("building_owner"), buildingOwner: text("building_owner"),
description: text("description"), description: text("description"),
isPublicBuilding: integer("is_public_building", { mode: "boolean" })
.notNull()
.default(false),
singlePhaseVoltageV: integer("single_phase_voltage_v").notNull().default(230), singlePhaseVoltageV: integer("single_phase_voltage_v").notNull().default(230),
threePhaseVoltageV: integer("three_phase_voltage_v").notNull().default(400), threePhaseVoltageV: integer("three_phase_voltage_v").notNull().default(400),
enabledDistributionBoardSupplyTypes: text( enabledDistributionBoardSupplyTypes: text(
@@ -6,7 +6,8 @@ import {
export const projectSettingsUpdateCommandType = export const projectSettingsUpdateCommandType =
"project.update-settings" as const; "project.update-settings" as const;
export const projectSettingsUpdateCommandSchemaVersion = 1 as const; export const projectSettingsUpdateCommandSchemaVersion = 2 as const;
const previousProjectSettingsUpdateCommandSchemaVersion = 1 as const;
export interface ProjectSettingsValues { export interface ProjectSettingsValues {
name: string; name: string;
@@ -14,21 +15,37 @@ export interface ProjectSettingsValues {
externalProjectNumber: string | null; externalProjectNumber: string | null;
buildingOwner: string | null; buildingOwner: string | null;
description: string | null; description: string | null;
isPublicBuilding: boolean;
singlePhaseVoltageV: number; singlePhaseVoltageV: number;
threePhaseVoltageV: number; threePhaseVoltageV: number;
enabledDistributionBoardSupplyTypes: DistributionBoardSupplyType[]; enabledDistributionBoardSupplyTypes: DistributionBoardSupplyType[];
} }
export interface ProjectSettingsUpdateProjectCommand type PreviousProjectSettingsValues = Omit<
ProjectSettingsValues,
"isPublicBuilding"
>;
export interface CurrentProjectSettingsUpdateProjectCommand
extends SerializedProjectCommand<ProjectSettingsValues> { extends SerializedProjectCommand<ProjectSettingsValues> {
schemaVersion: typeof projectSettingsUpdateCommandSchemaVersion; schemaVersion: typeof projectSettingsUpdateCommandSchemaVersion;
type: typeof projectSettingsUpdateCommandType; type: typeof projectSettingsUpdateCommandType;
} }
interface PreviousProjectSettingsUpdateProjectCommand
extends SerializedProjectCommand<PreviousProjectSettingsValues> {
schemaVersion: typeof previousProjectSettingsUpdateCommandSchemaVersion;
type: typeof projectSettingsUpdateCommandType;
}
export type ProjectSettingsUpdateProjectCommand =
| CurrentProjectSettingsUpdateProjectCommand
| PreviousProjectSettingsUpdateProjectCommand;
export function createProjectSettingsUpdateProjectCommand( export function createProjectSettingsUpdateProjectCommand(
values: ProjectSettingsValues values: ProjectSettingsValues
): ProjectSettingsUpdateProjectCommand { ): CurrentProjectSettingsUpdateProjectCommand {
const command: ProjectSettingsUpdateProjectCommand = { const command: CurrentProjectSettingsUpdateProjectCommand = {
schemaVersion: projectSettingsUpdateCommandSchemaVersion, schemaVersion: projectSettingsUpdateCommandSchemaVersion,
type: projectSettingsUpdateCommandType, type: projectSettingsUpdateCommandType,
payload: { ...values }, payload: { ...values },
@@ -42,7 +59,8 @@ export function assertProjectSettingsUpdateProjectCommand(
): asserts command is ProjectSettingsUpdateProjectCommand { ): asserts command is ProjectSettingsUpdateProjectCommand {
if ( if (
command.type !== projectSettingsUpdateCommandType || command.type !== projectSettingsUpdateCommandType ||
command.schemaVersion !== projectSettingsUpdateCommandSchemaVersion (command.schemaVersion !== projectSettingsUpdateCommandSchemaVersion &&
command.schemaVersion !== previousProjectSettingsUpdateCommandSchemaVersion)
) { ) {
throw new Error("Unsupported project settings update command."); throw new Error("Unsupported project settings update command.");
} }
@@ -58,8 +76,7 @@ export function assertProjectSettingsUpdateProjectCommand(
!isNullableTrimmedString(command.payload.buildingOwner, 200) || !isNullableTrimmedString(command.payload.buildingOwner, 200) ||
!isNullableTrimmedString(command.payload.description, 2000) || !isNullableTrimmedString(command.payload.description, 2000) ||
!isPositiveFiniteNumber(command.payload.singlePhaseVoltageV) || !isPositiveFiniteNumber(command.payload.singlePhaseVoltageV) ||
!isPositiveFiniteNumber(command.payload.threePhaseVoltageV) || !isPositiveFiniteNumber(command.payload.threePhaseVoltageV)
Object.keys(command.payload).length !== 8
) { ) {
throw new Error( throw new Error(
"Project settings update command contains invalid values." "Project settings update command contains invalid values."
@@ -74,6 +91,18 @@ export function assertProjectSettingsUpdateProjectCommand(
"Project settings update command contains invalid supply types." "Project settings update command contains invalid supply types."
); );
} }
const isPreviousSchema =
command.schemaVersion === previousProjectSettingsUpdateCommandSchemaVersion;
if (
(isPreviousSchema && Object.keys(command.payload).length !== 8) ||
(!isPreviousSchema &&
(typeof command.payload.isPublicBuilding !== "boolean" ||
Object.keys(command.payload).length !== 9))
) {
throw new Error(
"Project settings update command contains invalid values."
);
}
} }
function isValidSupplyTypes( function isValidSupplyTypes(
@@ -19,13 +19,14 @@ import {
resolveProjectVoltage, resolveProjectVoltage,
} from "../services/project-voltage.service.js"; } from "../services/project-voltage.service.js";
export const projectStateSnapshotSchemaVersion = 1 as const; export const projectStateSnapshotSchemaVersion = 2 as const;
const previousProjectStateSnapshotSchemaVersion = 1 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();
const finiteNumberSchema = z.number().finite(); const finiteNumberSchema = z.number().finite();
const projectSchema = z const previousProjectSchema = z
.object({ .object({
id: idSchema, id: idSchema,
name: z.string().trim().min(1), name: z.string().trim().min(1),
@@ -42,6 +43,10 @@ const projectSchema = z
}) })
.strict(); .strict();
const projectSchema = previousProjectSchema
.extend({ isPublicBuilding: z.boolean() })
.strict();
const distributionBoardSchema = z const distributionBoardSchema = z
.object({ .object({
id: idSchema, id: idSchema,
@@ -267,6 +272,15 @@ export const projectStateSnapshotSchema = z
}) })
.strict(); .strict();
const previousProjectStateSnapshotSchema = z
.object({
schemaVersion: z.literal(previousProjectStateSnapshotSchemaVersion),
project: previousProjectSchema,
distributionBoards: z.array(distributionBoardSchema),
...projectStateSnapshotContents,
})
.strict();
export type ProjectStateSnapshot = z.infer< export type ProjectStateSnapshot = z.infer<
typeof projectStateSnapshotSchema typeof projectStateSnapshotSchema
>; >;
@@ -274,12 +288,35 @@ export type ProjectStateSnapshot = z.infer<
export function parseProjectStateSnapshot( export function parseProjectStateSnapshot(
value: unknown value: unknown
): ProjectStateSnapshot { ): ProjectStateSnapshot {
const parsedSnapshot = projectStateSnapshotSchema.parse(value); const parsedSnapshot = projectStateSnapshotSchema.parse(
upgradePreviousProjectStateSnapshot(value)
);
const snapshot = normalizeSnapshotPhaseTypes(parsedSnapshot); const snapshot = normalizeSnapshotPhaseTypes(parsedSnapshot);
assertProjectStateSnapshotRelations(snapshot); assertProjectStateSnapshotRelations(snapshot);
return snapshot; return snapshot;
} }
function upgradePreviousProjectStateSnapshot(value: unknown): unknown {
if (
value === null ||
typeof value !== "object" ||
Array.isArray(value) ||
(value as { schemaVersion?: unknown }).schemaVersion !==
previousProjectStateSnapshotSchemaVersion
) {
return value;
}
const previous = previousProjectStateSnapshotSchema.parse(value);
return {
...previous,
schemaVersion: projectStateSnapshotSchemaVersion,
project: {
...previous.project,
isPublicBuilding: false,
},
};
}
function normalizeSnapshotPhaseTypes( function normalizeSnapshotPhaseTypes(
snapshot: ProjectStateSnapshot snapshot: ProjectStateSnapshot
): ProjectStateSnapshot { ): ProjectStateSnapshot {
@@ -14,6 +14,7 @@ export interface ProjectSettingsInput {
externalProjectNumber: string | null; externalProjectNumber: string | null;
buildingOwner: string | null; buildingOwner: string | null;
description: string | null; description: string | null;
isPublicBuilding: boolean;
singlePhaseVoltageV: number; singlePhaseVoltageV: number;
threePhaseVoltageV: number; threePhaseVoltageV: number;
enabledDistributionBoardSupplyTypes: DistributionBoardSupplyType[]; enabledDistributionBoardSupplyTypes: DistributionBoardSupplyType[];
@@ -54,6 +55,9 @@ export function ProjectSettingsModal({
const [description, setDescription] = useState( const [description, setDescription] = useState(
project.description ?? "" project.description ?? ""
); );
const [isPublicBuilding, setIsPublicBuilding] = useState(
project.isPublicBuilding
);
const [singlePhaseVoltageV, setSinglePhaseVoltageV] = useState( const [singlePhaseVoltageV, setSinglePhaseVoltageV] = useState(
String(project.singlePhaseVoltageV) String(project.singlePhaseVoltageV)
); );
@@ -82,6 +86,7 @@ export function ProjectSettingsModal({
externalProjectNumber: toNullableString(externalProjectNumber), externalProjectNumber: toNullableString(externalProjectNumber),
buildingOwner: toNullableString(buildingOwner), buildingOwner: toNullableString(buildingOwner),
description: toNullableString(description), description: toNullableString(description),
isPublicBuilding,
singlePhaseVoltageV: Number(singlePhaseVoltageV), singlePhaseVoltageV: Number(singlePhaseVoltageV),
threePhaseVoltageV: Number(threePhaseVoltageV), threePhaseVoltageV: Number(threePhaseVoltageV),
enabledDistributionBoardSupplyTypes, enabledDistributionBoardSupplyTypes,
@@ -269,6 +274,27 @@ export function ProjectSettingsModal({
value={description} value={description}
/> />
</div> </div>
<div className="col-12">
<label className="form-check">
<input
checked={isPublicBuilding}
className="form-check-input"
id="public-building"
onChange={(event) =>
setIsPublicBuilding(event.target.checked)
}
type="checkbox"
/>
<span className="form-check-label">
Öffentliches Gebäude
</span>
</label>
<div className="form-text ms-4">
Wird bei der späteren Leitungsauslegung berücksichtigt,
insbesondere bei der Auswahl halogenfreier Kabel und
Leitungen.
</div>
</div>
<div className="col-12 col-md-6"> <div className="col-12 col-md-6">
<label className="form-label" htmlFor="single-phase-voltage"> <label className="form-label" htmlFor="single-phase-voltage">
Standardspannung 1-phasig [V] Standardspannung 1-phasig [V]
+1
View File
@@ -18,6 +18,7 @@ export interface ProjectDto {
externalProjectNumber: string | null; externalProjectNumber: string | null;
buildingOwner: string | null; buildingOwner: string | null;
description: string | null; description: string | null;
isPublicBuilding: boolean;
singlePhaseVoltageV: number; singlePhaseVoltageV: number;
threePhaseVoltageV: number; threePhaseVoltageV: number;
enabledDistributionBoardSupplyTypes: DistributionBoardSupplyType[]; enabledDistributionBoardSupplyTypes: DistributionBoardSupplyType[];
+1
View File
@@ -245,6 +245,7 @@ export function updateProjectSettings(
externalProjectNumber: string | null; externalProjectNumber: string | null;
buildingOwner: string | null; buildingOwner: string | null;
description: string | null; description: string | null;
isPublicBuilding: boolean;
singlePhaseVoltageV: number; singlePhaseVoltageV: number;
threePhaseVoltageV: number; threePhaseVoltageV: number;
enabledDistributionBoardSupplyTypes: ProjectDto["enabledDistributionBoardSupplyTypes"]; enabledDistributionBoardSupplyTypes: ProjectDto["enabledDistributionBoardSupplyTypes"];
@@ -8,6 +8,7 @@ export const createProjectSchema = z.object({
externalProjectNumber: z.string().trim().max(100).optional(), externalProjectNumber: z.string().trim().max(100).optional(),
buildingOwner: z.string().trim().max(200).optional(), buildingOwner: z.string().trim().max(200).optional(),
description: z.string().trim().max(2000).optional(), description: z.string().trim().max(2000).optional(),
isPublicBuilding: z.boolean().optional(),
singlePhaseVoltageV: z.number().positive().optional(), singlePhaseVoltageV: z.number().positive().optional(),
threePhaseVoltageV: z.number().positive().optional(), threePhaseVoltageV: z.number().positive().optional(),
}); });
@@ -20,6 +21,7 @@ export const updateProjectSettingsSchema = z
externalProjectNumber: z.string().trim().max(100).nullable(), externalProjectNumber: z.string().trim().max(100).nullable(),
buildingOwner: z.string().trim().max(200).nullable(), buildingOwner: z.string().trim().max(200).nullable(),
description: z.string().trim().max(2000).nullable(), description: z.string().trim().max(2000).nullable(),
isPublicBuilding: z.boolean(),
singlePhaseVoltageV: z.number().positive(), singlePhaseVoltageV: z.number().positive(),
threePhaseVoltageV: z.number().positive(), threePhaseVoltageV: z.number().positive(),
enabledDistributionBoardSupplyTypes: z enabledDistributionBoardSupplyTypes: z
+5
View File
@@ -1057,6 +1057,7 @@ describe("project command service", () => {
externalProjectNumber: null, externalProjectNumber: null,
buildingOwner: null, buildingOwner: null,
description: null, description: null,
isPublicBuilding: true,
singlePhaseVoltageV: 240, singlePhaseVoltageV: 240,
threePhaseVoltageV: 415, threePhaseVoltageV: 415,
enabledDistributionBoardSupplyTypes: [ enabledDistributionBoardSupplyTypes: [
@@ -1073,6 +1074,7 @@ describe("project command service", () => {
assert.deepEqual( assert.deepEqual(
context.db context.db
.select({ .select({
isPublicBuilding: projects.isPublicBuilding,
singlePhaseVoltageV: projects.singlePhaseVoltageV, singlePhaseVoltageV: projects.singlePhaseVoltageV,
threePhaseVoltageV: projects.threePhaseVoltageV, threePhaseVoltageV: projects.threePhaseVoltageV,
}) })
@@ -1080,6 +1082,7 @@ describe("project command service", () => {
.where(eq(projects.id, "project-1")) .where(eq(projects.id, "project-1"))
.get(), .get(),
{ {
isPublicBuilding: true,
singlePhaseVoltageV: 240, singlePhaseVoltageV: 240,
threePhaseVoltageV: 415, threePhaseVoltageV: 415,
} }
@@ -1093,6 +1096,7 @@ describe("project command service", () => {
assert.deepEqual( assert.deepEqual(
context.db context.db
.select({ .select({
isPublicBuilding: projects.isPublicBuilding,
singlePhaseVoltageV: projects.singlePhaseVoltageV, singlePhaseVoltageV: projects.singlePhaseVoltageV,
threePhaseVoltageV: projects.threePhaseVoltageV, threePhaseVoltageV: projects.threePhaseVoltageV,
}) })
@@ -1100,6 +1104,7 @@ describe("project command service", () => {
.where(eq(projects.id, "project-1")) .where(eq(projects.id, "project-1"))
.get(), .get(),
{ {
isPublicBuilding: false,
singlePhaseVoltageV: 230, singlePhaseVoltageV: 230,
threePhaseVoltageV: 400, threePhaseVoltageV: 400,
} }
@@ -47,6 +47,7 @@ function getSettings(context: DatabaseContext) {
externalProjectNumber: projects.externalProjectNumber, externalProjectNumber: projects.externalProjectNumber,
buildingOwner: projects.buildingOwner, buildingOwner: projects.buildingOwner,
description: projects.description, description: projects.description,
isPublicBuilding: projects.isPublicBuilding,
singlePhaseVoltageV: projects.singlePhaseVoltageV, singlePhaseVoltageV: projects.singlePhaseVoltageV,
threePhaseVoltageV: projects.threePhaseVoltageV, threePhaseVoltageV: projects.threePhaseVoltageV,
enabledDistributionBoardSupplyTypes: enabledDistributionBoardSupplyTypes:
@@ -69,6 +70,7 @@ function settings(
externalProjectNumber: null, externalProjectNumber: null,
buildingOwner: null, buildingOwner: null,
description: null, description: null,
isPublicBuilding: false,
singlePhaseVoltageV: 230, singlePhaseVoltageV: 230,
threePhaseVoltageV: 400, threePhaseVoltageV: 400,
enabledDistributionBoardSupplyTypes: [ enabledDistributionBoardSupplyTypes: [
@@ -204,6 +206,7 @@ describe("project settings project-command repository", () => {
externalProjectNumber: null, externalProjectNumber: null,
buildingOwner: null, buildingOwner: null,
description: null, description: null,
isPublicBuilding: false,
singlePhaseVoltageV: 240, singlePhaseVoltageV: 240,
threePhaseVoltageV: 415, threePhaseVoltageV: 415,
enabledDistributionBoardSupplyTypes: [ enabledDistributionBoardSupplyTypes: [
@@ -231,6 +234,7 @@ describe("project settings project-command repository", () => {
...settings(), ...settings(),
name: "Neuer Projektname", name: "Neuer Projektname",
buildingOwner: "Beispiel Bau GmbH", buildingOwner: "Beispiel Bau GmbH",
isPublicBuilding: true,
singlePhaseVoltageV: 240, singlePhaseVoltageV: 240,
threePhaseVoltageV: 415, threePhaseVoltageV: 415,
enabledDistributionBoardSupplyTypes: [ enabledDistributionBoardSupplyTypes: [
@@ -252,6 +256,7 @@ describe("project settings project-command repository", () => {
...settings({ ...settings({
name: "Neuer Projektname", name: "Neuer Projektname",
buildingOwner: "Beispiel Bau GmbH", buildingOwner: "Beispiel Bau GmbH",
isPublicBuilding: true,
singlePhaseVoltageV: 240, singlePhaseVoltageV: 240,
threePhaseVoltageV: 415, threePhaseVoltageV: 415,
}), }),
@@ -273,6 +278,7 @@ describe("project settings project-command repository", () => {
externalProjectNumber: null, externalProjectNumber: null,
buildingOwner: null, buildingOwner: null,
description: null, description: null,
isPublicBuilding: false,
singlePhaseVoltageV: 230, singlePhaseVoltageV: 230,
threePhaseVoltageV: 400, threePhaseVoltageV: 400,
enabledDistributionBoardSupplyTypes: [ enabledDistributionBoardSupplyTypes: [
@@ -300,6 +306,7 @@ describe("project settings project-command repository", () => {
externalProjectNumber: null, externalProjectNumber: null,
buildingOwner: null, buildingOwner: null,
description: null, description: null,
isPublicBuilding: false,
singlePhaseVoltageV: 230, singlePhaseVoltageV: 230,
threePhaseVoltageV: 400, threePhaseVoltageV: 400,
enabledDistributionBoardSupplyTypes: [ enabledDistributionBoardSupplyTypes: [
@@ -338,6 +345,7 @@ describe("project settings project-command repository", () => {
externalProjectNumber: null, externalProjectNumber: null,
buildingOwner: "Beispiel Bau GmbH", buildingOwner: "Beispiel Bau GmbH",
description: null, description: null,
isPublicBuilding: true,
singlePhaseVoltageV: 240, singlePhaseVoltageV: 240,
threePhaseVoltageV: 415, threePhaseVoltageV: 415,
enabledDistributionBoardSupplyTypes: [ enabledDistributionBoardSupplyTypes: [
@@ -365,6 +373,66 @@ describe("project settings project-command repository", () => {
} }
}); });
it("preserves the building classification when applying a baseline command", () => {
const context = createTestDatabase();
try {
context.db
.update(projects)
.set({ isPublicBuilding: true })
.where(eq(projects.id, "project-1"))
.run();
const repository = new ProjectSettingsProjectCommandRepository(
context.db
);
const forward = repository.executeUpdate({
projectId: "project-1",
expectedRevision: 0,
source: "user",
command: {
schemaVersion: 1,
type: "project.update-settings",
payload: {
name: "Stand aus Baseline-Command",
internalProjectNumber: "INT-1",
externalProjectNumber: null,
buildingOwner: null,
description: null,
singlePhaseVoltageV: 230,
threePhaseVoltageV: 400,
enabledDistributionBoardSupplyTypes: [
"AV",
"SV",
"EV",
"USV",
"MSR",
"SiBe",
],
},
},
});
assert.equal(getSettings(context)?.name, "Stand aus Baseline-Command");
assert.equal(getSettings(context)?.isPublicBuilding, true);
assert.equal(forward.inverse.schemaVersion, 1);
const history = new ProjectHistoryRepository(context.db);
const undoStep = history.getNextCommand("project-1", "undo");
assert.ok(undoStep);
repository.executeUpdate({
projectId: "project-1",
expectedRevision: 1,
source: "undo",
historyTargetChangeSetId: undoStep.changeSetId,
command: forward.inverse,
});
assert.equal(getSettings(context)?.name, "Testprojekt");
assert.equal(getSettings(context)?.isPublicBuilding, true);
} finally {
context.close();
}
});
it("persists the project supply-type selection and rejects disabling an in-use type", () => { it("persists the project supply-type selection and rejects disabling an in-use type", () => {
const context = createTestDatabase(); const context = createTestDatabase();
try { try {
+13 -1
View File
@@ -18,6 +18,7 @@ function minimalSnapshot() {
externalProjectNumber: null, externalProjectNumber: null,
buildingOwner: null, buildingOwner: null,
description: null, description: null,
isPublicBuilding: true,
singlePhaseVoltageV: 230, singlePhaseVoltageV: 230,
threePhaseVoltageV: 400, threePhaseVoltageV: 400,
enabledDistributionBoardSupplyTypes: [ enabledDistributionBoardSupplyTypes: [
@@ -142,7 +143,18 @@ describe("project state snapshot model", () => {
); );
}); });
it("rejects pre-baseline snapshot shapes", () => { it("upgrades the supported baseline snapshot with a safe building default", () => {
const baseline = structuredClone(minimalSnapshot());
baseline.schemaVersion = 1 as typeof baseline.schemaVersion;
delete (baseline.project as Record<string, unknown>).isPublicBuilding;
const upgraded = parseProjectStateSnapshot(baseline);
assert.equal(upgraded.schemaVersion, projectStateSnapshotSchemaVersion);
assert.equal(upgraded.project.isPublicBuilding, false);
});
it("rejects malformed baseline and unknown snapshot shapes", () => {
const outdated = structuredClone(minimalSnapshot()); const outdated = structuredClone(minimalSnapshot());
delete (outdated.project as Record<string, unknown>).description; delete (outdated.project as Record<string, unknown>).description;
assert.throws(() => parseProjectStateSnapshot(outdated)); assert.throws(() => parseProjectStateSnapshot(outdated));
+2
View File
@@ -247,6 +247,7 @@ describe("project settings presentation", () => {
externalProjectNumber: null, externalProjectNumber: null,
buildingOwner: null, buildingOwner: null,
description: null, description: null,
isPublicBuilding: true,
singlePhaseVoltageV: 230, singlePhaseVoltageV: 230,
threePhaseVoltageV: 400, threePhaseVoltageV: 400,
enabledDistributionBoardSupplyTypes: [ enabledDistributionBoardSupplyTypes: [
@@ -262,6 +263,7 @@ describe("project settings presentation", () => {
assert.match(markup, /MSR/); assert.match(markup, /MSR/);
assert.match(markup, /SiBe/); assert.match(markup, /SiBe/);
assert.match(markup, /in Verwendung/); assert.match(markup, /in Verwendung/);
assert.match(markup, /Öffentliches Gebäude/);
}); });
}); });