Tuesday 13 December 2022

Calling REST API from AX 2012

As we know, JSON is not supported by AX 2012, and calling REST API is challenging.  The alternate way is to use XML webRequest below is the code, which calls the API and gets the response as a string. The code also reads the response and displays it in the info log.

 Here is the code to call REST API from AX 2012


static void Send_RestAPI(Args _args)

{

    System.IO.Stream streamstr,responsestr;

    System.IO.StreamWriter streamWriter;

    System.Net.HttpWebRequest request;

    System.Net.WebHeaderCollection headers;

    System.Net.HttpWebResponse response;

    System.IO.StreamReader reader;

    str wsResponse;

    str xml;

    str                                 xmlOut;

    //

    XmlDocument     xmlDocument;

    XmlNodeList     xmlScriptList;

    XmlNodeList     xmlResponseList;

    XmlElement      nodeScript;

    XmlElement      nodeResponse;

    XMLParseError   xmlError;

     // Define temporary variables

    str _xmlMsg;

    int i, j;

    ;

    new InteropPermission(InteropKind::ClrInterop).assert();

    try

    {

        // request url

        request = System.Net.WebRequest::Create('https://api.xxxxxx.net/1.0/Tokenxxx') as  System.Net.HttpWebRequest;


        // http method

        request.set_Method('Post');


        // headers

        request.set_ContentType("application/xml; charset=utf-8");

        // request body

         xml =

            "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +

            "<RequestToken xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">" +

            "<grant_type>asaasass</grant_type>" +

            "<username>asasadsdsdd</username>" +

            "<password>******</password>" +

            "</RequestToken>";


        streamstr = request.GetRequestStream();

        streamWriter = new System.IO.StreamWriter(streamstr);

        streamWriter.Write(xml);

        streamWriter.Flush();

        streamWriter.Close();

        streamWriter.Dispose();


        // response

        response = request.GetResponse();

        reader = new System.IO.StreamReader(response.GetResponseStream());

        wsResponse = reader.ReadToEnd();



       // --------------------- READ FROM XML-----------------

       xmlDocument             = XmlDocument::newXml(wsResponse);


         // Get the root element and its child nodes

       nodeScript = xmlDocument.getNamedElement("Tokenxx");

       xmlScriptList = nodeScript.childNodes();

       for(i=0; i < xmlScriptList.length(); i++)

        {

            nodeResponse = xmlScriptList.item(i);

            if (nodeResponse.nodeName() == 'rexxxToken')

            {

                 info(strFmt("rToken: %1", nodeResponse.text()));

            }

            if (nodeResponse.nodeName() == 'axxxtoken')

            {

                info(strFmt("axxxtoken: %1", nodeResponse.text()));

            }

            if (nodeResponse.nodeName() == 'expiresIn')

            {

                  info(strFmt("exxxIn: %1", nodeResponse.text()));

            }

            //xmlResponseList = nodeResponse.childNodes();

            //if(nodeResponse.selectSingleNode('refreshToken'))

          /*  if (i == 0)

                info(strFmt("refreshToken: %1", nodeResponse.text()));//innerText()));//text()));

            if (i == 1)

                 info(strFmt("access_token: %1", nodeResponse.text()));

            if (i == 2)

                 info(strFmt("expiresIn: %1", nodeResponse.text()));*/


        }


        // --------------------- READ FROM XML-----------------

        //

        reader.Close();

        //info(wsResponse);

    }

    catch (Exception::CLRError)

    {

        throw error(AifUtil::getClrErrorMessage());

    }

    CodeAccessPermission::revertAssert();

}


Tuesday 15 November 2022

Get default dimension from GeneralJournalAccountEntry


Here we will see how to get the default Dimension from the table GeneralJournalAccountEntryEntity.

1. Add DimensionCombinationEntity as DS 

2. Add a relation with LedgerDimension and Recid as shown below.

3. Add the field Display value from DimensionCombination DS. This will give you the Default Dimension for GeneralJournalAccountEntryEntity.





 

Wednesday 2 November 2022

Dynamic query ranges to run queries in Microsoft Dynamics AX

 Use the standard flexibility of the queries


The standard queries already support a level of flexibility with predefined functions. You can read more about that in this blog post: 

http://daxmusings.codecrib.com/2013/07/custom-query-range-functions-using.html


The class “SysQueryRangeUtil” contains several predefined methods which can be used to make your query more dynamic. The format is plain and simple: ( some code ), you only need the parentheses around the method.


Just to explain how this can be used, here are some samples:




Many other scenarios are possible. This second option gives you the ultimate flexibility to create dynamic queries.

Thursday 27 October 2022

Data entity error "Change Tracking Query error in D365 Finance and Operations Data Export" D365FO

Change Tracking Query error in D365 Finance and Operations Data Export

Recently there was an issue with Change Tracking for BYOD. We keep getting the following error even after Change Tracking is enabled for the data entity.

"Incremental push is not supported for entity X, as change tracking query is not enabled on it."


Solution :

After debugging I found that the system will check if the record exists in the table "AifSqlCdcEnabledTables" with the field TableName and return int (1/0).

Run the below query in SQL to find if the list contains your table.

 select RecId, * from AifSqlCdcEnabledTables       where AifSqlCdcEnabledTables.TableName = 'XXXXX'

In my case, the Primary table was missing and causing the issue, whereas the secondary tables are present in the list. So I have changed the Entity to make the Primary Data source as secondary and secondary as Primary and the issue is fixed.

Wednesday 26 October 2022

SQL Command to Enable and Disable Change Tracking (SQL Server) + D365 FO

 Below are the commands used to enable/disable change tracking in the SQL. 


1. Enable change tracking on the database

ALTER DATABASE AXDBNAME  

SET CHANGE_TRACKING = ON  

(CHANGE_RETENTION = 2 DAYS, AUTO_CLEANUP = ON)  


2. Enable Change Tracking for a Table

ALTER TABLE TableName

ENABLE CHANGE_TRACKING  

WITH (TRACK_COLUMNS_UPDATED = ON)


3. Disable Change Tracking for a Database 


ALTER DATABASE AdventureWorks2012  

SET CHANGE_TRACKING = OFF


4. Disable Change Tracking for a Table

ALTER TABLE TableName  

DISABLE CHANGE_TRACKING;


Tuesday 9 August 2022

Add address to Customer AX 2012 X ++

 //How to create a new customer address using x++ code

static void MK_createCustomerAddress(Args _args)

{

    LogisticsPostalAddress address;

    LogisticsLocation       location;

    LogisticsAddressCountryRegion conuntry;

    LogisticsAddressState           state;

    DirPartyPostalAddressView   addressView;

    DirPartyLocation    partylocation;

    DirParty dirParty;

    DirPartyTable   dirPartyTable;

    CustTable   custTable;

    container  roles;

    LogisticsPostalAddressEntity    postalAddressEntity;

    LogisticsPostalAddressView      paddressView;

    date dVF,dvt;


    select custTable

          join  dirPartyTable where custtable.Party ==   dirPartyTable.RecId

          //join  location where dirPartyTable.PrimaryAddressLocation == location.RecId

                                //&& location. == "Mrs Carol Davis"

           //join  address  where address.Location == location.RecId

        && custtable.AccountNum == "C012436877";

    if (dirPartytable.RecId)

    {

        conuntry = LogisticsAddressCountryRegion::findByISOCode('GB');

        //state = LogisticsAddressState::find(conuntry

        location.Description = 'Mr paddressView';

        location.IsPostalAddress = true;

        location.insert();

        address.Street = "Mr paddressView 909 Market St 3";

        address.ZipCode = "LE65 1AH";

        address.City = "Ashby";

        //address.State = 

        address.CountryRegionId = "GBR";

        address.Location = location.RecId;


        dvf = str2Date("08/9/2022", 213);

        dvt = str2Date("08/9/2029", 213);


        address.ValidFrom = DateTimeUtil::newDateTime(dvf,0);

        address.ValidTo = DateTimeUtil::newDateTime(dateMax(),0);

        //partylocation.Party = CustTable.Party;

        addressView.initFromPostalAddress(address);

        addressView.Location = address.Location;

        addressView.State = 'Derbyshire';

        //addressView.LocationName = logisticsLocation.Description;


        addressView.IsPrimary = true;

        addressView.LocationName = location.Description;


        addressView.Party = CustTable.Party;

       // addressview.initFromPostalAddress(address);

        

        paddressView.initFromPartyPostalAddressView(addressView);

        //paddressView.insert();

        postalAddressEntity = LogisticsPostalAddressEntity::construct();

        postalAddressEntity.createPostalAddress(paddressView);

        

        DirParty = DirParty::constructFromPartyRecId(CustTable.Party);


        roles = [LogisticsLocationRole::findBytype(LogisticsLocationRoleType::Business).RecId];

        DirParty.createOrUpdatePostalAddress(addressView,roles);



        //

        info(strFmt("Customer %1 Description %2 Address %3",custtable.AccountNum,addressView.LocationName,address.Street));

    }

         info("done");


 } 

Friday 11 February 2022

Custom lookup on Form in D365

 [Form]

public class MKCustomLookupForm extends FormRun

{

    [DataSource]

    class MKCustomDSTable

    {

        [DataField]

        class FieldIdCust 

        {

            /// <summary>

            /// </summary>

            /// <param name = "_formControl">Form control</param>

            /// <param name = "_filterStr">Fileter condition, if any</param>

            public void lookup(FormControl _formControl, str _filterStr)

            {

                CustTable   custTable;

                 SysTableLookup sysTableLookup = SysTableLookup::newParameters(tablenum(MKTable), _formControl);

        Query query = new Query();

        QueryBuildDataSource queryBuildDataSource;


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

        queryBuildDataSource.addSortField(fieldNum(MKTable, FieldId), SortOrder::Ascending);


        sysTableLookup.addLookupfield(fieldnum(MKTable, FieldId), true);

        sysTableLookup.addLookupfield(fieldnum(MKTable, FieldName));


        sysTableLookup.parmQuery(query);

        //logic

        sysTableLookup.parmTmpBuffer(fieldList);

        sysTableLookup.performFormLookup();

            }

        }

    }

}

Thursday 27 January 2022

Validate field method for Form DataSource FormDataFieldEventType::Validating

   /// <summary>

    ///

    /// </summary>

    /// <param name="sender"></param>

    /// <param name="e"></param>

    [FormDataFieldEventHandler(formDataFieldStr(VendTable, VendTable, ProcessToProphet), FormDataFieldEventType::Validated)]

    public static void ProcessToProphet_OnValidated(FormDataObject sender, FormDataFieldEventArgs e)

    {

        var dataObject = sender as FormDataObject;

        var args = e as FormDataFieldCancelEventArgs;

        DirPartyBaseType        dirPartyBaseTypeOrg;

        FormDataSource         dataSource = sender.datasource();

        FormRun    element = dataSource.formRun();

        FormControl     dirtype = element.design(0).controlName("type");

        str  dirPartyBaseTypestr =   dirtype.valueStr();

        dirPartyBaseTypeOrg = str2Enum(dirPartyBaseTypeOrg , dirPartyBaseTypestr);

       // info (strFmt ("%1 ", dirtype.valueStr()) )    ;

      //  DirPartyBaseType selected = any2Enum(dirtype.valueStr());

        if (args != null && dataObject != null)

        {

            var vendTable_ds = dataObject.datasource() as FormDataSource;

            if (vendTable_ds != null)

            {

                VendTable vendRec = vendTable_ds.cursor() as VendTable;

                if (vendRec.RecId > 0)

                {

                    if (dirPartyBaseTypeOrg != DirPartyBaseType::Organization)

                    {

                        boolean doCancel = !checkFailed("Not Organization");

                        args.cancel(doCancel);

                    }

                }

            }

        }

    }

Tuesday 18 January 2022

FormDataSourceEventHandler to enable field

 class IPLVendTableTriggerForm_EventHandler

{

    /// <summary>

    ///

    /// </summary>

    /// <param name="sender"></param>

    /// <param name="e"></param>

    [FormDataSourceEventHandler(formDataSourceStr(VendTable, VendTable), FormDataSourceEventType::Activated)]

    public static void VendTable_OnActivated(FormDataSource sender, FormDataSourceEventArgs e)

    {

        VendTable       vendTable = sender.cursor();

        FormDataSource  vendTable_ds = sender.formRun().dataSource("VendTable");

        FormRun         element = sender.formRun();

        FormControl     processTo = element.design(0).controlName("BISTrigger_ProcessTo");


        if(vendTable.ProcessTo == NoYes::Yes)

        {

            processTo.enabled(false);

        }

        else

        {

            processTo.enabled(true);

        }

    }


}

Update NuGet package to new MS D365FO version

1. Import the NuGet package files from LCS for that particular version please take the PU version files only. a. Goto LCS-->Asset Libra...