Wednesday, July 23, 2014

How to calculate time consumed running a process in X++

This code can be used to calculate the time consumed running a process

 static void Job21(Args _args)  
 {  
   int startTime = timeNow();  
   int endTime;  
   sleep(5000);  
   endTime = timeNow();  
   info(strFmt("Process took %1", timeConsumed(startTime, endTime)));  
 }  

Setting LedgerDimension field on LedgerJournalTrans table in AX 2012 via X++ code

The following code can be used to update LedgerDimension field on the LedgerJournalTrans

 RecId getDimension(str _ledgerAccount, str _businessUnit, str _costCentre, str _jurisdiction, str _subscriberType)  
 {  
   DimensionServiceProvider      DimensionServiceProvider = new DimensionServiceProvider();  
   LedgerAccountContract        LedgerAccountContract = new LedgerAccountContract();  
   DimensionAttributeValueContract   ValueContract;  
   List                ListValueContract = new List(Types::Class);  
   dimensionAttributeValueCombination dimensionAttributeValueCombination;  
   DimensionStorage          dimStorage;  
   if (_businessUnit)  
   {  
     ValueContract = new DimensionAttributeValueContract();  
     ValueContract.parmName('BusinessUnit') ;  
     ValueContract.parmValue(_businessUnit);  
     ListValueContract.addEnd(ValueContract);  
   }  
   if (_costCentre)  
   {  
     ValueContract = new DimensionAttributeValueContract();  
     ValueContract.parmName('CostCentre') ;  
     ValueContract.parmValue(_costCentre);  
     ListValueContract.addEnd(ValueContract);  
   }  
   if (_jurisdiction)  
   {  
     ValueContract = new DimensionAttributeValueContract();  
     ValueContract.parmName('Jurisdiction') ;  
     ValueContract.parmValue(_jurisdiction);  
   }  
   if (_subscriberType)  
   {  
     ValueContract = new DimensionAttributeValueContract();  
     ValueContract.parmName('SubscriberType') ;  
     ValueContract.parmValue(_subscriberType);  
     ListValueContract.addEnd(ValueContract);  
   }  
   LedgerAccountContract.parmMainAccount(_ledgerAccount);  
   LedgerAccountContract.parmValues(ListValueContract);  
   dimStorage = DimensionServiceProvider::buildDimensionStorageForLedgerAccount(LedgerAccountContract);  
   dimensionAttributeValueCombination = DimensionAttributeValueCombination::find(dimStorage.save());  
   return dimensionAttributeValueCombination.RecId;  
 }  

Setting Default Dimension using code in AX 2012

The following method can be used to set the default dimension e.g. on CustTable when importing data via X++
 static DimensionDefault findDefaultDimension(str _businessUnit, str _costCentre, str _jurisdiction, str _subscriberType)  
 {  
   Struct             struct = new Struct();  
   container            defDimensionCon;  
   DimensionDefault        dimensionDefault;  
   DimensionAttributeSetItem    dimAttrSetItem;  
   DimensionAttribute       dimAttribute;  
   int i;  
   //Read required dimensions  
   while select Name, BackingEntityType from dimAttribute  
     where dimAttribute.BackingEntityType == tableNum(DimensionFinancialTag) &&  
        dimAttribute.Type       != DimensionAttributeType::DynamicAccount  
        join dimAttrSetItem  
         where dimAttrSetItem.DimensionAttribute == dimAttribute.RecId &&  
            dimAttrSetItem.DimensionAttributeSet == DimensionCache::getDimensionAttributeSetForLedger()  
   {  
     //Add the Dimension name and value to struct  
     if (_businessUnit && dimAttribute.BackingEntityType == tableNum(DimensionFinancialTag) && dimAttribute.Name == 'BusinessUnit')  
     {  
       struct.add(dimAttribute.Name, _businessUnit);  
     }  
     if (_costCentre && dimAttribute.BackingEntityType == tableNum(DimensionFinancialTag) && dimAttribute.Name == 'CostCentre')  
     {  
       struct.add(dimAttribute.Name, _costCentre);  
     }  
     if (_jurisdiction && dimAttribute.BackingEntityType == tableNum(DimensionFinancialTag) && dimAttribute.Name == 'Jurisdiction')  
     {  
       struct.add(dimAttribute.Name, _jurisdiction);  
     }  
     if (_subscriberType && dimAttribute.BackingEntityType == tableNum(DimensionFinancialTag) && dimAttribute.Name == 'SubscriberType')  
     {  
       struct.add(dimAttribute.Name, _subscriberType);  
     }  
   }  
   defDimensionCon += struct.fields();  
   for (i = 1; i <= struct.fields(); i++)  
   {  
     defDimensionCon += struct.fieldName(i);  
     defDimensionCon += struct.valueIndex(i);  
   }  
   if (struct.fields())  
   {  
     //Get the DimensionAttributeValueSet RecId  
     dimensionDefault = AxdDimensionUtil::getDimensionAttributeValueSetId(defDimensionCon);  
   }  
   return dimensionDefault;  
 }  

Tuesday, June 24, 2014

Workflow Stopped (error): X++ Exception: Work item could not be created. Insufficient rights for user ?

I've had this problem a number of times and the error message doesn't give you enough information to know exactly what permissions need to be added for the user.

There are a couple of ways to determine the menu items and web menu items associated with the workflow.

The first way is to go to the AOT and look under the Workflow > Approvals node

The required display menu and web display menu items are highlighted below

The required action and web action menus are highlighted below

Check that the user has permission to access these display and action menu and webmenu items.

You can also use the Visual Studio 2010 debugger on the SysWorkflowDocument.assertAsUser() class method to locate the menu item(s) that the user doesn't have access to.




“SysDictEnum object not initialised” error running AIF document service creation wizard


Came across an issue with creating a new AIF document service today. The error reported in the InfoLog was “SysDictEnum object not initialised”, which gave me an indication of a problem with an Enum.
The stack trace occurred during the creation of one of the document classes and by looking at the partially created class I was able to determine the database field that was causing the problem.
A quick check of the EDT properties revealed the problem - the Enum that I had created and added to my table didn’t have an EnumType. After correcting the Enum I was able to rerun the AIF create document service wizard successfully this time.

Wednesday, November 6, 2013

Display required license on Secuirty privileges form in AX2012

To assist with creating security roles for lower license levels (Functional, Self-serve, etc) I made a small mod to the Security prviledges form to display the license level required for each permission object. Easier than having to generate and run the license report everytime you make a change or having to lookup each permission in the AOT to see the license level.









These fields can be added by creating two new display methods on the securableObject data source on the SysSecTasksEditPS form:


 public display UserLicenseType displayViewUserAccessLicense(SecurableObject _object)  
 {  
   #AOT  
   #Properties  
   #define.PropertyReadUserLicense('ViewUserLicense')  
   #define.PropertyFullAccessUserLicense('MaintainUserLicense')  
   UserLicenseType userLicType;  
   TreeNode    treeNode;  
   EntryPointType type = Global::enum2int(_object.Type);  
   str       nodePath =  
             (type == EntryPointType::MenuItemDisplay) ? strFmt('%1\\%2', #MenuItemsDisplayPath, _object.Name) :  
             (type == EntryPointType::MenuItemOutput) ? strFmt('%1\\%2', #MenuItemsOutputPath, _object.Name) :  
             (type == EntryPointType::MenuItemAction) ? strFmt('%1\\%2', #MenuItemsActionPath, _object.Name) :  
             (type == EntryPointType::WebUrlItem) ? strFmt('%1\\%2', #WebMenuItemsUrlPath, _object.Name) :  
             (type == EntryPointType::WebActionItem) ? strFmt('%1\\%2', #WebMenuItemsActionPath, _object.Name) :  
             (type == EntryPointType::WebManagedContent) ? strFmt('%1\\%2', #WebContentItemsManagedPath, _object.Name) :  
             '';  
   Map typeSet = new Map(Types::String, Types::Enum);  
   // Available types property values  
   typeSet.insert('', UserLicenseType::None);  
   typeSet.insert('None', UserLicenseType::None);  
   typeSet.insert('SelfServe', UserLicenseType::SelfServe);  
   typeSet.insert('Task', UserLicenseType::Task);  
   typeSet.insert('Functional', UserLicenseType::Functional);  
   typeSet.insert('Enterprise', UserLicenseType::Enterprise);  
   typeSet.insert('Server', UserLicenseType::Server);  
   if (nodePath != '')  
   {  
     treeNode = TreeNode::findNode(nodePath);  
   }  
   if (treeNode)  
   {  
     userLicType = typeSet.lookup(treeNode.AOTgetProperty(#PropertyReadUserLicense));  
     treeNode.treeNodeRelease();  
   }  
   return userLicType;  
 }  

The above code adds a display method to display the ViewUserAccessLincense. The other display method is exactly the same except #PropertyFullUserLicense is used instead of  #PropertyReadUserLicense on the following line:

userLicType = typeSet.lookup(treeNode.AOTgetProperty(#PropertyReadUserLicense));

Then all you need to do is add the display methods to the grid in the form design and you're done
 

Sunday, October 27, 2013

Displaying an image on a form grid in Dynamics AX

Sometimes it is useful to display an image in  a data grid on a from to highlight certain records e.g. records with validation errors as in the example below:
















To achieve this requires 2 main steps:

1. Adding a display method on the table/form datasource that returns an ImageRes
 //BP Deviation Documented  
 display ImageRes errorExist()  
 {  
   #resAppl;  
   return this.ErrorLogged ? #ImageError : #ImageBlank2;  
 }  

2. Adding a new field control on the form by right-clicking on the Grid control within the form design and selecting New Control > Window




 

Set the properties of the new control as follows: