Periodic Processing in Axapta, Part I

 

Patrick G. Hodo
Chief Technical Officer
Red Maple Press, Inc.
Red Maple Technologies, Inc.

**Applies to:
    **Microsoft Business Solutions—Axapta

July 2005

Summary: This article describes how to set up periodic (job-based) processing within Microsoft Business Solutions—Axapta. (This is the first of two articles about processing.) (15 printed pages)

The reader should be a beginning- to mid-level X++ programmer, and be familiar with the Runbase and RunbaseBatch classes. Programmers (in general) should have some X++ training and be familiar with programming concepts such as inheritance and object-oriented programming (OOP). This knowledge is helpful but not essential. Knowledge of Microsoft Business Solutions—Axapta is assumed.

Download the sample code: axp_PerProcessingP1.exe

Contents

Introduction
Overview of Job-Based Processing
Periodic Processing in Axapta
Conclusion

Introduction

This article shows you how to set up periodic (job-based) processing within Axapta. You can do so by using the Runbase and RunbaseBatch classes in your own class to be called by a periodic menu item. Also covered is a broad introduction to these types of classes.

You can download a blank template (blankperiodic.xpo) to help you create your own periodic classes. The following steps and the template should help you create periodic processes to manipulate data and run reports.

The second article has further advanced topics about periodic processes. It shows you in detail how to use the Runbase and RunbaseBatch classes and explores their more advanced features.

**Note   **To use the example code, you must select Configuration Key "Commissions". The strings are in English rather than label IDs (for example, "Commission" rather than "@SYS71025") to make the code more readable.

Overview of Job-Based Processing

If you've been programming in the Axapta Application Object Tree (AOT) and need to write periodic processes or create sophisticated reports, you might have in the past copied some functionality from another Periodic menu item or used a form to approximate the functionality. While this may have worked at first, you might have encountered problems. First, copying classes without being aware of the underlying methods can create unexpected results. Second, if you use forms, your periodic screen looks different from a standard Axapta periodic interface. In addition, batch processes cannot be run by using forms.

Periodic processes require the collection of parameters, the retrieval of data, and the processing of that data according to the collected parameters. Axapta provides two classes that make this programming task easier. They are the Runbase and RunbaseBatch classes.

This article helps you to create a periodic process that updates inventory items to reflect a new Commission Item Group. You will collect parameters, define the data that you want to change, retrieve that data, and then modify it for your purposes.

Periodic Processing in Axapta

This article shows you how to create a periodic process in Axapta in the following functional areas:

  • Create a new class (or use the downloadable blankperiodic.xpo template).
  • Collect parameters.
  • Use those parameters to retrieve data to work with.
  • Process the data.

Creating a Class Based on the Runbase and RunbaseBatch Classes

Runbase and Runbasebatch are the two most common classes that drive the periodic processes in Axapta. With the Runbase class, you can create a periodic process but it cannot be run in a batch. RunbaseBatch inherits from Runbase. You can, however, run batch processes by using the RunbaseBatch class. To use these classes, you must create a new class that inherits the methods and properties of one of these classes.

If you do not want to create your own class, import the myPeriodic.xpo template into the AOT. It has the data that you need to complete the following periodic processing steps.

Note   Being familiar with inheritance is a helpful programming concept. For an explanation of inheritance, arguments about the benefits of using it, and examples, see Code Complete, Second Edition, by Steve McConnell.

To create a Runbase class in Axapta

  1. Create a new class in the AOT.

  2. Open the ClassDeclaration method in your new class.

  3. From the ClassDeclaration method, use the following code to change the title of the class to the following (or something similar):

    class myRunbase extends Runbase
    {
    }
    
  4. Click Save on the toolbar. Your new class should now reflect its new name.

Inheritance gives this class versatility, even if the only method present is ClassDeclaration.

Collecting Parameters

Collecting parameters can make a periodic process more useful and meaningful to a user. As a result, a periodic process rarely runs without input from a user (beyond clicking OK). You may need to make provisions for the user to enter some information before you run your periodic process.

Your users may want their periodic process to also retain some of the information that they previously entered from when they last ran their periodic process. Retained parameters provide the users the convenience of not having to repeatedly enter the same information into Axapta.

Retaining Parameters

Before you can collect parameters, you must establish the foundation for retaining the parameter values within your new class. Retaining parameters is a process that has several steps. First, you must define the parameters that you want to retain. Second, retain the choices (parameters) that your user's make by using the memory part of the Runbase class. To do so, use the Pack and Unpack methods in the Runbase class. These methods retain and retrieve the choices that have been made by the users by using a pre-defined macro for just such a case.

To define what fields your class retains

  1. Define the fields that will be used in the Runbase class.

  2. Include those fields in a macro in the ClassDeclaration method. You must separate each field in the list with a comma.

  3. Type the following code into the ClassDeclaration method.

    CommissItemGroup  commissItemGroup;  // Extended Data Type
                                         // for Item Groups
    NoYes    CreateNew;  //Enum field for a check box
    QueryRun    queryRun;  //QueryRun object for later use
    
    #define.CurrentVersion(1)  //Define the version of data in the macro
                               //(more later)
    #localmacro.CurrentList  //Data macro; notice that you include
                             // the two variables
       commissItemGroup,     //Declared previously, separated by commas
       createNew
    #endmacro
    

Create the Methods that Store and Retrieve the Fields

Now that you have established what fields that your class retains, create two different methods: one that stores the fields and one that retrieves the fields. They are as follows:

  • The Pack method stores the fields.
  • The Unpack method retrieves the fields.

To create a new Pack method, type the following code.

container pack()
{
    return [#CurrentVersion,#CurrentList, QueryRun.pack()];
}

The Pack method creates a container data type and stores the information that the macro in the ClassDeclaration method defines. The code includes the two macros from the ClassDeclaration method and the QueryRun object.

To create a new Unpack method, type the following code.

boolean unpack(container packedClass)
{
    Integer    _version    = conPeek(packedClass,1);
    container  _packedQuery;

    switch (_version)
    {
        case #CurrentVersion:
            [_version,#CurrentList,_packedQuery] = packedClass;
            if (!_packedQuery)
                return false;
            QueryRun = new QueryRun(_packedQuery);
            break;
        case 1                  :
            [_version,#CurrentList] = packedClass;
            QueryRun = new QueryRun(QueryStr(InventTable));
            break;
        default :
            return false;
    }

    return true;
}

The previous code is more in depth than is covered in the scope of this article. However, note that the Unpack method is not only analyzing the container for the version of the parameters, but it is also defining the InventTable query that you will run.

After you have defined your fields and then created the methods to store and retrieve the field values, you must now create and place those values into the fields on the dialog.

Create some dialog fields so that users can enter parameter values. To accomplish this task, you must modify the ClassDeclaration method.

To add the additional dialogCommissItemGroup and dialogNewField variables to that class, type the following code.

DialogField    dialogCommissItemGroup;
DialogField    dialogNewField;

To create a method called dialog, type the following code.

Object dialog()
{
    DialogRunBase    dialog = super();  //This uses the inherited method.

    dialogCommissItemGroup.value(commissItemGroup);  //Inserting our
                                                     //retrieved value
    dialogNewField.value(newItem);                   //Inserting our
                                                     //retrieved value

    return super();
}

The previous code has helped you accomplish the following:

  • Inherit the dialog method from the Runbase class.
  • Insert two values into the dialog fields.

Normally, declaring a variable and inserting a value into it would not accomplish much. In this case, the Unpack method was automatically called in the Runbase class prior to this Dialog method being called, allowing the Unpack method to fill in these variables with values. As a result, the fields show the user's prior selections when you open your ("MyPeriodic") class. Do not open the class now; that is covered later in this article.

Collecting Parameters

There are two ways to collect information (parameters) from a user. They are as follows:

  • Define a standard query, which allows a user to customize the fields and values for that query (covered later in this article).
  • Define custom fields and information that can be used in both the query and the periodic processing.

Add a Standard Query

A standard query in Axapta is an object in the AOT, and defines a database lookup. A standard query can be as straightforward as one table or as sophisticated as multiple tables. Figure 1 shows an InventTable query in the AOT.

Figure 1. InventTable query.

In figure 1, the Ranges node is highlighted. Only the ItemId field is listed. The Ranges node is where you establish the information that you want to collect. If you want to include the item name, for instance, add it to the query.

Note   Create your own query if you want to collect specific information. Adding extra ranges to an established standard query may inconvenience other users.

To add the query to your process, type the following code. (It was previously mentioned in your ClassDeclaration method.)

   QueryRun queryRun;

The QueryRun method is a means of running the query that you define. After you add the QueryRun method to your class, you must ensure that it is initialized before you proceed. Initialize your class by using one of the following methods:

  • New()
  • initParmDefault()

For now, place the code in the new() method.

To initialize the QueryRun object

  1. Create a method called new.

  2. Enter the following code:

        ;
        super();
    
        queryRun = new QueryRun(querystr(inventTable));
    

When you run your class, the QueryRun method is automatically initialized (instantiated). The QueryRun method is filled in with the InventTable query, which was previously discussed. You can fill in your own query here (the second article in this series shows you how to create an on-the-fly query). Make sure that you include the QueryStr function with the query. Otherwise, Axapta interprets your text as the InventTable table, not the query.

To finish creating your query, add the QueryRun method by typing the following code.

QueryRun queryRun()
{
    return QueryRun;
}

Note   To allow users to modify the query that you are using, you must give them that option. To do so, create a new QueryValues method by entering the following code.

boolean showQueryValues()
{
    return true;
}

Otherwise, set the value to false.

Defining Custom Parameters

Defining customer parameters begins by collecting from the user the information that does not exist in the query. In the previous code, you added two fields in the ClassDeclaration method of a data type called DialogField:

  • dialogCommissItemGroup
    This field is where the user can define the Commission Item Group.
  • dialogNewItem
    This field is used to automatically create a new Commission Item group while running the Periodic process.

To use these fields, you must take the following steps.

To use the dialogCommissItemGroup and dialogCommissItemGroup fields

  1. Publish the fields by modifying the dialog method (so that the user can see them).
  2. Transfer the values that have been entered by the user into variables that you can use by creating the getFromDialog method.

To modify the dialog method, type the following code.

;
dialogCommissionItemGroup = 
    dialog.addField(typeid(CommissItemGroup),"Commission Item Group");

dialogNewField = dialog.addField(typeId(NoYes),"Create new group");

dialogCommissItemGroup.value(commissItemGroup); //Inserting our
                                                //retrieved value
dialogNewField.value(createNew);                //Inserting our
                                                //retrieved value


return super();

To create the getFromDialog method, type the following code.

public boolean getFromDialog()
{
    ;
    commissItemGroup    = dialogCommissItemGroup.value();
    createNew           = dialogNewField.value();

    return super();
}

You have transferred the information that the user entered into the dialog fields to the variables that you previously declared.

You have completed the process of getting information from the user. However, the MyPeriodic class is not yet ready to run. The following section shows you how to run your class.

Run the Periodic Function

To run the MyPeriodic class, you must create a menu item to open your class. That menu item automatically opens your class by using a preset method called Main. As a result, to allow a class to be opened by a menu item, you must manually create the Main method and the run-time instructions. This is typically done with the following steps.

To create the Main method and the run-time instructions

  1. Create a static method called Main. This allows your new class to be called from a menu item.
  2. Create a Run method. This creates the run-time instructions for your new class.

Main Method

The Main method is a static method that is inherited from the underlying Runbase classes. (Discussion of the inheritance of this method is beyond the scope of this article.)

To create the new Main method, type the following code.

static void main(Args args)
{
    MyRunbase   myRunbase;
    ;
    myRunbase = new myRunbase();

    if (myRunbase.prompt())
    {
        myRunbase.run();
    }
}

More information about the Main method (as used in the previous code) is as follows:

  • The MyRunbase class has a modifier: static. This indicates that this class is available without it having to be instantiated. As a result, it can be called from a menu item.
  • You have passed the Args variable to the Main method. Args is a generic variable that can contain any number of variables. In this case, however, it contains the variables that are passed from the menu item that is calling your new class.
  • You have declared your new class and instantiated it. You have done this from within your class because you are using a static method. As a result, you can only work with your new class in a static method by declaring it separately.
  • You have used the prompt method. It is inherited from the underlying Runbase class. The prompt method calls the dialog method. It displays the fields and query that you previously created.

Run Method

The Run method creates the run-time instructions for your new class. It is also where you perform the actual data processing. Set up a Try/Catch structure in the Run method to use the advanced event handling available in Axapta.

To set up a Try/Catch structure in the Run method, type the following code.

Void run()
{

try
{
}
catch (Exception::Deadlock)
{
    retry;
}
catch (Exception::Error)
{
}

}

You can now right-click your new class and select Open. If everything is coded correctly, you should see the dialog form.

For more information on the previous Try/Catch code, see the Microsoft Axapta Developer Help or the Microsoft Axapta Developer's Guide.

To add a description to the dialog form (as it doesn't yet have one), type the following code into your new class.

client server static public ClassDescription description()
{
    return "SPIFF Creator";
}

You are now ready to process the user's choices, and then get the data based on those choices.

Process the Data

To process the data, merge together the fields that you want to collect from your user and your newly defined query that gets the required data in the run() method.

When users modify the query that you established in the QueryRun() method, they select the inventory items that they want to include in the Commission Item Group. The Commission Item Group field, which you populated in the Dialog() and getFromDialog() methods, is the specific item group to use. The last field, CreateNew, allows users to automatically create a new Commission Item Group value instead of using the actual Commission Item Group form. When a user selects this field, your new class creates the value before running the rest of your new class.

To create local variables in the Run() method, type the following code.

    InventTable     inventTable;
    InventTable     updateTable;
    ;

Ensure that a user has entered the information that you need.

if (!commissItemGroup)
       throw error("You must enter a commission item group.");

Check if the user has selected the New dialog box that you created. If so, use a new method called CreateItemGroup to create the Commission Item Group.

if (createNew == NoYes::Yes)
   this.createItemGroup();

To create the CreateItemGroup method, type the following code.

void createItemGroup()
{
    CommissionItemGroup     itemGroupTable;
    ;
    itemGroupTable.clear();
    itemGroupTable.GroupId = commissItemGroup;
    itemGroupTable.Name = "New Item Group";
    itemGroupTable.insert();
}

In the CreateItemGroup() method, you have taken the value entered by the user and automatically created a Commission Item Group. The Name field is a string (in English) and in double quotes. This string is not essential; you could have left the field blank.

Work with the records to update the Inventory table to reflect your new commission item group. You cannot modify records in a query in this scenario. Instead, transfer the query record into a table variable. You can then modify the record. This is why you earlier declared the InventTable query.

To go through the query and transfer any records to update your table variable, type the following code.

...
try
    {
        while (queryRun.next())
        {
            inventTable = queryRun.get(tableNum(inventTable));
        }
    }
...

In the previous code, you are getting the record from the query and placing that record into your variable. You have to specify the name of the table that you are retrieving the record from. This is because, in some queries, you may have more than one table. As a result, you must specify which one to use.

To update the records, use the ttsbegin and ttscommit system methods to begin and end your data updates. Type the following code into the Run method.

...
inventTable = queryRun.get(tableNum(inventTable));
        
ttsbegin;

select firstonly forupdate updateTable
                index hint itemIdx
                where updateTable.RecId == inventTable.RecId;

updateTable.CommissionGroupId = commissItemGroup;
updateTable.update();

tscommit;
...

More information about what you have done in the previous code is as follows:

  • Selected the record that you wanted to update to begin the process of changing the record's values. To begin this process in Axapta, you used the forupdate keyword in the select statement.
  • Placed the new value into the Inventory table variable that you used (in this case, the updateTable variable).
  • Updated the record by using the update method. The transaction system ensures that your record is saved.

The following is a review of the coding that you have done so far:

  • Collected the parameters from the user. The user can enter specific criteria and modify the query.
  • Retrieved the data to modify by using a query.
  • Modified the data using the run() method. This saved the data to the database.

If you want to add additional error handling into the method, you can do so by adding code to thecatch (Exception::Error)section of the code.

To run your new class from a menu

  1. Do one of the following:

    Drag your new class into the Menu Items > Action in the AOT (which automatically creates your menu item).
    -or-
    Right-click the Menu Items > Action node of the AOT, and then select New Menu Item. In the Label property, type a descriptive word or phrase, such as Item commission population.

  2. Expand the Menus node in the AOT.

  3. Find a location in the periodic section of the Cust menu for your new periodic process.

  4. Drag the menu item into the Cust menu. The item now appears and is shown in figure 2.

    Figure 2. SPIFF Creator.

Batch processing

The Runbasebatch class allows for batch processing. To use it, you must make the following three changes to the class.

  1. Change the ClassDeclaration method to readextends Runbasebatchinstead ofextends Runbaseas shown in the following code. Duplicate your class and rename it to myRunbaseBatch to see the differences.

    class myRunbaseBatch extends Runbasebatch
    {
    }
    
  2. Add a construct static method to the myRunbaseBatch class with the following code.

    server static myRunbaseBatch construct()
    {
        return new myRunbaseBatch();
    }
    
  3. Modify the Main() class to use your newly created Construct method with the following code.

    MyRunbasebatch   myRunbasebatch;
    ;
    myRunbasebatch = myRunbasebatch::construct();
    
    if (myRunbasebatch.prompt())
    {
        myRunbasebatch.run();
    }
    

When you open the form, your new tab interface to the dialog appears. In that interface, there are two tabs: General (general information) and Batch (batch functionality). See figure 3.

Figure 3. SPIFF Creator General tab.

You can now use the batch interface as you would on any other class.

Conclusion

This article has shown you how to begin writing periodic classes using the Runbase and Runbasebatch classes. The next article in this series explores writing periodic classes more in depth and covers their more advanced features.