import { escapeODataString } from '../../core/sl/odata'; import { type HeaderField, keysOfFields, requiredOfFields } from '../documents/document-layouts'; import { type DocRecord, compact } from '../documents/document-model'; import type { MasterLayout } from './master-types'; const isEmpty = (v: unknown): boolean => v === null || v === undefined || (typeof v === 'string' && v.trim() === ''); /** Todos los campos del formulario: los de arriba y los de cada pestaña. */ export const masterFieldsOf = (layout: MasterLayout): HeaderField[] => [ ...layout.header.left, ...layout.header.right, ...layout.tabs.flatMap((t) => [...t.left, ...t.right]), ]; /** Campos de SAP que se leen, se editan y se envían. */ export const masterKeysOf = (layout: MasterLayout): readonly string[] => keysOfFields(masterFieldsOf(layout)); /** Campos obligatorios con el texto del aviso ("Indique el código."). */ export const masterRequiredOf = (layout: MasterLayout): { key: string; label: string }[] => requiredOfFields(masterFieldsOf(layout)); /** Campos que no se pueden cambiar una vez creado el registro (el código). */ export const masterImmutableOf = (layout: MasterLayout): ReadonlySet => new Set(masterFieldsOf(layout).flatMap((f) => (f.type === 'text' && f.immutable ? [f.key] : []))); /** Valores editables de un registro de SAP (los vacíos quedan como texto vacío). */ export function pickMaster(doc: DocRecord | undefined, keys: readonly string[], blank: DocRecord): DocRecord { if (!doc) return blank; return Object.fromEntries(keys.map((k) => [k, doc[k] ?? ''])); } /** Errores que se pueden detectar sin preguntarle a SAP. */ export function validateMaster(header: DocRecord, required: readonly { key: string; label: string }[]): string[] { return required.filter((f) => isEmpty(header[f.key])).map((f) => `Indique ${f.label.toLowerCase()}.`); } /** Cuerpo para crear: cabecera sin campos vacíos más los valores fijos de la pantalla. */ export const createMasterPayload = (header: DocRecord, fixed?: DocRecord): DocRecord => ({ ...compact(header), ...fixed }); export type MasterNavTarget = 'first' | 'prev' | 'next' | 'last'; /** Consulta de un solo registro para ir al primero / anterior / siguiente / último por su clave de texto. */ export function masterNavigationQuery( target: MasterNavTarget, keyField: string, current: string | null, ): { $filter?: string; $orderby: string } { const asc = `${keyField} asc`; const desc = `${keyField} desc`; const quoted = current === null ? '' : `'${escapeODataString(current)}'`; switch (target) { case 'first': return { $orderby: asc }; case 'last': return { $orderby: desc }; case 'prev': return current === null ? { $orderby: desc } : { $filter: `${keyField} lt ${quoted}`, $orderby: desc }; case 'next': return current === null ? { $orderby: asc } : { $filter: `${keyField} gt ${quoted}`, $orderby: asc }; } }