vendredi 20 décembre 2013

Working with Data in Forms - Processing multiple

records

Note: This tutorial works on all Ax versions, the example that I made was on AX 2009.

we are going to explore several ways of achieving this goal. We will modify the Items form in the Inventory management module by adding a new button to it, which lists currently selected records in the overview grid.

How to do it....

1. In AOT, open the InventTable form and create a new method with the following code:
      
   void processSelectedItems()
      {
            InventTable inventTableLocal;
            ;
         for (inventTableLocal = InventTable_ds.getFirst(true) ?
                     InventTable_ds.getFirst(true) :
                      InventTable;
                      inventTableLocal;
                       inventTableLocal = InventTable_ds.getNext())
     {
               info(strfmt(  "You've selected item '%1'", inventTableLocal.ItemId));
        }
    }

 2. Add a new Button to the form's ButtonGroup group:

Property
Value
Name
ProcessSelectedItems
Text
Process
MultiSelect
Yes

       3. Override its clicked() with the following code:
              
             void clicked()
           {     ;
                        super();
                        element.processSelectedItems();
            }
 4. To test the record selection, open Inventory management | Item Details, select several records using SHIFTor CTRL and click the Process button. The selected items should be displayed in the Infolog:

       

How it works...


The key element in this recipe is a for statement in processSelectedItems(). First, it checks if more than one record is selected by calling getFirst(true) on the InventTable form data source. If yes, the for loops all the selected records from the data source, otherwise it uses the cursor, which corresponds to the currently selected single record. All selected records are then stored in the local variable inventTableLocal. In this example, we just output inventory item numbers into the Infolog using the global info() function.
The ProcessSelectedItems button is used to call the function above once the user clicks it. Notice that its property MultiSelect is set to Yes to ensure it is still enabled when multiple records are selected.
There's more...
I have also experienced that sometimes Dynamics AX users struggle to select multiple records using SHIFTor CTRL. Sometimes, it is not clear that the form itself supports multiple record processing. In such cases a more convenient way of selecting multiple records could be to add a new checkbox in front of each record. This would remove all confusion and improve user experience. In order to implement that, we need to make few changes to the example above.

First, we add a new Map variable to the form's class declaration:

       Map marked;

And in the form's init() right after the variable declaration section we create its instance:

      marked = new Map(Types::Int64, Types::String);

And in the form's init() right after the variable declaration section we create its instance:
  
      marked = new Map(Types::Int64, Types::String);

Then, we create a new edit method on the InventTable data source with the following code:

       edit boolean editMark( boolean _set, InventTable _inventTable, boolean _mark)

       {    ;
            if (_set)
            {
                if (!_mark)
                {
                     if (marked.exists(_inventTable.RecId))
                     {
                          marked.remove(_inventTable.RecId);
                     }
                }
                else
               {
                    marked.insert(_inventTable.RecId, _inventTable.ItemId);
               }
            }
            return marked.exists(_inventTable.RecId);
       }

We also add a new CheckBox to the top of the form's Grid in the Overview tab page with the following properties:

          
     Property
 Value
     Name
 Mark
     Label
Select
     DataSource
InventTable
     DataMethod
editMark

Finally, we have to modify processSelectedItems() to loop though the map. Replace the method with the following code:

       void processSelectedItems()

      {
            MapEnumerator mapEnumerator;
            ;
            mapEnumerator = marked.getEnumerator();
            while (mapEnumerator.moveNext())
            {
                  info(strfmt("You've selected item '%1'", marked.lookup(mapEnumerator.currentKey())));
            }
      }

Open the Items form again and notice that now it has a new checkbox Select in front of each record. Pick several records using it and click Process. The selected items should be displayed in the Infolog:

         

The principle of this technique is that we use a Map type object to store the list of selected item numbers. The editMarked() method is bound to the checkbox control and is responsible for adding a record to the map upon user selection and removing it from the map if the user deselects the checkbox.

We also use the MapEnumerator class to retrieve item numbers from the map for further processing.

jeudi 19 décembre 2013



The best way to create a Class in Dynamics AX


Some time ago, I talked about the ‘construct’ pattern that is use a lot in AX. Although I use it often, it’s not perfect because it violates some best practices. (Also, you could end up with a large class hierarchy of classes that extend each other, but I’ll discuss that in an other post.)

First off, know that you can change the compiler level and the best practices the compiler checks.
To do this, compile something so the compiler output window pops up. Then click the setup button, and adjust the settings.
I used compiler level 4 and enabled all BP checks for this example.

So, let create a simple class. This class will simply print information of an InventTable record to the screen.

1. ClassDeclaration
     When we think about this class a bit, we immediately realize that we need to declare an InventTable record as a member of out class:

/// <summary>
/// Class written to test construct and new... patterns
/// </summary>
/// <remarks>
/// Delete this class when proven a point :)
/// </remarks>

class KLFORTestBP
{
InventTable inventTable;
}


Also note that I added documentation to this method. I will do so for other methods too. When you don’t, you’ll get a BP deviation warning.

2. Parm method
One of the things that are important (or at least I find important), is using parm methods for your class variables. So, let’s create one:

// use parm methods to access class variables
public InventTable parmInventTable(InventTable _inventTable = inventTable)
{
;
inventTable = _inventTable;

return inventTable;
}


3. New method
You should never write code in the new method, or add parameters to it. On the contrary, you should always set this method to protected, and use a static construct() or a static new…() method instead.

// a new method should be protected
// use static new... or construct instead

protected void new()
{
}


4. Run method
The run method will be our method that contains our logic. Here, we print info to the screen

/// <summary>
/// Executes logic on the parameters of this class
/// </summary>
/// <remarks>
/// Just for demo purposes
/// </remarks>

public void run()
{
;
info(this.parmInventTable().NameAlias);
}


Note that we are using the parm method instead of directly accessing the variable.

5. Construct method
Because the new method is now protected, we will need a contruct method to be able to create an instance of this class.

private static KLFORTestBP construct()
{
;
return new KLFORTestBP();
}


When a class doesn’t need parameters, you can leave out the ‘private’, but now, it is private because we plan on creating a new…() method that contains parameters. A construct method shouldn’t contain parameters, and should only be used to create a new instance of your class. It is also the only place in your application where you can call the new() method of your class.

6. New…() method
As said earlier, because we need a parameter (an InventTable record), we use a new…() instead of the construct method to create an instance of our class. I will name it newFromInventTable() based on the parameter it has.
In this method, we call the contruct() method to create an instance (instead of new()), set the parm method from the parameter and return the object.

/// <summary>
/// Creates a new KLFORTestBP object from an InventTable record
/// </summary>
/// <param name="_inventTable">
/// The InventTable used to create the object
/// </param>
/// <returns>
/// New instance of KLFORTestBP with the InventTable parameter set
/// </returns>
/// <remarks>
/// Use a new... method instead of the constuct method because it takes parameters/// A
/// </remarks>

public static KLFORTestBP newFromInventTable(InventTable _inventTable)
{
KLFORTestBP kLFORTestBP;
;

kLFORTestBP = KLFORTestBP::construct();
kLFORTestBP.parmInventTable(_inventTable);

return kLFORTestBP;
}


You could also create an init() method that initializes you class (we don’t need initialization now). The init() method should return a boolean if initialization can go wrong, so you can throw an error.
It would look like this:

public static KLFORTestBP newFromInventTable(InventTable _inventTable)
{
KLFORTestBP kLFORTestBP;
;

kLFORTestBP = KLFORTestBP::construct();
kLFORTestBP.parmInventTable(_inventTable);

if(!kLFORTestBP.init())
{
throw error("Item record has no data"); // todo: create label
}

return kLFORTestBP;
}

Remember to create a label for the text string, or you will get a best practice error.

7. Main method
Now, all we need more is a main method, so this class is runable (by pressing F5 or as a menu item).

server public static void main(Args _args)
{
KLFORTestBP kLFORTestBP;
InventTable inventTable;
;


if(_args && _args.dataset() == tablenum(InventTable))
{

// a button was pressed on a InventTable datasource
inventTable = _args.record();
}
else
{

// for demo, simple select the first one
select firstonly inventTable;
}


// display item information
kLFORTestBP = KLFORTestBP::newFromInventTable(inventTable);
kLFORTestBP.run();
}


Normally, you will get the record from the args object, but for demo purposes, I added the ‘select firstonly’ so you can run the class by pressing F5. As you can see, the new…() method is used to create an instance of the class, and then the run method is called.

There you go, you now have a best practice deviation free class!

8. Using the class
Using the class as a menu item or in code couldn’t be easier.

You can drag/drop the class to the action menu item node in the AOT to create a menu item, and you will be able to use it on any form with the inventTable datasource.

From code, you can simple write this line (assuming there is a variable called inventTable):

KLFORTestBP::newFromInventTable(inventTable).run();

Conclusion:
When writing code that doesn’t contain any best practice deviations (especially when checking them all on compiler level 4), you quickly end up with a lot of methods in your class and wondering if the effort is worth it. It can be a bit painful to get your code best practice deviation free.
However, you’ll also realize that following the best practices has its advantages. Your code will be cleaner, easier to understand, easier to debug because of the parm, constuct and run methods, less subject to change, contain less errors and your class will be used in the way you intended when you wrote it.
How to create a sales order form X++ code

Hi,
In this tutorial, I'm going to be showing you how to create a sales order form code, for that I'll need to create two methods:
 - The first one will create the sales header.
 - The second one will create Lines for each sales header.

static void createSalesTable(CustAccount _custAccount) {
 SalesTable    salesTable;
 NumberSeq   numberSeq;
;
NumberSeq = NumberSeq::newGetNumFromCode(SalesParameters::numRefSalesId().numberSequence);
salesTable.SalesId = NumberSeq.num();
salesTable.initValue();
salesTable.CustAccount = _custAccount;
salesTable.initFromCustTable();
salesTable.insert();
}
static void createSalesLine(SalesId _salesId, ItemId _itemId) {
SalesLine salesLine;
;
salesLine.clear();
salesLine.SalesId = _salesId;
salesLine.ItemId = _itemId;
salesLine.createLine(NoYes::Yes, // Validate
NoYes::Yes, // initFromSalesTable
NoYes::Yes, // initFromInventTable
NoYes::Yes, // calcInventQty
NoYes::Yes, // searchMarkup
NoYes::Yes); // searchPrice
}

lundi 16 décembre 2013

How to send an Alert Email notifying changes on records

Let's create a simple class with a static method to alert when a record is changed somewhere and what changes are made in the records:

static void CompareAndEmail(str emailTemplateName, str nameField, str recipient, Common original, Common modified)
{
    UserInfo    userInfo;
    Map         emailParameterMap = new Map(Types::String, Types::String);
    str         changes;
    int         i, fieldId;   
    DictTable   dictTable = new DictTable(original.TableId);
    DictField   dictField;
;

    for (i=1; i<=dictTable.fieldCnt(); i++)
    {
        fieldId = dictTable.fieldCnt2Id(i);
        dictField = dictTable.fieldObject(fieldId);

        if (dictField.isSystem())
            continue;

        if (original.(fieldId) != modified.(fieldId))
        {
            changes += strfmt("%1: %2 -> %3 \n\r",
                dictField.name(),
                original.(fieldId),
                modified.(fieldId)
            );
        }
    }

    //Send Notification Email
    select Name from UserInfo where userInfo.id == curUserId();
    emailParameterMap.insert("modifiedBy", userInfo.Name);
    emailParameterMap.insert("tableName", dictTable.name());
    emailParameterMap.insert("recordName", original.(dictTable.fieldName2Id(nameField)));
    emailParameterMap.insert("recordChanges", changes);

    SysEmailTable::sendMail(emailTemplateName, "en-us", recipient, emailParameterMap);
}

Then in the .update() method we  add this one line
//Compare and email differences
RecordChangeNotification::CompareAndEmail(
    "RecChange",            //Template to use
    "Name",                 //Name field of the record (MUST BE VALID)
    "user@domain.com", //Recipient email
    this_Orig,              //Original record
    this                    //Modified record
);

we have created an email template in Ax, using the %varname% as a placeholder for items added to the map when you send the email with this:
emailParameterMap.insert("modifiedBy", userFullName);
emailParameterMap.insert("vendorName", vendorName);

SysEmailTable::sendMail("VendChanges", "en-us", "user@domain.com", emailParameterMap);

vendredi 13 décembre 2013

Creating automatic email confirmations from Dynamics AX

Automating e-mail communication is a great way to streamline your business processed. One example of doing this would be to have sales order confirmations sent out when they are approved within the ERP system.
You can do this through workflows if you want, but in this example we will show how you can add a small function to Dynamics AX that will automatically send the confirmations through your local SMTP server, without the user having to even touch their e-mail system.

Step 1: Make Sure That Your SMTP Parameters Are Set

The first step is to make sure that you have the SMTP server registered and configured correctly within Dynamics AX. To do this go to the System Administration – Setup – System – Email Parameters.

Step 2: Create An E-mail Template

Rather than hard code the e-mail body, we are going to take advantage of the e-mail templates functionality that is built into Dynamics AX. You can find the e-mail templates in the Organization – Setup – E-Mail templates form.
After you open up the form, you will want to create a new template.
And also create a new e-mail layout.
When you open up the layout, you can paste in the e-mail body that you want to use.
For the dynamic sections of the e-mail, place tokens surrounded by “%”.
After you have done this, you can preview the e-mail.

Step 3: Find Your Form To Extend

Now we want to start creating a little bit of X++ code. But before we do that we want to find the form that we want to modify. For this example, I want to override the Conformation button on the Sales Order form. To find the form name, I just opened up the Personalization form, and it will give me the real form name.
From there, we can open up AOT and can drill down into the ActionPane items and find the Confirm button that we want to override.

Step 4: Create A New Project

Rather than change the form directly, it’s better to wrap the code within a project. So we will create a new project.
Now we will drag the form design from AOT over to the project.
Now we are going to add our own code to the form for the e-mail. To do this, we will override the “clicked” method on the form.
That will open up the X++ code editor for us. Notice that the method already has a super() function that will call down to the lower level code, so here we just need to add our own code either before or after this function.
To start off we will create a couple of variables to load in the Email Template, and also set a default recipient email address.


Also, after the super() function we will perform a sendEmail. This function will create an e-mail and send it through the default SMTP server. This is a little different from the normal method which requires the e-mail to be sent through the users e-mail package.
To make this a little more dynamic, we will find the customer record for the customer that is referenced in the sales order, and then find their e-mail.
Finally rather than just sending out a form letter with no personalization, we will create a mapping variable, and tell the system to replace all of the tokens that we have in the email with the values from the sales order.

Step 5: Send Your E-mail

Now we can test the function. Before we do that though, make sure that you have a default e-mail address assigned to the customer.
Now we will click on the Confirmation button and see what happens.
I set a break point on the function just so that I could make sure that it’s running correctly, and it looks like it is.

And also all of the standard logic is working as well.

If you look at the Email Sending Status in the System Administration menu, you will also be able to see the e-mail as it is queuing up to be sent.

Result: E-mail Sent

And now the customer also receives a confirmation without ant e-mail interaction from the customer service rep.