Este post no está necesariamente vinculado al Firebird, esta mas bien relacionado al ADO. Lo escribo aquí porque en diversos foros he encontrado que este tema les resulta confuso a diversos foristas y con esto post pretendo (ojala lo logre) aclarar el tema. Empezamos.

¿Que es un reporte Cabecera – Detalle?
Es también llamado Maestro-Detalle y se aplica también a los informes, básicamente se pretende mostrar registros que tienen a su vez “sub-registros” relacionados a el, estos “sub-registros” relacionados al registro principal (cabecera o maestro) son registros que detallan o complementan la información del registro principal o en todo caso nos muestra su pertenencia. Por cierto que esto puede tener mas niveles pero aqui solo trataremos un nivel.

¿Podría aplicarse a una Factura este principio?
Definitivamente si, ya que este documento está compuesto por datos cabecera (Nombre de cliente, numero de factura, fecha, etc) y por datos detalle (cantidad, nombre del producto, precio unitario). Por supuesto que esto también se aplica a las boletas de venta, tickets, recibo por honorarios, notas de crédito, débito, etc.

¿Como lo genero?
Para generar este documento o reporte, necesitamos que existan al menos dos tablas en la base de datos que contengan esta información, una guardara los datos de cabecera y la otra los datos de detalle, ambas deben estar relacionados por algún campo identificador, ademas estas tablas pudieran estar relacionados a otras tablas propias del diseño del sistema que manejan. Entonces, haremos una consulta jerárquica hacia esas dos tablas la cual almacenaremos en un recordset y luego poblamos al reporteador (en este post usaremos el DataReport que viene en Visual Basic 6).

¿Y por qué no puedo usar una consulta simple?
Una consulta simple cuyo resultado almacenamos en un recordset esta formado por filas y columnas, consideremos lo como una tabla en memoria que contiene registros obtenidos como resultado de una consulta SQL que hallamos ejecutado.

Una consulta jerárquica esta compuesta por digamos “dos tablas” en memoria y que están relacionadas en la consulta, esta consulta almacenada en un solo recordset esta a la espera de almacenar dicha información en el objeto que pueda recibirlo, por ejemplo, el control MSHFlexgrid (H de Hierarchy=Jerarquia) o el DataReport.

Con una consulta simple no es posible mostrar detalle, y si lo fuera seria real y absurdamente tedioso.

¿Como construyo esta consulta?
Basicamente armaremos 2 consultas simples y las unificaremos usando el comando SHAPE, esto se almacenará en un recordset y luego este recordset lo conectamos al DataReport y listo.

Bien, vamos a practicar creando una factura. Para esto necesitamos una tabla cabecera de la factura y otra tabla que contendrá el detalle de dicha factura. Llamemos a la tabla cabecera CAB y la de detalle DET (usaremos pocos campos ya que esto va como un ejemplo)

Tabla CAB

Cab2

Haz clic para verlo mas grande

 

Tabla DET

DET

Haz clic para verlo mas grande

Bien, con estas dos tablas tenemos para formar la factura. Bien, supongamos que queremos ver la factura 456 (campo CAB_NUMDOC de la tabla CAB), esta factura tiene como Id de cabecera el numero 4, este numero 4 se encuentra en el campo DET_CAB_ID de la tabla DET en dos registros, quiere decir que esta factura tiene 2 items como su puede ver en las imágenes. Por tanto, el parámetro que pasaremos sera el numero de factura 456.

Ahora haremos la consulta, naturalmente ya habrán establecido la conexión a la base de datos, sino puede checar en este post como se establece la conexión.

Escribiremos esta consulta a la tabla CAB dentro de una función que llamaremos sqlC:

Private Function sqlC(Id As Integer)
sqlC = “Select CAB_ID, CAB_NOMCLI, CAB_RUC, CAB_FECHA FROM CAB ”
If Id > 0 Then sqlC = sqlC & “Where CAB_NUMDOC = ” & Id
End Function

Si en Id no va ningún valor entonces el sql no aplicara el Where y por lo tanto mostrara todos los registros de la tabla CAB. Como en este caso le pasaremos el valor 456 en el parámetro Id entonces obtendremos 1 solo registro.

… Y ¿para que meterlo en una función? Muchas veces lo hago por orden, pero principalmente porque estas funciones se pueden reutilizar, mas adelante podria querer un reporte de facturas emitidas pero queriendo ver solo las cabeceras y no los detalles, esta funcion me puede servir en ese caso.

Ahora escribiremos la consulta a la tabla DET dentro de una funcion que llamaremos sqlD

Private Function sqlD()
sqlD = _
“Select DET_ID, DET_CAB_ID, DET_CANT, DET_PROD, DET_PRECIO,” & _
“(DET_CANT * DET_PRECIO) AS SUBT FROM DET”
End Function

Aqui estamos obteniendo todos los registros de la tabla DET ya que lo filtraremos después, en esta parte por performance puede ser interesante filtrar también en la tabla DET los registros para hacer la consulta mas veloz (dependiendo claro de la definición de indices), de momento lo dejaremos asi, la consulta ademas esta multiplicando los campos de cantidad y precio para tener un subtotal.

Ahora lo que sigue es la función que une ambas consultas, esta funcion la llamaremos sqlCD (CD de cabecera-detalle, en todos los casos pueden usar los nombres que mas les acomode):

Private Function sqlCD(Id As Integer)
sqlCD = _
“SHAPE {” & sqlC(Id) & “} as Cabecera ” & _
“APPEND ({” & sqlD & “} as Detalle ” & _
“RELATE CAB_ID to DET_CAB_ID) as Deta”
End Function

Lo que hace este código es que unirá el resultado de la consulta sqlD a sqlC relacionándolos por el campo CAB_ID de la tabla CAB con el campo DET_CAB_ID de la tabla DET, a este sub-resultado lo llamaremos Deta que luego lo usaremos en el reporte para poblar el detalle.

Ahora, para ejecutar todo lo de arriba usamos este codigo (supongamos que lo tenemos en un boton llamado btnReporte)

Private Sub btnReporte_Click()
Dim rsCD As New ADODB.Recordset

rsCD.Open sqlCD(Val(txtID)), dB, 1, 1
Set rptMiReporte.DataSource = rsCD
rptMiReporte.Show
End Sub

Bien, en la primera linea declaramos el recordset rsCD como habitualmente lo hacemos. En la siguiente linea realizamos la consulta pasando como parametro el valor que tengamos en el textbox txtID, es en ese textbox donde escribiremos el numero 456 (u otro) para ejecutar la consulta. dB es el conector que declare previamente, y 1,1 es el cursor y el tipo de bloqueo. Esa consulta se almacena en el recordset rsCD y luego se la asignamos al reporte rptMiReporte y en la siguiente linea lo mostramos.

Ahora veamos como preparamos el reporte. Agregamos un reporte al proyecto y lo nombramos rptMiReporte:

rep1

Luego hacemos clic derecho sobre el reporte y en el menu emergente seleccionamos “Insertar encabezado o pie de grupo”

rep2

Luego agregamos los controles al reporte:

REP3

En cada control rptTextBox deberá poner en la propiedad DataField el nombre del campo NO DE LA TABLA sino del recordset, el reporte no se conecta a la tabla directamente sino al recordset que tiene el resultado de la consulta. Presten especial cuidado con el area de detalle porque los controles de esa area ademas de tener definido el DataField también debe definirse el campo DataMember como Deta que es con el nombre que definimos en la consulta, si usan otro nombre recuerden modificar aquí también.

REP4

Y con eso concluimos la factura (en su forma basica claro), al ejecutar el programa tendremos la factura asi:

REP5

 

¿Y que pasa si no le damos ningun valor? Obtendremos este reporte.

REP6

Naturalmente esto no es una factura solo estoy mostrando las posibilidades, hay que hacer validaciones, restricciones, etc, lo que les muestro aquí es un ejemplo básico que pueda servir como punto de partida.

Si alguien me pregunta ¿como usar este código para mostrar las facturas emitidas en un rango de fechas?

Respuesta: Pasen como parámetros las dos fechas con la cual filtraran en CAB y la vinculación seguirá siendo por el campo de relación. Obviamente tienen que modificar o crear un DataReport diseñándolo debidamente para que se muestre como un reporte tal cual.

Espero haber sido claro, estoy adjuntando el código, ciertamente alli lleva una BD hecha en Access y la intención es que este post tenga mayor alcance.

EjemFactura

Buenas.