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
@@ -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,
"tag": "0000_whole_surge",
"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) {
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)) {
throw new Error("Project settings did not change.");
}
const inverse = createProjectSettingsUpdateProjectCommand({
const inverseValues: ProjectSettingsValues = {
name: current.name,
internalProjectNumber: current.internalProjectNumber,
externalProjectNumber: current.externalProjectNumber,
buildingOwner: current.buildingOwner,
description: current.description,
isPublicBuilding: current.isPublicBuilding,
singlePhaseVoltageV: current.singlePhaseVoltageV,
threePhaseVoltageV: current.threePhaseVoltageV,
enabledDistributionBoardSupplyTypes:
current.enabledDistributionBoardSupplyTypes,
});
};
const inverse =
input.command.schemaVersion === 1
? createBaselineProjectSettingsUpdateCommand(inverseValues)
: createProjectSettingsUpdateProjectCommand(inverseValues);
assertDisabledSupplyTypesAreUnused(tx, input.projectId, target);
const updated = tx
.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(
current: ProjectSettingsValues,
target: ProjectSettingsValues
@@ -81,6 +112,7 @@ function projectSettingsEqual(
current.externalProjectNumber === target.externalProjectNumber &&
current.buildingOwner === target.buildingOwner &&
current.description === target.description &&
current.isPublicBuilding === target.isPublicBuilding &&
current.singlePhaseVoltageV === target.singlePhaseVoltageV &&
current.threePhaseVoltageV === target.threePhaseVoltageV
&& sameSupplyTypes(
@@ -208,6 +208,7 @@ function replaceProjectState(
externalProjectNumber: state.project.externalProjectNumber,
buildingOwner: state.project.buildingOwner,
description: state.project.description,
isPublicBuilding: state.project.isPublicBuilding,
singlePhaseVoltageV: state.project.singlePhaseVoltageV,
threePhaseVoltageV: state.project.threePhaseVoltageV,
enabledDistributionBoardSupplyTypes:
@@ -39,6 +39,7 @@ export function readProjectStateSnapshot(
externalProjectNumber: projects.externalProjectNumber,
buildingOwner: projects.buildingOwner,
description: projects.description,
isPublicBuilding: projects.isPublicBuilding,
singlePhaseVoltageV: projects.singlePhaseVoltageV,
threePhaseVoltageV: projects.threePhaseVoltageV,
enabledDistributionBoardSupplyTypes:
@@ -233,6 +234,7 @@ export function readProjectStateSnapshot(
externalProjectNumber: project.externalProjectNumber,
buildingOwner: project.buildingOwner,
description: project.description,
isPublicBuilding: project.isPublicBuilding,
singlePhaseVoltageV: project.singlePhaseVoltageV,
threePhaseVoltageV: project.threePhaseVoltageV,
enabledDistributionBoardSupplyTypes:
@@ -25,6 +25,7 @@ export class ProjectRepository {
externalProjectNumber: input.externalProjectNumber ?? null,
buildingOwner: input.buildingOwner ?? null,
description: input.description ?? null,
isPublicBuilding: input.isPublicBuilding ?? false,
singlePhaseVoltageV: input.singlePhaseVoltageV ?? 230,
threePhaseVoltageV: input.threePhaseVoltageV ?? 400,
enabledDistributionBoardSupplyTypes: [
+3
View File
@@ -9,6 +9,9 @@ export const projects = sqliteTable("projects", {
externalProjectNumber: text("external_project_number"),
buildingOwner: text("building_owner"),
description: text("description"),
isPublicBuilding: integer("is_public_building", { mode: "boolean" })
.notNull()
.default(false),
singlePhaseVoltageV: integer("single_phase_voltage_v").notNull().default(230),
threePhaseVoltageV: integer("three_phase_voltage_v").notNull().default(400),
enabledDistributionBoardSupplyTypes: text(
@@ -6,7 +6,8 @@ import {
export const projectSettingsUpdateCommandType =
"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 {
name: string;
@@ -14,21 +15,37 @@ export interface ProjectSettingsValues {
externalProjectNumber: string | null;
buildingOwner: string | null;
description: string | null;
isPublicBuilding: boolean;
singlePhaseVoltageV: number;
threePhaseVoltageV: number;
enabledDistributionBoardSupplyTypes: DistributionBoardSupplyType[];
}
export interface ProjectSettingsUpdateProjectCommand
type PreviousProjectSettingsValues = Omit<
ProjectSettingsValues,
"isPublicBuilding"
>;
export interface CurrentProjectSettingsUpdateProjectCommand
extends SerializedProjectCommand<ProjectSettingsValues> {
schemaVersion: typeof projectSettingsUpdateCommandSchemaVersion;
type: typeof projectSettingsUpdateCommandType;
}
interface PreviousProjectSettingsUpdateProjectCommand
extends SerializedProjectCommand<PreviousProjectSettingsValues> {
schemaVersion: typeof previousProjectSettingsUpdateCommandSchemaVersion;
type: typeof projectSettingsUpdateCommandType;
}
export type ProjectSettingsUpdateProjectCommand =
| CurrentProjectSettingsUpdateProjectCommand
| PreviousProjectSettingsUpdateProjectCommand;
export function createProjectSettingsUpdateProjectCommand(
values: ProjectSettingsValues
): ProjectSettingsUpdateProjectCommand {
const command: ProjectSettingsUpdateProjectCommand = {
): CurrentProjectSettingsUpdateProjectCommand {
const command: CurrentProjectSettingsUpdateProjectCommand = {
schemaVersion: projectSettingsUpdateCommandSchemaVersion,
type: projectSettingsUpdateCommandType,
payload: { ...values },
@@ -42,7 +59,8 @@ export function assertProjectSettingsUpdateProjectCommand(
): asserts command is ProjectSettingsUpdateProjectCommand {
if (
command.type !== projectSettingsUpdateCommandType ||
command.schemaVersion !== projectSettingsUpdateCommandSchemaVersion
(command.schemaVersion !== projectSettingsUpdateCommandSchemaVersion &&
command.schemaVersion !== previousProjectSettingsUpdateCommandSchemaVersion)
) {
throw new Error("Unsupported project settings update command.");
}
@@ -58,8 +76,7 @@ export function assertProjectSettingsUpdateProjectCommand(
!isNullableTrimmedString(command.payload.buildingOwner, 200) ||
!isNullableTrimmedString(command.payload.description, 2000) ||
!isPositiveFiniteNumber(command.payload.singlePhaseVoltageV) ||
!isPositiveFiniteNumber(command.payload.threePhaseVoltageV) ||
Object.keys(command.payload).length !== 8
!isPositiveFiniteNumber(command.payload.threePhaseVoltageV)
) {
throw new Error(
"Project settings update command contains invalid values."
@@ -74,6 +91,18 @@ export function assertProjectSettingsUpdateProjectCommand(
"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(
@@ -19,13 +19,14 @@ import {
resolveProjectVoltage,
} 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 nullableStringSchema = z.string().nullable();
const finiteNumberSchema = z.number().finite();
const projectSchema = z
const previousProjectSchema = z
.object({
id: idSchema,
name: z.string().trim().min(1),
@@ -42,6 +43,10 @@ const projectSchema = z
})
.strict();
const projectSchema = previousProjectSchema
.extend({ isPublicBuilding: z.boolean() })
.strict();
const distributionBoardSchema = z
.object({
id: idSchema,
@@ -267,6 +272,15 @@ export const projectStateSnapshotSchema = z
})
.strict();
const previousProjectStateSnapshotSchema = z
.object({
schemaVersion: z.literal(previousProjectStateSnapshotSchemaVersion),
project: previousProjectSchema,
distributionBoards: z.array(distributionBoardSchema),
...projectStateSnapshotContents,
})
.strict();
export type ProjectStateSnapshot = z.infer<
typeof projectStateSnapshotSchema
>;
@@ -274,12 +288,35 @@ export type ProjectStateSnapshot = z.infer<
export function parseProjectStateSnapshot(
value: unknown
): ProjectStateSnapshot {
const parsedSnapshot = projectStateSnapshotSchema.parse(value);
const parsedSnapshot = projectStateSnapshotSchema.parse(
upgradePreviousProjectStateSnapshot(value)
);
const snapshot = normalizeSnapshotPhaseTypes(parsedSnapshot);
assertProjectStateSnapshotRelations(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(
snapshot: ProjectStateSnapshot
): ProjectStateSnapshot {
@@ -14,6 +14,7 @@ export interface ProjectSettingsInput {
externalProjectNumber: string | null;
buildingOwner: string | null;
description: string | null;
isPublicBuilding: boolean;
singlePhaseVoltageV: number;
threePhaseVoltageV: number;
enabledDistributionBoardSupplyTypes: DistributionBoardSupplyType[];
@@ -54,6 +55,9 @@ export function ProjectSettingsModal({
const [description, setDescription] = useState(
project.description ?? ""
);
const [isPublicBuilding, setIsPublicBuilding] = useState(
project.isPublicBuilding
);
const [singlePhaseVoltageV, setSinglePhaseVoltageV] = useState(
String(project.singlePhaseVoltageV)
);
@@ -82,6 +86,7 @@ export function ProjectSettingsModal({
externalProjectNumber: toNullableString(externalProjectNumber),
buildingOwner: toNullableString(buildingOwner),
description: toNullableString(description),
isPublicBuilding,
singlePhaseVoltageV: Number(singlePhaseVoltageV),
threePhaseVoltageV: Number(threePhaseVoltageV),
enabledDistributionBoardSupplyTypes,
@@ -269,6 +274,27 @@ export function ProjectSettingsModal({
value={description}
/>
</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">
<label className="form-label" htmlFor="single-phase-voltage">
Standardspannung 1-phasig [V]
+1
View File
@@ -18,6 +18,7 @@ export interface ProjectDto {
externalProjectNumber: string | null;
buildingOwner: string | null;
description: string | null;
isPublicBuilding: boolean;
singlePhaseVoltageV: number;
threePhaseVoltageV: number;
enabledDistributionBoardSupplyTypes: DistributionBoardSupplyType[];
+1
View File
@@ -245,6 +245,7 @@ export function updateProjectSettings(
externalProjectNumber: string | null;
buildingOwner: string | null;
description: string | null;
isPublicBuilding: boolean;
singlePhaseVoltageV: number;
threePhaseVoltageV: number;
enabledDistributionBoardSupplyTypes: ProjectDto["enabledDistributionBoardSupplyTypes"];
@@ -8,6 +8,7 @@ export const createProjectSchema = z.object({
externalProjectNumber: z.string().trim().max(100).optional(),
buildingOwner: z.string().trim().max(200).optional(),
description: z.string().trim().max(2000).optional(),
isPublicBuilding: z.boolean().optional(),
singlePhaseVoltageV: z.number().positive().optional(),
threePhaseVoltageV: z.number().positive().optional(),
});
@@ -20,6 +21,7 @@ export const updateProjectSettingsSchema = z
externalProjectNumber: z.string().trim().max(100).nullable(),
buildingOwner: z.string().trim().max(200).nullable(),
description: z.string().trim().max(2000).nullable(),
isPublicBuilding: z.boolean(),
singlePhaseVoltageV: z.number().positive(),
threePhaseVoltageV: z.number().positive(),
enabledDistributionBoardSupplyTypes: z