23 enero 2009

Creación de informes con QuickReport (IV)

Si creéis que con los informes que hemos hecho hemos cubierto casi todas las posibilidades estáis equivocados. La mente retorcida de nuestros clientes siempre hace dar otra vuelta de tuerca a nuestros informes hasta límites insospechados. Simplemente quieren verlo todo en un folio.

MAS DIFICIL TODAVIA

A partir de las tablas de CLIENTES y FACTURAS vamos a hacer un informe de ventas por cliente que nos va a mostrar lo que ha facturado cada uno (desglosado), con el total y un suma y sigue. Además vamos a totalizar toda la facturación:


Podéis verlo mejor si hacéis clic sobre la imagen. El diseño del informe sería este:


A la primera banda la vamos a llamar Cabecera y va a ser un objeto TQRBand cuya propiedad BandType será rbTitle. Sólo va a tener el título del listado y el encabezado:


La segunda banda se va a llamar Detalle y también será de tipo TQRBand. La banda va a ser de tipo rbDetail y va a mostrar los nombres de los clientes y las cabeceras de la factura mediante campos TQRDBText:


La tercera banda va a ser un objeto TQRSubDetail que vamos a llamar Subdetalle. En esta banda vamos a mostrar los importes de cada factura (también con TQRBText):


La cuarta banda también va a ser un objeto TQRSubDetalle que llamaremos Subdetalle2. Esta banda va a encargarse de totalizar las facturas de cada cliente y hacer un suma y sigue:


En esta banda, la primera fila de campos son etiquetas TQRLabel que vamos a reprogramar para que nos sumen el total de facturación de cada cliente. Para cumplir este cometido creamos en la sección privada del formulario de este informe estas variables:

private
{ Private declarations }
dSumaBase, dSumaIva, dSumaTotal: Double;

Ahora programamos el evento OnBeforePrint de la banda Detalle (rbDetail) para que inicialice estas variables y además nos filtre las facturas del cliente actual:

procedure TFListadoVentas.DetalleBeforePrint(Sender: TQRCustomBand;
var PrintBand: Boolean);
begin
with Form1 do
begin
Facturas.Filter := 'IDCLIENTE=' + #39 +
Clientes.FieldByName('ID').AsString + #39;
Facturas.Filtered := True;
end;

dSumaBase := 0;
dSumaIva := 0;
dSumaTotal := 0;
end;

Ahora le decimos antes de imprimir la banda Subdetalle2 que cambie las etiquetas por lo que llevamos aculumado:

procedure TFListadoVentas.Subdetalle2BeforePrint(Sender: TQRCustomBand;
var PrintBand: Boolean);
begin
ESUMABASE.Caption := FormatFloat( '###,###,#0.00', dSumaBase );
ESUMAIVA.Caption := FormatFloat( '###,###,#0.00', dSumaIva );
ESUMATOTAL.Caption := FormatFloat( '###,###,#0.00', dSumaTotal );
end;

Y para que haga la suma le decimos en el evento AfterPrint (después de imprimir) de la banda Detalle que vaya sumando los totales de cada registro:

procedure TFListadoVentas.SubdetalleAfterPrint(Sender: TQRCustomBand;
BandPrinted: Boolean);
begin
dSumaBase := dSumaBase + Form1.Facturas.FieldByName('BASEIMPONIBLE').AsFloat;
dSumaIva := dSumaIva + Form1.Facturas.FieldByName('IVA').AsFloat;
dSumaTotal := dSumaTotal + Form1.Facturas.FieldByName('TOTAL').AsFloat;
end;

Esta banda de totales también contiene campos de tipo TQRExpr (expresiones) que no van vinculados a bases de datos pero que permiten realizar operaciones con los campos del formulario. Estos campos los utilizamos para hacer el suma y sigue de todos los clientes. Para acumular la suma del campo BASEIMPONIBLE le decimos a la propiedad Expression del objeto TQRExpr que haga esto:


Igual tenemos que hacer para sumar el IVA y el TOTAL: SUM(IVA) y SUM(TOTAL), cada uno en su campo correspondiente. Como puede verse hay otras funciones para calcular la media, el máximo, pasar a mayúsculas, etc.

Y la quinta y última banda va a ser un objeto TQRBand cuyo tipo va a ser rbSummary. Aquí vamos a meter campos de tipo TQRExpr para sumar Base Imponible, Importe IVA y Total.

Aunque todo esto parezca una paranoia mental os puedo asegurar que se hace rápidamente respecto a otros editores como Rave (el que tenga valor que intente hacer este ejemplo).

Hay que procurar utilizar las funciones que trae el objeto TQRExpr para que nos resuelva los problemas, pero en casos como el que hemos visto de totales los podemos solucionar metiendo etiquetas y haciendo el trabajo a mano por programación.

EXPORTANDO EL INFORME A PDF

Aunque hoy en día se suelen utilizar programas como Primo PDF para convertir los documentos impresos a PDF, a los usuarios de los programas de gestión no les suele hacer mucha gracia porque cada vez que van a imprimir tiene que seleccionar la impresora Primo PDF y elegir la ubicación.

Por fortuna, las últimas versiones de QuickReport permiten exportar el formato a PDF. Esto podemos hacerlo directamente mediante código cuando vamos a imprimir el documento:

Application.CreateForm( TFListadoVentas, FListadoVentas );
FListadoVentas.Informe.ExportToFilter(
TQRPDFDocumentFilter.Create( 'C:\ListadoVentas.pdf' ) );

Para poder compilar esto tenemos que añadir la unidad QRPDFFilt en nuestro formulario. Con una sola línea de código hemos solucionado el problema.

EXPORTANDO A OTROS FORMATOS

Igualmente podemos pasarlo a HTML de este modo:

Application.CreateForm( TFListadoVentas, FListadoVentas );
FListadoVentas.Informe.ExportToFilter(
TQRGHTMLDocumentFilter.Create( 'C:\ListadoVentas.html' ) );

Este filtro necesita la unidad QRWebFilt.

Para exportar el informe a un documento RTF compatible con Microsoft Word sería así:

Application.CreateForm( TFListadoVentas, FListadoVentas );
FListadoVentas.Informe.ExportToFilter( TQRRTFExportFilter.Create( 'C:\ListadoVentas.rtf' ) );

Ese filtro necesita la unidad QRExport.

También podemos exportar a Microsoft Excel de este modo:

Application.CreateForm( TFListadoVentas, FListadoVentas );
FListadoVentas.Informe.ExportToFilter( TQRXLSFilter.Create( 'C:\ListadoVentas.xls' ) );

Utiliza también la unidad QRExport.

Si nos interesa extraer tanto los datos como los metadatos se puede exportar todo a XML:

Application.CreateForm( TFListadoVentas, FListadoVentas );
FListadoVentas.Informe.ExportToFilter( TQRXDocumentFilter.Create( 'C:\ListadoVentas.xml' ) );

Este filtro está en la unidad QRXMLSFilt.

Se puede exportar a un formato gráfico de 16 bits de gráficos vectoriales (aunque también soporta bitmaps) con extensión WMF que era antiguamente utilizado por Windows 3.0. Este formato puede verse con programas de diseño gráfico como GIMP. Se exporta de este modo:

var
FiltroWMF: TQRWMFExportFilter;
begin
FiltroWMF := TQRWMFExportFilter.Create( 'C:\ListadoVentas.wmf' );
Application.CreateForm( TFListadoVentas, FListadoVentas );
FListadoVentas.Informe.Prepare;
FListadoVentas.Informe.ExportToFilter( FiltroWMF );
end;

Un formato que considero muy interesante es pasarlo a texto plano para impresoras matriciales de tickets:

Application.CreateForm( TFListadoVentas, FListadoVentas );
FListadoVentas.Informe.ExportToFilter( TQRASCIIExportFilter.Create( 'C:\ListadoVentas.txt' ) );

Este formato de texto también viene muy bien para impresoras matriciales de papel continuo que tango nos castigan cuando se desajustan verticalmente.

Por último y no menos importante, tenemos el formato CSV que permite exportar los datos en texto plano entre comillas y separados por comas (ideal para exportar datos a Excel, Access, MySQL, etc.):

Application.CreateForm( TFListadoVentas, FListadoVentas );
FListadoVentas.Informe.ExportToFilter( TQRCommaSeparatedFilter.Create( 'C:\ListadoVentas.csv' ) );

Con esto cubrimos todas las necesidades de exportación de nuestros informes a otros programas ofimáticos y de terceros. Estos últimos filtros que hemos visto también tiran de la unidad QRExport.

No os podéis ni imaginar la cantidad de horas que he tenido que echar para averiguar como se exporta a todos estos formatos (mirando el código fuente). Todavía no entiendo por que narices los que fabrican componentes tan buenos como estos no les sale de los ... de escribir una documentación y ejemplos decentes (y eso también va por los Indy). No me extraña de que algunos programadores abandonen Delphi y elijan otros lenguajes por esta dificultad. No entiendo que a estas alturas (Delphi 2009) los programadores de Delphi tengamos que sufrir tanto para averiguar cuatro tonterías. A ver si "patera" technlogies se pone las pilas de una vez.

La semana que viene seguiré investigando para sacarle todo el partido posible a QuickRerport.

Pruebas realizadas en RAD Studio 2007.

20 comentarios:

Anónimo dijo...

Te doy las gracias por tu blog!. Es muy dificil encontrar algo parecido y que se actualize con tanta regularidad y profesionalidad, lo que no deja de crear "adictos sanos" a tu espacio!

Felicidades, felicidades y gracias 1000 !

Sebas.

Administrador dijo...

Ojalá tuviera más tiempo para escribir más artículos y más frecuentemente.

Pero hay que trabajar para comer. Así es la vida.

Saludos y gracias por tu apoyo.

Jackoman dijo...

Sobre la documentación: el que hace QuickReports no ha hecho precisamente un master en documentación. Aún hoy, la mejor documentación es el FAQ escrito sobre las versiones de antes del diluvio.

Afortunadamente hay gente como tú que divulga, y las news de los servidores de Borland (qué hubiese hecho yo sin ellas ;)

Un saludo!

Administrador mikropic dijo...

Hola, una pregunta un poco salida del post, necesito conectar con una base de datos acces pero teniendo el archivo de base de datos en la misma carpeta del ejecutable. Espero me puedas alludar.

Administrador dijo...

Pues si te digo la verdad, hasta ahora he tenido la gran suerte de no tener que trabajar con bases de datos de Access pero según un compañero de trabajo dice que no es muy difícil.

Hay componentes como ADO que permiten conectarse directamente a las tablas de Access utilizando su controlador ODBC.

Siento no poder ayudarte pero no estoy muy puesto en este asunto. Pero me suena haberme cruzado con tres o cuatro artículos de páginas web donde hablaban del tema.

Una de ellas creo que era la del Rinconcito Delphi.

Saludos.

Jackoman dijo...

A ver si te puedo ayudar yo:
Si usas ExtractFilePath(application.ExeName)

te devuelve la ruta completa del ejecutable de tu aplicación excuyendo el nombre de este (por ejemplo: "c:\Archivos de programa\Tu Empresa\NombreAplicacion\")

A esto le concatenas el nombre del fichero de la base de datos y listo.

Si tu pregunta es por el tipo de acceso desde delphi puedes usar: BDE, Ado, DBExpress + driver específico (¿existe?), DBExpress + driver odbc ( por ejemplo dbxoodbc) o buscar alguna librería específica.

Yo uso DBExpress + driver odbc.

Anónimo dijo...

felicidades por estas publicaciones, en verdad son buenisimas, me han ayudado mucho; aunque tengo una pregunta como puedo imprimir horizontalmente en los reportes los datos de un campo es decir en vez de imprimir verticalmente

DATO1
DATO2
DATO3
DATO4
DATO5...

me gustaria poder hacerlo horizontal:
DATO1,DATO2,DATO3,DATO4,DATO5...

Administrador dijo...

Creo que lo mejor sería procesar todo en una variable de tipo string y luego mardarlo todo al informe como un memo.

Si a alguien se le ocurre algo mejor aceptamos sugerencias.

Anónimo dijo...

Hola,

Con Delphi 7 y QuikReport Pro 4.07, el informe en excel pierde la presentación del reporte original.

¿Es así o es falla de la versión que ocupo?

Saludos,

Jorge

Jackoman dijo...

No es de la versión. Que yo sepa es así. No obstante, es normal: piensa que el report no es más que una especie de "imagen". Cuadrar todo lo que puedes hacer en un report en las celdas de Excel no debe ser sencillo, así que QuickReports se limita a presentar tus datos "modestamente" encuadrados en Excel.

Se que no es lo que necesitas oir (leer), pero a no ser que diseñes el report para que cuadre en las celdas de Excel (sin usar muchas "florituras" y probando la exportación a cada cosa que cambies) no vas a conseguir mucho más.

Unknown dijo...

Antes que nada, muchas gracias por toda la información que brinda en el blog.
Ahora una consulta. Estoy tratando de hacer un reporte como el de arriba, pero no consigo que me separe las factura por cliente, sino que muestro el apellido del mismo tantas veces como facturas tengo guardadas. Estoy utilizando un componente ADOQuery.
Si es necesario que vea el código, con gusto se lo envío.

Administrador dijo...

Puede ser que lo que falle sea el filtro (filter) que cambio en tiempo real antes de imprimir.

Mira las propiedades del objeto ADOQuery a ver si funciona bien el Filter. A veces los filtros no funcionan (como en las tablas de memoria RX).

Saludos.

Oscar Guzmán dijo...

Hola que tal, es la primera vez que llego a este blog y te felicito por todo el contenido ofrecido en especial estos artículos de quickreport, está muy claro e interesante sobre todo para una persona como yo que no tiene grandes conocimientos sobre QuickReport.

Quisiera saber si hay disponible algún tema acerca del manejo de la expresión SUM en quickreport para totalizar una columna sin que se tomen en cuenta los valores negativos que hay en ella.

Muchas Gracias de antemano
Oscar

Jackoman dijo...

Hola!

Puedes usar la función if dentro del sum. Algo así:

sum(if(dato<0,0,dato))

Espero que te sirva! :)

L.N.B. Colinas Del Llano dijo...

Te Felicito de verdad...por el excelente trabajo que haces... una duda como podria hacer un filtrado doble.. es decir... tengo 2 ComboBox uno para el mes (1-12) y otro para el año (2009 - 2020) de manera que si el usuario elige generar reporte del mes 6(1er combobox) del año 2010 (2do combobox) el reporte imprima solo los registros de esa fecha... gracias de antemano...

nelson mateo de la cruz dijo...

amigo tengo un problema con el reporte y es que me puestra el cliente en varias etapas con diferentes facturas
ejemplo
jose martinez
factura
01
jose martinez
05
cuando deveria ser
jose martinez
01
05
por favor ayudeme con este problema

nelson mateo de la cruz dijo...

utiliso dbe y sqlserver con odbc
y para date una idea de la diferencia del envento
beforeprint
el digo de filtrado me queda mas o menos asi\
dm.factura.filter:=('codfactura').asstring +#39+dm.cliente.fielbyname:=('codcliente').astring+#39;
dm.factura.filtrere:=true;

Unknown dijo...

Solo un día aquí y al igual que casi todos los que visitamos esta enciclopedia de conocimientos te doy muchísimas gracias por la bondad de compartir lo que tanto tiempo y dedicación te ha costado.
Gracias a ti y a las personas que valorando tanto lo que haz creado han sentido la necesidad de no irse sin contribuir a tu desinteresada causa, también a los que te agradecen dejándote textos y comentarios.
Lo que hace gratificante recibir estas acciones positivas es hacer las cosas sin esperar nada a cambio.
Saludos.

Administrador dijo...

Gracias por tu apoyo.

Ojalá tuviese más tiempo para seguir escribiendo toda la información que tengo en la cabeza, pero con la crisis, el trabajo es el trabajo.

Saludos y gracias.

Unknown dijo...

HOLA GRACIAS POR TU DEDICACION ESTOY TRABAJANDO EN DELPHI 6 Y NECESITO GUARDAR UN REPORTE EN PDF DE MANERA AUTOMATICA Y POR LOQ UE VEO QRExports LO HACE PERO LO INTENTO AGRAGFAR A LOS COMPONENTES Y NO PUEDO


ESPERO TU RESPUESTA GRACIAS :)

Publicidad