Buscar en este blog

viernes, 31 de octubre de 2014

Consultas en tablas con ValidTimeState en Ax 2012

Aquí un tip para cuando es necesario consultar registros en tablas que tienen los campos ValidTo y ValidFrom.


Existe la propiedad ValidTimeStateFieldType para las tablas, cuando nosotros ponemos esta propiedad = UTCDateTime como por ejemplo en la tabla de empleados HCMEmployment (la tabla de empleados) Ax en automático agrega los campos ValidFrom y ValidTo, que son las fechas donde los empleados son válidos, quiere decir que esa tabla va a tener trazabilidad en fechas y que al hacer una consulta  de un select normalito solo te va a mostrar campos validos a la fecha que estés realizando la consulta, por ejemplo, si estas haciendo la consulta hoy 31 de Octubre del 2014 y existe un empleado que "caduco" el 30 de Octubre del 2014, tu consulta no te va a mostrar ese empleado, esto es práctico en algunas consultas, pero algunas veces necesitamos la consulta de toooodos los registros, entonces debemos poner en nuestra consulta el parámetro de validTimeState.

Aquí el ejemplo:



Post relacionados:
Lookup para dimensiones financieras personalizadas



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


No olvides que te puedes unir a la página en Facebook Aprendiendo Dynamics Ax donde únicamente se tratan temas de desarrollo y se busca crear una comunidad de desarrollador@s de Ax en nuestro idioma. 


jueves, 30 de octubre de 2014

Compilación paralela en Dynamics Ax 2012 R2 CU7

Desde que salio el CU7 para AX 2012 R2, se incluyo la funcionalidad del AxBuild que nos ayuda a que la compilación pueda hacerse en menos tiempo. En versiones anteriores, la compilación se realizaba en... 3 o 4 horas aprox, dependiendo del servidor. Pero con la llegada de AxBuild este tiempo se reduce considerablemente, ahora tarda entre 25 y 50 minutos!!! Muy bueno no?

Pues bien, los requisitos para ejecutar esta compilación son:
- Tener acceso al servidor donde esta instalado el server de Ax
- Poder ejecutar la consola de comandos (cmd) con permisos de administrador
- Tener acceso al configuration utility, no importa si es solo en modo lectura

Comenzamos:
1. Lo primero es abrir nuestro configuration utility e identificar cuál AOS es que el queremos compilar, como se muestra a continuación:

Así que este número de AOS nos lo apuntamos en algún notepad porque lo vamos a necesitar para los parámetros de compilación.

2. Buscamos la carpeta bin del SERVIDORde ax, que generalmente esta en esta ruta: 
C:\Program Files\Microsoft Dynamics Ax\60\Server\MicrosoftDynamicsAx\bin

De nuevo, nos la guardamos en el mismo notepad del paso anterior.

3. Buscamos la carpeta bin del CLIENTE de ax, que generalmente esta en esta ruta: 
C:\Program Files(x86)\Microsoft Dynamics Ax\6.0\Client\Bin

De nuevo, nos la guardamos en el mismo notepad del paso anterior.

4. Abrimos un símbolo del sistema o consola de comandos o cmd, como sea que le conozcan pero con permisos de administrador.

5. Nos posicionamos en la ruta del Bin del servidor del punto 2 y ejecutamos el AxBuild como sigue:
axbuild.exe xppcompileall /S=01 /nocleanup /altbin="C:\Program Files(x86)\Microsoft Dynamics Ax\6.0\Client\Bin"

donde: 
S=01 es el AOS que vamos a compilar, el del punto 1
nocleanup es para no borrar los archivos con el detalle de la compilación
altbin es la ruta del bin del cliente, la del paso 3

Y aquí es donde comienza la magia, el servidor ocupa tantos trabajadores pueda, en este caso en mi servidor solo ocupo tres porque es chiquito y después de varios minutos (44:58.566192 exactamente)... la compilación termino!!! Aunque he hecho mas compilaciones en servidores mas potentes donde ha tardado hasta 20 minutos.


Listo, AOS compilado.

Y bueno, aquí el video: http://adf.ly/ta9M9

Nota importante: Cuando tenemos varios AOS, la compilación se debe realizar por cada uno de los AOS. Y aquí va un tip, para hacerlo mas rápido, lo mas recomendable es bajar los servicios de los AOS dependientes, borrar el contenido de la carpeta xppil de los AOS dependientes, compilar SOLO un AOS y cuando levantamos el resto de AOS estos van a volver a generar el contenido del xppil pero ya compilado, asi nos ahorramos tiempo de compilar cada AOS.

Si quieres la documentación oficial de microsoft, aquí la referencia:


Y por cierto, acuerdate de darle click a algún anuncio si el post te sirvio de algo (porque ya me di cuenta que nadie me va a donar ni un dolar para mi cafe y mi dona).


No olvides que te puedes unir a la página en Facebook Aprendiendo Dynamics Ax donde únicamente se tratan temas de desarrollo y se busca crear una comunidad de desarrolladores de Ax en nuestro idioma. 



<<< Anterior - Exportar ModelStore






martes, 28 de octubre de 2014

Exportar e importar ModelStore de Dynamics Ax 2012

En el post anterior vimos cómo exportar un modelo que se haya creado con un fin especifico por ejemplo para un desarrollo o todos los desarrollos de una empresa en particular. Ahora lo que voy a mostrar es cómo exportar e importar todo el modelo (modelstore) completo de ax de un AOS a otro mediante PowerShell, aclarando que existe otra opción de hacerlo mediante AxUtil.

Requisitos:
1. IP del AOS origen
2. IP del AOS destino
3. Usuario con privilegios y acceso a la consola porque vamos a compilar por comando
4. Acceso al SQL destino (por si acaso)

Lo primero es exportar el modelo del primer AOS, que en este caso es un servidor de consultoría y necesito que se mande todo el modelo hacia el ambiente que se esta haciendo de producción. Para eso te debes ubicar en el servidor donde esta instalado el AOS de donde se va a exportar el modelo y debemos asegurarnos que la configuración del Configuration Utility de Ax este apuntando al AOS origen como se muestra en la siguiente imagen.

En este momento no es necesario bajar el servicio de Ax.

Desde ese servidor ir a Inicio > Todos los programas > Herramientas administrativas > Microsoft Dynamics AX Management Shell

Y poner: Export-AXModelStore -File dax_cu7_consul -Details
En este caso dax_cu7_consul es el nombre que va a tener mi archivo de modelo y donde Ax va a guardar todo el modelo. El parámetro "-Details" es para que visualicemos en pantalla los datos de la exportación cuando este en proceso. Este proceso es un poco tardado, alrededor de 10 minutos y el resultado es como el que sigue:

Ya que se exportó el modelo, copiamos nuestro archivo que se haya generado a nuestro ambiente destino (en mi caso es producción) y comencemos con la importación!!!!

El archivo se debe copiar en C:\Windows\System32 ya que es desde esa ruta que se lee con el comando. Ya que lo tenemos en esa ruta, abrimos de nuevo el Magement Shell de Dynamics (Inicio > Todos los programas > Herramientas administrativas > Microsoft Dynamics AX Management Shell) y tecleamos el comando para realizar la importación: 

import-axmodelstore -file -dax_cu7_consul -idonflict:overwrite

He visto que al menos dos de cada tres veces, la importación presenta al menos un problema con una tabla llamada ModelOperationHistory como lo muestra la siguiente imagen:


En este caso, lo que se debe hacer es truncar esta tabla directo desde SQL con la instrucción:
TRUNCATE TABLE dbo.ModelOperationHistory

Después de esto se vuelve a hacer la importación y ahora si se importa sin problemas! (Claro que tarda otros 10 minutos). Y aqui el resultado de que ahora si se completo la importación!!!!


Una vez que termino, aun nos falta camino... debemos reiniciar el servicio de Ax, compilar todo!!! y sincronizar objetos!!, osea como una hora y media mas... pero como entiendo que el reinicio de servicios y la sincronización no tiene mayor problema, entonces vamonos a ver la compilación en paralelo! claro... en el siguiente post   :D



Por cierto, la novedad ahora, es que hay video de esto, por si no quieres leer claro, y si ya lo leiste y me vas a reclamar que porque no lo dije al principio... pues... igual ya lo leiste no? jajaja
Pero bueno, estoy viendo si esto de los videos funciona, y por cierto, acuerdate de darle click a algún anuncio si el post te sirvio de algo (porque ya me di cuenta que nadie me va a donar ni un dolar para mi cafe y mi dona).


No olvides que te puedes unir a la página en Facebook Aprendiendo Dynamics Ax donde únicamente se tratan temas de desarrollo y se busca crear una comunidad de desarrolladores de Ax en nuestro idioma. 



<<<<<     Anterior - Modelos en Ax                                      Compilación parela en Ax - Siguiente     >>>>







jueves, 23 de octubre de 2014

Calcular los impuestos de una línea de venta o de compra por código X++ en AX 2012

Aquí una manera rápida de calcular los impuestos totales de una línea de compra o de venta, siempre es de mucha utilidad.

Para una línea de orden de venta:

static void Job2(Args _args)
{
    real impuestosDeVenta;
    SalesLine   salesLine;
    ;
    select firstOnly salesLine;
 
    impuestosDeVenta = Tax::calcTaxAmount(Salesline.TaxGroup,
                                          Salesline.TaxItemGroup,
                                          Systemdateget(),
                                          Salesline.CurrencyCode,
                                          Salesline.LineAmount,
                                          TaxModuleType::Sales);

    info( strfmt("Impuestos: %1" , impuestosDeVenta));

}


Para una línea de factura de orden de compra:

static void Job2(Args _args)
{
    real impuestosDeVentaCompra;
    VendInvoiceTrans   vendInvoiceTrans;
    ;
    select firstOnly vendInvoiceTrans;
 
    impuestosDeVentaCompra = Tax::calcTaxAmount(vendInvoiceTrans.TaxGroup,
                                          vendInvoiceTrans.TaxItemGroup,
                                          Systemdateget(),
                                          vendInvoiceTrans.CurrencyCode,
                                          vendInvoiceTrans.LineAmount,
                                          TaxModuleType::Purch);

    info( strfmt("Impuestos: %1" , impuestosDeVentaCompra));

}

En Ax, para confirmar nuestro cálculo, podríamos consultarlo en un diario, por ejemplo este impuesto sale de una ventana como la siguiente:



Post relacionados:
Calcular impuestos en ordenes de venta facturadas y no facturadas
Reportes SQL Reporting services (SSRS) en Microsoft Dynamics Ax 2012
Particiones en Ax (un poco de teoría)


Y por cierto, acuérdate de darle click a algún anuncio si el post te sirvió de algo.


No olvides que te puedes unir a la página en Facebook Aprendiendo Dynamics Ax donde únicamente se tratan temas de desarrollo y se busca crear una comunidad de desarrollador@s de Ax en nuestro idioma. 



martes, 7 de octubre de 2014

Leer XML en Ax 2012

Algunas veces en AX vamos a necesitar leer un XML, ya sea que provenga de una respuesta de un servicio WCF o web que estamos consumiendo o simplemente porque tenemos archivos externos que son necesarios leerlos para mostrar información a un usuario o bien porque necesitamos importar información de un xml hacia Ax.
En este caso, yo necesito leer un XML que es la respuesta de consumir un servicio, y tengo dos casos, uno cuando el XML es una respuesta correcta (en ese caso no se presenta nada al usuario) y la otra cuando viene especificado el error, en ese caso se requiere presentar al usuario el código de error y el mensaje de error.

Aquí los dos XML.
1. Cuando el proceso es correcto


2. Cuando el proceso es incorrecto
   <soap:Body>
      <actualizarCatalogosResponse xmlns=" http://sitioPrueba.com/ ">
         <actualizarCatalogosResult>
            <Resultado xmlns="">
               <Proceso>Incorrecto</Proceso>
               <Mensaje>El campo Tipo de Dimensión no es válido (Sólo admite valores: 1, 2, 3).</Mensaje>
               <Codigo>E001</Codigo>
            </Resultado>
         </actualizarCatalogosResult>
      </actualizarCatalogosResponse>
   </soap:Body>
</soap:Envelope>


Y el código en ax, para leer la cadena que me responde el servicio.
public static void showMessageXML( str _sourceXMLFile)
{
    str                 proceso, mensaje, codigoError;
    XmlDocument         xmlDocument;
    XmlNodeList         nodeList;
    XmlNode             node;
    XMLNodeListIterator xmlNodeListIterator;
    Counter             counter;

    xmlDocument = XmlDocument::newXml(_sourceXMLFile);
    nodeList = xmlDocument.selectNodes( '//Resultado'); // Nombre del nodo padre que quiero iterar

    xmlNodeListIterator = new xmlNodeListIterator(nodeList);
    while(xmlNodeListIterator.moreValues())   // Comenzando a iterar el nodo padre
    {
        counter++;
        node = xmlNodeListIterator.value();

        if(node.selectSingleNode('Proceso' ))    // Nombre del nodo que queremos leer 
            proceso = node.selectSingleNode( 'Proceso').text(); // Asignando valor del nodo
        if(node.selectSingleNode('Mensaje' ))
            mensaje = node.selectSingleNode( 'Mensaje').text();
        if(node.selectSingleNode('Codigo' ))
            codigoError = node.selectSingleNode( 'Codigo').text();

        // Si existe un mensaje de error, se presenta al usuario junto con el mensaje de error
        if (node.selectSingleNode('Mensaje' ) && node.selectSingleNode('Codigo')) 
        {
            Box::warning( strFmt("Error: %1 - %2" , codigoError, mensaje), "Interfaz Ax");
        }

        xmlNodeListIterator.nextValue();
    }
}

Y por cierto, acuérdate de darle click a algún anuncio si el post te sirvió de algo.


No olvides que te puedes unir a la página en Facebook Aprendiendo Dynamics Ax donde únicamente se tratan temas de desarrollo y se busca crear una comunidad de desarrollador@s de Ax en nuestro idioma. 



lunes, 6 de octubre de 2014

Crear y postear un LedgerVoucher en Ax 2012

En Dynamics Ax, todas las transacciones financieras sin importar en dónde sean generadas terminan en el módulo de General ledger (libro mayor). Cualquier modificación personalizada que se requiera en este módulo, los desarrolladores debemos usar las APIs de Ax para crearlas, de otra forma, si las creamos directamente en las tablas es muuuuy probable que afectemos la exactitud de los datos financieros.

Entonces, para asegurarnos de mantener la coherencia entre datos debemos usar estas APIs. Uno de ellos es el proceso de ledgervoucher, que en español es como un identificador de la transacción. Esto nos permite "postear" o registrar un voucher financiero en el libro de mayor.

Los vouchers en Dynamics Ax son las entradas de balance financiero representadas en una sola operación. Estos voucher incluyen 2 o mas transacciones del ledger. La API de LedgerVoucher se asegura que todos los criterios requeridos como números de vouchers, periodos financieros, cuentas ledger, dimensiones financieras, saldos y otros, son válidos.

Aquí voy a poner cómo podemos crear y postear un ledger voucher desde código. Voy a crear un único voucher con dos transacciones (operaciones).

private container postTransToLedger()
{
    Voucher                     voucher;
    RefRecId                    generalJournalEntryRecid;
    LedgerVoucher               voucherPaym;
    LedgerVoucherObject         voucherObject;
    LedgerVoucherTransObject    voucherTransObject;
    Ledger                      ledger = Ledger::findByLegalEntity(CompanyInfo::find().RecId);
    GRWModoPagoCaja             modoPago = GRWModoPagoCaja::find(this.ModoPagoId);
    NumberSeq                   numberSeq;
    ;

    numberSeq           = numberSeq::newGetNum(GRWCajaParameters::CajaVoucherTrans());
    voucher             = numberSeq.num();
    voucherPaym         = LedgerVoucher::newLedgerPost( DetailSummary::Detail,
                                                        SysModule::Ledger,
                                                        GRWCajaParameters::CajaVoucherTrans().numberSequenceTable().NumberSequence);

    voucherObject       = LedgerVoucherObject::newVoucher(  voucher,
                                                            this.TransDate,
                                                            SysModule::Ledger,
                                                            LedgerTransType::None,
                                                            false,
                                                            OperationsTax::Current);

    voucherPaym.addVoucher(voucherObject);

    voucherTransObject = LedgerVoucherTransObject::newBasicDefault( voucherObject,
                                                                    LedgerPostingType::LedgerJournal,
                                                                    DimensionDefaultingService::serviceCreateLedgerDimension(modoPago.CuentaPuente), // Cuenta de cuenta puente
                                                                    this.Currencycode,
                                                                    this.AmountCur,
                                                                    this.AmountMST,
                                                                    Currency::curAmount2CurAmount(this.AmountMST, ledger.AccountingCurrency, ledger.ReportingCurrency));

    voucherTransObject.parmTransTxt(strfmt("Transacción de caja Manual Puenteo %1", this.ModoPagoId));
    voucherObject.addTrans(voucherTransObject);

    voucherTransObject = LedgerVoucherTransObject::newBasicDefault( voucherObject,
                                                                    LedgerPostingType::LedgerJournal,
                                                                    DimensionDefaultingService::serviceCreateLedgerDimension(modoPago.CuentaCaja), // Cuenta caja
                                                                    this.Currencycode,
                                                                    -this.AmountCur,
                                                                    -this.AmountMST,
                                                                    -Currency::curAmount2CurAmount(this.AmountMST, ledger.AccountingCurrency, ledger.ReportingCurrency));

    voucherTransObject.parmTransTxt(strfmt("Transacción de caja Manual Concentración %1", this.ModoPagoId));
    voucherObject.addTrans(voucherTransObject);

    voucherPaym.end();



    return [voucher, generalJournalEntryRecid];
}


No olvides que te puedes unir a la página en Facebook Aprendiendo Dynamics Ax donde únicamente se tratan temas de desarrollo y se busca crear una comunidad de desarrolladores de Ax en nuestro idioma.

viernes, 3 de octubre de 2014

Multiselect de grid en Ax 2012

Mas de una vez nos hemos topado con querer seleccionar varias lineas de un grid para luego hacer alguna acción con las mismas, como por ejemplo, actualizar estatus de las seleccionadas o imprimir un reporte solo con los datos de esas líneas seleccionadas por el usuario o mostrar al usuario una suma de montos de esas líneas, etc.


La manera mas rápida para recorrer esas líneas, es crear un botón para que en los argumentos se lleve los registros seleccionados y nosotros solo tengamos que leerlos, un ejemplo de lectura es el siguiente:

static void main(Args _args)
{    
    GRW_StoreHistorical table_storeHistorical;
    MultiSelectionContext claseLista;
    boolean statusPagado = true ;
    Amount sumaPago;
    ;

    //Asignamos la lista al objeto MultiSelectionContext
    claseLista = _args.multiSelectionContext();
    
    //Asignamos a nuestro buffer el primer registro de la lista que selecciono el usuario
    table_storeHistorical = claseLista.getFirst();
    
    //Si existe el registro, leemos y comenzamos a recorrer los registros del multiselect
    while (table_storeHistorical.RecId != 0 )
    {
        if (table_storeHistorical.StatusAudit != GRWStatusAudit::Pay)
            statusPagado = false ;
        else
            sumaPago += table_storeHistorical.AmountAccumulated;

        //Asignamos el siguiente elemento de la lista que seleccionó el usuario al buffer
        table_storeHistorical = claseLista.getNext();
    }

    if (!statusPagado)
        box::warning( "Todas las líneas seleccionadas No han sido pagadas." "No es posible reimprimir");
    else
        info(strfmt("Total de pagos: %1", sumaPago));

}

No olvides que te puedes unir a la página en Facebook Aprendiendo Dynamics Ax donde únicamente se tratan temas de desarrollo y se busca crear una comunidad de desarrolladores de Ax en nuestro idioma.

jueves, 2 de octubre de 2014

Crear lookup en clase dialog en Ax 2012

Muchas veces necesitamos crear una clase dialog con un lookup para que el usuario seleccione un valor desde un combo para que después internamente nosotros podamos hacer "algo" con ese valor.

Una clase dialog vería así:

Ahora el código:

public class GRWPurchAgreementAddItem  extends RunBase
{
    //Declarando lo que se va a ocupar en la clase
    PurchAgreementHeader    purchAgreementHeader;
    TmpPurchLine            tmpPurchLine;
    ItemId                  itemId;
    DialogField             dlgItemId;
}

public ClassDescription caption()
{
    //Titulo que aparece el usuario (se ve en la imagen)
    return "Agregar artículo al acuerdo" ;
}

//Esta la importante
protected Object dialog()
{
    DialogRunbase       dialog = super ();

    FormStringControl   control;
    ;
    dialog = super ();

    //Aquí agregamos el campo a nuestra clase dialog, hasta todo igual que una
    //clase dialog cualquiera
    dlgItemId = dialog.addFieldValue( extendedTypeStr (ItemId), itemId);
   
    //Esta es la parte importante, aquí sobreescribimos el lookup del control que acabamos 
    //de crear y lo llenamos con el resultado de nuestro query (metodo en la misma clase: itemIdLookup)
    //que es la consulta que nos va a traer los datos que queremos presentar
    control = dlgItemId.control();
    control.registerOverrideMethod( methodStr (FormStringControl, lookup), methodStr (GRWPurchAgreementAddItem, itemIdLookup), this);

    return dialog;
}

public boolean getFromDialog()
{
    ;
    //Rescatamos el valor que el usuario seleccione en el combo
    itemId   = dlgItemId.value();

    return super();
}

public void itemIdLookup(FormStringControl _control)
{
    //Consultamos los valores que queremos presentar en el combo
    SysTableLookup          sysTableLookup;
    QueryBuildDataSource    queryBuildDataSource;
    QueryBuildRange         rangeitemId;
    Query                   query = new Query();

    queryBuildDataSource = query.addDataSource( tablenum(AgreementLine));

    sysTableLookup = SysTableLookup::newParameters( tablenum(AgreementLine), _control);
    sysTableLookup.addLookupfield( fieldnum(AgreementLine, ItemId),  true);
    //Aqui solo estoy aplicando un filtro al query que yo necesito por regla de negocio
    rangeitemId = query.dataSourceNo( 1).addRange(fieldNum (AgreementLine, Agreement));
    rangeitemId.value( int642str(purchAgreementHeader.RecId));

    sysTableLookup.parmQuery(query);
    sysTableLookup.performFormLookup();
}

public container pack()
{
    return connull();
}

public PurchAgreementHeader parmPurchAgreementHeader(PurchAgreementHeader _purchAgreementHeader = purchAgreementHeader)
{
    purchAgreementHeader = _purchAgreementHeader;
    return purchAgreementHeader;
}

public TmpPurchLine parmTmpPurchLine(TmpPurchLine _tmpPurchLine = tmpPurchLine)
{
    tmpPurchLine = _tmpPurchLine;
    return tmpPurchLine;
}

public void run()
{
    ;
    try
    {
        ttsbegin;
        //Aquí va lo que se vaya a hacer con el valor que selecciono el usuario en el combo
        tmpPurchLine.ItemId = itemId;
        tmpPurchLine.insert();

        ttscommit;
    }
    catch (Exception::Deadlock)
    {
        retry;
    }
    catch (Exception::UpdateConflict)
    {
        if (appl.ttsLevel() == 0)
        {
            if (xSession::currentRetryCount() >= 3)
            {
                throw Exception::UpdateConflictNotRecovered;
            }
            else
            {
                retry;
            }
        }
        else
        {
            throw Exception::UpdateConflict;
        }
    }
}

public boolean runsImpersonated()
{
    return true;
}

public boolean unpack(container packedClass)
{
    return true;
}


En mi caso yo necesitaba mandar llamar la clase a partir de un form


public void grwAddItem()
{
    GRWPurchAgreementAddItem gRWPurchAgreementAddItem;
    ;
    //Enviar a la clase, la temporal de las líneas
    //Enviar el purchAgreementHeader
    gRWPurchAgreementAddItem = new GRWPurchAgreementAddItem();
    gRWPurchAgreementAddItem.parmPurchAgreementHeader(purchAgreementHeader);
    gRWPurchAgreementAddItem.parmTmpPurchLine(TmpPurchLine);

    if (gRWPurchAgreementAddItem.prompt())
    {
        gRWPurchAgreementAddItem.run();
        TmpPurchLine_ds.executeQuery();
    }
}


Lookup para dimensiones financieras de organización
Rellenar o incluir caracteres en cadenas con strFix



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


No olvides que te puedes unir a la página en Facebook Aprendiendo Dynamics Ax donde únicamente se tratan temas de desarrollo y se busca crear una comunidad de desarrollador@s de Ax en nuestro idioma.