Showing posts with label Jobs. Show all posts
Showing posts with label Jobs. Show all posts

Wednesday, January 14, 2015

Creating batch jobs with X++

Two jobs to create batch jobs with X++, one for runBase and other for SysOperation.
The two jobs are essentially the same, except that the SysOperation job uses both controller and contract classes.
You could also choose to add dependency between two tasks.
batchHeader.addTask(batchTaskToRun);
batchHeader.addTask(dependsOnBatchTask);
batchHeader.addDependency(batchTaskToRun, dependsOnBatchTask);

You will get the same result as here, my previous blog post on adding dependency between jobs manually.

 static void batchRunBase(Args _args)  
 {  
   #define.recurrenceStartTime('23:00')  
   #define.recurrenceInterval(1)  
   #define.noOfRetriesOnFailure(4)  
   BatchHeader                  batchHeader;  
   BatchInfo                    batchInfo;  
   SysRecurrenceData            sysRecurrenceData;  
   utcDateTime                  batchStartTime;  
   Batch                        batch;  
   Tutorial_RunBaseBatch        yourRunBaseBatchClass;  
   ;  
   select firstOnly RecId from batch  
     where batch.ClassNumber == classNum(Tutorial_RunBaseBatch) &&  
       batch.Company == curext() &&  
       batch.DataPartition == getcurrentpartition();  
   if (!batch.RecId)  
   {  
     yourRunBaseBatchClass = Tutorial_RunBaseBatch::construct();  
     batchInfo = yourRunBaseBatchClass.batchinfo();  
     batchInfo.parmRetriesOnFailure(#noOfRetriesOnFailure);  
     // Create the batch job  
     batchHeader = BatchHeader::construct();  
     batchHeader.addTask(yourRunBaseBatchClass); // or controller class  
     //batchHeader.parmCaption("Tutorial");  
     batchStartTime = DateTimeUtil::newDateTime(systemDateGet(), str2time(#recurrenceStartTime), DateTimeUtil::getUserPreferredTimeZone());  
     // Set the recurrence data  
     sysRecurrenceData = SysRecurrence::defaultRecurrence();  
     sysRecurrenceData = SysRecurrence::setRecurrenceStartDateTime(sysRecurrenceData, batchStartTime);  
     sysRecurrenceData = SysRecurrence::setRecurrenceNoEnd(sysRecurrenceData);  
     sysRecurrenceData = SysRecurrence::setRecurrenceUnit(sysRecurrenceData, SysRecurrenceUnit::Day, #recurrenceInterval);  
     batchHeader.parmRecurrenceData(sysRecurrenceData);  
     // Set the batch alert configurations  
     batchHeader.parmAlerts(NoYes::No, NoYes::Yes, NoYes::No, NoYes::Yes, NoYes::Yes);  
     batchHeader.parmStartDateTime(batchStartTime);  
     // Save the batch job  
     batchHeader.save();  
   }  
 }  
 static void batchSys(Args _args)  
 {  
   #define.recurrenceStartTime('23:00')  
   #define.recurrenceInterval(1)  
   #define.noOfRetriesOnFailure(4)  
   BatchHeader                                 batchHeader;  
   BatchInfo                                   batchInfo;  
   SysRecurrenceData                           sysRecurrenceData;  
   utcDateTime                                 batchStartTime;  
   Batch                                       batch;  
   AbcDemandForecastServiceController          AbcDemandForecastServiceController;  
   AbcDemandForecastServiceContract            AbcDemandForecastServiceContract;  
   ;  
   select firstOnly RecId from batch  
     where batch.ClassNumber == classNum(AbcDemandForecastServiceController) &&  
       batch.Company == curext() &&  
       batch.DataPartition == getcurrentpartition();  
   if (!batch.RecId)  
   {  
     AbcDemandForecastServiceController = new AbcDemandForecastServiceController(classStr(AbcDemandForecastServiceOperation), methodStr(AbcDemandForecastServiceOperation, calcDemandForecast), SysOperationExecutionMode::Synchronous);  
     AbcDemandForecastServiceContract = AbcDemandForecastServiceController.getDataContractObject(classStr(AbcDemandForecastServiceContract));  
     batchInfo = AbcDemandForecastServiceController.batchInfo();  
     batchInfo.parmRetriesOnFailure(#noOfRetriesOnFailure);  
     // Create the batch job  
     batchHeader = BatchHeader::construct();  
     batchHeader.addTask(AbcDemandForecastServiceController);   
     //batchHeader.parmCaption("Tutorial SysOperation");  
     batchStartTime = DateTimeUtil::newDateTime(systemDateGet(), str2time(#recurrenceStartTime), DateTimeUtil::getUserPreferredTimeZone());  
     // Set the recurrence data  
     sysRecurrenceData = SysRecurrence::defaultRecurrence();  
     sysRecurrenceData = SysRecurrence::setRecurrenceStartDateTime(sysRecurrenceData, batchStartTime);  
     sysRecurrenceData = SysRecurrence::setRecurrenceNoEnd(sysRecurrenceData);  
     sysRecurrenceData = SysRecurrence::setRecurrenceUnit(sysRecurrenceData, SysRecurrenceUnit::Day, #recurrenceInterval);  
     batchHeader.parmRecurrenceData(sysRecurrenceData);  
     // Set the batch alert configurations  
     batchHeader.parmAlerts(NoYes::No, NoYes::Yes, NoYes::No, NoYes::Yes, NoYes::Yes);  
     batchHeader.parmStartDateTime(batchStartTime);  
     // Save the batch job  
     batchHeader.save();  
   }  
 }  

Sunday, February 16, 2014

Handy script to update storage & tracking dimension on an item

Below code snippet updates an item's storage and tracking dimensions.

 InventTableInventoryDimensionGroups::updateDimensionGroupsForItem(curext(), inventTable.ItemId,  
 5637144577,  
 5637144577);  

All you need is itemId and recIds of the storage and tracking dimensions to update, which can be found in EcoResStorageDimensionGroup and EcoResTrackingDimensionGroup tables holding the recIds of Storage and Tracking dimensions.

I had a recent requirement to make sure there are no itemIds in the system which dont have storage or tracking dimensions not set as I had to create and post some counting journals that  require these dimensions. Luckily all the items required the same storage and tracking dimensions so my job became easier.

I used the below job to run a while loop with notexists join finding out items that don't have these dimensions set and update the same.

 static void ScriptToUpdateStorageTrackingDimensions(Args _args)  
 {  
   InventTable                       inventTable;  
   EcoResStorageDimensionGroupItem   EcoResStorageDimensionGroupItem;  
   EcoResTrackingDimensionGroupItem  EcoResTrackingDimensionGroupItem;  
   int                               storageCount, trackingCount;  
   
     while select inventTable  
     notexists join EcoResStorageDimensionGroupItem  
     where EcoResStorageDimensionGroupItem.itemId == inventTable.itemId &&  
     EcoResStorageDimensionGroupItem.ItemDataAreaId == 'abc'  
   {  
     storageCount++;  
     InventTableInventoryDimensionGroups::updateDimensionGroupsForItem(  
       curext(), inventTable.ItemId,  
       5637144577,  
       0);  
   }  
     while select inventTable  
     notexists join EcoResTrackingDimensionGroupItem  
     where EcoResTrackingDimensionGroupItem.itemId == inventTable.itemId &&  
     EcoResTrackingDimensionGroupItem.ItemDataAreaId == 'abc'  
   {  
     trackingCount++;  
     InventTableInventoryDimensionGroups::updateDimensionGroupsForItem(  
       curext(), inventTable.ItemId,  
       5637144577,  
       5637144577);  
   }    
   //sw - storage - 5637144577  
   //none - tracking - 5637144577  
   info(int2str(storageCount));    
   info(int2str(trackingCount));  
   info('Done');   
 }  

Friday, October 18, 2013

Job to fetch sales orders with lot of lines

A quick job to fetch sales orders with lot of lines.

static void salesTableFetch(Args _args)
{
    SalesLine     salesLine;    
    ; 
   
    while select count(recId), SalesId from salesLine
        group by SalesId

    {
        if (salesLine.RecId > 100)
        {
            info(salesLine.salesId);
        }
    }
}

Thursday, September 19, 2013

Read Enum Elements

A quick job to fetch enum elements using a for loop. Much better than having to manually type the values.

 static void EnumValues(Args _args)  
 {  
   DictEnum      enum = new DictEnum(enumName2Id("ABC"));  
   int         i;  
   for (i=0; i < enum.values(); i++)  
   {      
     info(strFmt("%1-%2", enum.index2Label(i), enum.index2value(i)));   
   }   
 }  

Thursday, May 24, 2012

Services in AX 2012 - working example


In Microsoft Dynamics AX 2012, the Application Integration Framework (AIF) underwent a number of dramatic changes and is now known as Services. There are more number of WCF service hosts and simplified forms and concepts.
There are new terms like Service Groups and Ports (Basic, Enhanced), which we need to understand.
Service group - collection of Services. Why would you collect multiple services under a group? Consider a case of creation of a sales order and customer specified doesn't exist in AX. You will need another service for creation of customer. Both these services can be now clubbed together under one Service group. Also, with a single WSDL, you only need to add one service reference in Microsoft Visual Studio to access all of the objects from the services in the Service group.
Ports - Erstwhile (read AX 2009) local endpoints, channels and endpoints have been consolidated into a port.
Basic port - created automatically as soon as you have deployed your Service group. Also, Basic ports are deployed as WCF services only to the AOS and use the NetTcp adapter.
Enhanced port - created manually. They give you several options around hosting, adapter types, document processing, error handling and security. The choices of adapter types are:
•    File system adapter
•    HTTP- To consume services over the Internet, you must use the HTTP Adapter and host services on IIS. IIS routes all service requests to the AOS. Regardless of the origin of the service request, Internet or Intranet, all the service requests are processed on the AOS.
•    MSMQ - Similar to File adapter. For the URI of the Address and Response addresses browse to the directories that are mail queue directories.
•    NetTcp- Use this adapter when you want to use a service on the Intranet and not on the Internet.

Using the File Adapter
In this example, you will configure an enhanced port to use the file system adapter. Then you will import a sales order using the port.
Overview of the task
1. Create a new enhanced port.
2. Set the Adapter to File system adapter.
3. Set the default file owner to the administrator account.
4. Add the SalesOrder create operation to the list of service operations exposed on this port.
5. Activate the port.
6. Create an xml document that contains a sales order.
7. Run an X++ job that runs the AIF classes and imports the sales order.

Step by Step Instructions
Create and Activate an Enhanced Port
1. In Windows Explorer, create a Services directory and inside of it create an Inbound and an Outbound directory.
2. In Microsoft Dynamics AX under System administration, click Setup, click Services and Application Integration Framework, click Inbound ports and then click New.
3. Enter the name FileSystemAdapterPort for the port.
4. Click Register Adapters to ensure you see all of the adapters available in the environment.
5. Click Register services to ensure you see all of the service operations available in the environment.
6. In the Address section for the Adapter field, select File system adapter.
7. In the Address section for the URI field, click the dropdown and navigate to the Services - Inbound directory.
8. In the Response address section for the Adapter name, select File system adapter.
9. In the Response address section for the URI field, click the dropdown and navigate to the Services - Outbound directory.

10. In the Address section, click Configure.
11. Mark the Enable default file owner and select the Admin user. Admin will be the default owner of the xml files submitted to the Inbound directory.
12. Click Services operations.
13. From the Remaining service operations list, select SalesSalesOrderService.create and click the < arrow.
14. Click Close.
15. Click Activate.
16. Close the ports form. 
 
Create a XML File

1. In Windows, open Notepad.
2. Copy the following to a new Notepad document: (Note: Not able to display the below XML code in the blog, somehow displays junk characters, workaround i found is to use # in front of each row. Take care to remove the # in your actual code.)
# 
# 
#
#northamerica\dyndust #CEU #http://schemas.microsoft.com/dynamics/2008/01/services/SalesOrderService/create #
#Body # # # #DAT-000001 #2011-01-16 #en-us #PO123 # #1001 #88.00 #ea # #HD #01 #2 #42 # # # # # #/Body #
 
3. Save the file in the Services - Inbound directory. Name the file SalesOrder.xml.

Create a Job

1. In the AOT, create a new job with the following code in it:

AifGatewayReceiveService agrs = new AifGatewayReceiveService(); 
AifInboundProcessingService aip = new AifInboundProcessingService(); 
AifOutboundProcessingService aop = new AifOutboundProcessingService(); 
AifGatewaySendService agss = new AifGatewaySendService(); 
; 
agrs.run();
aip.run(); 
aop.run(); 
agss.run();


2. Save the job.
3. Execute the job.

Verify the Results
 In AX, navigate to Sales and marketing, click Common, click Sales orders and then click All sales orders. You should have a new order created for the customer account 1101. If you do not have a new sales order in Microsoft Dynamics AX, navigate to System administration, click Services and Application Integration Framework and then click Exceptions to see the exceptions logged.

Friday, December 23, 2011

UtilElements table

Being an AX developer, you must be familiar with .aod files. (For the uninitiated, .aod files contain all the application level code). Ever wondered, what is inside those .aod files, how the data is organised etc. Well, AX provides us a way to do just that. The answer lies in the UtilElements table.

This table forms part of the broader topic of Reflection APIs in AX. More on this later.

UtilElements table belongs to a set of Util prefix tables which contain all element definitions. In other words, these tables give you direct access to the content inside .aod files.

Lets see this table in action.

Suppose you want to find all classes beginning with Proj and that have been changed within the last month. One way to achieve this will be as follows.

static void findProjClasses(Args _args)
{
    UtilElements ue;
    ;
    
    while select name from ue
        where ue.RecordType == UtilElementType::Class
            && ue.Name like 'Proj*'
            && ue.ModifiedDateTime >
                DateTimeUtil::addDays(DateTimeUtil::getSystemDateTime(), -30)
    
    {
        info(strfmt("%1", ue.Name));
    }
}

You can also use the UtilElements table to find out objects belonging to a particular layer or maybe to fulfil a more specific requirement like listing all the static methods belonging to a particular table. For eg,

static void findStaticEmplTable(Args _args)
{
    UtilElements ue;
    
    while select name from ue
        where ue.recordType == UtilElementType::TableStaticMethod
            && ue.ParentId == tableNum(EmplTable)

    {
        info(strfmt("%1", ue.name));
    }
}