Buscar en este blog

martes, 30 de septiembre de 2014

Leer un query mediante X++ en Dynamics Ax

Hola,
Esta vez, les voy a poner un fragmento de código para leer un query, en este caso, este código aplica tanto para Ax 2009 como para Ax 2012. Algunas veces cuando creamos un query en el AOT de Ax necesitamos varias cosas para validar que esta consultando la información que requerimos, por ejemplo:
  1. queremos ver algún campo o muchos campos específicos aplicando uno o mas filtros o rangos en distintos campos
  2. queremos ver la sentencia SQL que esta haciendo a la base de datos
  3. queremos ver toooooda la información que arroja la consulta en una tabla

Para cada uno de estos puntos tenemos soluciones.
Para comenzar, aquí nuestro query en el AOT


1. Queremos ver algún campo o muchos campos específicos aplicando uno o mas filtros o rangos en distintos campos; aquí el código en un job indicándole el nombre del query, el filtro que queremos y asignamos a nuestros buffer de las tablas el resultado.
static void grwQueryEtapa1(Args _args)
{   
    QueryBuildRange range;
   
    QueryRun queryRun;
    Counter recordsFound;
    GRWTransportTable gRWTransportTable;
    GRWTransportInput gRWTransportInput;
    ;
   // Indicamos el nombre del query
    queryRun = new QueryRun(queryStr (GRWTransportQuery));
    //Le indicamos que en dataSource numero 1, osea mi primera tabla del query, le queremos aplicar un filtro al campo Shipment
    range = queryRun.query().dataSourceNo( 1).addRange(fieldNum (GRWTransportTable, Shipment));
    range.value( "12345");

    //Con el promt se lanza el dialogo del query por si quisieramos poner mas filtros a mano, esto es opcional, podriamos evitar este if
    if(queryRun.prompt())
    {
        //Recorremos los resultados del query
        while(queryRun.next())
        {
            //Asignamos a cada buffer el resultado del query
            gRWTransportTable = queryRun.get( tableNum(GRWTransportTable));
            gRWTransportInput = queryRun.get( tableNum(GRWTransportInput));
           //Mostramos en pantalla el resultado
            info( strFmt("%1, %2, %3" ,gRWTransportTable.Shipment, gRWTransportTable.MaterialType, gRWTransportTable.MovementType));
            info( strFmt("%1, %2, %3" , gRWTransportInput.QualificationGeneral, gRWTransportInput.QualificationUnit, gRWTransportInput.AuthorizedDate));
            recordsFound++;
        }
    }
    //Mostramos el total de registros leidos
    info( strFmt("registros: %1" , recordsFound));
}

2. Queremos ver la sentencia SQL que esta haciendo a la base de datos
static void grwQueryEtapa1(Args _args)
{   
    QueryBuildRange range;
    QueryRun queryRun;    
    ;
   // Indicamos el nombre del query
    queryRun = new QueryRun(queryStr (GRWTransportQuery));
    //Le indicamos que en dataSource numero 1, osea mi primera tabla del query, le queremos aplicar un filtro al campo Shipment
    range = queryRun.query().dataSourceNo( 1).addRange(fieldNum (GRWTransportTable, RecId));
    range.value( "12345");

    
    //Mostramos la sentencia SQL
    info( strFmt("consulta: %1" queryRun.query().toString()));
}



3. Queremos ver toooooda la información que arroja la consulta en una tabla; Creamos una vista arrastrando nuestro query al datasource de la vista y abrimos la vista como si fuera una tabla, ya sea con click derecho -> Abrir o posicicionandonos en el nombre de la vista y presionando Ctrl+O


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.

lunes, 29 de septiembre de 2014

Listas y containers desde Ax 2012 para .Net mediante business connector (BC)

Aquí les dejo un ejemplo de código que trabaja con el business connector de Ax. En este caso quiero comunicar Ax 2012 R2 con una aplicación externa desarrollada en C# de .Net.
Lo que hago primero es consultar todos los valores de dimensiones en Ax enviando como parámetro el nombre de la dimensión financiera que estoy buscando y guardo esos valores en una lista, el problema surge cuando vas a leer en .Net, yo me encontré con la pregunta de qué usar, si listas o containers de ax, y en caso de usar uno u otro, cómo leerlo en .Net, así que abajo dejo el código para leer tanto containers como listas provenientes de Ax 2012 R2.

Método en Ax

static List getDimensionValuesFromAttribute(Name nameDimensionAttribute)
{
    DimensionAttribute          attribute;
    Common                      common;
    //container                   conValues;
    //int i = 1;
    List                        values = new List(Types::String);
    DimensionFinancialTag       finanacialTag;
    Query                       query;
    QueryRun                    queryRun;
    QueryBuildDataSource        qbDS;
    ;

    attribute = DimensionAttribute::findByName(nameDimensionAttribute);

    query = new Query();
    qbDS = query.addDataSource(attribute.BackingEntityType);
    qbDS.addSelectionField(attribute.ValueAttribute);
    qbDS.addSelectionField(attribute.NameAttribute);

    if(attribute.Type == DimensionAttributeType::CustomList)
    qbDS.addRange( fieldNum (DimensionFinancialTag, FinancialTagCategory)).value(SysQuery::value(attribute.financialTagCategory()));

    queryRun = new QueryRun(query);
    while(queryRun.next())
    {
        common = queryRun.get(attribute.BackingEntityType);
        //Si quisiera usar container
        //conIns(conValues, i, common.(attribute.ValueAttribute));
        //info(strFmt("valor en contenedor: %1, %2", i, conPeek(conValues, i)));
        //i += 1;

        //Usando Listas
        values.addEnd(common.(attribute.ValueAttribute));
    }

    return values;
}


El método en C# quedaría de la siguiente forma:

public List <InfoComboEntidad > ConsultaValoresDimension(string nombreDimensionAx)
        {
            InfoComboEntidad infoCombo = new InfoComboEntidad ();
            List< InfoComboEntidad> listaInfoCombo = new List <InfoComboEntidad >();
            Axapta ax = new Axapta();
            AxaptaObject axObjeto;
            AxaptaObject axObjetoParseado;
            //AxaptaContainer contenedor;

            try
            {
                // autenticación con ax
                ax.Logon( null, null, null, null);

                // Funciona si AX regresa un container
                //contenedor = (AxaptaContainer)ax.CallStaticClassMethod("GRWTools", "getDimensionValuesFromAttribute", "CostCenter");
                //for (int i = 1; i <= contenedor.Count; i++)
                //{
                //    string dato = contenedor.get_Item(1).ToString();
                //}

                // Funciona si AX regresa un List
                axObjeto = ax.CreateAxaptaObject( "Object");
                axObjeto = ( AxaptaObject)ax.CallStaticClassMethod("GRWTools" , "getDimensionValuesFromAttribute" , "CostCenter" );
                axObjetoParseado = ( AxaptaObject)axObjeto.Call("getEnumerator" );

                while ( Convert.ToBoolean(axObjetoParseado.Call( "moveNext")))
                {
                    infoCombo = new InfoComboEntidad();
                    infoCombo.Clave = axObjetoParseado.Call( "current").ToString();
                    infoCombo.Descripcion = axObjetoParseado.Call("current" ).ToString();
                    listaInfoCombo.Add(infoCombo);
                }               

                ax.Logoff();

                return listaInfoCombo;
            }
            catch ( Exception ex)
            {
                ax.Logoff();
                throw new Exception(ex.Message);
            }
        }



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.


domingo, 28 de septiembre de 2014

Visualizar reporte SSRS en AX 2012 mediante código X++

Para visualizar un reporte SSRS creado en Ax 2012, tenemos dos opciones, una es mediante código como les voy a mostrar en el siguiente método y la otra es mediante un Menu Item que se ponga en un menu o en un formulario.

Casos:
1. SSRS basado en query y  sin clase controladora --> Crear MenuItem del tipo Output para el objeto del reporte SSRS ó por código como se muestra abajo.
2. SSRS RDP sin clase controladora --> mismo caso que el anterior.
3. SSRS basado en query y  con clase controladora  --> Crear MenuItem del tipo Output a la clase controladora.
4. SSRS RDP con clase controladora --> mismo caso que el anterior.

Entonces, en caso de no querer usar un menu item, también se puede hacer la llamada por código, aunque la práctica recomendable es usar menu items.

private void ssrsReporteView(RetailStoreId _idStore, date _startProcess)
{
    SrsReportRun srsReportRun;

    //Inicializando el reporte, en este caso mi reporte se llama GRWDepositReport y el diseño se llama Report
    srsReportRun = new SrsReportRun ("GRWDepositReport.Report" );
    srsReportRun.init();
    //Título del reporte que se visualiza en la ventana donde se abre (report viewer)
    srsReportRun.reportCaption( "Reporte de depósito");
    //Si tiene parámetros, se los paso igual por código
    srsReportRun.reportParameter( "DepositDS_IdStore").value(_idStore);
    srsReportRun.reportParameter( "DepositDS_DateStartProcess").value(_startProcess);
    //Le digo que no me presente el dialog de los parámetros, esto para que muestre el reporte directo al usuario
    srsReportRun.showDialog( false);

    if( srsReportRun )
    {
        //Aquí se ejecuta el reporte
        srsReportRun.executeReport();
    }
}

Finalmente, si buscamos imprimir el reporte directo a PDF, aquí el código.

private void ssrsReporteView(RetailStoreId _idStore, date _startProcess)
{
    SrsReportRun srsReportRun;

    //Inicializando el reporte, en este caso mi reporte se llama GRWDepositReport y el diseño se llama Report
    srsReportRun = new SrsReportRun ("GRWDepositReport.Report" );
    srsReportRun.init();
    //Si tiene parámetros, se los paso igual por código
    srsReportRun.reportParameter( "DepositDS_IdStore").value(_idStore);
    srsReportRun.reportParameter( "DepositDS_DateStartProcess").value(_startProcess);
    //Le digo que no me presente el dialog de los parámetros, esto para que muestre el reporte directo al usuario
    srsReportRun.showDialog( false);

    //Imprimir a un archivo PDF
    srsReportRun.printDestinationSettings().printMediumType(SRSPrintMediumType::File);
    srsReportRun.printDestinationSettings().fileFormat(SRSRepoFileFormar::PDF);
    srsReportRun.printDestinationSettings().overwriteFile(true);
    srsReportRun.printDestinationSettings().fileName(@"C:\ReporteDeposito.pdf");

    if( srsReportRun )
    {
        //Aquí se ejecuta el reporte
        srsReportRun.executeReport();
    }
}



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.



<<    Anterior - Crear reportes RDP....                              Siguiente - Repetir encabezados....    >>



<<<<<     Indice de tutorial de Reportes SSRS....


sábado, 27 de septiembre de 2014

Diagramas entidad relación de AX 2012 R2

En AX 2012 R2 existe por parte de Microsoft un Diccionario de Datos  y con diagramas, este diccionario tiene los modelos ERD (Entidad-Relación) por tabla.



Espero que les sea de utilidad


Aquí un ejemplo!!



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.



viernes, 26 de septiembre de 2014

Buscar cuenta contable de un cliente en Ax 2012

Algunas veces necesitamos buscar la cuenta contable de un cliente para hacer asientos contables, la manera mas rápida de hacer esto es con el siguiente código.

private LedgerDimensionDefaultAccount findCustAccount()
{
    CustPostingProfile  _custPostingProfile;
    CustLedgerAccounts custLedgerAccounts;
    DimensionAttributeValueCombination dimensionAttributeValueCombination;
    LedgerDimensionDefaultAccount cuentaClientes;
      DimensionDynamicAccount cuentaClientesDim;
    ;
    _custPostingProfile = CustParameters::find().PostingProfile;
   
    select firstonly custLedgerAccounts
        where custLedgerAccounts.PostingProfile == _custPostingProfile &&
              custLedgerAccounts.AccountCode == TableGroupAll::All;
   
    dimensionAttributeValueCombination = DimensionAttributeValueCombination::find(custLedgerAccounts.SummaryLedgerDimension);

    cuentaClientes = dimensionAttributeValueCombination.RecId;

    return cuentaClientes;

}




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.

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.