import type { SerializedProjectCommand } from "./project-command.model.js"; import { distributionBoardSupplyTypes, type DistributionBoardSupplyType, } from "../../shared/constants/distribution-board.js"; export const projectSettingsUpdateCommandType = "project.update-settings" as const; export const projectSettingsUpdateCommandSchemaVersion = 2 as const; const previousProjectSettingsUpdateCommandSchemaVersion = 1 as const; export interface ProjectSettingsValues { name: string; internalProjectNumber: string | null; externalProjectNumber: string | null; buildingOwner: string | null; description: string | null; isPublicBuilding: boolean; singlePhaseVoltageV: number; threePhaseVoltageV: number; enabledDistributionBoardSupplyTypes: DistributionBoardSupplyType[]; } type PreviousProjectSettingsValues = Omit< ProjectSettingsValues, "isPublicBuilding" >; export interface CurrentProjectSettingsUpdateProjectCommand extends SerializedProjectCommand { schemaVersion: typeof projectSettingsUpdateCommandSchemaVersion; type: typeof projectSettingsUpdateCommandType; } interface PreviousProjectSettingsUpdateProjectCommand extends SerializedProjectCommand { schemaVersion: typeof previousProjectSettingsUpdateCommandSchemaVersion; type: typeof projectSettingsUpdateCommandType; } export type ProjectSettingsUpdateProjectCommand = | CurrentProjectSettingsUpdateProjectCommand | PreviousProjectSettingsUpdateProjectCommand; export function createProjectSettingsUpdateProjectCommand( values: ProjectSettingsValues ): CurrentProjectSettingsUpdateProjectCommand { const command: CurrentProjectSettingsUpdateProjectCommand = { schemaVersion: projectSettingsUpdateCommandSchemaVersion, type: projectSettingsUpdateCommandType, payload: { ...values }, }; assertProjectSettingsUpdateProjectCommand(command); return command; } export function assertProjectSettingsUpdateProjectCommand( command: SerializedProjectCommand ): asserts command is ProjectSettingsUpdateProjectCommand { if ( command.type !== projectSettingsUpdateCommandType || (command.schemaVersion !== projectSettingsUpdateCommandSchemaVersion && command.schemaVersion !== previousProjectSettingsUpdateCommandSchemaVersion) ) { throw new Error("Unsupported project settings update command."); } if (!isPlainObject(command.payload)) { throw new Error( "Project settings update command contains invalid values." ); } if ( !isTrimmedString(command.payload.name, 1, 200) || !isNullableTrimmedString(command.payload.internalProjectNumber, 100) || !isNullableTrimmedString(command.payload.externalProjectNumber, 100) || !isNullableTrimmedString(command.payload.buildingOwner, 200) || !isNullableTrimmedString(command.payload.description, 2000) || !isPositiveFiniteNumber(command.payload.singlePhaseVoltageV) || !isPositiveFiniteNumber(command.payload.threePhaseVoltageV) ) { throw new Error( "Project settings update command contains invalid values." ); } if ( !isValidSupplyTypes( command.payload.enabledDistributionBoardSupplyTypes ) ) { throw new Error( "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( value: unknown ): value is DistributionBoardSupplyType[] { return ( Array.isArray(value) && value.length > 0 && new Set(value).size === value.length && value.every( (entry) => typeof entry === "string" && distributionBoardSupplyTypes.includes( entry as DistributionBoardSupplyType ) ) ); } function isPositiveFiniteNumber(value: unknown): value is number { return ( typeof value === "number" && Number.isFinite(value) && value > 0 ); } function isPlainObject(value: unknown): value is Record { return value !== null && typeof value === "object" && !Array.isArray(value); } function isTrimmedString( value: unknown, minimumLength: number, maximumLength: number ): value is string { return ( typeof value === "string" && value === value.trim() && value.length >= minimumLength && value.length <= maximumLength ); } function isNullableTrimmedString( value: unknown, maximumLength: number ): value is string | null { return ( value === null || (isTrimmedString(value, 1, maximumLength)) ); }