AX2012 Create SSRS Report using Data Provides Classes

Create the SSRS Report in AX2012 using Report Data Provider (RDP) functionality.

RDP implments the standard MVC(Model View Controller) design pattern.
In this post I am building one report which is used in the production packing list report for production module.

What is MVC?

  • Model-view-controller (MVC) is a pattern used to isolate business logic from the user interface.
  • Model: Responsible for retrieving data and for business logic, this can included queries, data methods, or other classes that are designed toretrieve data.
  • View: Responsible for the User Interface, this can also be thought of as the design for the report.
  • Controller: Orchestrates the flow between Model and View



For this Report I am creating the Query,Contract,Controller,DataProvider classes in below.
In this I used lot of my custom fields that we created and used in the functionality

TmpTable- ProdPackingSlipDetailsTmp
Query Name-ProdPackList
Contract class-ProdPacklistContract
Controller class-ProdPackListController Extends SRSReportRunController
Data ProvideClass-ProdPacklistDP Extends SRSReportDataProvidePreProcess
SSRS Report-ProdPackList

Starting with Query-Build the Query as the following data sources with the fields and relations.



Create the TMP table as below fields and properties of the table




define Properties of the table mainly createTransactionID=Yes
















Create the Contract  Class first,
This class is used to create parm methods for the reports, So if you have any parameters that you want to pass to report then create parm methods for those as datamembers.

for ex.-

[    DataMemberAttribute('UseQuantity')]
public ProdBOMJournalQty parmUseQuantity(ProdBOMJournalQty _useQuantity = useQuantity)
{
    useQuantity = _useQuantity;
    return useQuantity;
}


In my report I am using Query,So I dont have any parameters..directly to define.


[DataContractAttribute ]
public class ProdPacklistContract
{
}


Create Controller class which extends SrsReportRunController

/// <summary>
///    The <c>ProdPackListController</c> class is the controller class for <c>ProdPackList</c> SSRS report.
/// </summary>
public class ProdPackListController extends SrsReportRunController
{
}
Create the Main method where you have to define the reportname and design and this was the method which will calls the report to exectue...starting point of the report.

public static void main(Args _args)
{
    SrsReportRunController    controller = new  ProdPackListController  ();
    controller.parmReportName("ProdPackList.Report");
    controller.parmArgs(_args);
    controller.startOperation();
}



Override the method preRunModifyContract method


protected void preRunModifyContract()
{
    GNProdPacklistContract    contract;
    contract = this.parmReportContract().parmRdpContract() as GNProdPacklistContract;
}


if you want to handle the record count and no records then you can over ride this method and you can handle the no records warnings.
protected container preRunValidate()

{
    // This report is only containing dynamic filters and via testing it's been determined
    // that on warm box it performs under 10 seconds with a 500 records and under 10 minutes
    // with 50000 records. The rest of the contract parameters just define layout and UI, so
    // no additional filtering on those needs to be done in this method.
    // The granularity of the query is determined by the join of ProdJournalBOM and ProdBOM tables.
    #define.ErrorLimit(50000)
    #define.WarningLimit(500)

    container               validateResult;
    Query                   firstQuery = this.getFirstQuery();
    int                     rowCount = QueryRun::getQueryRowCount(firstQuery, #ErrorLimit + 1);

    if (rowCount > #ErrorLimit)
    {
        validateResult = [SrsReportPreRunState::Error];
    }
    else if (rowCount > #WarningLimit)
    {
        validateResult = [SrsReportPreRunState::Warning];
    }
    else if(rowCount <=0)
    {
       // validateResult = [SrsReportPreRunState::Error];
         throw error("No records available for the Selected criteria");
    }
    else
    {
        validateResult = super();
    }

    return validateResult;
}


if you want to pass the values for the report before promting to user for input you can override this method
prePromptModifyContract 

In my case I am overriding to pass the value and ranges for my query before showing the dialog to user.


/// <summary>
///    Sets query ranges based on the caller.
/// </summary>
/// <exception cref="M:Exception::Error">
///    The method is called from a invalid path.
/// </exception>
protected void prePromptModifyContract()
{
    QueryBuildRange         queryBuildRangeSerial;
    QueryBuildRange         queryBuildRangeProd,qbrTransStatus;

    QueryBuildDataSource    queryBuildDataSource,queryBuildDataSource1,queryBuildDataSource2;
    ProdTable               localProdTable;
    Query                   query;
    // get the report query
    query                   = this.parmReportContract().parmQueryContracts().lookup(this.getFirstQueryContractKey());

    queryBuildDataSource    = SysQuery::findOrCreateDataSource(query, tableNum(ProdTable));
    queryBuildDataSource1   = SysQuery::findOrCreateDataSource(query, tableNum(InventDim));
    queryBuildDataSource2   = SysQuery::findOrCreateDataSource(query, tableNum(InventTrans));

    queryBuildRangeProd     = SysQuery::findOrCreateRange(queryBuildDataSource, fieldNum(ProdTable,ProdId));
    queryBuildRangeSerial   = SysQuery::findOrCreateRange(queryBuildDataSource1, fieldNum(InventDim,inventSerialId));
    qbrTransStatus          = SysQuery::findOrCreateRange(queryBuildDataSource2, fieldNum(InventTrans,StatusReceipt));
    qbrTransStatus.value(queryValue(StatusReceipt::Received));

    if (this.parmArgs().dataset() ==  tableNum(ProdTable))
    {
        localProdTable = this.parmArgs().record();
        queryBuildRangeProd.value(queryValue(localProdTable.ProdId));
        this.parmShowDialog(true);
    }
    else if ((this.parmArgs().menuItemName() == menuitemOutputStr(ProdPacklist)) && (this.parmArgs().dataset() ==0))
    {
        this.parmShowDialog(true);
    }
    else
    {
        throw error(strFmt("Packing list can only printed from Production",funcName()));
    }

}


Create DataProvider class which extends SrsReportDataProviderPreProcess
In My report I am using the Barcode setup functionality for printing the serial numbers aswell.



[
    SRSReportQueryAttribute(queryStr(ProdPackList)),
    SRSReportParameterAttribute(classStr(ProdPacklistContract))
]
class ProdPacklistDP extends SrsReportDataProviderPreProcess
{

    boolean                     showQuery;
 
    boolean                     firstPage;
    ProdTable                   prodTable;
    ProdId                      prodId;

    CompanyInfo                 companyInfo;

    ProdBOM                     prodBOM;
    InventDim                   inventDim;

   ProdPackingSlipDetailsTmp prodPackingSlipDetailsTmp;
    BarcodeSetup                barCodeSetup;
    BarcodeSetupId  barcodeSetupId;
    Barcode         barcode;
}



Create the method which return the tmp table object

[
    SRSReportDataSetAttribute(tableStr('ProdPackingSlipDetailsTmp'))
]
public ProdPackingSlipDetailsTmp getProdPacklistDetailsTmp()
{
    select prodPackingSlipDetailsTmp;
    return prodPackingSlipDetailsTmp;
}

override the method ProcessReport where you will write the business logic to fill into thetmp table

/// <summary>
///    Processes the report business logic.
/// </summary>
/// <remarks>
///    Calls the sub methods to insert data into the temporary table.
/// </remarks>
[SysEntryPointAttribute(false)]
public void processReport()
{
    QueryRun                queryRun;
     ProdPacklistContract contract       = this.parmDataContract() as  ProdPacklistContract ;
    // Set the userconnection to use on table.
    // This is required to ensure that createdTransactionId of inserted record is different than default  
           transaction.
    prodPackingSlipDetailsTmp.setConnection(this.parmUserConnection());
    this.init();
    this.setupBarcode();
    queryRun                        = new QueryRun(this.parmQuery());
    while (queryRun.next())
    {
        prodTable       = queryRun.get(tableNum(ProdTable));
        inventDim       = queryRun.get(tableNum(InventDim));
        prodId          = prodTable.ProdId;
        if(prodTable.InventRefType==InventRefType::Sales)
        {
           this.insertHeader();
           this.insertDetails(prodId,inventDim.inventSerialId);
        }
    }
}


private void init()
{
    companyInfo = CompanyInfo::find();
    firstPage   = true;
}







//BP Deviation documented
protected BarCodeString barcode(str _SerialNumber)
{
    str jobId = strupr(_SerialNumber);

    if (barcodeSetup.validateBarcode(jobId))
    {
        barcode.string(true, jobId);
        barcode.encode();
    }
    else
    {
        throw(error(strfmt("@SYS41409", barcode.barcodeType(), jobId)));
    }
    return barcode.barcodeStr();
}


/// <summary>
/// Initialize barcode settings.
/// </summary>
protected void setupBarcode()
{
    barcodeSetupId = JmgParameters::find().getBarcodeSetupId();
    barcodeSetup = BarcodeSetup::find(barcodeSetupId);
    barcode = barcodeSetup.barcode();
}

// assigns the data into header information first

private void insertHeader()
{
    prodPackingSlipDetailsTmp.clear();
    prodPackingSlipDetailsTmp.initValue();
    prodPackingSlipDetailsTmp.SalesId=prodTable.InventRefId;
    prodPackingSlipDetailsTmp.SerialNumber=inventDim.inventSerialId;


    prodPackingSlipDetailsTmp.ProdItemId                = prodTable.ItemId;
    prodPackingSlipDetailsTmp.Description               = prodTable.Name;
    prodPackingSlipDetailsTmp.Customer                  = SalesTable::find(prodPackingSlipDetailsTmp.SalesId).CustAccount;


    prodPackingSlipDetailsTmp.initValue();
    prodPackingSlipDetailsTmp.barcodeSetupId=barCodeSetup.barcodeSetupId;
    prodPackingSlipDetailsTmp.barcodeType=barCodeSetup.barcodeType;
    prodPackingSlipDetailsTmp.fontName=barCodeSetup.fontName;
    prodPackingSlipDetailsTmp.fontSize=barCodeSetup.fontSize;
    prodPackingSlipDetailsTmp.maximumLength=barCodeSetup.maximumLength;
    prodPackingSlipDetailsTmp.minimumLength=barCodeSetup.minimumLength;
    prodPackingSlipDetailsTmp.SerialNumberBarCode=this.barcode(inventDim.inventSerialId);
}


private void insertDetails(ProdId _prodId,InventSerialId _inventSerialId)
{
    SMAServiceObjectTable smaServiceObjectTable;
    SMAServiceBOMTable    smaServiceBOMTable;
    ProdBOM               prodBOMTable;

    while select prodBOMTable order by InventTransId asc
        where prodBOMTable.ProdId==_prodId
    {
        if(InventTable::Find(prodBOMTable.ItemId).PrintItemProduction)
        {
            prodPackingSlipDetailsTmp.ItemId=prodBOMTable.ItemId;
            prodPackingSlipDetailsTmp.Qty=prodBOMTable.BOMQty;
            prodPackingSlipDetailsTmp.Name=prodBOMTable.itemName();
            if(prodBOMTable.SerialnoControlled())
            {
                select TemplateBOMId,ServiceObjectId from smaServiceObjectTable
                    where smaServiceObjectTable.InventSerialId==_inventSerialId
                    && smaServiceObjectTable.ReferenceId==_prodId
                    && smaServiceObjectTable.ReferenceCategory==InventTransType::Production
                join InventSerialId from smaServiceBOMTable where
                    smaServiceBOMTable.ItemId==prodBOMTable.ItemId
                    && smaServiceBOMTable.ObjectId==smaServiceObjectTable.ServiceObjectId
                    && smaServiceBOMTable.ServiceBOMId==smaServiceObjectTable.TemplateBOMId;

                prodPackingSlipDetailsTmp.SerialNo=smaServiceBOMTable.InventSerialId;
            }
            prodPackingSlipDetailsTmp.SalesFormNotes=FormLetterRemarks::find(companyInfo.LanguageId,FormTextType::ProductionPackingList).Txt;
            prodPackingSlipDetailsTmp.insert();
            prodPackingSlipDetailsTmp.SerialNo="";
        }
    }
}


After creation of all the classes above mentioned then we have to create the report,
Open .netDevelopment enviorment and create project and select Microsoft Dynamics AX from the Left pane and select the ReportModel Project from the list of project and give your project name and click ok.
































select the project and right click and click add and select report.














It will create the new report as follow.Right click on report and select properties and give the name as ProdPackList.













Select DataSets and right click and click AddDataset and provide the DatasetName and select the dataset that you created and right click and properties.








click on Query it will opens the dialog with the DataProvider classes,in that select the DataProvider class that we built and click next. It will opens the fields selection window where we will select the list of fields that required to show in the report
















based on your requirements you can select or deselect the fields and click on. Now the dataset with the fields are ready for the report.
Now you can drag and drop that dataset on to the designs node, It will generate auomatic design for those fields.
If you want to design your own format then create new precision Design by right click on the designnode and  add precisionDesign and change that design name as we mentioned in the controller class main method(Report).


I Created the Precision design as follows.










This is how my report looks like above.

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.