Monday 26 December 2011

Iterator vs Enumerator in Ax 2009


Iterators Vs. Enumerators
December 13, 2011
Iterators Vs. Enumerators
We can traverse our collections by using either an enumerator or an iterator.It is a best practice to use the MapEnumerator class instead of the MapIterator class, because enumerators are automatically created on the same tier as the map (when calling the Map.getEnumerator method). Using the MapIterator class avoids a potential problem in code marked as Called from, where the iterator and map can end up on separate tiers. In addition, because map enumerators require less code than map iterators, they perform slightly better. The only situation where you have to use a map iterator, is when you want to delete items from a list (use the MapIterator.delete method).
.Simply what we do is that just replace iterator with the enumerator.

First theoretically what happens behind the scenes is as follows:

When collection classes were first introduced in DAX, the iterator was the only option.But because of a few unwarranted drawbacks that appear as hard-to-find errors, enumerators were added, and iterators were kept for the backward compatibility. Just see the below listed code snippet

List list   = new List(Types::Integer);
ListIterator  iterator;
ListEnumerator  enumerator;
;
//Populate List
…..
…..

//Traverse using an iterator.
iterator = new ListIterator(list);

while(Iterator.more())
{
print iterator.value());
iterator.next();
}

//Traverse using an enumerator
enumerator = list.getEnumerator();

while(enumerator.moveNext())
{
print enumerator.current();
}

The 1st difference is the way iterator and enumerator instances are created.For the iterator,you call new,and for the enumerator,you get an instance from the collection class by calling the getEnumerator method.

In most cases, both approaches will work equally well. However, when the collection class resides on the opposite tier from the tier on which it is traversed,the situation is quite different.

For example, if the collection resides on the client tier and is traversed on the server tier, the iterator approach fails because the iterator does not support cross-tier referencing.

The enumerator does not support cross-referencing either, but it doesn’t have to because it is instantiated on the same tier as the collection class. Traversing on the server tier using the client tier enumerator is quite network intensive, but the result is logically correct because some code is marked as “Called From”, meaning that it can run on either tier,depending on where it is called from. You could have broken logic if you use iterators, even if you test one execution path.In many cases, hard-to-track bugs such as this surface only when an operation is executed in batch mode.

The 2nd difference is the way traversing happens which is another potential threat as the onus lies on the developer to ensure that he moves the pointer by using .next() method else the code can land into endless loop.So again enumerator is clear winner here

But there is still one scenario while iterator holds edge over enumerator, if you want to delete/insert items from list.See the code snippet below:

List   list = new List(Types::Integer);
ListIterator iterator;
;

list.addEnd(100);
list.addEnd(200);
list.addEnd(300);

iterator = new  ListIterator(list);
while(iterator.more())
{
if(iterator.value() == 200)
iterator.delete();
iterator.next();
}
print list.toString();   //{100,300}
pause;
}

create a dialog with custtable fields

Hi Friends,

         Today we will create  a dialog with custTable fields and auto populate the fields using the methods,

1.  In AOT, create a new class named CustSelect with the following code:
class CustSelect extends RunBase
{
    DialogField fieldAccount;
    DialogField fieldName;
    DialogField fieldGroup;
    DialogField fieldCurrency;
    DialogField fieldPaymTermId;
    DialogField fieldPaymMode;
}
public container pack()
{
    return connull();
}
public boolean unpack(container packedClass)
{
    return true;
}
protected Object dialog()
{
    Dialog          dialog;
    DialogTabPage   tabGeneral;
    DialogTabPage   tabDetails;
    DialogGroup     groupCustomer;
    DialogGroup     groupPayment;

   ;
    dialog = super();
    dialog.caption("Customer information");
    dialog.allowUpdateOnSelectCtrl(true);
    tabGeneral      = dialog.addTabPage("General");
    fieldAccount    = dialog.addField(
        typeid(CustAccount),
        "Customer account");
    fieldName       = dialog.addField(typeid(CustName));
    fieldName.enabled(false);
    tabDetails      = dialog.addTabPage("Details");
    groupCustomer   = dialog.addGroup("Setup");
    fieldGroup      = dialog.addField(typeid(CustGroupId));
    fieldCurrency   = dialog.addField(typeid(CurrencyCode));
    fieldGroup.enabled(false);
    fieldCurrency.enabled(false);
    groupPayment    = dialog.addGroup("Payment");
    fieldPaymTermId = dialog.addField(typeid(CustPaymTermId));
    fieldPaymMode   = dialog.addField(typeid(CustPaymMode));
    fieldPaymTermId.enabled(false);
    fieldPaymMode.enabled(false);
    return dialog;
}
public void dialogSelectCtrl()
{
    CustTable custTable;
    ;
    custTable   = CustTable::find(fieldAccount.value());
    fieldName.value(custTable.Name);
    fieldGroup.value(custTable.CustGroup);
    fieldCurrency.value(custTable.Currency);
    fieldPaymTermId.value(custTable.PaymTermId);
    fieldPaymMode.value(custTable.PaymMode);
}
static void main(Args _args)
{
    CustSelect custSelect = new CustSelect();
    ;
    if (CustSelect.prompt())
    {
        CustSelect.run();
    }
}

Sunday 25 December 2011

How to create a Query(dynamically) and add a link in Ax 2009

Hi friends,

     Today we are joining two tables dynamically and adding a  link type between the two tables. Open the Aot and in jobs write the following code

static void CustTableSales1(Args _args)
{
    Query       query;
    QueryRun    queryrun;
    QueryBuildDataSource    qbds1;
    QueryBuildDataSource    qbds2;
    QueryBuildRange         qbr1;
    QueryBuildRange         qbr2;
    CustTable               custTable;
    ;
    query   = new query();
    qbds1   =   query.addDataSource(tablenum(CustTable));
    qbds1.addSortField(fieldnum(custTable,AccountNum),Sortorder::Descending);
    qbr1    = qbds1.addRange(fieldnum(custTable,custGroup));
    qbr1.value(queryvalue('10'));
    qbr2    =  qbds1.addRange(fieldnum(custTable,Blocked));
    qbr2.value(queryvalue(CustVendorBlocked::No));
    qbds2   = qbds1.addDataSource(tablenum(SalesTable));
    qbds2.relations(false);
    qbds2.joinMode(joinmode::ExistsJoin);
    qbds2.addLink(fieldnum(CustTable,AccountNum),fieldnum(SalesTable,CustAccount));
    queryrun    = new queryrun(query);
    while(queryrun.next())
    {
    custTable   = queryrun.get(tablenum(custTable));
    info(strfmt("%1 - %2",custtable.AccountNum,custTable.Name));
    }
}

Sending Email using outlook in AX 2009

Hi Friends,

    Today we are going to send an email in Ax 2009 by using the outlook.

Before we start with this recipe, we need to create an email template. This is standard Dynamics
AX functionality. Open Basic | Setup | E-mail templates, and create the following record:
Next click on the Template button, and enter the email body:

1.  In AOT, create a new class called SendEmail with the following code:
class SendEmail extends RunBase
{
    SysEmailId           emailId;
    DialogField          dlgEmailId;
    CustTable            custTable;
    LanguageId           languageId;
    #define.CurrentVersion(1)
    #localmacro.CurrentList
        emailId
    #endmacro
}
void new()
{;
    super();
    languageId = infolog.language();
}

public CustTable parmCustTable(
    CustTable _custTable = custTable)
{;
    custTable = _custTable;
    return custTable;
}
public container pack()
{
    return [#CurrentVersion, #CurrentList];
}

public boolean unpack(container packedClass)
{

    int version = RunBase::getVersion(packedClass);
    ;
    switch (version)
    {
        case #CurrentVersion:
            [version, #CurrentList] = packedClass;
            return true;
        default :
            return false;
    }
    return false;
}

public static SendEmail construct(CustTable _custTable)
{
    SendEmail sendEmail;
    ;
    sendEmail = new SendEmail();
    sendEmail.parmCustTable(_custTable);
    return sendEmail;
}

protected Object dialog()
{
    Dialog          dialog;
    ;
    dialog = super();
    dialog.caption("Select email template");
    dlgEmailid = dialog.addFieldValue(
        typeid(SysEmailId), emailId);
    return dialog;
}

public boolean getFromDialog()
{;

    emailId = dlgEmailId.value();
    return true;
}
str subject()
{
    return SysEmailMessageTable::find(
        emailId, languageId).Subject;
}
str processMappings(str _message)
{
    Map mappings;
    ;
    mappings = new Map(Types::String, Types::String);
    mappings.insert('name', custTable.Name);
    mappings.insert('company', CompanyInfo::name());
    return SysEmailMessage::stringExpand(_message, mappings);
}

str message()
{
    COM                  document;
    COM                  body;
    str                  ret;
    SysEmailMessageTable message;
    #help
    ;
    message = SysEmailMessageTable::find(emailId, languageId);
    ret = this.processMappings(message.Mail);
    ret = WebLet::weblets2Html4Help(ret, '');
    document = new COM(#HTMLDocumentClassName);
    SysEmailTable::insertHTML2Document(document, ret);
    body = document.body();
    if (!body)
    {
        return '';
    }
    return body.outerText();
}
public boolean validate()

{;
    if (!custTable)
    {
        return false;
    }
    return true;
}
public void run()
{
    SysInetMail mail;
    ;
    mail = new SysInetMail();
    mail.parmForceSendDialog(true);
    mail.sendMail(
        custTable.Email,
        this.subject(),
        this.message(),
        true);
}

public static void main(Args _args)
{
    SendEmail sendEmail;
    ;
    if (!_args || !_args.record())
    {
        throw error(Error::missingRecord(funcname()));
    }
    sendEmail = SendEmail::construct(
        _args.record());
    if (sendEmail.prompt())
    {
        sendEmail.run();
    }
}

2.  In AOT, create a new Action menu item with the following properties
Property Value
Name SendEmail
ObjectType Class
Object SendEmail
Label Send email
3.  Add the newly created menu item to the bottom of the CustTable form's ButtonGroup
group by creating a new MenuItemButton with the following properties:
Property Value
Name SendEmail
MenuItemType Action
MenuItemName SendEmail
DataSource CustTable



How it works...

For this  we created a new RunBase-based class. It contains the following
member methods:
 f new() is the class constructor, and here we initialize the language variable.
 f parmCustTable() sets or gets the custTable parameter.
 f pack() prepares user data to be stored for the next time.
 f unpack() retrieves stored data (if any) .
 f construct() creates a new instance of this class.
 f dialog() changes the default dialog caption and adds the email template
selection field.
 f getFromDialog() stores the user file selection into the emailId variable.
 f subject() returns the email subject from the email template table.
 f processMappings() replaces the template placeholders with the actual values.
Here we use stringExpand() of the SysEmailMessage class for this purpose.

 f message() returns the email body from the email template set up with the
processed placeholders. Because the body text is stored as a Dynamics AX weblet,
we need to convert it into text format.
First, we get rid of the weblet tags by using weblets2Html4Help() method of
WebLet application class.
Next, we convert HTML to text by using a COM class to create an HTML document
object and get its content as text by calling outerText() on its body element.
 f validate() checks if a valid buffer is passed.
 f run() creates a new instance of SysInetMail and uses its sendMail() to send the
email. This method takes customer email address, message subject, and message
body as arguments.
Here we also call parmForceSendDialog() with true right before sendMail().
This stops email from being sent automatically and allows the user to modify the
message. This parameter overrides the last parameter of sendMail(), which does
exactly the same but only if there is no email body text.
 f main() puts everything together.
Lastly, we create an Action menu item pointing to this class, and add it to the Customers form
as a button. This allows us to use the button to send an email to the selected customer.














Friday 23 December 2011

How to Import data from Excel file to Ax 2009

Hi friends,

   Today we are going to import data from excel to Ax 2009 by code and by using inbuilt features.
First we import data to a dummy CustTable and how to import data without writing any code.

static void Excelimport(Args _args)
{
SysExcelApplication     application;
SysExcelWorkbooks       workbooks;
SysExcelWorkbook        workbook;
SysExcelWorksheets      worksheets;
SysExcelWorksheet       worksheet;
SysExcelCells           cells;
SysExcelCell            cell;
SysExcelRange           totrange,range;
//ysOperationProgress    simpleProgress;
COMVariant              file;
int                     i,imported,  noofrows;
str                     strExample1[],strExample2[];
CustTable1              custTable;//created my own table
#excel
#avifiles
;
try
    {
        application = SysExcelApplication::construct();
        application.visible(false);
        workbooks   = application.workbooks();
        file    = new COMVariant();
        file.bStr("D:\Book1.xlsx");
        workbook    = workbooks.add(file);
        worksheets   = workbook.worksheets();
        worksheet   = worksheets.itemFromNum(1);

    }
catch(Exception::Error)
    {
        throw error("cannot open excel file");
    }
try
    {
        totrange   =   worksheet.cells().range(#ExcelDataRange);
        range   =   totrange.find("*", null, #xlFormulas, #xlWhole,
                                    #xlByRows, #xlPrevious);
        if(range)
        {
            noofrows    =   range.row();
        }
        else
        {
            application.workbooks().close();
            application.quit();
            throw error("D:\Book1.xlsx");
        }

    }
catch(exception::error)
    {
       application.workbooks().close();
       application.quit();
       throw error("error has occured");
    }
    cells   =   worksheet.cells();
 //   simpleProgress = SysOperationProgress::newGeneral(#aviUpdate, 'file is importing', 100);
    ttsbegin;

    //read rows of excel
    for(i=1; i<=noofrows; i++)
    {
        imported++;
       print(imported);

        strExample1[i]           = cells.item(i,2).value().bStr();
        strExample2[i]           = cells.item(i,1).value().bStr();
        custTable.AccountNum     = strExample1[i];
        custTable.Name           = strExample2[i];
        custTable.insert();




    }
    pause;
    ttscommit;

    //display info
    info(int2str(imported) + " records imported.");
    application.workbooks().close();
    application.quit();
}

/* we can also have other values such as

pONo                    =   COMVariant2Str(cells.item(row, 1).value());
itemid                  =   COMVariant2Str(cells.item(row,2).value());
InventSize         =   COMVariant2Str(cells.item(row, 3).value());
batchNumber             =   COMVariant2Str(cells.item(row, 4).value());
poddate                 =   cells.item(row, 5).value().date();
expdate                 =   cells.item(row, 6).value().date();
srNo                    =   COMVariant2Str(cells.item(row, 7).value());
Certs                =   str2enum(Certs,cells.item(row, 8).value().bStr());
CertiAnalysis        =   str2enum(CertiAnalysis,cells.item(row, 9).value().bStr());
quantity                =   cells.item(row, 10).value().double();
_ConfigId               =   COMVariant2Str(cells.item(row, 12).value());
_InventColorId          =   COMVariant2Str(cells.item(row, 13).value());
_InventSiteId           =   COMVariant2Str(cells.item(row, 14).value());
_WMSLocationId          =   COMVariant2Str(cells.item(row, 15).value());
_InventLocationId       =   COMVariant2Str(cells.item(row, 16).value());
_WMSPalletId            =   COMVariant2Str(cells.item(row, 17).value());
ClosedTransactions      =   str2enum(ClosedTransactions,cells.item(row, 18).value().bStr());
ClosedTransQty          =   str2enum(ClosedTransQty,cells.item(row, 19).value().bStr());*/


now we see another type


The following steps may be used in importing the vendors’ multiple addresses in AX2009 through the excel template. Similar approach can be adopted for the Customers also by slightly changing the view i have recommended in this post and at other relevant areas. Send me a mail if you find any trouble . I will be happy to help.

1. Navigate to CEU -> Administration from the Address bar by clicking the navigation path arrows.
2. Click Area Page node: Administration -> Periodic -> Data export/import -> Excel spreadsheets -> Template Wizard.
Form name: Microsoft Office Excel Template Wizard
clip_image002
3. Click the Next > button.
4. Switch to the Open workbook tab on the Microsoft Office Excel Template Wizard form.
clip_image004
5. Change File name from '' to 'C:\….Path…..\MultiAdd_Vendors.xls'.
6. Click the Next > button.
7. Switch to the Select tables tab on the Microsoft Office Excel Template Wizard form.
clip_image006
8. Click Show all tables & Select the “Address” table.
9. Click ‘>’ button to move the table ‘Address’ to selected objects and then Click the Next > button.
10. Switch to the Generate field list tab on the Microsoft Office Excel Template Wizard form.
clip_image008
11. Click the Next > button.
12. Switch to the Select fields tab on the Microsoft Office Excel Template Wizard form.
clip_image010
13. Click the Next > button.
14. Switch to the Import definition group tab on the Microsoft Office Excel Template Wizard form.
clip_image012
15. Click the Next > button.
16. Switch to the Export data tab on the Microsoft Office Excel Template Wizard form.
clip_image014
17. Click the Next > button.
18. Switch to the Finished tab on the Microsoft Office Excel Template Wizard form.
clip_image016
19. Click the Finish button.
20. Close the Microsoft Office Excel Template Wizard form.
21. Create a View in AX. As shown below. I wanted to attach the .xpo for the view but windows live spaces does not support . send me a message if you need and i will provide it over email.
clip_image018
22. Now come to MS Excel . Open the workbook template Multiadd_Vendor.xls we created. Insert a new worksheet and access the above views data in the worksheet as shown below.
a. Under data tab >Select the “From Other Sources” in Get External Data.
clip_image020
b. Type ‘ . ‘ in the server name if the SQL server is installed locally Else type the name of SQL server.
c. Select the Use Windows Authentication if the current user has access to the database else use the Unser name and password for accessing the database.
clip_image022
d. Select the database and the View we have just now created.
clip_image024
e. Click FInish
clip_image026
f.Select the worksheet location where you wish to get the data. ( $A$1 mostly).
clip_image028
You will get the data as shown below
clip_image030
23. Insert another worksheet . Copy the view’s data from the sheet in step (22f)… and paste it in the new worksheet in workbook MultiAdd_Vendors.xls .
24. Move the column AccountNum  to extreme left (make it the first column). The worksheet data must be sorted on the field AccountNum in ascending order. You may arrange the columns as shown below screenshot.
clip_image032
25. In the worksheet template for address i.e. worksheet name “Address_1” add a column “Vendor” at the end as shown below. Populate the template with address data for the Vendor Code in AX .
26.The template field “Table ID of the main table” must be set to 2303 which is the table ID of the DirPartyTable.
27. In the column “Reference” use the VLookup function as shown below. This will get the recid from the DirParty ( in sheet 9) for the vendor in sheet Address_1
clip_image034
28. Copy & paste the formula in all rows for column “Reference”.
29. Copy the column “Reference” and paste special into itself with only values. The data in this column should be in text format even if  numbers are entered. For this you may have to precede the numbers with an apostrophes so that there is a green mark on the each of the upper left corner of the cells as you can see in column Vendor 
Your data is ready to be pulled in AX.
30. Go to the Definition group created while creating the excel template . Click the Table setup button. Go to Import criteria tab.
image
31. Select the definition group and click import
clip_image036
32. You will get an infolog that ‘No data imported’. But do not worry . The data is imported. Refer to the following link for more information about the infolog.


Wednesday 21 December 2011

Reference the field added in LedgerJournalTrans to LedgerTrans in Ax 2009

Hi Friends,

        Today we will implement the field which is added in LedgerJournalTrans will be displayed on LedgerTrans . For example we are going to add an EmplId field in LedgerJournalTrans , the value which is given in the LedgerJournalTrans will have to be shown in LedgerTrans. Add the fields in the LedgerJournalTrans and show it in the form.
Note:: The code given below is not in a order..............
1. In classes LedgerVoucher ,in method initledgerTransList add the following code

protected void initLedgerTransList()
{
    ledgerTransList = new RecordSortedList(tablenum(LedgerTrans));
    ledgerTransList.sortOrder(
        fieldnum(LedgerTrans, Voucher),
        fieldnum(LedgerTrans, EmplId), // This is the field which we have added
        fieldnum(LedgerTrans, TransDate),
        fieldnum(LedgerTrans, AccountNum),
        fieldnum(LedgerTrans, PeriodCode),
        fieldnum(LedgerTrans, OperationsTax),
2.In classes LedgerVoucherObject  initialize the EmplId by declaring it,
 and in the same class create a new method of ParmEmplId , use the code as follows

public S3_EmplId parmEmplId_S3(S3_EmplId _emplId = emplId)
{
    ;
    emplId = _emplId;
    return emplId;
}
3.In same class in allocate method use the follwing code
 
    allocationTransObject =
            LedgerVoucherTransObject::newVoucherTrans(
                this,
                LedgerPostingType::Allocation,
                baseLedgerTrans.AccountNum,
                baseLedgerTrans.Dimension,
                baseLedgerTrans.CurrencyCode,
                baseLedgerTrans.Txt,
                baseLedgerTrans.TransDate,
                baseLedgerTrans.EmplId, // Added the code here
                baseLedgerTrans.
                -totalTrans.Qty,
                -totalTrans.AmountCur,
                -totalTrans.AmountMST,
4.And in same class of method postRoundingDifferencesPerDate modify it

              this.addTrans(
                LedgerVoucherTransObject::newVoucherTrans(
                    this,
                    LedgerPostingType::MSTDiffSecond,
                    accountNum,
                    dimension,
                    companyCurrencyCode,
                    transactionTxt.txt(),
                    ledgerTrans.TransDate,
                    ledgerTrans.EmplId,
                    0,
                    0,
                    0,
                    -ledgerTrans.AmountMSTSecond,
                    NoYes::No,
                    true,
                    tmpVoucherMap),
                false);
5.And in the   LedgerVoucherTransObject Class   of method newTransLedgerJournal add the code


    ledgerTransObject.parmLedgerTransType(LedgerTransType::None);
    ledgerTransObject.parmPeriodCode(PeriodCode::Regular);
    ledgerTransObject.parmSourceTableId(_ledgerJournalTrans.TableId);
    ledgerTransObject.parmSourceRecId(_ledgerJournalTrans.RecId);
    ledgerTransObject.parmCompany(_ledgerJournalTrans.Company);
    ledgerTransObject.parmLedgerAccount(_ledgerJournalTrans.AccountNum);
    ledgerTransObject.parmVoucher(_ledgerJournalTrans.Voucher);
    ledgerTransObject.parmTransDate(_ledgerJournalTrans.TransDate);
    ledgerTransObject.parmTransTxt(_ledgerJournalTrans.Txt);
    ledgerTransObject.parmEmplId(_ledgerJournalTrans.EmplId);// we have used our EmplId here
6. And in the same class of method newVoucherTrans add the code

static LedgerVoucherTransObject newVoucherTrans(LedgerVoucherObject    _ledgerVoucherObject,
                                                 LedgerPostingType     _ledgerPostingType,
                                                 LedgerAccount         _ledgerAccount,
                                                 Dimension             _dimension,
                                                 CurrencyCode          _currencyCode,
                                                 TransTxt              _transTxt,
                                                 EmplId              _emplId,// we have used it here
                                                 TransDate             _transDate



    ledgerTransObject.parmEUROTriangulation(_euroTriangulation);
    ledgerTransObject.parmThisIsARoundingTrans(_thisIsARoundingTrans);
    ledgerTransObject.parmTmpVoucherMap(_tmpVoucherMap);
    ledgerTransObject.parmEmplId();// we have used it here
7.In the same class of initfromledgervoucher object

     void initFromLedgerVoucherObject(LedgerVoucherObject _ledgerVoucherObject)
{
    ;
    ledgerTrans.Voucher                                 = _ledgerVoucherObject.parmVoucher();
    ledgerTrans.EmplId                               = _ledgerVoucherObject.parmEmplId();//Added here




8. Add Parmmethod of EmplId in the same class 
By doing all this mopdifications in the classes u will get the desired result
                                                                Vivek Chirumamilla.