import { assertReadOnly, paramNames } from './sapdb-sql.js'; import { CLIENT_SAPDB_QUERIES } from './sapdb.extensions.js'; import type { SapDbQuery } from './sapdb.types.js'; /** Consultas del proyecto base: solo una de comprobación (la base responde). Las de negocio van en `sapdb.extensions.ts`. */ export const BASE_SAPDB_QUERIES: readonly SapDbQuery[] = [ { name: 'ping', title: 'Comprobar la conexión con la base de datos', sql: { hana: 'SELECT 1 AS "ok" FROM "DUMMY"' }, params: {}, menu: null, }, { // Tablas estándar de SAP B1 (OBTQ: cantidad por lote y almacén; OBTN: maestro de lotes; OWHS: almacenes). `absEntry` es el // `DocEntry` del lote en el Service Layer. name: 'batch-stock', title: 'Stock de un lote por almacén', sql: { hana: `SELECT T1."WhsCode" AS "WarehouseCode", W."WhsName" AS "WarehouseName", T1."Quantity" AS "Quantity", T1."CommitQty" AS "Committed", T1."CountQty" AS "Counted" FROM "OBTQ" T1 INNER JOIN "OBTN" T0 ON T1."MdAbsEntry" = T0."AbsEntry" AND T1."ItemCode" = T0."ItemCode" LEFT JOIN "OWHS" W ON W."WhsCode" = T1."WhsCode" WHERE T0."AbsEntry" = :absEntry ORDER BY T1."WhsCode"`, }, params: { absEntry: { type: 'number' } }, menu: '/inventario/lotes', maxRows: 1000, }, ]; /** Comprueba que los nombres no se repitan, que el SQL sea de solo lectura y que use justo los parámetros declarados. Lanza si no. */ export function validateQueries(queries: readonly SapDbQuery[]): readonly SapDbQuery[] { const seen = new Set(); for (const q of queries) { if (!/^[a-z0-9][a-z0-9-]{0,59}$/.test(q.name)) throw new Error(`Nombre de consulta inválido: ${q.name}`); if (seen.has(q.name)) throw new Error(`Consulta repetida: ${q.name}`); seen.add(q.name); const engines = Object.entries(q.sql); if (!engines.length) throw new Error(`La consulta ${q.name} no tiene SQL`); for (const [engine, sql] of engines) { assertReadOnly(`${q.name} (${engine})`, sql); const used = new Set(paramNames(sql)); const declared = new Set(Object.keys(q.params)); for (const n of used) if (!declared.has(n)) throw new Error(`La consulta ${q.name} (${engine}) usa :${n} sin declararlo`); for (const n of declared) if (!used.has(n)) throw new Error(`La consulta ${q.name} (${engine}) declara ${n} y no lo usa`); } } return queries; } export const SAPDB_QUERIES: ReadonlyMap = new Map(validateQueries([...BASE_SAPDB_QUERIES, ...CLIENT_SAPDB_QUERIES]).map((q) => [q.name, q]));