Wednesday, December 31, 2014

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.
  1. Modifying a report query based on the input data
  2. Modifying report contract data based on the input data
  3. Control a report parameters dialog
  4. Open different reports/designs from the same menu item based on the input data
  5. 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();

}

Wednesday, December 24, 2014

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="">

Tuesday, December 9, 2014

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));

}

Monday, December 8, 2014

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


Monday, September 8, 2014

Failed to create a session; confirm that the user has the proper privileges to log on to Microsoft Dynamics

I faced this issue "Failed to create a session; confirm that the user has the proper privileges to log on to Microsoft Dynamics" when I imported MS contoso data into my new created AX environment. Though this is not specific to MS contoso data import, you can have on original post which solved my problem too.

http://www.ksaelen.be/wordpresses/dynamicsaxblog/2013/02/ax2012-r2-db-sync-failed-to-create-a-session/

Number sequence framework step by step

There are many posts over the internet address Number Sequence framework, I found a worth sharing slides which tells you;

How to create new number sequence
How to use number sequence in form
How to use number sequence in X++

Delete legal entities in AX 2012

Delete a legal entity seems to be a straight forward option in AX, just click on delete button on legal entity form. This does not work sometimes as per your thoughts and you face errors like below.

Cannot delete a record in Ledger (Ledger).
The corresponding AOS validation failed.
Stack trace: Call to TTSCOMMIT without first calling TTSBEGIN.
Stack trace
(S)\Classes\xRecord\Delete
(S)\Data Dictionary\Tables\DirPartyTable\Methods\Delete - line 14
(S)\Data Dictionary\Tables\DirOrganizationBase\Methods\Delete - line 17
(S)\Data Dictionary\Tables\CompanyInfo\Methods\Delete - line 31
(S)\Classes\xRecord\dbOpInTransaction
(C)\Classes\FormDataSource\delete
(C)\Classes\FormRun\task
(C)\Forms\OMLegalEntity\Methods\task
(C)\Classes\SysSetupFormRun\task

OR

Cannot delete a record in Batch job (BatchJob). The corresponding AOS validation failed.

These errors appear when you have transaction data in the legal entity which you are after for delete or there are batch jobs in running state stopping this delete process. 

There are certain steps to get rid of these errors and also to have a smooth delete process without any issue.
  1. Select the company (legal entity) which you need to delete.
  2. Go to AOT (Press Ctrl + D) and find class "SysDatabaseTransDelete" to delete transactions first.
  3. Run class "SysDatabaseTransDelete", it will prompt you Yes/No option to delete transactions. Delete them first it will take a while depend on the transaction data you have in your system in this company.
  4. Open legal entity form from Organization Administration | Setup | Organization | legal entities. 
  5. Select legal entity for which you deleted transaction data in step 1 and click delete button. It will take a while.

Wednesday, June 18, 2014

AX 2012 - Import Free Text Invoices - Header and lines - from one excel file

I got a requirement to upload free text invoices into AX 2012 from one excel file. All the header and lines information was in one excel sheet so I had to came up with different solution than the import process described on many blogs or can be done through built in AX classes. However, those import process work perfectly if you have data into seperate files or only for one record.

Here is the  job which I used to import free text invoices into AX 2012. In this job I used a table CustomerInvoices (new created table) which contains the data of all excel file. You can easily find out how to import data from excel then instead of using this table you can directly use the excel rows to get the data and insert into Free Text Invoice tables.

static void CreateFTInvoicesFromTable(Args _args)
{
    CustInvoiceTable    custInvoiceTable;
    CustInvoiceLine     custInvoiceLine;
    CustTable           custTable;
    CustomerInvoices    customerInvoices; // Customized table
    Map                 customerFTI = new Map(Types::String, Types::String);
    Set                 failedCustAccount = new Set(Types::String);
    MapEnumerator       mapEnum;
    SetEnumerator       setEnum;
    LineNum             lineNum;

    ttsBegin;

    while select customerInvoices
            order by customerInvoices.AccountNum
    {
        if (!customerFTI.exists(customerInvoices.AccountNum) && !failedCustAccount.in(customerInvoices.AccountNum))
        {
            select custTable
                where custTable.AccountNum == customerInvoices.AccountNum;

            custInvoiceTable.clear();

            if (custTable.RecId != 0)
            {
                custInvoiceTable.OrderAccount = custTable.AccountNum;
                custInvoiceTable.modifiedField(fieldNum(CustInvoiceTable, OrderAccount));
                custInvoiceTable.InvoiceId = customerInvoices.InvoiceId;
                custInvoiceTable.insert();
                lineNum = 0;
                customerFTI.insert(customerInvoices.AccountNum, custInvoiceTable.InvoiceId);
            }
            else
            {
                if (!failedCustAccount.in(customerInvoices.AccountNum))
                {
                    failedCustAccount.add(customerInvoices.AccountNum);
                }
            }
        }

        if (custInvoiceTable.RecId != 0)
        {
            custInvoiceLine.initFromCustInvoiceTable(custInvoiceTable);
            custInvoiceLine.LedgerDimension = customerInvoices.LedgerDimension;
            custInvoiceLine.Description     = customerInvoices.Description;
            custInvoiceLine.TaxGroup        = customerInvoices.TaxGroup;
            custInvoiceLine.TaxItemGroup    = customerInvoices.TaxItemGroup;
            custInvoiceLine.Quantity        = customerInvoices.Quantity;
            custInvoiceLine.UnitPrice       = customerInvoices.UnitPrice;
            custInvoiceLine.AmountCur       = customerInvoices.Quantity * customerInvoices.UnitPrice;
            custInvoiceLine.ParentRecId     = custInvoiceTable.RecId;         

            lineNum += 1;
            custInvoiceLine.LineNum = lineNum;
            custInvoiceLine.insert();
        }
    }

    ttsCommit;

    mapEnum = customerFTI.getEnumerator();

    info(strFmt('Following Free Text Invoices are successfully created.'));

    while (mapEnum.moveNext())
    {
        info(strFmt('Customer Account : %1 , Invoice Id : %2', mapEnum.currentKey(), mapEnum.currentValue()));
    }

    if (!failedCustAccount.empty())
    {
        info(strFmt('Following Customer Accounts are not valid.'));

        setEnum = failedCustAccount.getEnumerator();
        while (setEnum.moveNext())
        {
            info(strFmt('Customer Account: %1', setEnum.current()));
        }
    }
}

Stopped (error): X++ Exception: Could not find user at SysWorkflowDueDateProvider

I developed a custom workflow and its setup its corresponding configuration, I was able to submit and everything but ended up getting the following error in my workflow history when the workflow system tried to assign it to user which is the part of managerial hierarchy.

"Stopped (error): X++ Exception: Could not find user at SysWorkflowDueDateProvider-resolve SysWorkflowDueDateProvider-resolveDueDate
SysWorkflowQueue-resume"



I setup the workflow assignment for managerial hierarchy (position hierarchy) which was correctly setup under Human Resource > Common > Positions > Position hierarchy.


Later when I checked the worker association with users I found that under user relation screen the user IDs are wrongly associated with workers. Unfortunately this error message was coming due to wrong or missing worker and user assoication which is a must step for workflow to be processed perfectly.

Workflow always work on Employee (worker) level not on user level so user MUST be associated with the user. I hope this will help someone who runs into the same issue in the future.

How to enable new Microsoft teams - Public Preview!

New Microsoft Teams is just AWESOME, quick but useful post below shows how you have this preview feature to make your life EASY!  Open Micr...