Saturday, December 24, 2011

Manage, Update & Deploy Workflows in AX 2012

I found a very good video describing how we can manage, update and deploy workflows in AX 2012. This demo is demonstrating how workflow is configured and running in Purchase Requisition Process.

Link to video: Manage, Update & Deploy Workflows

Import sales orders from master and details text files

The are many code snippets which you can find easily over the internet to import sales orders from a text file or from excel. The purpose of this post is to share my experience of uploading data into sales order form from two different text files.

void ImportSalesOrders()
{
    Container                  con;
    CustTable                 custTable;
    SalesTable                salesTable;
    SalesLine                  salesLine;
    Headers                    headers;
    Stock                        details;

    num                           newSalesId;
    Description                custDescription;
    
    InventTable                inventTable;
    InventDim                  inventDim;
    InventSiteId               inventSiteId;
    InventLocationId         inventLocationId;


    CustAccount             custAccount;
    real                           itemPrice;
    ItemId                       itemId;

    InventDim                  frominventDim,ToinventDim;

    int                             x,y;
    NumberSeq               num;

    SysOperationProgress progressbar = new SysOperationProgress();
    int i;
    ;

    progressbar.setCaption('Importing Journals');
    progressbar.setAnimation(filenameHeader);
    progressbar.setTotal(30000);
    for (i = 1; i <= 30000; i++)
    {
        progressbar.setText(strfmt("@SYS105740", i));
        progressbar.setCount(i, 1);
    }

    this.importData();
    ttsbegin;
    while select headers
    {
        SalesTable = SalesTable::findDocumentNumber(headers.DocumentNumber);

        if(!SalesTable)
        {
            //create sales table
            salesTable.initValue();

            num = NumberSeq::newGetNum(SalesParameters::numRefSalesId());
            newSalesId = num.num();

            if (salesTable::exist(newSalesId))
            {
                num.abort();
                throw error("@SYS23020");
            }

            salesTable.SalesId = newSalesId;
            num.used();

            if(custDescription != "")
            {
                salesTable.SalesName = custDescription;
            }

            custAccount = headers.CustAccount;

            //fetch the customer details from cust table using account no selected from drop-down list
            select * from custTable where custTable.AccountNum == custAccount;

            salesTable.CustAccount = custTable.AccountNum;
            salesTable.InvoiceAccount = custTable.InvoiceAccount;
            salesTable.SalesType = SalesType::Sales;
            salesTable.SalesStatus = SalesStatus::None;
            salesTable.CurrencyCode = custTable.Currency;

            salesTable.CustGroup = custTable.CustGroup;
            salesTable.DeliveryDateControlType = SalesDeliveryDateControlType::SalesLeadTime;

            //Set dates as systemDateTime
            salesTable.ReceiptDateRequested = systemdateget();
            salesTable.ShippingDateRequested = systemdateget();

            salesTable.LanguageId = 'en-us';
            salesTable.DlvMode = '10';

            inventSiteId = headers.InventSiteId;
            inventLocationId = headers.InventSiteId;

            salesTable.InventSiteId = inventSiteId;
            salesTable.InventLocationId = inventLocationId;
            salesTable.DocumentNumber = headers.DocumentNumber;

            //Craete Sales Order
            salesTable.insert();

            while select details
            where headers.DocumentNumber == details.DocumentNumber
            {
                salesLine.clear();

                // Create Sales Order Line
                salesLine.SalesId = salesTable.SalesId;
                salesLine.initFromSalesTable(salesTable);

                itemId = details.ItemId;

                salesLine.ItemId = itemId;
                salesLine.SalesUnit = InventTable::find(salesLine.ItemId).salesUnitId();

                select * from inventTable where inventTable.ItemId == salesLine.ItemId;
                salesLine.initFromInventTable(inventTable);

                inventDim.clear();
                inventDim.InventSiteId = inventSiteId;
                inventDim.InventLocationId = inventSiteId;

                salesLine.InventDimId = InventDim::findOrCreate(inventDim).inventDimId;

                //Set Qty as 1 by default
                salesLine.SalesQty = details.Qty;
                salesLine.ConfirmedDlv = salesTable.ShippingDateConfirmed;
                salesLine.lineNum = SalesLine::lastLineNum(salesLine.salesId) + 1.0;
                salesLine.RemainInventPhysical = 1;
                salesLine.RemainSalesPhysical = 1;
                salesLine.DlvMode = '10';
                salesLine.SalesStatus = SalesStatus::None;
                salesLine.SalesPrice = details.UnitPrice;
                salesLine.LineAmount = salesLine.SalesQty * salesLine.SalesPrice;

                //Insert sales line items
                salesLine.insert();
            }
        }
        ++x;
    }
    ttscommit;
    info(strfmt("%1 Header(s) imported with their details", x));
}

void importData()
{
    Container               con;
    // I created these two table headers and details to make the import process fast I had huge data files to import
    Headers                 headers;
    Stock                   details;

    TextBuffer              tbHeader = new TextBuffer();
    TextBuffer              tbDetails = new TextBuffer();

    int                     cntHeader, cntDetails;
    int                     numLinesHeader, numLinesDetails;
    container               inLineHeader, inLineDetails;

    int i;
    ;

    tbHeader.fromFile(filenameHeader);
    tbDetails.fromFile(filenameDetail);

    numLinesHeader = tbHeader.numLines();
    numLinesDetails = tbDetails.numLines();

    delete_from headers;
    delete_from details;

    if(numLinesHeader)
    {
        ttsbegin;
        inLineHeader = str2Con(tbHeader.nextToken(true));


        for(cntHeader = 1; cntHeader < numLinesHeader; ++cntHeader)
        {
            inLineHeader = str2Con(tbHeader.nextToken(true));

            headers.DocumentNumber = conpeek(inLineHeader, 1);
            headers.CustAccount = conpeek(inLineHeader, 2);
            headers.insert();
        }

        inLineDetails = str2Con(tbDetails.nextToken(true));

        for(cntDetails = 1; cntDetails < numLinesDetails; ++cntDetails)
        {
            inLineDetails = str2Con(tbDetails.nextToken(true));

            details.DocumentNumber = conpeek(inLineDetails, 1);
            details.ItemId = conpeek(inLineDetails, 2);
            details.Qty = conpeek(inLineDetails, 3);
            details.UnitPrice = conpeek(inLineDetails, 4);
            details.Discount = conpeek(inLineDetails, 5);
            details.UnitDiscount = conpeek(inLineDetails, 6);
            details.insert();
        }

        ttscommit;
    }
}

Thursday, December 22, 2011

Dynamics AX and database synchronization error

Due to huge customization in application we may come across database and application synchronization failure issue. Sometimes you will see this error when you add some new fields in CustTable or SalesTable, this is just for an example you can have this issue in any table or view.

Following job will forcefully synchronize all the tables in AOT.


static void forceDbSynchronize(Args _args)
{
    Dictionary                             dict;
    int                                        idx, lastIdx, totalTables;
    TableId                                  tableId;
    Application                            application;
    SysOperationProgress           progress;
    StackBase                            errorStack;
    ErrorTxt                                 errorTxt;
    ;

    application = new Application();
    dict = new Dictionary();
    totalTables = dict.tableCnt();
    progress = new SysOperationProgress();
    progress.setTotal(totalTables);
    progress.setCaption("@SYS90206");
    errorStack = new StackBase(Types::String);

    lastIdx = 0;
    try
    {
        for (idx = lastIdx+1; idx <= totalTables; idx++)
        {
            tableId = dict.tableCnt2Id(idx);
            progress.setText(dict.tableName(tableId));

            lastIdx = idx;
            application.dbSynchronize(tableId, false, true, false);
            progress.incCount();
        }
    }
    catch (Exception::Error)
    {
        errorTxt = strFmt("Error in table '%1' (%2)", tableId, dict.tableName(tableId));
        errorStack.push(errorTxt);
        retry;
    }

    setPrefix("@SYS86407");
    errorTxt = errorStack.pop();
    while (errorTxt)
    {
        error(errorTxt);
        errorTxt = errorStack.pop();
    }
    info('Sychrnonization is now done.');
}

Delete duplicate records from table

Sometimes we lost unique identity check in the tables while doing some customization in Dynamics AX. It may happen due to failure of database synchronization.

Here is the code to remove the duplicate records from a table, I used Dimension table as an example;

static void deleteduplicate(Args _args)
{
    set fieldSet = new set(Types::Integer);

    // create dicindex from the unique index
    DictIndex dictIndex = new Dictindex(tablenum(Dimensions),indexnum(dimensions, DimensionIdx));
    ;

    // these are the fields from the index
    // add them to a set
    fieldset.add(fieldnum(Dimensions, DimensionCode));
    fieldset.add(fieldnum(Dimensions, Num));

    // set allow duplicates
    ReleaseUpdateDB::indexAllowDup(dictIndex);


    // delete duplicate records
    ReleaseUpdateDB::deleteDuplicatesUsingIds(tablenum(Dimensions), 1, fieldset);

    // re-enable index
    ReleaseUpdateDB::indexAllowNoDup(dictIndex);

    info('Done');
}  

Format the system date in Dynamics AX


The following code show the dates in format like December 22, 2011

static void datesJob(Args _args)
{
    str dd, mm, yy, dt;

    dt = date2Str(systemDateGet(), 123, DateDay::Digits2, DateSeparator::Slash, DateMonth::Digits2,
           DateSeparator::Slash, DateYear::Digits4);

    dd = substr(dt, 0, 2);
    mm = substr(dt, 4, 2);
    yy = substr(dt, 7, 4);

    dt = mthname(str2int(mm)) + ' ' + dd + ', ' + yy;

    print   dt;
    pause;
}


Monday, December 19, 2011

Dynamics AX – Use definition groups to import data

It’s perfectly correct that we always learn when we want to or when we are in need of achieving goals. This happened to me for last couple of weeks when I was looking for different ways to import data in Dynamics AX 2009. I came to know that we can use data definition groups under Administration > Periodic > Data export/import > Definition groups to import data from excel or csv files.

Here you can find the complete information to import data using data definition group.

Any questions and comments will highly be appreciated!

How to get the filtered datasource query after pressing Ctrl +G


Question: How to get filtered query after using filter by grid option in grid

 Grid showing filter records                                           Grid showing complete list of records
                        
Answer: You can get the query which is created after providing filter criteria like this;

datasourceName.queryRun().query().datasourceNo(1).ToString();

Thursday, October 27, 2011

Find On-Hand inventory

While working on inventory module I wrote this code to find out the On-Hand inventory by using the InventOnHand class.

static void findingOnHand(Args _args)
{
ItemId itemId;
InventDim inventDim, inventDimCriteria;
InventDimParm inventDimParm;
InventOnhand inventOnhand = new InventOnhand();
;

itemId = '00200956';

inventDimCriteria.InventLocationId = '08';
inventDimParm.initFromInventDim(inventDimCriteria);

inventOnhand.parmInventDim(inventDimCriteria);
inventOnhand.parmInventDimParm(inventDimParm);
}

Find On-Hand inventory on given date

This piece of code can help you to find out On-Hand inventory at a particular date.

static void findingOnHandByDate(Args _args)
{
ItemId itemId;
InventDim inventDimCriteria;
InventDimParm inventDimParm;
InventSumDateDim inventSumDateDim;
;
// Specify the item to get onhand info on
itemId = '00005';
//inventDimCriteria.InventColorId = '02';
inventDimParm.initFromInventDim(inventDimCriteria);
inventSumDateDim = InventSumDateDim::newParameters(mkdate(31,12,2009), itemId, inventDimCriteria, inventDimParm);
info(strfmt("PostedQty: %1",inventSumDateDim.postedQty()));
info(strfmt("DeductedQty: %1",inventSumDateDim.deductedQty()));
info(strfmt("ReceivedQty: %1",inventSumDateDim.receivedQty()));
}

Check Active Inventory Dimension

In recent time I came across with a requirement how to find out an active inventory dimension associated with an item.

Here is a sample job to do this task;

static void checkInventDimActive(Args _args)
{
ItemId itemid = "1000";
;

info(strfmt("%1", InventDimSetup::find(InventTable::find(itemid).dimGroupId, fieldNum(InventDim, configId)).Active));
}

Thursday, May 5, 2011

Dynamics Ax 4.0 installation

This question is being from many folks. How to install the Dynamics AX on non-domain computers.

This article is going to help you to resolve the issue.


Sunday, April 24, 2011

Update record after record insert in table


If you are getting the following error while inserting and updating records.

Cannot edit a record in Table (Tablename).
An update conflict occurred due to another user process deleting the record or changing one or more fields in the record.


You will be doing it in this way;


1.Table table;
2.;
3.table.Field1 = 'A';
4.table.insert();
5.
6.table.Field2 = 'Desc';
7.table.update();

To avoid such cases:


01.Table table;
02.;
03.ttsbegin;
04.table.Field1 = 'A';
05.table.insert();
06.
07.table.Field2 = 'Desc';
08.table.update();
09.ttscommit;

Or this way:


01.Table table;
02.;
03.table.Field1 = 'A';
04.table.insert();
05.
06.// time consuming other code here
07.
08.ttsbegin;
09.table.selectForUpdate(true);
10.table.reread();
11.table.Field2 = 'Desc';
12.table.update();
13.ttscommit;

Thursday, February 10, 2011

Easy Extended AX Table-Browser

During implementations and regular developments in AX, I observed that many consultants/developers open the tables from AOT in the Table-Browser and by hand attempt to add/delete the records by hitting Ctrl+N/Alt+F9 keys and often copies the records from the table-browser and paste them to excel for checking/saving/printing/... the table data. The distress of manually copying/pasting of records and recalling the control-keys can be avoided by simply extending the Table-Browser by adding the Standard data Toolbar to the form

Step-1: Go to the AOT and select \Forms\SysTableBrowser\Designs\Design
and then change the design property WindowType value from “Popup” to “Standard”



Fig1: Changing WindowType property value to “Standard”

Step-2: Save and compile the SysTableBrowser Form after changing the WindowType property value





Fig2: The original Table-Browser (WindowType property is “Popup”)




Fig3: Easy Extened Table-Browser with Standard-Toolbar (WindowType property is “Standard”)

Sunday, January 9, 2011

Document management directory issue in AX

Question:

When using document handling in AX 2009, there are 3 options: note, file, and document. 

While using any one of the options I am getting the following error; "Document management directory does not exist".

Answer:

Check the Document Management directory by navigating to Basic > Setup > Document management > Document types form, on the General tab in the archive directory field ensure that the path is correct as the system appears to be trying to save the document but cannot find it.



Microsoft Dynamics AX Financial Videos

Enjoy the comprehensive videos over Microsoft Dynamics AX financials from here

How to enable new Microsoft teams - Public Preview!

New Microsoft Teams is just AWESOME, quick but useful post below shows how you have this preview feature to make your life EASY!  Open Micr...