Wednesday, September 24, 2014

Ax2012: Using ADO.Net with X++

http://floditt.blogspot.in/2011/08/using-adonet-with-x.html

public void adoConnection()
{
    str serverName;
    str catalogName;
    str ConnectionString;
    str sqlQuery;
    //ADO.Net via CLR objects. Requires referenced System.Data
    System.Data.SqlClient.SqlConnectionStringBuilder connectionStringBuilder;
    System.Data.SqlClient.SqlConnection connection;
    System.Data.SqlClient.SqlCommand command;
    System.Data.SqlClient.SqlParameterCollection parameterCollection;
    System.Data.SqlClient.SqlDataReader dataReader;
    System.Exception netExcepn;
    ;
    new InteropPermission( InteropKind::ClrInterop ).assert();
   
    sqlQuery = "SELECT * from  INVENTTABLE";
    //ceating the ConnectionString dynamically, based on the current connection
    serverName = SysSQLSystemInfo::construct().getLoginServer();
    serverName = "GBSQLDEV2"; //change to different server
    catalogName = SysSQLSystemInfo::construct().getloginDatabase();
    catalogName="MicrosoftDynamicsAx"; //change to different database
    connectionStringBuilder = new System.Data.SqlClient.SqlConnectionStringBuilder();
    connectionStringBuilder.set_DataSource(serverName);
   
    //here it becomes interesting. The current execution context will be used to
    //establish a conection. If this is executed by a batch, this is the user
    //configured for the batch
    connectionStringBuilder.set_IntegratedSecurity(true);
    connectionStringBuilder.set_InitialCatalog(catalogName);
    //all this to prevent working with a fixed string...
    //on my computer, this would be equal to
    //"Data Source=DYNAMICSVM;Initial Catalog=DynamicsAx1;Integrated Security=True"
    ConnectionString = connectionStringBuilder.get_ConnectionString();

    //initializing connection and command
    connection = new System.Data.SqlClient.SqlConnection(ConnectionString);
    command = new System.Data.SqlClient.SqlCommand(sqlQuery);
    command.set_Connection(connection);

    //executing SQL-query
    try
    {
        //open within catch, so that the object can correcly be disposed
        //all these try-catch are quite ennoying in X++, but this because
        //X++ does not know finally...
        connection.Open();
        try
        {
            //All code after the open must be in a seperate catch, so that the
            //open connection-object can correcly be disposed.
            dataReader = command.ExecuteReader();

            while(dataReader.Read())
            {
                //use the named columns instead of index.
               info( dataReader.get_Item("ItemId"));
            }
            //Dispose ADO.Net objects ASAP
            dataReader.Dispose();
        }
        catch //should be more precise in a real-world application
        {
            //if exception occures while reading, DataReader need to be
            dataReader.Dispose();
        }
        catch(Exception::CLRError) //CLR exception need to be handled explicitely
        //otherwise they might be 'lost'. Happy copy&pasteing
        {
            //if exception occures while reading, DataReader need to be
            dataReader.Dispose();
        }
        connection.Dispose();
    }
    catch (Exception::Error) //should be more precise in a real-world application
    {      
       
        connection.Dispose(); //disposing connection if it fails before opening it
    }
    catch(Exception::CLRError)
    {
        netExcepn = CLRInterop::getLastException();
        info(netExcepn.ToString());
        connection.Dispose();
    }
    command.Dispose();
    CodeAccessPermission::revertAssert();
   
   
}

Tuesday, September 2, 2014

Ax 2012 SSRS row visibility in case of hierarchy groups.

When having multiple grouping and totals for each grouping in SSRS reports, the row visibility property cannot be used to hide or show based on certain conditions

In such scenario, the split cells functionality can be used. Once we use the split cells, then the row visibility property will be available.

So if we have 3 groups hierarchy, we should first  do the group by for the first required column, add totals and then split the column. Now the row visible property will be available. Set the required expression for showing or hidding the group.
Again do the grouping for next one and follow the same steps as above.
Once a new group is added, the previous group row visibility will not be available.

Uploading MS Word documents to Blogger - via Google Docs

RDP based SSRS report

A RDP based SSRS report consist of the following:
Temporary table
Data contract class
UI builder class
RDP class
Controller class
SSRS report
Output menu item

Step 1: Temporary table
Create a new inmemory temporary table which will be used in the RDP class.


Step 2: Data contract class.

  1. Class declaration
The following should be set before the class declaration.
[
   DataContractAttribute,
   SysOperationContractProcessingAttribute(classStr(xxxReportUIBuilder)),
 SysOperationGroupAttribute('Parameter',"Parameter",'1'),
   SysOperationGroupAttribute('Select',"Select",'2')

]

Where xxxReportUIBuilder is the UI builder class name.
SysOperationGroupAttribute should be used if we need to group parameters and the order in which the group should be shown.

If validation is required, The class should implement SysOperationValidatable
public class LF_BookedSalesReportContract implements SysOperationValidatable

Variables should be declared which are required as parameters.
TransDate           fromDate;
TransDate           toDate;
DimensionValue      branch;
hcmWorkerRecId      employee;


  1. Add parm methods for the variables declared.
[
DataMemberAttribute('FromDate'),
SysOperationLabelAttribute(literalstr("@SYS24050")),
SysOperationGroupMemberAttribute('Parameter'),
SysOperationDisplayOrderAttribute('1')

]
public TransDate parmFromDate(TransDate _fromDate = fromDate)
{
   fromDate = _fromDate;
   return fromDate;
}

SysOperationGroupMemberAttribute should be used identify the group name and SysOperationDisplayOrderAttribute should be used to set the display order of the parameter in the group.

///
/// Gets or sets the value of the datacontract parameter ToDate.
///
///
/// The new value of the datacontract parameter ToDate; optional.
///
///
///  The current value of datacontract parameter ToDate
///
[
   DataMemberAttribute('ToDate'),
   SysOperationLabelAttribute(literalstr("@SYS80934"))
]
public TransDate parmToDate(TransDate _toDate = toDate)
{
   toDate = _toDate;
   return toDate;
}

  1. Validate variables.

public boolean validate()
{
   boolean isValid = true;

if (this.parmToDate() && this.parmFromDate() > this.parmToDate())
   {
       // Invalid date interval
        isValid = checkFailed("@SYS91020");
   }
       if(isValid )
       {
           return true;
       }
       else
       {
           return false;
       }

   }

To validate dimensions:
   DimensionAttribute              dimAttribute;
   DimensionAttributeDirCategory   dimAttributeDirCategory;
   DimensionFinancialTag           DimensionFinancialTag;

   void checkDimension(DimensionValue  _value, Name    _dimname)
   {
       dimAttribute = DimensionAttribute::findByName(_dimname);
       select RecId from DimensionFinancialTag where DimensionFinancialTag.Value == _value
         join DirCategory from dimAttributeDirCategory
         where dimAttributeDirCategory.DimensionAttribute == dimAttribute.recId &&
            dimAttributeDirCategory.DirCategory == DimensionFinancialTag.FinancialTagCategory ;
       if (!DimensionFinancialTag.RecId)
           isValid = checkFailed(strfmt("%1 value %2 does not exist.",_dimname,_value));
   }

   department = this.parmDepartment();
   if(department)
   {
checkDimension(department,"Departments"); checkDimension(department,"Departments");
   }



Step 3: UI builder class.
  1. Class declaration
Should extend SrsReportDataContractUIBuilder
Declare a variable for the contract class created above.
xxxDPContract rdpContract

  1. Post run method
This method can be overridden and can be used to register overridden method for controls like the lookup, modified method etc. Also can be used to set the labels for the controls.

public void postRun()
{
   DialogField dialogField;
   Dialog dialogLocal = this.dialog();
   ;

   super();
dialogLocal.caption("Report Name");

// This method should be called in order to handle events on dialogs. This method should not be called in case of multi select control overridden.
   dialogLocal.dialogForm().formRun().controlMethodOverload(false);
   rdpContract = this.dataContractObject();
   //Set default values in the contract
rdpContract.parmToDate(systemDateGet());
   rdpContract.parmSalesEmpType(LF_SalesEmpType::LF_SalesResponsible);
   dialogField = this.bindInfo().getDialogField(this.dataContractObject(),
methodStr(LF_BookedSalesReportContract, parmToDate));
   dialogField.value(systemDateGet());

   dialogField = this.bindInfo().getDialogField(this.dataContractObject(),
methodStr(LF_BookedSalesReportContract, parmSalesChannel));
   dialogField.registerOverrideMethod(methodstr(FormStringControl, lookup),
methodstr(LF_BookedSalesReportUIBuilder,  lookupSalesChannel), this);

}

lookup method overridded:
public void lookupSalesChannel(FormStringControl _control)
{
    SysTableLookup          sysTableLookup;
    Query                   query;
    QueryBuildDataSource    qbdsdimFinancialTag,qbdsSPSContract;
    DialogField             dlgFldSalesProjType;

    dlgFldSalesProjType = this.bindInfo().getDialogField(this.dataContractObject(),
methodStr(LF_BookedSalesReportContract, parmSalesProjType));
    if (dlgFldSalesProjType.value() == LF_SalesProjType::LF_SmallProjSales)
    {
        sysTableLookup  = SysTableLookup::newParameters(tableNum(DimensionFinancialTag), _control);
        sysTableLookup.addLookupfield(fieldNum(DimensionFinancialTag, Value));
        sysTableLookup.addLookupfield(fieldNum(DimensionFinancialTag, Description));

        query   = new query();
        qbdsdimFinancialTag = query.addDataSource(tableNum(DimensionFinancialTag));
        qbdsSPSContract = qbdsdimFinancialTag.addDataSource(tableNum(LF_SmallProjSalesData));
       qbdsSPSContract.addLink(fieldNum(DimensionFinancialTag,RecId),fieldNum(LF_SmallProjSalesData,LF_SalesChannelRecId));
        qbdsSPSContract.joinMode(JoinMode::ExistsJoin);

        sysTableLookup.parmQuery(Query);
        sysTableLookup.performFormLookup();
    }
}

Example of modified method
public boolean modifiedCustAcct(FormStringControl _control)
{
   CustTable               custTable;
   LogisticsPostalAddress  deliveryAddress;
   DialogField             dialogField;


   dialogField = this.bindInfo().getDialogField(this.dataContractObject(), methodStr(LF_ShippingLabelContract, parmCustAccount));

   if(dialogField.value())
   {
       select firstonly custTable where custTable.AccountNum == dialogField.value();
       if (custTable)
       {
           deliveryAddress = LogisticsLocationEntity::findPostalAddress(custTable,LogisticsLocationRoleType::Delivery);
           if (deliveryAddress)
               this.setAddressFields(deliveryAddress);
           else
               this.setAddressFields(custTable.postalAddress());

           dialogField = this.bindInfo().getDialogField(this.dataContractObject(), methodStr(LF_ShippingLabelContract, parmCustName));
           dialogField.value(custTable.name());
           contract.parmCustName(custTable.name());
       }
   }
   return true;
}

Example to clear control values.
public void preBuild()
{
   FormBuildGroupControl       formBuildGroupControl;


   super();
   contract = this.dataContractObject();



   contract.parmJobNum("");
   contract.parmCustName("");
   

   formBuildGroupControl = dialog.curFormGroup();
   formBuildGroupControl.columns(2);
}

Step 4: RDP class.
  1. Class declaration
The following should be set before the class declaration.
[
   SRSReportParameterAttribute(classstr(xxxReportContract)),
SRSReportQueryAttribute(queryStr(QueryName)) //used when query is also part of the rdp.

]

Where xxxReportContract is the Data Contract class name.

The class should extends SRSReportDataProviderBase //SrsReportDataProviderPreProcess //
SrsReportDataProviderPreProcess can be used for debugging the rdp class.

In the class declartion, declare a variable for the tmp which will be sent to the SSRS report, and for the queryrun.
LF_BookedContractSalesTmp    bookedContractSalesTmp;
QueryRun                 queryRun;



  1. Process report
Write the logic to insert the data into the temp table
   LF_ScheduledDollarsBranchContract      dataContract; // Get the query from the runtime using a dynamic query.

   dataContract = this.parmDataContract();
   projFromDate = dataContract.parmProjectFromDate();
   queryRun = new SysQueryRun(this.parmQuery());

   //Add more filters to the query based on the user selected values
    refer the setQueryRanges() below.

   while (queryRun.next()
   {


   }


Example to modify the query.
private void setQueryRanges(ProjResponsibleFinancialWorker _projectAccountant)
{
   QueryBuildDataSource qbdsProjTable, qbdsContractLineItems, qbdsProjInvTeamTable;
   qbdsProjTable = queryrun.query().dataSourceTable(tablenum(ProjTable));
   qbdsContractLineItems = qbdsProjTable.addDataSource(tableNum(PSAContractLineItems));
   qbdsContractLineItems.addSelectionField(fieldnum(PSAContractLineItems, FeeProjId));
   qbdsContractLineItems.addSelectionField(fieldnum(PSAContractLineItems, ChangeOrderNum));
   qbdsContractLineItems.addSelectionField(fieldnum(PSAContractLineItems, LineDesc));
   qbdsContractLineItems.addSelectionField(fieldnum(PSAContractLineItems, LineValue));
   qbdsContractLineItems.addSelectionField(fieldnum(PSAContractLineItems, CreatedDateTime));
   //qbdsContractLineItems.addSelectionField(fieldnum(PSAContractLineItems, ProjInvoiceProjId));
   qbdsContractLineItems.addRange(fieldnum(PSAContractLineItems,LF_ContractLineStatusId )).value("TM");
   qbdsContractLineItems.addRange(fieldnum(PSAContractLineItems,LF_ContractLineStatusId )).value("PN");
   qbdsContractLineItems.addRange(fieldnum(PSAContractLineItems,LF_ContractLineStatusId )).value("P$");
   qbdsContractLineItems.addRange(fieldnum(PSAContractLineItems,LF_ContractLineStatusId )).value(SysQuery::valueEmptyString());
   qbdsContractLineItems.addRange(fieldnum(PSAContractLineItems, FeeProjId)).value(SysQuery::valueNotEmptyString());
   qbdsContractLineItems.addSortField(fieldNum(PSAContractlineitems,FeeProjId));
   qbdsContractLineItems.addSortField(fieldNum(PSAContractlineitems,ChangeOrderNum));
   qbdsContractLineItems.addLink(fieldNum(ProjTable, ProjId), fieldNum(PSAContractLineItems, FeeProjId));
   qbdsContractLineItems.joinMode(JoinMode::InnerJoin);

   qbdsProjInvTeamTable = qbdsContractLineItems.addDataSource(tablenum(LF_ProjInvoiceTeamTable));
   qbdsProjInvTeamTable.addSelectionField(fieldnum(LF_ProjInvoiceTeamTable, LF_ProjectAcct));
   qbdsProjInvTeamTable.addLink(fieldNum(PSAContractLineItems, ProjInvoiceProjId), fieldNum(LF_ProjInvoiceTeamTable, LF_ProjInvoiceId));
   if (_projectAccountant)
   {
       qbdsProjInvTeamTable.joinMode(JoinMode::InnerJoin);
       qbdsProjInvTeamTable.addRange(fieldNum(LF_ProjInvoiceTeamTable,LF_ProjectAcct)).value(queryValue(_projectAccountant));
   }
   else
   {
       qbdsProjInvTeamTable.joinMode(JoinMode::OuterJoin);
   }
}



  1. method to set the tmp table
This method should set the report dataset attribute to the temporary table

[
   SRSReportDataSetAttribute("scheduledDollarBySchedulerTmp")
]
public LF_ScheduledDollarBySchedulerTmp getScheduledDollarBySchedulerTmp()
{
   select * from scheduledDollarBySchedulerTmp;

   return  scheduledDollarBySchedulerTmp;
}


Step 4: Controller class.
This class should extend SrsReportRunController. In the class declaration, declare a macro for the report name

#define.ReportName('LF_BookedSalesReport.Report')

Create a main method to call the SSRS report.
public static void main(Args _args)
{
   LF_BookedSalesReportController controller  = new LF_BookedSalesReportController();
   controller.parmReportName(#ReportName);
   controller.parmArgs(_args);
   controller.startOperation();
}

Override heh prePromptModifyContract to make changes to query filter.
protected void prePromptModifyContract()
{
   ProjTable   projTableLocal;
   //super();
   //add a range in the report query
   if(this.parmArgs().record())
   {
       projTableLocal = this.parmArgs().record();
       SrsReportHelper::addParameterValueRangeToQuery(this.getFirstQuery(),tableNum(ProjTable),fieldNum(ProjTable, ProjId),SysQuery::value(projTableLocal.projId));
   }
}



Step 5: SSRS Report.
Create a SSRS report in the VS report designer. Create an RDP based precision report and the dataset should use the rdp class created before.

Step 6: Output menu item.


Step 7: Add Output menu item to menu.