Microsoft_MVP_banner

Collection classes in AX – What, When and How to use

Data inserting, traversing and retrieving can be done with many different ways in AX. We do have different collection classes for it and each one has its own importance and performance impact. Let’s talk about each one by one and compare it. List –          are structures, contain values of any X++ type that are accessed sequentially –          all values must be of same X++ type (specified through baseEnum types) –          type is defined on creation and cannot be modified after initialization –          can be traversed through ListEnumerator class –          values can be added from both sides e.g. using addEnd() and addStart() methods  Example 1 static void ListExample1(Args _args) {     List list = new List(Types::String);    // Declare and initialize List of String type     ListEnumerator  enumerator;             // Declare ListEnumerator class to traverse list             list.addEnd(“Faisal”);                  // Add values at End of the List     list.addEnd(“Fareed”);                  // Add values at End of the List     list.addStart(“AX Developer”);          // Add values at Start of the List         enumerator = list.getEnumerator();      // initialize listEnumerator class object         while (enumerator.moveNext())     {         print enumerator.current();     }     pause;    }   Output: Traverse from left to right Example 2 Lists can be initialized from container too. static void ListExample2(Args _args) {     Container       companyRange;               // Declare container     List            list;                       // Declare List     ListEnumerator  enumerator;                 // Declare ListEnumerator class to traverse list                companyRange = [curext()];                  // initialize container with current company        // we can pass multiple values here but need to be of same             datatype as this is used in List in below code            list = con2List(companyRange);         enumerator = list.getEnumerator();          // initialize listEnumerator class object         while (enumerator.moveNext())     {         print enumerator.current();     }     pause;    } Example 3 Below code is actually an example to copy user roles from one user to another. Where I used ‘List’ to store records what roles have been moved across. static void ListExample3(Args _args) {     boolean                 ret = true;     SecurityRole            securityRole;     SecurityUserRole        securityUserRole;     SecurityUserRole        securityUserRoleExist;     SecurityUserRole        securityUserRoleInsert;     OMUserRoleOrganization  userRoleOrganization, userRoleOrganization_Insert;     List                    copiedUserRoles = new List(Types::String);     ListEnumerator          lEnumerator;        try     {                while select securityUserRole                 where securityUserRole.User == fromUser             notExists join * from securityUserRoleExist                 where securityUserRoleExist.SecurityRole    == securityUserRole.SecurityRole                     && securityUserRoleExist.User           == toUser         {             select securityRole where securityRole.RecId == securityUserRole.SecurityRole;             copiedUserRoles.addStart(securityRole.Name);             securityUserRoleInsert.initValue();             securityUserRoleInsert.SecurityRole = securityUserRole.SecurityRole;             securityUserRoleInsert.User         = toUser;             securityUserRoleInsert.insert();             securityUserRoleInsert.clear();             while select userRoleOrganization                     where userRoleOrganization.User == fromUser                         && userRoleOrganization.SecurityRole == securityUserRole.SecurityRole             {                 userRoleOrganization_Insert.initValue();                 userRoleOrganization_Insert.OMHierarchyType             = userRoleOrganization.OMHierarchyType;                 userRoleOrganization_Insert.OMInternalOrganization      = userRoleOrganization.OMInternalOrganization;                 userRoleOrganization_Insert.SecurityRole                = userRoleOrganization.SecurityRole;                 userRoleOrganization_Insert.SecurityRoleAssignmentRule  = userRoleOrganization.SecurityRoleAssignmentRule;                 userRoleOrganization_Insert.User                        = toUser;                 userRoleOrganization_Insert.insert();                 userRoleOrganization_Insert.clear();             }         }     }     catch     {         ret = false;     }     if (ret)     {         lEnumerator = copiedUserRoles.getEnumerator();         if (copiedUserRoles.empty())             info(strFmt(“User %1 and %2 already have the same user role”,fromUser, toUser));         while (lEnumerator.moveNext())         {             info(strFmt(‘%1’,lEnumerator.current()));         }     }     else         error(strFmt(“Aborted please review error list above”)); } You can also have a look on this post to find difference between Iterators and Enumerators.                      Maps –          can be used as a temp data store for the given scope of a process –          are much quicker than temp tables –          allows you to associate one value (the key) with another value –          values can be any valid X++ type static void mapExample1(Args _args) {     Map             mapExample;     MapEnumerator   mapEnumeratorExample;     CustTable       custTable;         mapExample = new Map(Types::String,Types::String);         select custTable where custTable.AccountNum == “144212”;         mapExample.insert(CustTable.AccountNum, CustTable.name());         mapEnumeratorExample = new MapEnumerator(mapExample);         while (mapEnumeratorExample.moveNext())     {         if (!mapExample.empty())         {                        print mapExample.lookup(custTable.AccountNum);         }            }     pause; } Good information about Maps is also shared here Containers MSDN page describes it very best http://msdn.microsoft.com/en-us/library/aa874816.aspx Containers can be used to get multiple values returned from any method. Original method private container getActualAmount() {     Container   amountContainer;     container   textContainer;        ;     for()     {                amountContainer = conIns(amountContainer);         textContainer   = conIns(textContainer);            }     return [amountContainer, textContainer]; } Called From container   AmountsC = conNull(); container   TextC    = conNull(); [AmountsC, TextC]   = this.getActualAmount(); Set values to use for your logic ExampleTable.Field1 = conPeek(AmountsC,1); ExampleTable.Field2 = conPeek(AmountsC,2); Important points about Containers They are value based structure. So no class based object stored on it. They are similar to linked list or array like structured. Their size fixed at time of declaration. When you insert update or delete any value from container, a new container is created. They always copied by value. Containers are base 1 not like array based zero.

How Exception handling Try/Catch statements work in AX

A sample code for Try/Catch statements in AX under run method in a class. public void run() {     #OCCRetryCount         try     {             }     catch (Exception::Deadlock)     {         retry;     }     catch (Exception::UpdateConflict)     {         if (appl.ttsLevel() == 0)         {             if (xSession::currentRetryCount() >= #RetryNum)             {                 throw Exception::UpdateConflictNotRecovered;             }             else             {                 retry;             }         }         else         {             throw Exception::UpdateConflict;         }     } }

Signing limits for Business Documents in AX 2012

A signing limit defines the largest financial commitment that a worker is authorized to make on behalf of an employer. There are two types of signing limits — spending limits and approval limits. A spending limit is the maximum amount a worker is authorized to spend on business-related purchases. An approval limit is the maximum amount a worker is authorized to approve for a specific business document when the document is submitted to workflow. The limits are associated with a worker’s job or compensation level. Signing limits can also vary depending on the transaction type — purchase requisitions, expense reports, purchase orders, and invoices can have different signing limits. Signing Limit Parameters Organizations assign default signing limits. Depending on the configuration of signing limits in an organization, a worker can obtain a default signing limit in one of the following ways. The worker is automatically assigned the default signing limit, based on the worker’s job or compensation level. The worker must submit a signing limit request in the Employee Services Portal in the Enterprise Portal for Microsoft Dynamics AX. Use the Signing limit parameters form to set up how signing limits apply to your organization’s employees. Click Organization administration > Setup > Signing limits > Signing limit parameters. In the Limit basis field, select Job if signing limits should be based on your employees’ job. Select Compensation level to base signing limits on your employees’ compensation level. Select the Require explicit signing limit request check box to force employees to submit a signing limit request so they can be granted a spending or approval limit. If this box is cleared, employees are automatically granted the default spending or approval limit as defined for their job or compensation level.       Require explicit signing limit request is UnMarked: The worker is automatically assigned the default signing limit, based on the worker’s job or compensation level Require explicit signing limit request is Marked: The worker must submit a signing limit request the Employee service portal in Enterprise Portal. Select the Signing limits for employees only check box if users with an employee record should be allowed to request and be granted signing limits. If this check box is not selected, contract employees will also be permitted to submit and be granted signing limits. Procedure: Define a Signing Limit Policy Default signing limit rules specify the spending and approval limits for each document type and each job or compensation level. To define a signing limit policy, follow these steps. Click Organization administration > Setup > Signing limits > Signing limit policies. Click Policy in the New group of the Action Pane. Enter a Name and Description for the policy. In the Policy organizations FastTab, select an organizational node to define the policy for and click Add. Click Close. Step 1 Step 2 to 5 Procedure: Define a Default Signing Limit Policy Rule You can define a default signing limit policy for purchase requisitions, purchase orders, expense reports, and invoices. To define a default signing limit policy for one of these transaction documents, follow these steps. Open Organization administration > Setup > Signing limits > Signing limit policies. Select the signing limit policy for which to configure a default signing limit, then click Edit in the Maintain group of the Action Pane. Click the Policy rules FastTab and select Default signing limit from the Policy rule type pane. Click Create policy rule in the Policy rules pane. Enter the Effective date and Expiration date for the policy rule, and then click New. In the Document type field, select the transaction type the rule applies to. Enter the default approval limit amount that is assigned to the specified compensation levels or jobs for the selected document type in the Approval amount field.In above screen shot; I put two rules associated with two Jobs; Accounts Assistant and IT System Support with amount 3,000 and 10,000 consecutively for Purchase Requisition. Select the Currency the approval amount is calculated in. Enter the default spending limit amount that is assigned to the specified compensation levels or jobs for the selected document type in the Spending amount field. In this example; I did not use spending limit as this example is only for approval limits. Select the Currency the spending amount is calculated in. Select the Jobs or Compensation levels that the rule applies to. Click Close. Important note: System will not restrict the submission of the corresponding document based on the signing limit. In our example; system will not throw an error when “Faisal” tries to submit a Purchase requisition for more than 10000 USD. Signing limits work just like a property i.e., Faisal’s spending limit is now set to 10000USD. You must incorporate the Signing limits in the workflow to make it working and see the impact of your setup. Configuring the Workflow using the “Signing limits”: Scenario: “Test systems” decided the spending limit for “IT System Support” as 10000USD for the Purchase requisition and thus any Purchase requisition below USD 10K doesn’t need any approval. But, a PR which crosses USD 10K must get the approval by “X” user. Note: I’m using the user based approval in this workflow to make it easy to understand. It can be done through “Managerial Hierarchy”.  In the above workflow, I’ve enabled the automatic actions by using preparer’s spending limit. “Purchase requisition.approval amount” returns the total amount of PRlines and if that is less than the “spending limit assigned” to the preparer, it must be approved automatically. Double click the “Approve PR” element to assign the user to whom the system should route when if the PR amount is more than the spending limit allowed to the employee. 1.       In the “step1” element> right click >Properties > Select Assignment 2.       Select Assignment type “user” and in the “user tab” specify the user. 3.       Now everything is set.  Create a PR with the amount below USD 10K and submit it, It will be approved automatically as the PR amount is less than the “spending limit” allowed for the employee. Create another PR with the amount above USD 10K, you can notice that it will be routed to the specific user. Note: You cannot edit the “Unit price” field in the PR, so edit the quantity to have the needed “PR amount”, alternatively, you can change the purchase price for the product in Released products.

SSRS report expressions – compiled and ongoing

How to get full AX company in SSRS report =Microsoft.Dynamics.Framework.Reports.DataMethodUtility.GetFullCompanyNameForUser(Parameters!AX_CompanyName.Value, Parameters!AX_UserContext.Value) How to show alternate row colour in SSRS report Set following expression to row’s background property =IIf(RowNumber(Nothing) Mod 2 = 0, “LightGrey”, “WhiteSmoke”) How to format a date =format(Parameters!Dataset1_AsPerDate.Value,“dd/MM/yyyy”) How to show date and time on report =Microsoft.Dynamics.Framework.Reports.DataMethodUtility.ConvertUtcToAxUserTimeZoneForUser(Parameters!AX_CompanyName.Value, Parameters!AX_UserContext.Value, System.DateTime.UtcNow, “d”, Parameters!AX_RenderingCulture.Value) & ” at “ & Microsoft.Dynamics.Framework.Reports.DataMethodUtility.ConvertUtcToAxUserTimeZoneForUser(Parameters!AX_CompanyName.Value, Parameters!AX_UserContext.Value, System.DateTime.UtcNow, “t”, Parameters!AX_RenderingCulture.Value) OutPut: 01/01/2015 at 8:14 AM                   Page 1 of 2 How to show page number on report =System.String.Format(Labels!@SYS182565, Globals!PageNumber & space(2) & Labels!@sys26401 & space(2) & Globals!TotalPages) How to get last date of the month Format(DateSerial(Year(Parameters!Dataset1_AsPerDate.Value), Month(Parameters!Dataset1_AsPerDate.Value), “1”).AddMonths(1).AddDays(-1),“dd/MM/yyyy”) Input: Parameters!Dataset1_AsPerDate.Value = “02/01/2015” Outout: 31/01/2015 How to get Month’s name and Year from date cstr(MonthName(month(Parameters!Dataset1_AsPerDate.Value))) + ” – “ + cstr(Year(Parameters!Dataset1_AsPerDate.Value))

Debug/Test SSRS report in AX 2012

People came across issues like report is not showing data on report or data is not per their expectation. It is really hard to judge where it goes wrong and what needs to be correct in which class RDP, Contract, UI or Controller. Following sample job can be used to debug SSRS report processing data and lead to find what goes unexpected. For more information about classes and table used in this example please read my previous post static void FF_ReportDPTest(Args _args) {     // temp table declaration     FF_ReportTmpTable       ffReportTmp;     // RDP class binded with FF_Report     FF_ReportDP             dataProvider = new FF_ReportDP();     // Contract class for report parameters     FF_ReportContract       contract = new FF_ReportContract();         // Parameters for reports        FreeText                companyName;     CustAccount             customerAccount;         // set parameters to contract class     contract.parmCompany(companyName);     contract.parmCustomerAccount(customerAccount);     // set contract parameters to RDP class     dataProvider.parmDataContract(contract);         // call Data provider class to process report.     dataProvider.processReport();         // retrieve data from RDP class into temp table     ffReportTmp = dataProvider.getFF_ReportReportTmp();     // select data from temp table for testing purpose.        while select ffReportTmp     {         info(ffReportTmp.AccountNum);     } }

Run SSRS report from AX form

Continue from my previous post where I developed an example using all SSRS framework classes. One of them is controller class which is used to call SRS report from AX forms. Just for the note controller class is used for following purposes. Modifying a report query based on the input data Modifying report contract data based on the input data Control a report parameters dialog Open different reports/designs from the same menu item based on the input data Reports that are opened from a form Example; how to call report from AX form button/menuitembutton click void clicked() {     CustOpenInvoices        custOpenInvoicesLocal;     SrsReportRunController  controller = new FF_ReportController();     SrsReportDataContract   contract;     FF_ReportContract       rdpContractClass;         controller.parmReportName(ssrsReportStr(FF_Report, PrecisionDesign));     controller.parmLoadFromSysLastValue(false);     contract = controller.parmReportContract();     rdpContractClass = contract.parmRdpContract() as FF_ReportContract;     rdpContractClass.parmCompany(Company.valueStr());         controller.startOperation(); }

Customise code tagging in AX 2012 – MS Dynamics AX Best practice

Code commenting is one of the best practice for any software development which really helps other team mates or future developers to understand code quickly. Code comments should be enough expounding for others. MS Dynamics AX provides diverse possibilities to comment out code in much better and generic way through scripts. This can be used by right click in any method within AOT objects as shown; Here we also have tagging option to use generic way to comment out our code and this could be really helpful when working on big projects. MS Dynamics AX uses a class EditorScript behind these scripts tool, I added a following method under this class for specific code comment pattern. public void tagging_CodeModifications(Editor editor) {     #Define.NextLine(‘n’)     int currentLineNo = editor.selectionStartLine();     int currentCol    = editor.selectionStartCol();     int selectedLine  = editor.selectionEndLine();      Dialog          dialog          = new Dialog(“Enter Project Number”);       DialogField     dlgExtTypeName  = dialog.addField(extendedTypeStr(Name));     container       dialogValue;      str tab = “”;      tab = strRep(‘t’, currentCol/4);      dialogValue = xSysLastValue::getValue(curExt(), curUserId(), UtilElementType::ClassInstanceMethod,    ‘SaveLastValue’);     if (dialogValue != conNull())     {         dlgExtTypeName.value(conPeek(dialogValue, 1));     }     if (dialog.run())     {         dialogValue = conNull();          dialogValue += dlgExtTypeName.value();          xSysLastValue::putValue(dialogValue, curExt(), curUserId(), UtilElementType::ClassInstanceMethod, ‘SaveLastValue’);          editor.gotoLine(currentLineNo – 1);         editor.gotoLine(currentLineNo);         editor.gotoCol(currentCol);          editor.insertLines(strFmt(‘// Project_%1 %2 %3 —> %4 %5’, dlgExtTypeName.value(), strReplace(curUserId(),‘I_’,“”), systemDateGet(), #NextLine, tab));         editor.gotoLine(selectedLine + 2);          editor.insertLines(strFmt(‘%1// Project _%2 %3 %4 <—‘, tab, dlgExtTypeName.value(), strReplace(curUserId(),‘I_’,“”), systemDateGet(), #NextLine));     } } Output: Click OK; following lines will be added into code. // Project_CustInvoice faisal.f 24/12/2014 —> // Project_CustInvoice faisal.f 24/12/2014 <— data-blogger-escaped-span=””>

Business/Working days in AX

Following job is to calculate business days/working days in AX using base calendars. In my case base calendar name is “24/5” you can change it as per your setup. static void businessdaysInPeriod(Args _args) {     WorkCalendarSched workCalendarSched;     date start;     date end;     counter workingdays;     counter totaldays;     CalendarId primCalendar=“24/5”;     ;     start = str2date(“21-11-2014”,123);     end   = str2Date(“26-11-2015”,123);     workCalendarSched = new workCalendarSched();     while(start<=end)     {         totaldays++;         if(workCalendarSched.isdateopen(primCalendar,start)==true)         {             workingdays++;         }         start++;     }     info(strfmt(“Total days: : %1”,totaldays));     info(strfmt(“Total working days: : %1”,workingdays)); }

Stop deletion of record from a table

Recently came across an issue where records from customized tables were being deleted occasionally. Following wat did a quick fix for me to stop further records delete from table.  Overwrite delete method on table and comment super(). This also stops deletion from code, e.g. if we write query delete_from table this commented super will prevent user to delete record from table. public void delete() {     //super(); } This will also stop deletion from code too. e.g. It worked fine but records can also be deleted by pressing Ctrl+F9 on table browser. To handle this situation I overwrite validateDelete method on table and returned False from there. public boolean validateDelete() {     boolean ret;     ret = super();     return false; }

Send email from AX using live or gmail exchange server

You may find few more posts over this topic to send emails from AX using live (Hotmail) or gmail exchange server. This is really helpful when we don’t have exchange is in place sometimes due to high cost or you just want to send emails for testing or demo purpose. Let’s see what parameters need to setup and how can we achieve this requirement. Go to System Administration | Setup | System | E-mail parameters P.S. I am using a dedicated email account at outlook (Hotmail) domain for this example. NTLM option can also be used; TechNet article is more helpful to get more information on these parameters. Here is the job I wrote to send email for selected user. I name it SendTextMail as in my following post I will be writing to send invitation from AX using Hotmail or Gmail exchange server. //SmtpSSL static void SendTextMail(Args _args) {     System.Net.Mail.MailMessage             mailMessage;     System.Net.Mail.SmtpClient              myMail;     System.Net.Mail.MailAddressCollection   mailcoll;     System.Net.Mail.MailAddress             mailFrom;     System.Net.Mail.MailAddress             mailTo;     System.Net.Mail.MailAddress             mailCC;     str                                     receiverMailAddress;     str                                     mailBody;     str                                     smtpServer;     str                                     mailSubject;     str                                     CcMailAddress;     int                                     SMTPPort;     #File     str                                     mail;     str                                     pwd;     Dialog dialog = new Dialog(‘Email’);     Dialogfield     person, emailSubject, emailBody;     HcmWorker       hcmWorker;     UserInfo        userInfo;     DirPersonUser   dirPersonUser;     SysUserInfo     sysUserInfo;     SysEmailParameters parameters;     // dialog field to select user to whom email will be send     person          = dialog.addField(extendedTypeStr(HcmWorkerRecId ), ‘Person :’ );        emailSubject    = dialog.addField(extendedTypeStr(Description), ‘Subject :’ );      // Email Subject     emailBody       = dialog.addField(extendedTypeStr(Notes), ‘Body :’ );               // Email Body     if(dialog.run())     {         parameters = SysEmailParameters::find();   // Find values from Email Parameters         new InteropPermission(InteropKind::ClrInterop).assert();         // gets HcmWorker record based on person selected from user dialog         hcmWorker = hcmWorker::find(person.value());          if(!hcmWorker.RecId)   // Verify either user exist or not         {             throw error(‘User not found’);         }         select firstOnly dirPersonUser             join userInfo                 where dirPersonUser.PersonParty == DirPartyTable::findByName(hcmWorker.name()).RecId &&                  userInfo.id == dirPersonUser.User;         select firstOnly sysUserInfo             where sysUserInfo.Id == userInfo.id;      // Retrieve user info record for selected user         mailSubject         = emailSubject.value();         mailFrom            = new  System.Net.Mail.MailAddress(parameters.SMTPUserName ,“Name”);         mailTo              = new  System.Net.Mail.MailAddress(sysUserInfo.Email);         //mailTo            = new  System.Net.Mail.MailAddress(“test1@gmail.com”);         //mailCC            = new  System.Net.Mail.MailAddress(“test2@gmail.com”;         mailcoll            = new  System.Net.Mail.MailAddressCollection();         mailBody            = emailBody.value();         try         {             // using the SMTP server ip //setup in email Parameters             smtpServer          = SysEmaiLParameters::find(false).SMTPRelayServerName;               mailMessage         = new System.Net.Mail.MailMessage(mailFrom,mailTo);             mailmessage.set_Subject(mailSubject);             mailmessage.set_Body(mailBody);             SMTPPort            = SysEmaiLParameters::find(false).SMTPPortNumber;             myMail              = new System.Net.Mail.SmtpClient(smtpServer, SMTPPort);            // For SSL enabled mail servers. Ex: gmail, smtp.gmail.com, port 465 or 587             myMail.set_EnableSsl(true);              pwd = SysEmaiLParameters::password();             mymail.set_Credentials(New System.Net.NetworkCredential(parameters.SMTPUserName, pwd));             mymail.Send(mailmessage);         }         catch(Exception::CLRError)         {             throw Exception::CLRError;         }         mailMessage.Dispose();         CodeAccessPermission::revertAssert();     } } UI of the utility

FaisalFareed@2025. All rights reserved

Design by T3chDesigns