Listados de impresión
Listados de impresión
Los listados de impresión son los documentos e informes imprimibles de a3ERP: facturas, ofertas o albaranes, pero también un resumen de pedidos o un informe de gastos. Cada listado es un fichero binario que vive en una carpeta compartida del servidor de a3ERP — por defecto C:\Users\Public\Documents\A3\Listados, salvo que se cambiara la ruta al desplegar la aplicación — con nombres como LSTIMPRFACV.003 o LSTREPEDCOFED.001: el prefijo identifica el tipo de listado y el sufijo numérico el fichero concreto.
El nombre con el que el usuario ve cada listado no está dentro del binario: la aplicación lo guarda en Sistema\Listados.INI, junto a la carpeta de listados, como un mapeo por tipo (LSTIMPRFACV.000=Factura de venta). En ese mismo INI registra también qué listado es el por defecto (entradas DEFECTO). Al guardar un listado desde a3ERP, la aplicación actualiza las dos cosas — el binario y el INI — así que normalmente no hay que tocar estos ficheros a mano; conocerlos sirve sobre todo para localizar listados en el servidor y para copiarlos entre entornos cuando toca una migración de servidor (binario + su entrada del INI).
El binario combina la definición gráfica del documento con su SQL: una consulta primaria que devuelve los datos principales y, opcionalmente, subconsultas que el motor llama por nombre y vuelca en bloques concretos del diseño (el logo de la empresa emisora, textos legales...). Hay listados que solo tienen la consulta primaria. Casi todo lo que recoge este artículo aplica por igual a cualquier consulta del listado, primaria o subconsulta; donde algo afecte solo a una de ellas, se indica. Si lo que buscas es algo para copiar y rellenar, ve directamente a las plantillas.
Cómo se trabaja con un listado#
El SQL se edita pegándolo en la consulta correspondiente dentro del diseñador de listados de a3ERP, y se comprueba previsualizando contra un documento real.
Para afinar la lógica antes de llegar ahí, la consulta se puede ejecutar en SSMS adaptando los parámetros, de cualquiera de estas maneras:
- Sustituir cada parámetro runtime por su valor real (el ID del documento con el que pruebas).
- Quitar los marcadores entre corchetes, o sustituirlos por su valor por defecto (
1=1en el caso de[CondicionesListado,Sysname,1=1]). - Declarar una variable con el mismo nombre que el parámetro y cambiar el prefijo
:por@—DECLARE @IdFacV INT = 12345;y, en la consulta,:IdFacVpasa a ser@IdFacV. Es la opción cómoda cuando el parámetro aparece muchas veces.
Eso sí: la prueba final es siempre dentro de a3ERP, porque los errores del motor de informes — al final de este artículo — no se reproducen en SSMS. Y conviene previsualizar varios casos: distintos idiomas, documentos con casuísticas especiales, filtros aplicados en el diálogo de impresión.
Parámetros runtime#
Las consultas de un listado no reciben parámetros T-SQL estándar: el intérprete de a3ERP reemplaza una serie de marcadores por valores reales antes de ejecutar cada consulta.
:nIdent,:IdFacV,:IdOfeV,:tipocont... son los IDs del documento que se está imprimiendo (o del tipo de contador). El nombre exacto depende del tipo de documento y de la consulta.[CondicionesListado,Sysname,1=1]se sustituye por los filtros que el usuario aplica en el diálogo de impresión. Va en la consulta primaria, tiene que aparecer al final de suWHEREy dejarse intacto: si falta, todo lo que el usuario haya rellenado en la ventana de a3ERP antes de imprimir (rangos de fechas, clientes, series...) se ignora sin avisar — el listado se comporta como si no se hubiera filtrado nada.
No usar dos puntos ni corchetes en los comentarios SQL
El intérprete lee cualquier :texto como llamada a un parámetro runtime y cualquier [texto] como marcador de parámetro, también dentro de los comentarios (-- y /* */), y el listado peta al ejecutar. Reformular sin esos caracteres: en vez de -- Filtro: solo facturas del año en curso, escribir -- Se filtran solo las facturas del año en curso.
Parámetros de usuario y "Otros listados"#
Un listado también puede llevar parámetros de usuario [Nombre,TIPO,Default] que a3ERP pide al imprimir. En los listados de documento son poco comunes, pero hay un caso donde son la herramienta principal: Otros listados, la opción de a3ERP que da al implantador un informe en blanco para montarlo desde cero. Como ese tipo de listado no va ligado a ningún documento, no recibe los :Id... automáticos — la única manera de pasarle valores es mediante parámetros de usuario. La sintaxis completa está en el artículo de vistas SQL, que es donde más se usan.
Convenciones y patrones T-SQL#
WITH (NOLOCK)en todos los joins. El listado se ejecuta en producción mientras alguien está facturando; sin el hint, la consulta puede bloquear documentos o quedarse bloqueada ella.- Multi-idioma con
CASE, donde aplique. En los listados que el cliente recibe en su idioma (facturas, ofertas...), los textos se eligen según el idioma de la cabecera:CASE Cabecera.CodIdioma WHEN 'CAT' THEN ... WHEN 'CAS' THEN ... ELSE ... END, donde elELSEhace de fallback (normalmente, el inglés). En resúmenes o informes de uso interno, el multi-idioma no suele tener sentido. - Patrones que se repiten en casi todos los listados:
DECLARE @TableVar TABLE (...)para cargar textos largos (cláusulas legales),STUFF + FOR XML PATH('')para concatenar varias filas en una sola cadena, yCHAR(13) + CHAR(10)para los saltos de línea dentro de los textos.
Trampas de SQL Server#
Dos errores clásicos que, a diferencia de los del motor de informes, sí se reproducen en SSMS:
- El
GROUP BYquiere la expresión completa. Estas consultas suelen agrupar por decenas de columnas. Si añades una columna no agregada alSELECT, tiene que ir también alGROUP BY— y si la columna es una expresiónCASE ... AS Alias, lo que va alGROUP BYes la expresión completa, no el alias. - Los códigos de a3ERP están "cuadrados". Se guardan completados con espacios por la izquierda, normalmente a 8 caracteres —
CODFAMEST = ' 1'son ocho. UnWHERE CODFAMEST = '1'sin los espacios no encuentra nada, así que respeta el espaciado exacto en los literales. Las longitudes que se salen de la norma (CODART, lotes, provincias...) están en la visión general; de aquí viene también el tipo de parámetroCUADRADO(n)de las vistas SQL.
Trampas del motor de informes (cds)#
Cada consulta del listado — la primaria y cada subconsulta — se carga en un dataset (cds) del motor de informes de a3ERP. Ese motor no es SQL Server: hace su propio análisis del texto y mapea los tipos de las columnas a su manera. De ahí salen errores con la forma (EDatabaseError) cds<NombreConsulta>: ... que no se reproducen en SSMS — la consulta corre perfecta contra la base de datos y solo falla dentro de a3ERP. Si te ha pasado eso, casi seguro que es uno de estos tres casos:
- El primer
ORDER BYtextual manda, y su campo debe proyectarse. El motor toma como campo de orden del dataset la primera aparición literal de las palabrasORDER BYen el texto de la consulta, y la columna que va detrás tiene que estar en elSELECTde salida. Si una subconsulta SQL anidada dentro de la propia consulta (por ejemplo, unFOR XML PATHque concatena) lleva su propioORDER BYpor un campo que no se proyecta, el listado falla conField '<CAMPO>' not found. La solución es no ponerORDER BYen las subconsultas anidadas y dejar uno único al final, por la clave que enlaza con la consulta principal (normalmente elId...del documento). - Ese escáner tampoco salta los comentarios. Un comentario que contenga literalmente las palabras
ORDER BYhace que el motor coja la palabra siguiente como campo de orden: un comentario "Sin ORDER BY aquí" produceField 'AQUI' not found. Simplemente no escribas esa expresión en comentarios. - Nada de tipos sin longitud. El cds no sabe mapear
VARCHAR(MAX)ni otros tipos de longitud indefinida; al imprimir falla conField '<Columna>' is of unknown type. Las concatenaciones largas y, sobre todo, el patrón... FOR XML PATH(...) ).value('.', 'VARCHAR(MAX)')producenVARCHAR(MAX), así que toda columna calculada debe envolverse en un tipo con longitud explícita:CAST(... AS VARCHAR(500)),DECIMAL(p,s)...