82 lines
3.3 KiB
TypeScript
82 lines
3.3 KiB
TypeScript
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<SapDbEngine, Sql> = {
|
||
|
|
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<UserFieldInfo | null> {
|
||
|
|
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,
|
||
|
|
};
|
||
|
|
}
|