#35124
Javier Ader
Participante

(este post iba a ser mas largo y explicativo… pero se me corto la luz…. despues, en cuanto me haga un poco de tiempo libre, hago un documento en pdf explicando un poco mas la idea y lo subo)
Bueno me habia quedado pensando en lo que planteo Antonio y lo retome porque pienso que la idea que se puede aplicare no solo Infos si no en general a otros tipos de ventanas que muestren pequeños busquedas/reportes (no necesariameten contra una tabla simple; si no algo en general).
Mi idea ataca a las dos formas de customizacion que planteo Antonio; la primera parte (acceso en relacion a perfiles) no requiere modificar tablas existens (obviamente si requiere agregar tablas y relativamente bastante codigo); la segunda requiere que se modifique AD_Column para que en el caso de que el tipo sea TableDir (esto es, el tipo que se mapea al control que permite disparar un InfoXXX), se permita agregar que “columnas externas” (depues explico un poco mas esto) se van a mostrar.

Las tablas seria basicamentge
-AD_Info: tiene bastantes cosas, pero la mas importante es una AD_Table_ID; esto es para saber que AD_Info usar en un determinado Lookup; de toadas maneras pienso que no requerir que sea NOT NULL; en realidad estas AD_Info puede usarse para pequeñas ventanas que no esten asociadas a una tabla en particular. Tampoco se requiere que AD_Table_ID sea unico, pero pienso que deberia NO ser necesario hacer mas una para una determinada tabla; la razón es que el control a nivel de perfil se da a nivel de que columnas se muestran (igual agregar tambien controal de acceso a este nivel no traeria mayores problemas).
Otro dato importante son : la sentencia sql “interna”, y el alias sobre esta. Si miran el codigo van a ver que las sentencias sql finales toman la forma

Code:
Select FROM
( SELECT INTERNO
WHERE INTERNO — depende de los parámetros de búsqueda, de “sql dinamico” que tiene el Tabledir, y posiblemente de las las restricciones “Role.addAccess”
ORDER BY ALGO — esto es requerido para que la paginación sea consistente
LIMIT X, OFFSET Y — esto aplica la paginación, actualme siempre en salto de 100
) AS NombreTablaExterna — este nombre es al que me refiero como “alias”

Un determinado AD_Info tiene uno o mas AD_InfoColumn; estas AD_InfoColumns son las que se van a usar para inferir lo que llame .

-AD_InfoColumn: esta es la representación en diccionario de datos de lo que en el código definí con la clase Info_ColumnNew (el “New” porque la clase Info_Column ya existia…). Los datos mas importantes son : la clase de “resullSet”(la clase con que se tiene que leer desde la base de datos) como la clase con que se muestra en el grid (esta clase sirve por ej para distinguir entre el conjunto de AD_InfoColumn cual va a ser la que va a tener el “id” de la entidad siendo mostrada; esto es necesario para saber que columna es la que tiene por ej, el M_Product.M_Produt_ID en InfoProduct); si se tiene que mostrar en el grid y en que posición “relativa”; si se tiene que mostrar en el panel de detalle y en que posición “relativa”; la posición relativa en el resultSet (“relativa” significa que lo importante es el orden entre las varias AD_InfoColumn dentro de una determinada AD_Info; desde codigo, esto luego se normaliza a secuencias de la forma 1,2,3,4… o 0,1,2,3…. según sea el caso), y finalmente una expresión de columna sql arbitraria (eso si, la expresión usada para calculara tiene que hacer referencia “NombreDeTablaExterna”, no al nombre de la tabla que se haya usado internamente ). Esta ultima expresión es la que va a ir parar como un elemento en y por defecto NO tiene que tener parametros sql (si se requieren parametros, se tiene que poner dentro de la expresion sql interna; no en la expresion de las columnas).
Ademas, hay que agregar un campo Value (esto es para la customizacion a nivel de ventanas; explico mas adelante), y otras que son mas cosméticas (ancho preferido de las columans; nombre corto en las columnas; tooltip).

-AD_InfoColumnAccess : esta es simplemente una relación de acceso entre roles y AD_InfoColumn. Como todos estos tipos de acceso la semantica es: si no hay ninguna restricción sobre un AD_InfoColumn, entocnes todos tienen acceso; si hay uno o mas SOLO los Roles especificados tienen acceso. La columnas importantes serian las obvias: AD_COlumnInfo_ID y AD_Role_ID (posiblemente también IsActive)

Ahora bien, el tema del “filtrado por contexto”, funciona agregando una sola columna string a AD_Column (se podría crear oooootra tabla solo para esto, pero no lo veo muy necesario) y “creando una convención” de filtrado. Llamemosle a esta columna InfoColumnFilter. La semantica detras del valor de esta string prodria ser:
-vacia, null o “*”, no se filtran ninguna
-“+CalueInfoColumn1,ValueInfoColumn2, etc” : se muestra SOLO las columnas que tengan estos “ValueInfoColumnX” en su “value”.
-“-ValueInfoColumn2,ValueInfoColumn2, etc” : se muestran todas las columnas salvo las que tengan un Value especificados en la lista
Esto es, la primer forma es “sin restricción”, la segunda (la que tiene prefijo +) es “sola estas”; la tercera (prefijo -), todas menos las que especifico.

Bien; el algoritmo de “restricción” seria mas o menos
1) Obtentgo el AD_Info (filtrando por AD_Table_ID)
2) Obtento todas sus AD_InfoColumn (menos la inactivas) relacionadas al AD_Info (filtrando por AD_Info_ID obtenido en 1)
2) Filtro la AD_InfoColumn segun el rol actual (este paso y el anteriro se puede hacer usando una sola sentencia sql; y en realidad queda mas claro hacerlo asi.
Conceptualmente la sentencia sql seria

Code:
select * from AD_infoColumn IC
where (
O AD_InfoColumnAcces no tiene registrro para AD_InfoColumn
O AD_InfoColumnAccess tiene una entrada para AD_infoCOlumn y el id del rol actual
)
AND IsActive = ‘Y’ AND AD_Info_ID =

Esto básicamente obtiene una lista de InfoColumn_New; tomando esta lista y el filtrado de acceso a info (el cual la clase VLookup pasa como parametro al InfoXXXX), se consigue otra lista aplicando el filtrado de “contexto”. Esto es, esta lista final, previa “normalización” de indices (indices en resulset, que comienza en 1; e indices en “grid” y en “panel” de detalles que que comienzan en 1). Va a ser la que finalmente , forme las en el sql final.

A esto, se le agrega un par de cosas mas que actualmente no estan soportadas y “deberia salir andado”. En particular la ordenación: el tema aca es que el ordenamiento se tiene que dar ANTES de aplicar la paginación, si no no tiene sentido (no tiene sentido ordernar mediante ; se tiene que hacer sobre las columnas que resueltan del select interno).
Para esto, pienso que hay que agregara a AD_Info otra columna, tambien una string que tambien siga una convención simple. Lo mas simple que veo eso esto:
OrderAvailables::Columna;: Columna2>, etc.
Con esta string la GUI puede generar un pequeño control visual que le permita al usario aplicar el ordenamiento.

Doy un ejemplo para que quede mas claro: info para MBParner:
AD_Info:

Code:
AD_Table_ID:Id de AD_Table_id de C_BParner
InnerSql: “SELECT * FROM C_BPartner”
NameExternSelect: “EC”
OrdersAvailables:”Nombre:C_BPartner.Name;CUIT:C_BPartner.CUIT”
LimitPagination: 100
(notese que OrdersAvaliable depende de la como se conforme InnserSql)

Algunos AD_InfoColumn

Code:
AD_InfoColumn1:
-value:Id
-Class: “ID”
En grid: si, en detalles, no
-Orden visual: en grid 1, en detalles 0 (si no va en detalles este valor no se va a usar),
-orden result set: 10
-ExternCol : “EC.C_BPartner_ID as ID” (el as no es strictametne necesario)
(notense que el externCol uiliza EC; esto es, AD_Info.NameExternSelect)

AD_InfoColum2:
-Value: Name
-Class: String
-En grid, si, en detalles, si
-orden: en grid 20, en detales 20
-orden result set: 20
-ExtenrnCol: “EC.Name as Name”

AD_InfoColumn3:
-Value:CUIT
-Class: String
-En grid, si, en detalles , si
-orden visual ; en grid 30, en detalles 30
-orden result set: 30
-ExternCol:”EC.CUIT as CUIT”

AD_InfoColumn4:
-Value:TaxIVA
-Class:String
-En Grid, NO, en detalles , si
-orden visual: en grid 0; en detalles 40
-orden result set: 40
-ExternoCol :
“(Select Name form C_TaxCategory where C_TaxCategory_ID = EC.C_Tax_Category_ID) AS CatIVA”
(notese que esta ultima columna externa es calculada; pero es calculada a partir de EC.C_Tax_Category_ID; NO de C_BPartner.C_BPartner_ID;

Bueno, en que sql resultaria todo esto? Asumiendo que no hay fitlrado por rol ni por contexto y que esta visulaizando la segunda pagina, ordenana por nombre (por defecto se ordena por la primer entrada en “OrdersAvailables”, la sentencia final seria algo asi

Code:
SELECT
EC.C_BPartner_ID as ID,
EC.Name as Name,
EC.CUIT as CUIT,
(Select Name form C_TaxCategory where C_TaxCategory_ID = EC.C_Tax_Category_ID) AS CatIVA
FROM
–aca viene el select interno, el filtrado, el orden y la paginación
(
SELECT * FROM C_BPartner — select interno
WHERE xxxxx — este valor depende de los paramtros de busqueda
ORDER BY Name — este se toma de alguna de las opciones en OrdersAvailables
LIMIT 100, OFFSET 99 — limit se toma de AD_Info; offset del estado interno del Info haciendo simplemente la cuneta (LIMIT * pagina que se quiere ver) – 1
) AS EC — este es el “alias” tomado de AD_Info

Con el resultado de esta query, y los datos dados en la lista normalizada de InfoColumn_New formada a partir de las AD_InfoColumns se puede mapear cada uno de los valores a sus respetivas posiciones tanto en el grid como en el panel detalle.

Notese:
1) el sql interno puede ser realmente complejo; puede ser tranquilamnete un “join” un “union” de varias tablas.
2) lo unico que se cambia entre busqueda y busqueda y que depende de los parametros de busqueda, es WHERE xxxx; este WHERE xxx contiene tanto el filtrado del usuario, como el filtrado “dinamico” de los table dir (validación de table) como las restricciones de acceso dadas a un permir sobre determinadas filas de determinadas talbas (ver MRole.addAccessRole(String); a metodo hay que llamarlo con el sql intenro, NO con el sql final)
3) Actualmente la logica de paginación no contempla retonrar el total de items “sin paginación” ; esto es facil de agregar haciendo el acceso en dos pasos: el primer generar una string final interna sin paginación y “redondeada” por un “SELECT Count(*)”; esto es algo como

Code:
SELECT COUNT(*) FROM
(SELECT INTERNO CON WHERE xxx , pero sin ordenamiento ni LIMIT y OFFSET) AS NombreExterno

Este select no calcula las columnas externas, ni las ordenas; solo aplica el WHERE xxx a la sql interna y al resultado de esto le obtiene la cantidad.

4) Todas estructuras de datos y la logica detras no tiene porque estar estrictamente asociada a un InfoXXXX en particular. Es facil utilizar todo esto para ventanas de consultas en general ; digamos, ver las facturas adeudadas. Utilizando esta estructra se puede generar rapidamente un “form” que tenga casi todo el trabajo echo por este miniframework diseñado originalmente para Info’s. De aca que AD_Info.AD_Table_ID pueda ser null (esto es, no esta necesariamente asociada a una tabla).

5) Siguiendo la idea de 4, a los infos o estas ventanas de consulta rapida, se les puede agregar reportes jasper “on the fly” (aunque aca no se si es tan facil) que tome como dataSource algo generado por la misma sql final (posiblemente sin paginación). El formato del reporte puede ser, a falta de otra informacion, de un formato por defecto; incluso con opciones simples como “mostrar tambine los detalles?”; “imprimir información de parametros?”. Fijense que hay bastante información como para generar un reporte Jasper generico (ancho de columnas, clases, si esta en detalle, si esta en el grid)

6) Uniendo 4 y 5 se logra algo que para mi puede darle a todo esto mucho más valor agregado: se elimina para muchos escenarios el reporteador interno, con la siguientes cualidades:
-uno tiene un preview directo (piensen el resultado de una busqueda de una InfoXXXX como un preview de que datos se van a imprimir)

-uno puede “reejecutar” todo el informe sin que siquiera se invoque a Jasper (porque lo que un hace es en realidad reejecutar la query y ve los resultados en una grilla Java)

-los parámetros no se piden de entrada; el usaurio los va probando y reejecutando la busqueda hasta que logra haber listado lo que realmente quiere reportear. Recién ahi apreta un boton “Print” (de aca en mas entra Jasper y el reporte generado al “vuelo”; lo unico que hay que darle es o la sentecia sql final, o directamente el resultSet)

Ok, 5 y 6 se van un poco de las manos, pero no lo veo para nada imposible y faltaría para igualar al reporteador interno actual, la habilidad de “zoom”. Esto creo que se puede hacer fácilmente via la columna “class” en AD_InfoColumn; por ej, una convención es que la “clase” ID_XXXX es de tipo entero y en realidad refiere a un id de la tabla XXXX; por ej, si el InfoProduct (o en un reporte de productos en general) aparece una columna (una AD_InfoColumn) con clase “ID_C_TaxCategory”, toda la lógica lo trata como un entero (y en general no lo visualiza, ni el grid ni el detalle), pero al seleccionar una determinada fila se activan botones de zoom (con un nombre descriptivo), uno por cada ID_xxx que se haya encontrado en el proceso de generación las InfoColumn_New; si el usuario presiona el botón zoom asociada ID_TaxCategory, se busca el valor asociado con la fila actual y se invoca el el método de zoom correspondiente para la tabla “C_Tax_Category” con el valor encontrado). Esto ultimo esta bueno independientemente de se implementa 5 y 6 ya que sirve para los Info.

Que opinan?

PD : bueno, termine escribiendo mas de que antes de que se me corte la luz….