Implement GST (Goods & Services Tax) functionality in Microsoft Dynamics AX

We have successfully implemented GST patches for India localization for Dynamics AX. You can go through the link below:

Axpedite GST Implementation

Dynamics AX GST Solution

Please contact us at sales@axpeditesoftware.com in case you need help in implementing GST patches for DAX.

#OneIndiaOneTax

Why should SAP and Oracle consultants consider a career move into Dynamics AX

Friends,

Here is a good article by Cognitive Group on reasons as to why other ERP consultants should rush to try their expertise in Dynamics AX. It’s a good read.

#DynamicsAX, #AXJobs

http://www.cognitive-group.com/blog/job-advice/why-should-sap-and-oracle-consultants-consider-a-career-move-into-dynamics-ax

AOSAuthorization property on tables

This property is part of Tables Permission Framework (TPF). The Table Permissions Framework (TPF) enables administrators to add an additional level of security to tables that store sensitive data. TPF adds table-level security that verifies access rights no matter the origin of the request.

To enable TPF, an administrator specifies a value for the AOSAuthorizationProperty on a specific table in the AOT. The AOSAuthorizationProperty authorizes Create, Read, Update, and Delete operations. For some tables, it is important to authorize all operations because the data is sensitive. For other tables, you might find it suitable to specify a subset of operations, such as Create, Update, and Delete. In the case when you have specified a subset, the AOS authorizes the Create, Update, and Delete operations, but allows users to perform View operations if they have access to Microsoft Dynamics AX.

TPF can be enabled on any table in the Microsoft Dynamics AX database. For the sake of time and efficiency, however, administrators assign TPF to tables that are considered to be sensitive or to be of critical business value.

For example, consider the following scenario:

  1. Microsoft Dynamics AX and allows users to access data by using the Microsoft Dynamics AX client, Enterprise Portal, the Application Integration Framework, and a third-party application that connects to Microsoft Dynamics AX by using the .NET Business Connector.
  2. The administrator configured a Microsoft Dynamics AX user group called Senior Accountants, and members of this group have access to sensitive data about financial information and trade secrets. One of the database tables that stores this sensitive information is called FinancialResults. This table was added as part of a customization done by a partner after Microsoft Dynamics AX was installed.
  3. In the Application Object Tree (AOT), the administrator configures the FinancialResults table so that the Application Object Server (AOS) must authorize all operations for that table. The administrator specifies the value CreateReadUpdateDelete for the AOSAuthorizationProperty.
  4. Soon thereafter, a malicious user discovers a vulnerability in Contoso’s third-party application that connects to Microsoft Dynamics AX by using the .NET Business Connector. The malicious user connects to the database as a member of the CRM_users group and attempts to read the data in the FinancialResults table.
  5. Before allowing the read operation, the AOS checks to see if the user is a member of the Senior Leadership user group and if members of the group have permission to read the data. The malicious user is not a member of the Senior Leadership group, so the AOS denies the read operation.

You can change or add TPF for a table, but its is recommended that you perform TPF changes in a test environment so that you can study the impact of TPF changes on user groups that access that table.

To enable TPF on database table:

  1. In the AOT, expand Data Dictionary > Tables.
  2. Right-click a table, and then click Properties.
  3. Click AOSAuthorizationProperty and select a new value by using the drop-down list.
  4. Click Save All.

If you added TPF to a table, you might need to specify or expand permissions for user groups that access that table. You can view which objects access a table by using the Used-by command in the AOT:

  1. In the AOT, expand Data Dictionary > Tables.
  2. Right-click a table, and then click Add-ins > Cross-reference > Update.
  3. Right-click a table, and then click Add-ins > Cross-reference > Used by. The Used by form is displayed. This form shows all objects that access the selected table and what permissions (the Reference column) are required when accessing the table. You might need to adjust user group permissions if you set tighter restrictions on a table.

Iterators Vs. Enumerators

We can traverse our collections by using either an enumerator or an iterator.But there is no clarity why sometimes iterator fails while on other occasions it is flawless.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;
}

Restoring delete sales order or purchase order

Many people know that when a sales order or purchase order is deleted, it is actually not purged from system like lot of other data but sits in the voided tables. These orders can be viewed from AR -> Inquiries -> History -> Voided sales order or AP -> Inquiries -> History -> Voided sales order.
 
Now from this form you can only view the deleted orders but there is not option of restoring them.
 
Below are some sample code that can be used to restore the voided orders.
 
Sales order restoration:
 
static void restoreDeletedSO(Args _args)
{
    SalesTableDelete    salesTableDelete;
    SalesLineDelete     salesLineDelete;
    SalesTable          salesTable;
    SalesLine           salesLine;
    ;
    SalesTableDelete = SalesTableDelete::find(‘00450_036’, true);
    ttsbegin;
    switch (salesTableDelete.Cancelled)
    {
        case Voided::Voided :
            salesTable  = conpeek(salesTableDelete.SalesTable, 1);
            salesTable.insert();
            while select forupdate salesLineDelete where salesLineDelete.SalesId == salesTableDelete.SalesId
            {
                salesLine = conpeek(salesLineDelete.SalesLine, 1);
                salesLine.insert();
            }
            salesTableDelete.delete();
            break;
        case Voided::linesVoided :
            while select forupdate salesLineDelete where salesLineDelete.SalesId == salesTableDelete.SalesId
            {
                salesLine = conpeek(salesLineDelete.SalesLine, 1);
                salesLine.insert();
                salesLineDelete.delete();
            }
            salesTableDelete.delete();
            break;
   }
   ttscommit;
}
Purchase order restoration:
static void restoreDeletedPO(Args _args)
{
    PurchTableDelete    purchTableDelete;
    PurchLineDelete     purchLineDelete;
    PurchTable          purchTable;
    PurchLine           purchLine;
    ;
    purchTableDelete = PurchTableDelete::find(‘00242_049’, true);
    ttsbegin;
    switch (purchTableDelete.Cancelled)
    {
        case Voided::Voided :
            purchTable  = conpeek(purchTableDelete.PurchTable, 1);
            purchTable.insert();
            while select forupdate purchLineDelete where purchLineDelete.PurchId == purchTableDelete.PurchId
            {
                purchLine = conpeek(purchLineDelete.PurchLine, 1);
                purchLine.insert();
            }
            purchTableDelete.delete();
            break;
        case Voided::linesVoided :
            while select forupdate purchLineDelete where purchLineDelete.PurchId == purchTableDelete.PurchId
            {
                purchLine = conpeek(purchLineDelete.PurchLine, 1);
                purchLine.insert();
                purchLineDelete.delete();
            }
            purchTableDelete.delete();
            break;
   }
   ttscommit;
}
One can easily create a class using this code to provide an option of restoring the orders. Here is the link to one such project
 
 

Insight Into Record IDs

Record IDs are unique IDs. In AX 4.0 Record id is unique for a table, a significant shift in 4.0 when compared to 3.0 where record ids were unique across the application. This allows AX to store more data and support enormous number of records.
 
In AX 4.0 SystemSequences table stores record ids details for all the tables in AX. The generation of Record ids is handled by class SystemSequence.
 
Record ids are generated at the time of saving the record. This is what system does. based on the table id, ID = -1 and name =’SEQNO’ system gets a block of record ids and caches them and stores in the client. The block is of size 250 (in 3.0 we could change the block size, but in 4.0 MS doesnt allow anybody to change the block size).
 
Here is a sample code that shows how we can get next record id in AX 4.0
 
static void getNextRecIdAX40(Args _args)
{
    //Table that stores record ids details for tables
    SystemSequences systemSequences;
 
    //Class that handles Record id generation
    SystemSequence  systemSequence = new SystemSequence();
    ;
 
    select firstonly systemSequences where systemSequences.tabId == tableNum(CustTable);
 
    systemSequence.suspendRecIds(systemSequences.tabId);
    info(strFmt(‘Next record id: %1’, systemSequence.reserveValues(systemSequences.minVal, systemSequences.tabId)));
    systemSequence.removeRecIdSuspension(systemSequences.tabId);
}
 
Here is a sample code that shows how we can get next record id in AX 3.0
 
static void getNaxtRecIdAX30(Args _args)
{
    SystemSequence  systemSequence;
    ;
 
    systemSequence = new SystemSequence();

    systemSequence.flushCache();
    systemSequence.setCacheSize(30)

 

    info(strFmt(‘Buffer size: %1’, systemSequence.getCacheSize()));
    info(strFmt(‘Next record id: %1’, systemSequence.nextVal();));
}
Below is a sample script to generate record ids while you are inserting data from SQL server scripts.
 
[Source for the code: How to Write Data Upgrade Scripts for Microsoft Dynamics AX 4.0 white paper from MS]

CREATE PROCEDURE initFromSMMQuotationTable @DATAAREAID NVARCHAR(3

AS DECLARE @NEXTVAL BIGINT, @ROWCOUNT BIGINT

 

SELECT ……,

RECID = IDENTITY(BIGINT,0,1) AS QUOTATIONID Assign an IDENTITY column with a starting value of 0 incremented by 1

INTO #TEMP

FROM DEL_SMMQUOTATIONTABLE WHERE QUOTATIONSTATUS = 0 SMMQuotationStatus::InProcess

 

SELECT @NEXTVAL=NEXTVAL Retrieve the next value for RECID for this table (by TABID)

FROM SYSTEMSEQUENCES

WITH(UPDLOCK, HOLDLOCK) WHERE ID = -1 AND TABID = 1967

 

INSERT INTO SALESQUOTATIONTABLE

(column-list)

SELECT ……,

RECID = QUOTATIONID+@NEXTVAL When we insert into the permanent table, we add the temporary tables IDENTITY column to the next value retrieved from SYSTEMSEQUENCES

FROM #TEMP

 

SELECT @ROWCOUNT = COUNT(*) FROM #TEMP

 

UPDATE SYSTEMSEQUENCES             We update SYSTEMSEQUENCES to reflect the number of rows that we have added to this table

SET NEXTVAL=NEXTVAL + @ROWCOUNT

WHERE ID = -1 AND TABID = 1967

GO

Email techniques in AX 4.0

In this article, I am going to demonstrate different email techniques that can be used in AX 4.0. Following classes can be used to send an email
 
  • Mapi and its related classes
  • SysMailer
  • SysInetMail
  • SysEmailBatch
  • SmmOutlookEmail

MAPI technique:

Following code demonstrates the usage of mapi class for sending email. It uses outlook to send mail.

static void emailThruMapi(Args _args)
{
    MapiEx      mapiEx;
    MapiExMail  mapiExMail;
    boolean     mapiInitialised;
    COM         outlook;
    COM         item;
    COM         outlookNameSpace;
    COM         folder;
    COM         attachments;
    str         storeId;
    str         entryId;

    #define.outlookapplication(‘Outlook.Application’)
    #define.mapi(‘Mapi’)
    #smmMSOutlook2002ObjectModelConstants
    #define.htmlText(‘<html><body>Hi There</body></html>’)
    ;

    outlook             = new COM (#outlookapplication);
    outlookNameSpace    = outlook.getNameSpace(#mapi);
    outlookNameSpace.logon();

    folder = outlookNameSpace.getDefaultFolder(#olFolderInbox);
    item = outlook.createItem(#olMailItem);
    storeId = folder.storeId();

    mapiEx = new MapiEx();

    if(mapiEx && mapiEx.mapiInitialised())
    {
        mapiInitialised = true;
        if (!mapiEx.logon("","",0) || !mapiEx.openMessageStore(storeId))
        {
            mapiInitialised = false;
            mapiEx.logout();
            mapiEx.finalize();
        }

        //To send mail in HTML format
        item.bodyFormat(#olFormatHTML);
        item.htmlBody(#htmlText);

        //To send mail in plain text format
        //item.body(‘Hi There’);

        item.subject(‘Test mail’);

        //—-Attachements——————-
        attachments = item.attachments();
        attachments.add(‘E:\\Test\\4000.pdf’, 1, 1, ‘4000.pdf’);

        item.saveSentMessageFolder(outlookNameSpace.getDefaultFolder(#olFolderSentMail));
        item.save();
        entryId = item.entryId();

        mapiExMail = mapiEx.getMailFromEntryId(entryId);
        if (!mapiExMail)
        {
            mapiInitialised = false;
            mapiEx.logout();
            mapiEx.finalize();
        }
    }

    if(item)
    {
        if (mapiInitialised && mapiExMail)
        {
            //TO
            mapiExMail.addRecipient(
‘sumit.loya@sumitloya.com’, "", #olTo);
            //CC
            mapiExMail.addRecipient(
‘sreenath.girigari@sreenath.com’,"",#olCC);
            //BCC
            mapiExMail.addRecipient(
‘ashish.singh@ashishsingh.com’,"",#olBCC);

            try
            {
                mapiExMail.save();
                mapiExMail.close();
                mapiExMail.finalize();
                item = outlookNameSpace.getItemFromID(strupr(entryId));

                //This will display the mail item
                //item.display();

                //This will directly send the mail without poping the mail window
                item.send();
            }
            catch
            {
                if (mapiInitialised)
                {
                    mapiEx.logout();
                    mapiEx.finalize();
                }

                // An error occured sending mail from outlook.
                throw error("@SYS97460");
            }
        }
    }
}

SysMailer:

In the following code you can see how to use SysMailer class for sending mails. To use SysMailer class you need to set Relay server or computer name, user name and password in Administration –> Setup –> Email parameters form. This class internally uses CDO.Message dll for communication purposes. Please note in AX 3.0 SysMailer uses Dundas.Mailer dll for communication.

static void emailThruSysMailer(Args _args)
{
    SysMailer   mailer = new SysMailer();
    SysEmailParameters parameters = SysEmailParameters::find();
    ;

    if (parameters.SMTPRelayServerName)
    {
        mailer.SMTPRelayServer(parameters.SMTPRelayServerName,
                           parameters.SMTPPortNumber,
                           parameters.SMTPUserName,
                           SysEmailParameters::password(),
                           parameters.NTLM);
    }
    else
    {
        mailer.SMTPRelayServer(parameters.SMTPServerIPAddress,
                           parameters.SMTPPortNumber,
                           parameters.SMTPUserName,
                           SysEmailParameters::password(),
                           parameters.NTLM);
    }

    mailer.fromAddress(‘sumit.loya@sumitloya.com’);
    mailer.tos().appendAddress(
‘sumit.loya@sumitloya.com’);
    mailer.body(‘hi’);
    mailer.sendMail();
}

SysInetMail:

SysInetMail internally uses Mapi framework only. But to use SysInetMail one has to setup email templates from Basic –> Setup –> Email templates. SysInetMail will automatically pick sender id, subject, sender name, email text etc. from the email template that you provide while sending from SysInetMail. If you provide a full email address and not the id from Email templates table then also mail will be sent but in that case you need to provide the details yourself.

static void emailThruSysInetMail(Args _args)
{
    SysInetMail mail = new SysInetMail();
    ;

    //To send to an email address directly
    mail.sendMailAttach(
‘sumit.loya@sumitloya.com’, ‘sreenath.girigari@sreenath.com’, ‘Test mail’, ‘Hi There’, false, ‘E:\\Test\\4000.pdf’);
   
    //To use an email template to send mail
    SysInetMail::sendEMail(‘Alerts’, ‘en-us’,
‘sumit.loya@sumitloya.com’);
}

SysEmailBatch:

SysEmailBatch internally uses SysMailer class and is used to send emails in batch. That is this class is batchable. Here is a small example for the class

static void emailThruSysEmailBatch(Args _args)
{
    SysEmailBatch   batch = new SysEmailBatch();
    ;

    batch.parmSenderAddr(‘sumit.loya@solugenix.com’);
    batch.parmEmailAddr(
‘sumit.loya@solugenix.com’);
    batch.parmMessageBody(‘Hi There’);
    batch.parmSubject(‘Test mail’);
    batch.run();
}

SmmOutlookEmail:

This class internally uses Mapi class and is extensively used in HRM module. One feature of this class is that we can specify email groups and it can send mails to all the members defined under this email group. Here is a sample code

static void emailThruSmmOutlookEmail(Args _args)
{
    SmmOutlookEmail smmOutlookEmail = new SmmOutlookEmail();
    ;

    if (smmOutlookEmail.createMailItem())
    {
        smmOutlookEmail.addEMailRecipient(
‘sumit.loya@sumitloya.com’);
        smmOutlookEmail.addSubject(‘Test mail’);
        smmOutlookEmail.isHTML(true);
        smmOutlookEmail.addBodyText(‘<html><body>Hi There</body></html>’);
        smmOutlookEmail.sendEMail(smmSaveCopyOfEmail::No);
    }
}