Search This Blog

Tuesday 16 February 2016

Customize infolog in Microsoft Dynamics AX 2009

In Microsoft Dynamics AX 2009 you can customize the infolog to add some new buttons on it to open some other forms.

For example in my scenario I have to generate the word document and once the word document is generated successfully than I create a activity automatically and show the activity number on the infolog. once the user clicks on that activity number on the infolog than the show button is appears on the same infolog and when user clicks on that show button than it opens the activity form in edit mode so the user can change the values if they want.

Below is the screen shot of infolog containing activity number and show button.




















Now when i click on that show button than it will open the activity form with the same activity number already filtered in it.












Below is the code that I have use to customize the infolog.

info(strfmt('Activity Created Successfully activity number is %1', smmActivities.ActivityNumber), "", Sysinfoaction_tablefield::newBuffer(smmActivities));

note:on smmActivities table the property formRef is set to form smmActivities thats why its opens the form smmActivities.

Below is link for more information on customizing infologs.

https://community.dynamics.com/ax/b/vishalsblogonmsdynamicsax/archive/2013/05/03/sysinfoaction-class-go-to-main-table-from-infolog-message-in-ax-2009-ax-2012


Thanks

Muhammad Zahid.




Tuesday 12 January 2016

Send report in Email in Microsoft Dynamics AX 2009

In this I will share the steps of how we can send the report in email.

First of all for sending the email we all need to setup the email templates based on our requirements.

Below is the path for the email templates:

Basic -> Setup -> Email Templates

























Each template is uniquely identified by the Email ID.

we can also create the setup form to allow the user to select the template based on his requirement in his feature.

For example for our daily tax invoice feature we have created a setup form for the user to select the email template.













 Now after creating and setting up the template below is the code to create the report and attach and send it in  an email.

public boolean send(PrintCopyOriginal _printCopyOriginal = PrintCopyOriginal::Copy)
{
    SysEmailId  emailId = crtParameters::find().DailyTaxInvoiceEmailId;
    //above code is use to get the email template from the setup table that is configured by the end user     //before

    str         tempFile;
    ;

    if (this.DailyTaxInvoiceEnabled())
    {

        tempFile = this.generatePDF(custInvoiceJour, _printCopyOriginal);
        above method generates the report in pdf in temporary location and send the address of that file           to be attached in the email
     
        SysEmailTable::sendMail(emailId,
                                'en-nz',
                                custTable.CRT_DailyTaxInvoiceEmail,
                                this.initMap(),
                                tempFile,
                                '',
                                true,
                                'admin',
                                true,
                                custInvoiceJour.RecId);
       //above method is use to send the email to the
       //First parameter is the email template id (The template which we will use to send      //the email)
       //second parameter is the language.
       //third parameter is the email address to whom we are sending the email
       //in fourth parameter we are just initializing the map based on the fields required in //the email            template for the subject and other things (will show this method below)
       //fifth parameter address of the report where report is saved after generation
       //eighth parameter is the user id from which we are sending the email (credentials are setup on            //the smtp server)
     
        new FileIOPermission(tempFile, 'rw').assert();
        WinAPIServer::deleteFile(tempFile);
        CodeAccessPermission::revertAssert();
        //above code is just deleting the report from the temporary location

        return true;
    }

    return false;
}

Below is the method for generating the pdf:

private str generatePDF(CustInvoiceJour _custInvoiceJour, PrintCopyOriginal _printCopyOriginal = PrintCopyOriginal::Copy)
{
    Args                args;
    ReportRun           rr;
    Report              rb;
    PrintJobSettings    pjs;

    str                 tempFile;
    str                 tempFolder;
    ;

    tempFolder = Global::strReplace(SysEmailParameters::find().AttachmentsPath,'//','/');
    //Above attachment path is define in the following navigation path of the setup of form                         //Administration -> setup -> E-mail Parameters
   
    tempFile = strfmt('%1\\TaxInvoice%2.pdf',tempFolder,custInvoiceJour.InvoiceId);

    args = new Args(reportstr(CRTFreeTextInvoice));
    args.record(_custInvoiceJour);

    args.parmEnum(_printCopyOriginal);

    // Set report run properties
    rr = new ReportRun(args,'');
    rr.suppressReportIsEmptyMessage(true);
    rr.query().interactive(false);

    // set report properties
    rb = rr.report();
    rb.interactive(false);

    // set print job settings
    pjs = rr.printJobSettings();
    pjs.fileName(tempFile);
    pjs.fitToPage(true);

    // break the report info pages using the height of the current printer's paper
    pjs.virtualPageHeight(-1);

    // force PDF printing
    pjs.format(PrintFormat::PDF);
    pjs.setTarget(PrintMedium::File);
    pjs.viewerType(ReportOutputUserType::PDF);

    // lock the print job settings so can't be changed
    // X++ code int the report may try to change the destination
    // to the screen for example but this does not make
    // sense when running a report here
    pjs.lockDestinationProperties(true);

    // Initialize the report
    rr.init();

    rr.run();

    return tempFile;
}

Below is the method for initiating the map for the email to insert the subject and other things that I have discussed above.

private Map initMap()
{
    Map     map = new Map(Types::String, Types::String);
    ;

    map.insert('subject', strfmt('Daily tax invoice %1- %2',                                                                           custInvoiceJour.InvoiceId,custInvoiceJour.DocumentDate));
    map.insert('phone', companyInfo::find().CRTFreePhone);
    map.insert('Customeraccount', strfmt('%1', custInvoiceJour.InvoiceAccount));
    map.insert('CustomerName', strfmt('%1',                                                                                                 CustTable::find(custInvoiceJour.InvoiceAccount).Name));
    map.insert('InvoiceNumber', strfmt('%1', custInvoiceJour.InvoiceId));

    return map;
}

This was all related to the generating the report and sending it in the email.

If you have any questions than feel free to post here.

Thanks
Muhammad Zahid.









Thursday 31 December 2015

SOLVED :"Illegal data conversion from original field (Dynamics AX 2009)"

After Restoring the data from Live Environment to the Test Environment When I synchronize the Data Dictionary after that I got the following error message.

Illegal data conversion from original field IPBCLIENTSESSION.CLIENTCOMPUTER to CRTFERTORDERTABLE.FertSupplier: Unable to convert data types to anything but character field type (0 to 4).

In Dynamics AX Environment I check the id of the table i.e CRTFERTORDERTABLE  and id of the field i.e FertSupplier

The id of table  CRTFERTORDERTABLE  is 30379

id of the field FertSupplier is 30002.

Than in Sql server I write the following query:

 SELECT TABLEID,FIELDID,NAME from SQLDICTIONARY
  where TABLEID = 30379 and  (FIELDID = 30002)

After running the query the result that I got is:








in the above image we can see that field CLIENTCOMPUTER contains the id 30002 while in AX Field ID 30002 belongs to FERTSUPPLIER

now I will update the SQLDICTIONARY table and set the CLIENTCOMPUTER ID to new id to remove the conflict.

below is the query to update the table

update SQLDICTIONARY set FIELDID = 300020
  where TABLEID = 30379 and FIELDID = 30002
 
Now after running this I will again synchronize the table in Dynamics AX and after the synchronization the error will not arrive.

Thanks

Muhammad Zahid.

Tuesday 29 December 2015

Dynamics AX 2009 Check name of the Database name and Database Server name

In Microsoft Dynamics AX 2009 we can check the name of the database and database server without opening the AX Server Information.

Below is the navigation path to check this.

Administration -> Database -> Database information

Once you click the database information below form will be open that contains that information .
















Thanks

Muhammad Zahid.

Friday 6 November 2015

Check the name of the calling object on the Form in Dynamics AX 2009

Some times on the form we need to check which object called this form and based on the calling object we do some changes on the form.

if the type of the calling object is class than below is the code inside if check through which we can identify the name of the caller class:

 if(SysDictClass::isEqualorSuperclass(classidget(element.args().caller()), classnum(nameoftheclass)))
{
     //write some logic to do some changes on the form if the caller class is the same is you check
}

if the type of the calling object is menuitem than below is the code:

if(this.formRun().args().menuItemName() == (menuItemDisplayStr(HcmUpdateToForecastPosition))
{
  //write some logic to do some changes on the form if the caller menuitem is the same as you           check 
}

if the type of calling object is form than below is the code:

Object  formRunObject;//write this in the class declaration of the form

//write below code in the init method of the form

formRunObject = element.

if (element.args().caller())
{
     formRunObject = element.args().caller();

     if(formRunObject.name() == formstr(ProjInvoiceTable))
     {
          //write some logic to do some changes on the form if the caller menuitem is the same as                //you check
     }
}

This are the three ways in which we can check the names of calling object.

you can write these code in the init method.

if you have any questions on this than feel free to comment on it.

Thanks
Muhammad Zahid

Tuesday 20 October 2015

Error Message When Synchronize Microsoft Dynamics AX 2009 Data Dictionary

After restoring the Database from the Production Environment on to the Test Environment I synchronized the data dictionary and than I got the sync error.

I investigate and found out that the issue was in the CRTCards table. It has the unique index for CardNumber Field and the table contains the duplicates records for card number.

I just go to the SQL Server Management Studio and write the below query and run it to check for the duplicate records and delete them.

WITH CTE AS(
 SELECT CARDNUMBER,
 RN = ROW_NUMBER()OVER(PARTITION BY CARDNUMBER ORDER BY CARDNUMBER)
 FROM CRTCARDS
 )
 DELETE FROM CTE WHERE RN > 1

Below are the links for more detail analysis on this:

https://support.microsoft.com/de-ch/kb/937765

http://stackoverflow.com/questions/18390574/how-to-delete-duplicate-rows-in-sql-server

Thanks

Muhammad Zahid.

Saturday 8 August 2015

Getting Financial Dimension Values through X++

I was working on the  SSRS reports there was the scenario of showing the individual dimensions values i.e Business Unit, Cost Center, Department, LineOfBusiness, LineOfRevenue.

I have the generalJournalAccountEntry table in my query which cantains the LedgerAccount that contains the financial dimension values in combine i,e 602100-001-026-014-. This account contains the values of main account i.e 602100, business unit i.e  011, department i.e 026,  costcentre i.e 014.

My scenario  was I have to break this accounts into individual dimensions at run time.

After some research I got the code to get the financial dimension values by ledgerDimension which is already present in GeneralJournalAccountEntry table.

Below is the code snippet to achieve this:

private void addDimensionSegments(MzkLedgerTransactionListStagingTmp _ProcessingStagingTable, GeneralJournalAccountEntry _generalJournalAccountEntry)
{
    // DimensionAttributeValueCombination stores the combinations of dimension values
    // Any tables that uses dimension  combinations for main account and dimensions
    // Has a reference to this table’s recid
    DimensionAttributeValueCombination  dimAttrValueComb;
    //GeneralJournalAccountEntry is one such tables that refrences        DimensionAttributeValueCombination
    //GeneralJournalAccountEntry          gjAccEntry;//TODO commment zahid
    // Class Dimension storage is used to store and manipulate the values of combination
    DimensionStorage        dimensionStorage;
    // Class DimensionStorageSegment will get specfic segments based on hierarchies
    DimensionStorageSegment segment;
    int                     segmentCount, segmentIndex;
    int                     hierarchyCount, hierarchyIndex;
    str                     segmentName, segmentDescription;
    SysDim                  segmentValue;
    ;

    setPrefix("Dimension values fetching");
    //Fetch the Value combination record
    dimAttrValueComb = DimensionAttributeValueCombination::find(_generalJournalAccountEntry.LedgerDimension);
    setPrefix("Breakup for " + dimAttrValueComb.DisplayValue);

    // Get dimension storage
    dimensionStorage = DimensionStorage::findById(_generalJournalAccountEntry.LedgerDimension);
    if (dimensionStorage == null)
    {
        throw error("@SYS83964");
    }

    // Get hierarchy count
    hierarchyCount = dimensionStorage.hierarchyCount();
    //Loop through hierarchies to get individual segments
    for(hierarchyIndex = 1; hierarchyIndex <= hierarchyCount; hierarchyIndex++)
    {
        setPrefix(strFmt("Hierarchy: %1", DimensionHierarchy::find(dimensionStorage.getHierarchyId(hierarchyIndex)).Name));
        //Get segment count for hierarchy
        segmentCount = dimensionStorage.segmentCountForHierarchy(hierarchyIndex);

        //Loop through segments and display required values
        for (segmentIndex = 1; segmentIndex <= segmentCount; segmentIndex++)
        {
            // Get segment
            segment = dimensionStorage.getSegmentForHierarchy(hierarchyIndex, segmentIndex);

            // Get the segment information
            if (segment.parmDimensionAttributeValueId() != 0)
            {
                // Get segment name
                segmentName = DimensionAttribute::find(DimensionAttributeValue::find(segment.parmDimensionAttributeValueId()).DimensionAttribute).Name;
                //Get segment value (id of the dimension)
                segmentValue        = segment.parmDisplayValue();
                //Get segment value name (Description for dimension)
                segmentDescription  = segment.getName();
                ///info(strFmt("%1: %2, %3", segmentName, segmentValue, segmentDescription));
                if(segmentName == 'BusinessUnit')
                {
                    _ProcessingStagingTable.BusinessUnit = segmentValue;
                }
                else if(segmentName == 'LineOfBusiness')
                {
                    _ProcessingStagingTable.LineOfBusiness = segmentValue;
                }
                else if(segmentName == 'LineOfRevenue')
                {
                    _ProcessingStagingTable.LineOfRevenue = segmentValue;
                }
                else if(segmentName == 'Location')
                {
                    _ProcessingStagingTable.Location = segmentValue;
                }
                else if(segmentName == 'Department')
                {
                    _ProcessingStagingTable.Department = segmentValue;
                }
            }
        }
    }
}

Thats all from my side.

More detail to this can be found on the below link:

https://sumitsaxfactor.wordpress.com/2011/12/16/getting-individual-dimension-combination-valuesdimension-storage-class-ax-2012/