import type { SerializedProjectCommand } from "./project-command.model.js"; export const circuitDeviceRowUpdateCommandType = "circuit-device-row.update" as const; export const circuitDeviceRowUpdateCommandSchemaVersion = 1 as const; export interface CircuitDeviceRowUpdateValues { linkedProjectDeviceId: string | null; sortOrder: number; name: string; displayName: string; phaseType: string | null; connectionKind: string | null; costGroup: string | null; category: string | null; level: string | null; roomId: string | null; roomNumberSnapshot: string | null; roomNameSnapshot: string | null; quantity: number; manualQuantity: number; powerPerUnit: number; simultaneityFactor: number; cosPhi: number | null; remark: string | null; overriddenFields: string | null; } export type CircuitDeviceRowUpdateField = keyof CircuitDeviceRowUpdateValues; export type CircuitDeviceRowUpdatePatch = Partial; export interface CircuitDeviceRowUpdateFieldChange< TField extends CircuitDeviceRowUpdateField = CircuitDeviceRowUpdateField, > { field: TField; value: CircuitDeviceRowUpdateValues[TField]; } export interface CircuitDeviceRowUpdateCommandPayload { rowId: string; changes: CircuitDeviceRowUpdateFieldChange[]; } export interface CircuitDeviceRowUpdateProjectCommand extends SerializedProjectCommand { schemaVersion: typeof circuitDeviceRowUpdateCommandSchemaVersion; type: typeof circuitDeviceRowUpdateCommandType; } export function createCircuitDeviceRowUpdateProjectCommand( rowId: string, patch: CircuitDeviceRowUpdatePatch ): CircuitDeviceRowUpdateProjectCommand { const normalizedPatch = patch.quantity !== undefined && patch.manualQuantity === undefined ? { ...patch, manualQuantity: patch.quantity } : patch; if ( normalizedPatch.quantity !== undefined && normalizedPatch.manualQuantity !== undefined && normalizedPatch.manualQuantity > normalizedPatch.quantity ) { throw new Error("manualQuantity must not exceed quantity."); } const changes = Object.entries(normalizedPatch).map(([field, value]) => ({ field: field as CircuitDeviceRowUpdateField, value, })) as CircuitDeviceRowUpdateFieldChange[]; const command: CircuitDeviceRowUpdateProjectCommand = { schemaVersion: circuitDeviceRowUpdateCommandSchemaVersion, type: circuitDeviceRowUpdateCommandType, payload: { rowId, changes }, }; assertCircuitDeviceRowUpdateProjectCommand(command); return command; } export function assertCircuitDeviceRowUpdateProjectCommand( command: SerializedProjectCommand ): asserts command is CircuitDeviceRowUpdateProjectCommand { if ( command.schemaVersion !== circuitDeviceRowUpdateCommandSchemaVersion || command.type !== circuitDeviceRowUpdateCommandType ) { throw new Error("Unsupported circuit device-row update command."); } if (!isPlainObject(command.payload)) { throw new Error("Circuit device-row update payload must be an object."); } const rowId = command.payload.rowId; const changes = command.payload.changes; if (typeof rowId !== "string" || !rowId.trim()) { throw new Error("Circuit device-row update command requires a row id."); } if (!Array.isArray(changes) || changes.length === 0) { throw new Error( "Circuit device-row update command requires at least one change." ); } const seenFields = new Set(); for (const change of changes) { if (!isPlainObject(change) || typeof change.field !== "string") { throw new Error( "Circuit device-row update command contains an invalid change." ); } if ( !isCircuitDeviceRowUpdateField(change.field) || seenFields.has(change.field) ) { throw new Error( "Circuit device-row update command contains an invalid or duplicate field." ); } assertCircuitDeviceRowUpdateFieldValue(change.field, change.value); seenFields.add(change.field); } } function assertCircuitDeviceRowUpdateFieldValue( field: CircuitDeviceRowUpdateField, value: unknown ) { if (field === "name" || field === "displayName") { if (typeof value !== "string" || !value.trim()) { throw new Error(`${field} must be a non-empty string.`); } return; } if (field === "sortOrder") { if (typeof value !== "number" || !Number.isFinite(value)) { throw new Error("sortOrder must be a finite number."); } return; } if ( field === "quantity" || field === "manualQuantity" || field === "powerPerUnit" || field === "simultaneityFactor" ) { if (typeof value !== "number" || !Number.isFinite(value) || value < 0) { throw new Error(`${field} must be a non-negative finite number.`); } if (field === "simultaneityFactor" && value > 1) { throw new Error("simultaneityFactor must not exceed 1."); } return; } if (field === "cosPhi") { if ( value !== null && (typeof value !== "number" || !Number.isFinite(value) || value <= 0) ) { throw new Error("cosPhi must be a positive finite number or null."); } return; } if (value !== null && typeof value !== "string") { throw new Error(`${field} must be a string or null.`); } } function isCircuitDeviceRowUpdateField( value: string ): value is CircuitDeviceRowUpdateField { return [ "linkedProjectDeviceId", "sortOrder", "name", "displayName", "phaseType", "connectionKind", "costGroup", "category", "level", "roomId", "roomNumberSnapshot", "roomNameSnapshot", "quantity", "manualQuantity", "powerPerUnit", "simultaneityFactor", "cosPhi", "remark", "overriddenFields", ].includes(value); } function isPlainObject(value: unknown): value is Record { return value !== null && typeof value === "object" && !Array.isArray(value); }