Thursday, May 31, 2012

Running report via code in Ax2012

This is a quick example of running an Ax2012 report via code - In this example we're outputting the Vendors report (Accounts Payable / Reports / Vendors / Vendors) directly to a PDF file.


static void RunSSRSReport(Args _args)
{    
    SrsReportRunController  reportRunController;
    Map                     queryContracts;
    MapEnumerator           mapEnum;
    Query                   query;
    QueryBuildRange         range;
    ;
    
    // Create the report run controller
    reportRunController = new SrsReportRunController();
    reportRunController.parmReportName('Vend.Report');
    reportRunController.parmLoadFromSysLastValue(false);
    
    // NB call to parmLoadFromSysLastValue must occur before any reference to 
    // parmReportContract, otherwise the previous values (query ranges etc)
    // are defaulted in.
    
    // Set printer settings (print to file, format, filename, etc).
    reportRunController.parmReportContract().parmPrintSettings().printMediumType(SRSPrintMediumType::File);
    reportRunController.parmReportContract().parmPrintSettings().overwriteFile(true);    
    reportRunController.parmReportContract().parmPrintSettings().fileFormat(SRSReportFileFormat::PDF);
    reportRunController.parmReportContract().parmPrintSettings().fileName('c:\\test.pdf');
        
    // Find/enumerate queries in the contract. The return from parmQueryContracts is
    // a map of type     
        
    queryContracts = reportRunController.parmReportContract().parmQueryContracts();
    mapEnum = queryContracts.getEnumerator();    
    while(mapEnum.moveNext())    
    {
        // Get the query and update the datasource as required
        query = mapEnum.currentValue();   
        range = SysQuery::findOrCreateRange(query.dataSourceTable(tableNum(VendTable)),fieldNum(VendTable,AccountNum));
        range.value('1*'); 
    }    
    
    // Run the report
    reportRunController.runReport();
    
}

Note that this displays a message to the inflog once the file has been written. This may not be desirable if the file generation is part of a background process - If you need more control of this, have a look at adding an additional flag (eg supressInfoLog) to the classSrsReportRunPrinter.

Ax utility function for sending mail via SMTP

The following is a quick utility function that can be used to send an email via a configured SMTP server. See comment in the code for the structure of the 'attachments' parameter. This was adapted from another code-sample, and should work with version 2009 and later.

static public void sendMail(
    str         fromAddress,        // NB single address only
    str         toAddress,          // " "
    str         ccAddress,          // " "
    str         subject,
    str         body,
    container   attachments = conNull())
{

    // Send mail via SMTP
    // Attachments is a container of format:
    // [ Source file 1, Attachment name 1, Source file 2, Attachment name 2, ... ]

    SysEmailParameters                          emailParams = SysEmailParameters::find();

    System.Net.Mail.MailMessage                 message;
    System.Net.Mail.Attachment                  attachment;
    System.Net.Mail.AttachmentCollection        attachementCollection;
    System.Net.Mail.SmtpClient                  mailClient;
    System.Net.Mail.MailAddressCollection       addressCollection;
    str                                         mailServer;
    int                                         mailServerPort;
    str                                         attachmentFilename;
    str                                         attachmentName;
    int                                         idx;

    System.Exception                            clrException;
    InteropPermission                           perm;
    ;

    if(!fromAddress)
        throw error("From address has not been specified");
    if(!toAddress)
        throw error("To address has not been specified");

    try
    {
        perm = new InteropPermission(InteropKind::ClrInterop);
        perm.assert();

        mailServer      = emailParams.SMTPRelayServerName;
        mailServerPort  = emailParams.SMTPPortNumber;

        if(!mailServerPort)
            mailServerPort = 25;    // default SMTP port

        message = new System.Net.Mail.MailMessage(
            new System.Net.Mail.MailAddress(fromAddress,''),
            new System.Net.Mail.MailAddress(toAddress,''));

        addressCollection = message.get_CC();
        if(ccAddress)
            addressCollection.Add(ccAddress);

        message.set_Subject(subject);

        message.set_Body(body);
        attachementCollection = message.get_Attachments();

        if((conlen(attachments) mod 2) != 0)
            throw error(error::wrongUseOfFunction(funcName()));

        for(idx = 1;idx <= conLen(attachments);idx += 2)
        {
            attachmentFilename  = conPeek(attachments,idx);
            attachmentName      = conpeek(attachments,idx + 1);

            attachment = new System.Net.Mail.Attachment(attachmentFilename);
            attachment.set_Name(attachmentName);
            attachementCollection.Add(attachment);
        }

        mailClient = new System.Net.Mail.SmtpClient(mailServer,mailServerPort);
        mailClient.Send(message);

    }
    catch(Exception::CLRError)
    {
        clrException = CLRInterop::getLastException();
        error(clrException.get_Message());
        if(clrException.get_InnerException() != null)
        {
            clrException = clrException.get_InnerException();
            error(clrException.get_Message());
        }
        throw Exception::Error;
    }

}

And to test it out (assuming the method has been added to a new class called MailHelper):
MailHelper::sendMail(
  'admin@bigbusiness.com',
  'client@gmail.com',
  'linemanager@bigbusiness.com',
  'Congratulations!!',
  'You have just won an email.');

A note for developers - A handy tool for testing mailers or email-related processes can be downloaded at http://smtp4dev.codeplex.com/. It allows you to run a light-weight SMTP server locally for development and testing, and is definitely worth a look.

Table structure and code sample for product attributes

The following code sample shows the table structure and relationships for the new product attribute structure. It will display the attribute values for the nominated item.

The setup is found under Procurement and Sourcing / Setup Categories Procurement categories, which are then attached to a global product (Product master form / Product categories).

Further comments in the code. As usual, the queries are expanded out for clarity.

static void ShowProductAttributes(Args _args)
{
    // Show all product attribute values for a specified item.
    // (Expanded queries)
    ItemId                          itemID = 'test01';
    InventTable                     inventTable;
    EcoResProduct                   product;
    EcoResProductCategory           productCat;
    EcoResCategory                  category;
    EcoResCategoryHierarchy         catHierarchy;
    EcoResCategoryAttributeLookup   catAttributeLookup;
    EcoResAttribute                 attribute;
    EcoResProductAttributeValue     prodAttrValue;      // view based on EcoResAttributeValue
    EcoResProductInstanceValue      prodInstanceValue;
    EcoResValue                     value;
    ;

    // Find local+global product
    inventTable         = inventTable::find(itemID);
    product             = EcoResProduct::find(inventTable.Product);

    // EcoResProductInstanceValue is another level of indirection between the
    // category/attribute setup and values back to the product number. Not sure
    // why this exists as opposed to just referencing field 'Product' directly.
    prodInstanceValue   = EcoResProductInstanceValue::findByProduct(product.RecId);

    setPrefix(product.DisplayProductNumber);

    // Select all categories that are attached to the product
    while select productCat
        order by catHierarchy.Name
        where   productCat.Product  == product.RecId
    join category
        where   category.RecId      == productCat.Category
    join catHierarchy
        where   catHierarchy.RecId  == category.CategoryHierarchy
    {

        // Select all product attributes attached to the category. NB the
        // category attribute lookup table (EcoResCategoryAttributeLookup)
        // includes entries for attributes inherited from parent levels.
        //
        // In contrast, table EcoResCategoryAttribute only defines attributes attached at
        // each level.
        while select catAttributeLookup
            where   catAttributeLookup.Category     == category.RecId
        join attribute
            where   attribute.RecId                 == catAttributeLookup.Attribute
        {
            // Select the 'value' record for the current attribute. This links
            // a product and attribute reference to an instance of EcoResValue
            // (an inherited table structure for the different data types).
            // Method EcoResValue.value() determines the display value based on the
            // type of that attribute.
            select firstOnly prodAttrValue
                where   prodAttrValue.Attribute == attribute.RecId
                &&      prodAttrValue.Product   == product.RecId
            join value
                where   value.RecId             == prodAttrValue.Value;

            info(strFmt("%1 = %2",attribute.Name,value.value()));

        }
    }

}

Quick walk-through of developing a report in Ax2012

The following is a quick-and-dirty approach to building a basic SSRS report in Ax2012. This uses an Ax query as the primary datasource, and uses display methods on the table(s) to retrieve additional information.

This is not an approach you should take for all reports, particularly those that require more complex calculations or parameters, but for a lot of requirements this will get the job done fairly quickly.

I'll be posting another similar walk-through that uses the data-provider approach, which is more flexible but also more time-consuming to develop.

We'll build a basic report across sales order lines, with additional columns showing a basic margin calculation. The steps are:

Setup the Visual Studio project


Create a new Dynamics Ax Report Model project in Visual Studio, named SalesReportTest1.

Right-click the project within the solution, and add a new report. Name it SalesMarginReport.

Create place-holder method in the table and basic query structure


Create a new Extended Data Type (data-type Real), called SalesMarginAmount.

Add the following display method to SalesLine. For now, it just returns a dummy-value of 88.

public display SalesMarginAmount salesMarginAmount()
{
    return 88;
}

Create a query named SalesMarginReport, with SalesLine as the primary table, and a join to SalesTable. Set the 'Dynamic' property on the fields node of each datasource to 'Yes' (select all fields). It's normally better to only select the fields you need, but for simplicity we'll have the query return everything.

Add ranges for ItemID and SaleStatus (SalesLine), and CustAccount, InvoiceAccount, CustGroup (SalesTable).

Create datasource and table in the report


Add a datasource named SalesMarginDS to the report. Datasource type is Query, and in the 'Query' property, pick SalesMarginReport. In the field/method selection screen, pick:
  • SalesLine.SalesID
  • SalesLine.ItemID
  • SalesLine.QtyOrdered
  • SalesLine.SalesStatus
  • SalesLine.SalesPrice
  • SalesLine.LineAmount
  • SalesLine.salesMarginAmount( )
  • SalesTable.DocumentStatus
  • SalesTable.InvoiceAccount
Create a new 'Auto design' under the 'Designs' node. Set the LayoutTemplate to ReportLayoutStyleTemplate. Set the title to "Sales margin report".

Create a new Table under the auto-design. Set the Style template to "TableStyleTemplate", and the Dataset to "SalesMarginDS". Under the 'Data' section of the table, if the fields aren't already present drag them from the datasource.

Build and deploy

Build the project. Right-click the project and select "Add to AOT". Go back to Ax and expand Visual Studio projects / Dynamics AX Model projects. If you don't see your project name there, right-click and 'Refresh'. All going well you should see a new entry for your reporting project. 

If you expand AOT / SSRS Reports / Reports, you should see a corresponding entry for your report definition. Right-click the report and select 'Deploy'.

Under some environments (possibly with missing/incomplete security setup), this may not work from the AOT directly. If you have problems doing it like that, do the following:
  • First off, ensure the SSRS service is running and is accessible.
  • From your Windows desktop, open Administrative tools / Microsoft Dynamics Ax 2012 Management shell. (Right-click and "Run as administrator")
  • NB this may be a separate step when installing the Ax client/server.
  • In the console, type Publish-AxReport -reportname SalesMarginReport.
  • If you get any errors from that, first off make sure your business connector configuration is pointing to the right environment. 

Create menu-item for the report

Create a new 'Output' menu item called SalesMarginReport. Set the caption, and object type to "SSRSReport", and the object to "SalesMarginReport". It will automatically select the first design but this can be overridden if you have separate designs within the same report.

At this point you should be able to run the menu item (right-click, open). The all-familiar Ax query prompt will be shown, then the report can be run as normal, giving you:


Better margin calculation

We'll now change the margin calculation to something a bit more meaningful.  To calculate the margin we'll take the cost of inventory per piece and multiply it by the order quantity. Modify SalesLine.salesMarginAmount as follows:

public display SalesMarginAmount salesMarginAmount()
{
    // Rough margin calculation - Cost/piece of item
    // multiplied by order quantity.
    
    InventDimParm       dimParm;
    InventOnhand        onHand;
    Amount              inventoryValue;
    ;
    dimParm.initFromInventDim(this.inventDim());
    onHand = InventOnhand::newItemDim(this.ItemId,this.inventDim(),dimParm);
    
    inventoryValue = this.QtyOrdered * onHand.costPricePcs();    
    return this.LineAmount - inventoryValue;
}

Now re-run the report and you should see the updated margin amount. 
It's sometimes (although not always) the case that SSRS doesn't pick up the relevant code changes. If that happens restarting the reporting service will do the job, even though it's not a great solution.

As mentioned this the simplest approach to adding calculated/extended information to a query-based report. For more complicated scenarios you'll need to use the Data Provider framework, which I'll provide a follow-up post on soon.

Wednesday, May 30, 2012

Microsoft® DynamicsTM Ax container manipulation using X++

Note: There is no warranty in the code written below. Use at your own risk.

Overview
Container is a built-in data type, X++ supports a number of functions to manipulate the contents of containers. This topic describes the built-in functions for manipulating containers. In other way we can say X++ has a general data type called a container that can be regarded as a dynamic untyped array of primitive data types, containers, and arrays. Containers can be used to hold a list of items of different types.

Allocation of containersContainers are automatically allocated, when a container variable is used which means that you do not have to create a container using new.
Disposal of containers
To explicitly dispose the contents of a container use the connull function.
void DisposalMethod()
{
container con;
con = connull( );

Length
When you operate on a container, you often need to know how many items it contains. The conlen function returns the number of items in the container.
void LengthMethod()
{
container con;
//gives output 0 as there are initially no elements in a container
print conlen(con);

Finding a sequence of elements
To locate a sequence of items in a container, use the confind function. confind takes a container and one or more items as arguments. The function returns 0 (the item was not found) or the item’s sequence number.
void SequenceMethod()
{
container con = [100,110, ”Hello World”];
int i;
i = confind( con, “Hello World”); //i has the value of Hello World
}

Insertion
Sometimes you need to insert some items in a container. Use the conins function, which takes a variable number of arguments: the container, position, and the items to be inserted.
void InsertionMethod()
{
container con;
con = conins(con,10,”Hello Testing”);
}


Deletion
To delete one or more items from a container, use the condel function. condel takes three arguments: the container, the start position for deletion, and the number of items to be deleted.
void DeletionMethod()
{
container con = ["Hello", 100, 5.55);
con = condel(con,1,2);
}


Replacement
To replace an item in a container, use the conpoke function. conpoke takes a variable number of arguments: the container, the position (to be poked), and the new item.
void ReplacementMethod()
{
container con = [10, 30.11, ”Hello”];
// Replaces the first item - 10 - with “Hello World”
con = conpoke(con, 1, ”Hello World”);
}

Extraction
A container allows you to hold a number of items, possibly of different types, and then use them in some other context. To use the items, you need to be able to extract them from the container. This is the purpose of the conpeek function. conpeek takes two arguments: the container and the position to be peeked.


The conpeek function automatically converts the peeked item into the expected return type. Strings can automatically be converted into integers and vice versa.
void ExtractionMethod() 
{
str 10 string;
container con = [10, 30.11, ”Good Testing”];
// Extracts the real 30.11 and converts it into a string: “30.11”
string = conpeek(con,2);
}


You can extract more than one element from a container with a composite assignment:
void ExtractionMethod1()
{
str 10 string;
int i;
real rl;
container con = [10,30.11,”Hello”];
[il,r, string] = con; // Extracts 10 into i, 30.11 into rl and “Hello” into string

}

How to open Dynamics Ax form through X++ code

This article shows how to open forms in Dynamics Ax through X++ code.
Note: Use at your own risk.
You should be familiar with X++ basic programming.

I applied it on Dynamics Ax 3.0

1) Open your Ax application then AOT.
2) Go to Jobs node and create a new job.
3) Add the following codes in your job to open a form by menuitem name.

MenuFunction menuFunction;
;
menuFunction = new MenuFunction(MenuItemDisplayStr(CustTable), MenuItemType::Display);
menuFunction.run();

Your job will look like:

static void Job1(Args _args)

{
MenuFunction menuFunction;
;
menuFunction = new MenuFunction(MenuItemDisplayStr(CustTable), MenuItemType::Display);
menuFunction.run();
}



You can open your form by form name also.
1) Create a new job.
2) Copy the below code and paste to your job.
static void Job2(Args _args)
{
Args args;
FormRun formRun;
;
args = new Args();
args.name(formstr(CustTable));
formRun = new FormRun(args);
formRun.run();
formRun.wait();
}

How to use CLR Interop with Dynamics Ax 4.0

This article will explain you how to use CLR Interop with Dynamics Ax 4.0.

Write your business logic in .Net environment and use that within Dynamics Ax.

It assume that you are familiar with Microsoft Dynamics Ax and Visual Studio .Net
You should have Ax 4.0 & Visual Studio .Net installed on your system.
There is No warranty on this article, use at your own risk. If it’s useful then refer to others.

.NET CLR Interop Overview
Classes in assemblies that are managed by the common language runtime (CLR) can be accessed in X++ code. This feature of Microsoft Dynamics AX is called common language runtime (CLR) interoperability, or CLR interop.
The CLR interop feature works only in the direction from X++ calling into CLR managed assemblies. It does not support calling X++ classes from a CLR managed assembly.
CLR interop is useful when you want your X++ code to access the functionalities in a CLR managed assembly.


1.
Open Visual Studio and create a new project. During creating a new project area, choose programming language Visual C# and template Class library. I have given the project name TalkToDotNet.








Figure 1.















Figure 2.

2.
By default you will get a standard class template as below:
using System;
using System.Collections.Generic;
using System.Text;

namespace TalkToDotNet
{
public class Class1
{
}
}



3.
Now add the following lines within Class1
public string CombinedMsg(string _msg)
{
string message1;
message1 = _msg;
return message1 + "From .Net: I am fine";
}

After addition the above lines the class1 will look like:

using System;
using System.Collections.Generic;
using System.Text;

namespace TalkToDotNet
{
public class Class1
{
public string CombinedMsg(string _msg)
{
string message1;
message1 = _msg;
return message1 + "From .Net: I am fine";
}
}


}



The above class is having a method called CombinedMsg which takes a string parameter and add "From .Net: I am fine" string with the parameter string.

4.
Now go to the project explorer window and select the project. Right click on the project and click on Properties. You will get the window as below.


Figure 3.

5.
Click on Signing Tab and then check the check box Sign the assembly.


Figure 4.
6. Choose New from the drop down box as shown below.


Figure 5.

7.
It will ask you for user id and password to protect your business logic. This is optional. You may ignore.

Figure 6.


8.
Click on Build menu then Build Solution.

Figure 7.
If any error is there then you will be prompted otherwise you will get the successful message. Now copy the dll from your project area and paste it to Bin directory of your Ax client folder.


9.
Now open your Ax application.


10.
Open AOT, select the References node and right click on it. On the context menu click on Add reference.

Figure 8.


11.
You will get the window as below. Click on Browse button and go to the Bin folder of your Ax client folder.

Figure 9.




Figure 10.




Figure 11.


12.
See the dll is added.

Figure 12.

13.
The dll is now added in the references node of AOT.


Figure 13.


14.
Now create a job in Ax and call the dll.

Figure 14.



Figure 15.


15.
The job is as below.

Figure 16.


static void TalkToDotNet(Args _args)
{
str strval;
TalkToDotNet.Class1 testCls = new TalkToDotNet.Class1();
strval = testCls.CombinedMsg('From Ax: How are you? ');
print strval;
pause;
}

16.
See the print message below. It takes a string message from Ax and adds it with the message returned by dll.

Figure 17.

Hope it would a helpful article for you guys.

How to identify the user that was used to change an object from AOT in AX2012

Get the object name for which we need to track these (user and date&time) information's. Login to SQL Server Management Studio an...