Tuesday, 26 September 2017

DIXF: Importing any table data with Financial Dimension (default Dimension) field in the table

DIXF: Importing any table data with Financial Dimension (default Dimension) field in the table

Lets say, I want to import the data into MKxyz Table with 3 fields as below.

1. StId
2. StName
3. DefaultDimension

Source: 
In the source we have the Dimension as
Department : A01
Purpose : Std
Ex:
StID,StName,Dept,Purpose
1,abc,A01,Std
2,xyz,A02,Ptc

Solution :

Here I am not going to talk about, how to import data using DIXF for that you can refer here
So now we are going to talk about, how to convert the above CSV file to use with DIXF.
we need to convert the file as below.
StID,StName,DefaultDimension
1,abc,A01--Std
2,xyz,A02--Ptc

To summaries
DefaultDimension is give as per the dimension sequence setup
lets say the sequence is
1. Department ,Cost-center and purpose
then the value in Default Dimension field is DD-CC-PP
2. Department ,Cost-center and purpose and we don't have cost-center value
then the value in Default Dimension field is DD--PP
3. Department ,Cost-center and purpose and we don't have purpose value
then the value in Default Dimension field is DD-CC-




Sunday, 24 September 2017

Update Customer / Vendor Email address or phone number AX 2012 X++

In this topic, we will learn how to update the customer / vendor Email Address  or Phone number
static void MK_CustomeremailUpdate(Args _args)
{
    DirPartyTable            dirparty;
    DirPartyLocation dirPartyLocation;
    LogisticsElectronicAddress elecAddress;
    LogisticsElectronicAddressRole elecAddressRole;
    LogisticsLocationRole locRole;
    CustTable                           cust = CustTable::find('CXXX'');///Replace with vendTable for Vendors
// Update all the customers use the while loop or you can just use the above fine method to get the customer.  
while select cust
    where cust.AccountNum != " "
    {
        select forUpdate firstOnly elecAddress
        where elecAddress.Location == DirPartyLocation::findOrCreate(cust.Party, 0).Location
        && elecAddress.Type == LogisticsElectronicAddressMethodType::Email; // Replace with Phone to update phone number
        if (elecAddress.RecId)
        {
            info(strFmt("%1 , %2 - email - %3", cust.AccountNum, cust.name(),elecAddress.Locator));
            ttsBegin;
            elecAddress.Type = LogisticsElectronicAddressMethodType::Email; // for phone type should be phone
            elecAddress.Locator = 'xxx.aaa@xxx.com'; // for phone number give the new Phone number
            elecAddress.update();
            ttsCommit;
            info(strFmt("%1 , %2 - email - %3", cust.AccountNum, cust.name(),elecAddress.Locator));
        }
       
    }
}

Thursday, 7 September 2017

Virtual Machine(VM) setup to access Dynamics 365 for Operation instance.

https://dynamicsax708.wordpress.com/2017/03/16/virtual-machinevm-setup-to-access-dynamics-365-for-operation-instance/

References:

SI NOTOPICSSOURCES
1Access Microsoft Dynamics 365 for Operations instanceshttps://ax.help.dynamics.com/en/wiki/access-microsoft-dynamics-ax-7-instances-2/
2Installation of Hyper-V and creating virtual machinehttps://technet.microsoft.com/enus/library/hh846766(v=ws.11).aspx

Tuesday, 8 August 2017

Using system classes to connect to database x++

Here in this topic, we will learn about how to connect SQL database using standard classes (without creating ODBC connection.

The code below uses the ODBCConnection class and LoginProperty class to connect to SQL DB.

server static ODBCConnection createConnection(str _SQLServerName, str _DatabaseName)
{
    ODBCConnection  connection;
    LoginProperty   loginProperty;
    ;
    loginProperty = new LoginProperty();
    loginProperty.setServer(_SQLServerName);


    loginProperty.setDatabase(_DatabaseName);

    connection = new ODBCConnection(loginProperty);

    return connection;
}

It will return the connection, which can be used to execute SQL statements.

Friday, 4 August 2017

Unpicking the sales order all at once AX 2012 x++

Unpicking the sales order all at once AX 2012 x++

Some times we need to cancel the picking sales order, for whatever reason.
if you want to unpick an  sales order, you have to go to the sales line and, one-by-one, unpick the order. You click on the Inventory button and select the Pick option. In the form, check the autocreate box, then click Post All in the lower area of the form. If you regularly have to pick and unpick orders, especially if you have a lot of lines on your orders, this can become very tedious.

To achieve the above functionality, we can just create a button on Sales Order form . Now I'm going to put an Unpick button on the sales order form at the order level and allow certain users (security will be used) to do this. I will most likely allow multiple orders to be selected so you can unpick multiple orders with one click of a button. The code for the this is as below:

1. Create a class "Mk_PickSalesUnReservation"
2. Add a main method with Args as parameters // This will accept SalesLine as Data source
static void main(Args _args)
{
}
3. add a run method and call it.

///
/// Contains the code that does the actual job of the class.
///
void  run()
{
    InventTrans                 inventTransLocal;
    InventTransOriginSalesLine  inventTransOriginSalesLine;
    InventTransOrigin           inventTransOrigin;
    SalesLine                   salesLine;
    InventMovement              movement;
    InventTransWMS_Pick         inventTransWMS_Pick;
    TmpInventTransWMS           tmpInventTransWMS;
    Query                       baseQueryInventTrans;
    QueryBuildDataSource        qbdsInventTrans;
    MultiSelectionHelper helper = MultiSelectionHelper::construct();

    baseQueryInventTrans =  new Query();
    qbdsInventTrans = baseQueryInventTrans.addDataSource(tableNum(InventTrans));
    baseQueryInventTrans.dataSourceTable(tableNum(InventTrans));
    qbdsInventTrans.clearDynalinks();
    qbdsInventTrans.clearRanges();
    qbdsInventTrans.addRange(fieldNum(InventTrans,StatusReceipt)).value(SysQuery::value(StatusReceipt::None));
    qbdsInventTrans.addRange(fieldNum(InventTrans,StatusIssue)).value(SysQuery::range(StatusIssue::Picked,StatusIssue::OnOrder));

    //inventTransWMS_Pick = InventTransWMS_Pick::newStandard(tmpInventTransWMS,baseQueryInventTrans);

    helper.parmDatasource(salesLine_ds);

    salesLine = helper.getFirst();
    while (salesLine.RecId != 0)
    {
        salesLine.selectForUpdate(true);
        //sALESlINE.iNVENTtRANSiD -> inventtransorigin.inventtransid -> inventrans
        select inventTransLocal
        where  inventTransLocal.ItemId                  == salesline.ItemId
            && inventTransLocal.StatusIssue             == StatusIssue::Picked
        exists join inventTransOrigin
        where   inventTransOrigin.RecId                  == inventTransLocal.InventTransOrigin
        &&      inventtransorigin.inventtransid         == salesline.InventTransId;

        if (inventTransLocal.RecId)
        {          

            tmpInventTransWMS = null;
            tmpInventTransWMS.initFromInventTrans(inventTransLocal);
            tmpInventTransWMS.InventQty = inventTransLocal.StatusIssue == StatusIssue::Picked ? inventTransLocal.Qty : -inventTransLocal.Qty;
            tmpInventTransWMS.insert();
            inventTransWMS_Pick = InventTransWMS_Pick::newStandard(tmpInventTransWMS,baseQueryInventTrans);
            inventTransWMS_Pick.createFromInventTrans(inventTransLocal);
            inventTransWMS_Pick.updateInvent();
        }
        salesLine = helper.getNext();
    }

}

4. Create a action menu Item for the above class
5. Add a menu Item button on Sales order form.

Sunday, 23 July 2017

Framework to create XML file from a query and read XML data

We have many frameworks like AIF, DIXF, etc. already available out of box in dynamics AX to export or import the data, you guys might have guessing why again a new framework?

The reason is simple, I just wanted to build one simple framework, which takes a query as input and gives the XML file with all the fields in the query. It is as that simple.

In the below example, I have a created a query for sales order, with Data source as SalesTable and Salesline. I have added few fields from Sales Table and also form sales line. The query look like this.


Query
Now I want to pass this Query and get the XML with all the fields which are ther in the query and also with the where condition (Range)

Now I have to create a FW class, which has a construc method which takes the Query as parameter as shown below.
public static server MK_ExportHelp construct(Query _query)
{
    return new MK_ExportHelp(_query);
}
This method call's the new method as below
protected void new(Query _query)
{
    dataQuery = _query;
}

///

/// This method is used to create a XML form the query
///
///
/// File path where the xml will be stored
///
///
/// Primary field to identify unique record
///
///
/// Export helper class by Mallik on 25/07/2017
///

public void getXMLFile(Filename _filePath,FieldID   _uniqueFieldID)
{
    container               lDSTablesCon;
    container               lFieldLists;
    int                     j,k;
    Query                   lquery;
    QueryRun                queryRun;
    QueryBuildDataSource    qbds;
    TableId                 lTableId;
    str                     tableName;
    Common                  commonTable;
    FileIoPermission        lPerm;
    str                     uniqFieldVal,preVal;
    boolean                 headChanged;

    XmlDocument xmlDoc; //to create blank XMLDocument
    XmlElement xmlRoot; // XML root node
    XmlElement xmlField;
    XmlElement xmlRecord;
    XmlElement xmlChild;
    XMLWriter xmlWriter;
    InventTable inventTable;
    DictTable dTable;// = new DictTable(tablenum(InventTable));
    DictField dField;
    int i, fieldIds;
    FieldId     lfieldId;
    str value;

    lDSTablesCon = this.getDataSource();
    if(lDSTablesCon != conNull())
    {
        lquery = new query(dataQuery);
        xmlDoc = XmlDocument::newBlank();
        xmlRoot = xmlDoc.createElement(lquery.name());

        queryRun = new QueryRun(lquery);
        k =0;
        //headChanged  = false;
        while (queryRun.next())
        {
            k++;
            headChanged = false;
            for(j = 1; j <= conLen(lDSTablesCon); j++)
            {
                tableName = conPeek(lDSTablesCon,j);
                lTableId = tableName2id(tableName);
                dTable = new DictTable(lTableId);
                lFieldLists = conPeek(allFieldlist,j);
                fieldIds = conLen(lFieldLists);
                commonTable =  queryRun.get(lTableId);

                if (k != 1 && uniqFieldVal != commonTable.(_uniqueFieldID))
                {
                    uniqFieldVal = commonTable.(_uniqueFieldID);
                    if (preVal != uniqFieldVal)
                    {
                        headChanged = true;
                    }
                    else
                    {
                        headChanged = false;
                    }
                    //continue;
                }
                if (k != 1 && !headChanged)
                {
                    headChanged = true;
                    continue;
                }
                preVal  = commonTable.(_uniqueFieldID);
                //info(common.(fieldnum(custTable, name)));
                // Loop through all the fields in the record
                // Create a XmlElement (record) to hold the
                // contents of the current record.
                if (j == 2)
                {
                    xmlChild = xmlDoc.createElement(tableName);

                }
                else
                {
                    xmlRecord = xmlDoc.createElement(tableName);
                }
                for (i=1; i<=fieldIds; i++)
                {
                    lfieldId = dTable.fieldName2Id(conPeek(lFieldLists,i));
                    // Find the DictField object that matches
                    // the fieldId
                    dField = dTable.fieldObject(lfieldId);

                    // Create a new XmlElement (field) and
                    // have the name equal to the name of the
                    // dictField
                    xmlField = xmlDoc.createElement(dField.name());
                    // Convert values to string. I have just added
                    // a couple of conversion as an example.
                    // Use tableName.(fieldId) instead of fieldname
                    // to get the content of the field.
                    switch (dField.baseType())
                    {
                        case Types::Int64 :
                        value = int642str(commonTable.(lfieldId));
                        break;
                        case Types::Integer :
                        value = int2str(commonTable.(lfieldId));
                        break;
                        default :
                        value = commonTable.(lfieldId);
                        break;
                    }
                    // Set the innerText of the XmlElement (field)
                    // to the value from the table
                    xmlField.innerText(value);
                    // Append the field as a child node to the record

                    if (j == 2)
                    {
                       xmlChild.appendChild(xmlField);
                       xmlRecord.appendChild(xmlChild);
                    }
                    else
                    {
                        xmlRecord.appendChild(xmlField);
                    }
                }// end of for
                // Add the record as a child node to the root
                xmlRoot.appendChild(xmlRecord);
            }// end of TableID for loop
           /* // Add the record as a child node to the root
            xmlRoot.appendChild(xmlRecord);*/
        }// end of while
        // Add the root to the XmlDocument
        xmlDoc.appendChild(xmlRoot);
        lPerm = new FileIoPermission(_filePath,'RW');
        lPerm.assert();
        // Create a new object of the XmlWriter class
        // in order to be able to write the xml to a file
        xmlWriter = XMLWriter::newFile(_filePath);//----------------------------------@"c:\Items.xml");
        // Write the content of the XmlDocument to the
        // file as specified by the XmlWriter
        xmlDoc.writeTo(xmlWriter);
    }// end of if
}

Internally this method calls getTable method and getFields which will return respective datasource tables and all the fields in the query. And here is the code for it.
public container getDataSource()
{
    query                   query;
    queryRun                queryRun;
    int                     numDataTables,numFields;
    int                     i,j;
    TableId                 lTableId;
    container               conDataTables;
    container               conFieldList;
    QueryBuildDataSource    qbds;
    QueryBuildFieldList     fieldList;
    query = new query(dataQuery);//  ('CustTableCube');
    numDataTables = query.dataSourceCount();
    allFieldlist = conNull();
    for (i =1;i <=numDataTables;i++)
    {
       calledDataSource = true;
       qbds =  query.dataSourceNo(i);
       lTableId = qbds.table();
       conDataTables = conIns(conDataTables,i,tableId2name(lTableId));
       fieldList = qbds.fields();
       numFields = fieldList.fieldCount();
       conFieldList = conNull();
       for (j=1; j <= numFields; j++)
        {
            conFieldList = conIns (conFieldList,j,fieldId2name(lTableId,fieldList.field(j)));
        }
        allFieldlist = conIns(allFieldlist,i,conFieldList);
    }
    return conDataTables;
}
public container getAllFields()
{
    if (calledDataSource)
    {
        return allFieldlist;
    }
    else
    {
        this.getDataSource();
        return allFieldlist;
    }
}

--------------------------------------------------------------------------------------------------------------------------Now the second part to read the XML data and return a container
--------------------------------------------------------------------------------------------------------------------------

The Final part is now, how we can read the data form the above created XML. Below is the code which can read the data form the XML and returns a container.
public container readXMLData(FileName    _fileName)
{
    #define.node('Envelope')
    XmlDocument xmlDocument;
    XmlNode     xmlInformationNode;
    XmlNode     xmlInformationNode1;
    XmlNodeList xmlInformationsNodeList;
    XmlNodeList xmlChildNodeList;
    XmlNodeList xmlChildNodeList1;
    XmlNodeList xmlChildNodeList2;
    XmlNodeList xmlChildNodeList3;
    XmlNodeList xmlChildNodeList4;
    XmlNodeList xmlChildNodeList5;
    XmlElement  nodeTable;
    XmlElement  salesTableNode;
    XmlElement  salesLineNode;
    XmlElement  finDimNode;
    int         m,i,l;
    int         j, k;
    container        headerCon, lineCon, recCon, retCon;
    int          nodecount;
    boolean      finDim = false;
    FileIOPermission        fioPermission;
    Commaio                 file;
    Map             recHeadData,recLineData;
    #define.filename(_fileName)
    #file
    CustAccount  accNum;
    nodecount = 0;

    // Assert permission.
    fioPermission = new FileIOPermission(#filename ,"R");
    fioPermission.assert();


    xmlDocument             = xmlDocument::newFile(_fileName);
    info(xmlDocument.documentElement().nodeName());
    xmlInformationsNodeList = xmlDocument.documentElement().childNodes();
    m = xmlInformationsNodeList.length();
    setPrefix("@SYS98689");
    headerCon = conNull();
    lineCon = conNull();
    recCon = conNull();
    retCon = conNull();
    recHeadData = new Map(Types::String, Types::String);
    recLineData = new Map(Types::String, Types::String);
    for ( m = 0; m < xmlInformationsNodeList.length(); m++) //Num of Recoreds-
    {
        headerCon = conNull();
        lineCon = conNull();
        recHeadData = new Map(Types::String, Types::String);
        recLineData = new Map(Types::String, Types::String);
        xmlChildNodeList = xmlInformationsNodeList.item(m).childNodes();
        j = xmlChildNodeList.length();
        l = 0;
        for (j = 0; j < xmlChildNodeList.length() ; j++)// num rows in record (Fields) header nodes-
        {
            xmlChildNodeList1 = xmlChildNodeList.item(j).childNodes();
            xmlInformationNode1 = xmlChildNodeList1.item(j);
            nodeTable    = xmlChildNodeList.nextNode();
            if (xmlInformationNode1 != null && xmlInformationNode1.hasChildNodes())
            {
                l++;
                k = xmlChildNodeList1.length();
                for (k = 0; k < xmlChildNodeList1.length() ; k++)// Fields has sub elemets (Lines) Node
                {
                    xmlInformationNode = xmlChildNodeList1.item(k);
                    nodeTable    = xmlChildNodeList1.nextNode();
                    recLineData.insert(nodeTable.nodeName(),nodeTable.text());
                 
                    if (xmlInformationNode.hasChildNodes())//getNamedElement('CustAccount');
                    {
                        xmlChildNodeList2 = xmlInformationNode.childNodes();
                        nodeTable = xmlChildNodeList2.nextNode();
                        // Line record
                       // lineCon = lineCon +  nodeTable.text();
                    }
                    //info(strFmt('%1 -> %2', xmlInformationNode.nodeName(), xmlInformationNode.innerText()));
                }// for k
                //lineCon += recLineData.pack();
                lineCon = conIns(lineCon,l, recLineData.pack());
            }
            else if (j == xmlChildNodeList.length() -1)
            {
                l++;
                for (k = 0; k < xmlChildNodeList1.length() ; k++)// Fields has sub elemets (Lines) Node
                {
                    xmlInformationNode = xmlChildNodeList1.item(k);
                    nodeTable    = xmlChildNodeList1.nextNode();
                    recLineData.insert(nodeTable.nodeName(),nodeTable.text());
                    if (xmlInformationNode.hasChildNodes())//getNamedElement('CustAccount');
                    {
                        xmlChildNodeList2 = xmlInformationNode.childNodes();
                        nodeTable = xmlChildNodeList2.nextNode();
                        // Line record
                        //lineCon = lineCon +  nodeTable.text();
                    }
                }
                //lineCon += recLineData.pack();
                lineCon = conIns(lineCon,l, recLineData.pack());
            }
            else
            {
                // Header record
                recHeadData.insert(nodeTable.nodeName(),nodeTable.text());
                headerCon = headerCon + nodeTable.text();
            }

        }// for j
        recCon = conIns(recCon,1, recHeadData.pack());//headerCon);
       // recCon = conIns(recCon,2, recLineData.pack());//lineCon);
        recCon = conIns(recCon,2,lineCon);
        retCon += recCon;
        recCon = conNull();
    }// for m
    CodeAccessPermission::revertAssert();
    return retCon;
}

-------------------------------------------------------------------------------------------------------------------------
How to use the framework class to do the things
--------------------------------------------------------------------------------------------------------------------------
We are almost there, now we have to read the container and do your stuff, for this I have created a job, which uses the above framework class and to create a XML and also reads the data from the created XML and pints the info.

static void MK_ExportText(Args _args)
{
    MK_ExportHelp      eh;
    container           conTab, recCon;
    container           hCon,lCon,lineCon;
    str                 fname;
    Filename            fileName;
    int                 i,j,k;
    Map                 hMap;
    Map                 lMap;
    MapEnumerator       henu,lenu;

    Query       lquery = new Query('MK_SalesOrder');
    //Construct the framework class object  with the query.
    eh = MK_ExportHelp::construct(lquery);

    fname =MK_ExportHelp::getFileName("AMS_SOTest1");

    fileName   = @"\\Dscax201201\aif\OutBound\Error\MK_SalesOrder.xml";
    // Call this method to create CSV file
    //eh.getCSVFile(fileName,fieldNum(SalesTable, SalesId),'|');

    // Call this method to create XML file
    eh.getXMLFile(fileName,fieldNum(SalesTable, SalesId));
 
    //Call this method to read the data from the above created XML file.
    conTab = eh.readXMLData(fileName);
    i = 1;
    while (i <= conLen(conTab))
    {
        recCon = conNull();
        hCon = conNull();
        lCon = conNull();
        hCon = conPeek(conTab,i);
        lCon = conPeek(conTab,i+1);
        hMap = Map::create(hCon);//conPeek(recCon,1));
        henu = new MapEnumerator(hMap);
        while (henu.moveNext())
        {
            info(strFmt("Header %1 -- > %2",henu.currentValue(),henu.currentKey()));
        }
        for (j = 1; j <= conLen(lCon);j ++)
        {
            lineCon = conNull();
            lineCon = conPeek(lCon,j);
            lMap = Map::create(lineCon);//conPeek(recCon,2));
            lenu = new MapEnumerator(lMap);
            while (lenu.moveNext())
            {
                info(strFmt("Line %1 -- > %2",lenu.currentValue(),lenu.currentKey()));
            }
        }
     
        i += 2;
    }
}

Wednesday, 31 May 2017

AIF Outbound Message - Replace data / inserting data into the XML message

In this blog we will lean how to replace the data in the Outbound XML ..


For this we need to override the "processingRecod()" method.

Lets see how it will work.

First we will look at the method
public void processingRecord(Common common)
{
 
    super(common);
}

so you can see that, it passes the common record.

First check if the common record, exist and is of your Table to be modified and update the buffer and assign back the data to common.
In the below eg. I am modifying the VendAccount to CustAccount.

public void processingRecord(Common common)
{
    PurchTable  lpurchTable;
    MK_IntercompanySOParam     parmSO = AMS_IntercompanySOParam::find();
    if (common)
    {
        if (common.TableId == lpurchTable.TableId)
        {
            lpurchTable = common.data();
            lpurchTable.OrderAccount = parmSO.CustAccount;
            common.data(lpurchTable);
           
        }
       
    }
    super(common);
}

After this, we need generate a incremental CIL.

-------------------------------------------------------------------------------------------------------------------------
Alternatively, we can use the outbound pipeline by adding this code in the execute method of the pipeline:
public void execute(AifPipelineComponentRecId componentRecId, AifMessage message, AifPipelineParms parms)
{
    XmlDocument xmlDoc = new XmlDocument();
    XmlNode xmlNode;
    XmlNodeList xmlNodeList;
    XmlElement xmlElement;
    ;

    xmlDoc = XmlDocument::newXml(message.getXml());
    xmlNodeList = xmlDoc.getElementsByTagName("SalesTable");

    // Obtain the first xmlNode.
    xmlNode = xmlNodeList.nextNode();
    xmlElement = xmlNode.getNamedElement("CustAccount");
    xmlElement.text("4005"); // If you use the sample CreateSalesOrder.xml file, the value should be 4000 instead of 4005.
    message.setXml(xmlDoc.xml());
}

Mallik 

How to Get Trade Agreement Line Discount Percentage by Item and Date in D365FO Using X++

  In Microsoft Dynamics 365 Finance and Operations (D365FO), trade agreements are used to manage prices and discounts for customers, vendor...