import type { SapDbAdapter, SapDbEngine } from './sapdb.types.js'; /** Lo que SAP sabe de un campo de usuario: su tipo, sus valores válidos y la tabla de usuario a la que está enlazado. */ export interface UserFieldInfo { table: string; alias: string; description: string; /** Tipo de SAP: `A` alfanumérico, `N` numérico, `D` fecha… */ type: string; size: number; /** Valores válidos del campo (vacío si acepta cualquier valor). */ validValues: { value: string; description: string }[]; /** Tabla de usuario enlazada (`SIS_REGSAN`), si el campo está vinculado a una. */ linkedTable: string | null; /** Filas (`Code` y `Name`) de la tabla enlazada. */ linkedRows: { code: string; name: string }[]; /** La tabla enlazada tiene más filas de las que se entregan. */ linkedTruncated: boolean; } /** Una tabla de SAP (`OBTN`, `@MI_TABLA`) y el alias de un campo de usuario (`SIS_ESTLOT`, sin el `U_`). */ export const TABLE_ID = /^@?[A-Za-z0-9_]{1,30}$/; export const FIELD_ALIAS = /^[A-Za-z0-9_]{1,30}$/; /** Una tabla enlazada se escribe en el SQL entre comillas: solo letras, dígitos y «_». */ const LINKED = /^[A-Za-z0-9_]{1,30}$/; interface Sql { info: string; values: string; linked: (table: string) => string; } /** El SQL de cada motor (`CUFD` define los campos de usuario y `UFD1` sus valores válidos; existen igual en HANA y MSSQL). */ const SQL: Record = { hana: { info: 'SELECT "FieldID", "Descr", "TypeID", "EditSize", "RTable" FROM "CUFD" WHERE "TableID" = ? AND "AliasID" = ?', values: 'SELECT "FldValue", "Descr" FROM "UFD1" WHERE "TableID" = ? AND "FieldID" = ? ORDER BY "IndexID"', linked: (t) => `SELECT "Code", "Name" FROM "@${t}" ORDER BY "Code"`, }, }; const text = (v: unknown): string => (v === null || v === undefined ? '' : String(v)); /** * Lee de la base la definición de un campo de usuario. Devuelve `null` si el campo no existe. La tabla enlazada sale de * la propia definición del campo (no la elige quien llama), así que no se puede leer una tabla cualquiera. */ export async function readUserField( adapter: SapDbAdapter, engine: SapDbEngine, table: string, alias: string, opts: { timeoutMs: number; maxRows: number }, ): Promise { const sql = SQL[engine]; const [field] = await adapter.query(sql.info, [table, alias], opts); if (!field) return null; const values = await adapter.query(sql.values, [table, field['FieldID']], opts); const linkedTable = text(field['RTable']) || null; let linkedRows: UserFieldInfo['linkedRows'] = []; let linkedTruncated = false; if (linkedTable && LINKED.test(linkedTable)) { try { const rows = await adapter.query(sql.linked(linkedTable), [], opts); linkedTruncated = rows.length > opts.maxRows; linkedRows = rows.slice(0, opts.maxRows).map((r) => ({ code: text(r['Code']), name: text(r['Name']) })); } catch { /* el enlace no es a una tabla de usuario con Code y Name: el campo se trata como de valores libres */ } } return { table, alias, description: text(field['Descr']), type: text(field['TypeID']), size: Number(field['EditSize'] ?? 0) || 0, validValues: values.map((v) => ({ value: text(v['FldValue']), description: text(v['Descr']) })), linkedTable, linkedRows, linkedTruncated, }; }