Buscar en este blog

jueves, 25 de septiembre de 2014

Crear reportes SSRS usando RDP (Report Data Provider) con clase controladora en Dynamics Ax 2012

La clase controller mejor conocida en el mundo latino como clase controladora implementa el patrón estándar de diseño MVC (Model View Controller). Lo que significa que con este patrón podemos separar la lógica de negocio de la interfaz de usuario, en este caso, podemos separar la lógica de negocio de la vista de nuestro reporte. 
Model: Es el responsable de recuperar los datos y la lógica de negocio, esto incluye: queries, datos de métodos o clases diseñadas para traer datos.
View: Esta es la interfaz de usuario, nuestro diseño del reporte.
Controller: Orquesta el flujo entre el Model y el View.

En general usamos este tipo de reportes para proveer grouping y validaciones para los parámetros de un reporte SSRS. Esta clase también es necesaria cuando necesitamos personalizar el dialogo que se muestra cuando abrimos un reporte, por ejemplo, cuando ciertos campos no son parámetros del reporte.

Para crearnos un reporte en Ax 2012, necesitamos crearnos algunos elementos, el orden sería el siguiente:
1. Un query que contenga todos los campos que vamos  necesitar en nuestro reporte
2. Una tabla de tipo temporal
3. Clase "contrato", en la declaración de la clase  usar el atributo [DataContractAttribute] y por cada parámetro necesario para el reporte usar un método parm con el atributo [DataMemberAttribute]
4. Clase controladora que extienda de SrsReportRunController, en esta clase crear el método main para definir nombre de reporte y diseño (este es nuestro punto de inicio del reporte). Sobreescribir los métodos necesarios para el arranque de nuestro reporte.
5. Clase DataProvider que extienda de SrsReportDataProviderPreProcess, en la declaración usar los atributos SRSReportQueryAttribute para indicar el query con que el trabaja en conjunto y el atributo SRSReportParameterAttribute  para indicar la clase contrato que va a proveer los parámetros del reporte. Crear el método getNombreTabla para devolver los registros de la tabla temporal con el atributo SRSReportDataSetAttribute.

Clase contrato
Esta clase es usada para crear metodos parm para el reporte. Si le vamos a pasar parametros al reporte necesitamos crear tantos métodos parm como parámetros necesitemos como "datamembers" de nuestra clase. Por ejemplo:

[DataMemberAttribute('UseQuantity')]
public ProdBOMJournalQty parmUseQuantity(ProdBOMJournalQty _useQuantity = useQuantity)
{
    useQuantity = _useQuantity;
    return useQuantity;
}

Crearemos un método de estos por cada parámetro que el usuario nos pida o que necesitemos.

Es posible definir las etiquetas y los textos de ayuda de los parámetros que definamos en la clase controladora, por ejemplo en cada uno de ellos pondríamos los atributos como sigue:

[
    DataMemberAttribute("DifferencesOnly"),   //Esto es para el nombre del parametro
    SysOperationGroupMemberAttribute("DifferencesOnlyGroup"),  //Para indicar si pertenece a un grupo de parametros
    SysOperationLabelAttribute( literalstr("@SYS57830" )),    //Indica la etiqueta del paramtero que se va a mostrar al usuario
    SysOperationHelpTextAttribute( literalstr("@SYS328295" )),  //Puedes poner un texto de ayuda que aparecerá en la barra inferior cuando posicionen el cursor en el parámetro
    SysOperationGroupMemberAttribute("1"),  //El orden en que aparecerá el parámetro si pertenece a un grupo
]
public boolean parmDifferencesOnly(boolean _differencesOnly = differencesOnly)
{
    differencesOnly = _differencesOnly;
    return differencesOnly;
}



Clase controladora
La clase controladora debe extender de SrsReportRunController. Esta es la clase que lanza el reporte y de esta clase es la cual hacemos el menu item para ponerlo en nuestros menus o en algun botón. Por ejemplo:

public class ProdPackListController extends SrsReportRunController
{
}

En el método "main" se debe definir el nombre de reporte y el diseño. El método parmReportName es usado para especificar el nombre del reporte que correrá con esta clase, en caso de tener dos diseños para un reporte, aquí es donde deberá especificarse.

El método startOperation() es usado para correr el reporte.

El método parmShowDialog(false) sirve para lanzar solo el reporte sin presentar el dialog antes. Esto se pone antes del starOperation() en el main.

Por ejemplo:
public static void main(Args _args)
{
    SrsReportRunController    controller = new  ProdPackListController  ();
    controller.parmReportName("ProdPackList.Report");
    controller.parmArgs(_args);
    controller.startOperation();
}

Cancelación de métodos:
PreRunModifyContract: Para modificar el query antes que el reporte se corra. Por ejemplo:
protected void preRunModifyContract()
{
    GNProdPacklistContract    contract;
    contract = this.parmReportContract().parmRdpContract() as GNProdPacklistContract;
}

prePromptModifyContract: Para modificar el query antes de que el dialog del reporte se lance.

preRunValidate: Aqui es posible manejar los warnings con este método.

La clase controladora debe implementar la interfase SySOperationValidatable. Esto solo es necesario en caso de que se necesite validación de los parámetros del reporte. Después se necesitara sobreescribir el método "validate" de la clase controladora.


Clase DataProvider
La clase DataProvider ya la vimos en el post pasado, pero aqui hay otro ejemplo, debe extender de SrsReportDataProviderPreProcess. Por ejemplo:
[   SRSReportQueryAttribute(queryStr(ProdPackList)),
    SRSReportParameterAttribute(classStr(ProdPacklistContract))]
class ProdPacklistDP extends SrsReportDataProviderPreProcess
{

    boolean                     showQuery;
 
    boolean                     firstPage;
    ProdTable                   prodTable;
    ProdId                      prodId;

    CompanyInfo                 companyInfo;

    ProdBOM                     prodBOM;
    InventDim                   inventDim;

   ProdPackingSlipDetailsTmp prodPackingSlipDetailsTmp;
    BarcodeSetup                barCodeSetup;
    BarcodeSetupId  barcodeSetupId;
    Barcode         barcode;
}

Debemos crear un método con el atributo SRSReportDataSetAttribute que devuelva todos los registros de la tabla temporal. Por ejemplo:
[    SRSReportDataSetAttribute(tableStr('ProdPackingSlipDetailsTmp'))]
public ProdPackingSlipDetailsTmp getProdPacklistDetailsTmp()
{
    select prodPackingSlipDetailsTmp;
    return prodPackingSlipDetailsTmp;
}

Y sobreescribimos el método ProcessReport, que es donde vamos a escribir la lógica del negocio y a llenar nuestra tabla temporal. Por ejemplo:
[SysEntryPointAttribute(false)]
public void processReport()
{
    QueryRun                queryRun;
     ProdPacklistContract contract       = this.parmDataContract() as  ProdPacklistContract ;
    // Set the userconnection to use on table.
    // This is required to ensure that createdTransactionId of inserted record is different than default 
           transaction.
    prodPackingSlipDetailsTmp.setConnection(this.parmUserConnection());
    this.init();
    this.setupBarcode();
    queryRun                        = new QueryRun(this.parmQuery());
    while (queryRun.next())
    {
        prodTable       = queryRun.get(tableNum(ProdTable));
        inventDim       = queryRun.get(tableNum(InventDim));
        prodId          = prodTable.ProdId;
        if(prodTable.InventRefType==InventRefType::Sales)
        {
           this.insertHeader();
           this.insertDetails(prodId,inventDim.inventSerialId);
        }
    }
}

Aquí les dejo otro processReport un poco mas complejo, por que si hacen reportes complejos, siempre se necesitan filtros personalizados.
public void processReport()
{
    QueryRun        qr;
    QueryBuildDataSource    qbDS;
    QueryBuildRange rangeOnlyOpen;
    QueryBuildRange rangeOnlyInvoices;
    QueryBuildRange rangeTransDate;
    QueryBuildRange rangeDueDate;
    ;
    contract            = this.parmDataContract() as GRWCustAccStatementContract;
    primaryCurrencyCode = CompanyInfo::standardCurrency();

    qr = new QueryRun(this.parmQuery());

    if(contract.parmOnlyOpen())
    {
        rangeOnlyOpen = qr.query().dataSourceNo( 2).addRange(fieldNum (CustTrans, Closed));
        rangeOnlyOpen.value(queryRangeConcat(queryRange(contract.parmToDate() + 1, maxdate ()), dateNull()));
    }

    if(contract.parmOnlyInvoices())
    {
        rangeOnlyInvoices = qr.query().dataSourceNo( 2).addRange(fieldNum (CustTrans, AmountCur));
        rangeOnlyInvoices.value(SysQuery::value( ">0"));
    }

    if(SysQuery::value(contract.parmFromDate()) != "" && SysQuery::value(contract.parmToDate()) != "")
    {
        rangeTransDate = qr.query().dataSourceNo( 2).addRange(fieldNum (CustTrans, DueDate));
        rangeTransDate.value( strFmt("(DueDate>=%1) && (DueDate<=%2)" , date2StrXpp(contract.parmFromDate()), date2StrXpp(contract.parmToDate())));
    }

    While (qr.next())
    {
        if(qr.changed(tableNum(custTable)))
        {
            custTable = qr.get( tableNum(custTable));
        }
        if(qr.changed(tableNum(CustTrans)))
        {
            custTrans = qr.get( tableNum(custTrans));
            custTrans.transactionPerDate(contract.parmToDate());
            if(contract.parmOnlyOpen() && !custTrans.remainAmountCur())
                continue;

            this.proccessRecord();
        }
    }
}




Por último, te invito a que te unas a la página de facebook recién creada para estar al día con las actualizaciones del blog y que podamos tener más comunicación. La meta? es hacer la comunidad de habla hispana mas grande sobre Dynamics Ax en cuestiones de desarrollo.



Y por cierto, acuerdate de darle click a algún anuncio si el post te sirvio de algo.



No hay comentarios.:

Publicar un comentario