import { distributionBoardSupplyTypes, type DistributionBoardSupplyType, } from "../../shared/constants/distribution-board.js"; import type { SerializedProjectCommand } from "./project-command.model.js"; export const distributionBoardUpdateCommandType = "distribution-board.update" as const; export const distributionBoardUpdateCommandSchemaVersion = 1 as const; export interface DistributionBoardUpdateValues { floorId: string | null; supplyType: DistributionBoardSupplyType | null; simultaneityFactor: number; } export type DistributionBoardUpdateField = keyof DistributionBoardUpdateValues; export type DistributionBoardUpdatePatch = Partial; export interface DistributionBoardUpdateFieldChange< TField extends DistributionBoardUpdateField = DistributionBoardUpdateField, > { field: TField; value: DistributionBoardUpdateValues[TField]; } export interface DistributionBoardUpdateCommandPayload { distributionBoardId: string; changes: DistributionBoardUpdateFieldChange[]; } export interface DistributionBoardUpdateProjectCommand extends SerializedProjectCommand { schemaVersion: typeof distributionBoardUpdateCommandSchemaVersion; type: typeof distributionBoardUpdateCommandType; } export function createDistributionBoardUpdateProjectCommand( distributionBoardId: string, patch: DistributionBoardUpdatePatch ): DistributionBoardUpdateProjectCommand { const command: DistributionBoardUpdateProjectCommand = { schemaVersion: distributionBoardUpdateCommandSchemaVersion, type: distributionBoardUpdateCommandType, payload: { distributionBoardId, changes: Object.entries(patch).map(([field, value]) => ({ field: field as DistributionBoardUpdateField, value, })) as DistributionBoardUpdateFieldChange[], }, }; assertDistributionBoardUpdateProjectCommand(command); return command; } export function assertDistributionBoardUpdateProjectCommand( command: SerializedProjectCommand ): asserts command is DistributionBoardUpdateProjectCommand { if ( command.schemaVersion !== distributionBoardUpdateCommandSchemaVersion || command.type !== distributionBoardUpdateCommandType || !isPlainObject(command.payload) ) { throw new Error("Unsupported distribution-board update command."); } const { distributionBoardId, changes } = command.payload; if ( typeof distributionBoardId !== "string" || !distributionBoardId.trim() || !Array.isArray(changes) || changes.length === 0 ) { throw new Error( "Distribution-board update command requires an id and changes." ); } const seen = new Set(); for (const change of changes) { if ( !isPlainObject(change) || (change.field !== "floorId" && change.field !== "supplyType" && change.field !== "simultaneityFactor") || seen.has(change.field) ) { throw new Error( "Distribution-board update contains an invalid or duplicate field." ); } if (change.field === "floorId") { if ( change.value !== null && (typeof change.value !== "string" || !change.value.trim()) ) { throw new Error("floorId must be a non-empty string or null."); } } else if (change.field === "supplyType") { if ( change.value !== null && !distributionBoardSupplyTypes.includes( change.value as DistributionBoardSupplyType ) ) { throw new Error("supplyType is invalid."); } } else if ( typeof change.value !== "number" || !Number.isFinite(change.value) || change.value < 0 || change.value > 1 ) { throw new Error( "simultaneityFactor must be between zero and one." ); } seen.add(change.field); } } function isPlainObject(value: unknown): value is Record { return value !== null && typeof value === "object" && !Array.isArray(value); }