Monday, 19 March 2018

Development of Report in Microsoft Dynamics 365

Data contract :  Data Contract class  ties with extended data types. This class contain properties with getter setters. At run time these properties act as report parameters.
Data Provider Class: This class act as container for report logic.  Here we get Report Query or parameters of Data contract class and perform queries on required tables. This class is extended with framework class.
Temperory table Temperory table is used as bridge for data between report designer (DataSet) and Report logic. We populate temporary table with required logic in data provider class. 
Report Requirement: we develop Customer Sales Report with Fields : Customer Name, Sale order Number, Item Id, Item group. Sales order Quantity, expected delivery date of Sale order and Unit Sales price. The report must be print in A4 paper size.
Mapping: So our first step is to create mapping. This mapping contain following things.
  • Expected field Name
  • Description / explaination / expected values
  • Orignal table field, which we read and map to temporary field.
  • Extended data type. You can get it from original table field. This is optional, but good in the case of create links and length of fields in table. 
So build following table with our requirement and generates mapping.
Report ColumnDescriptionDynamics 365 for operation MappingExtended data Type /  Base Enum
Order NumberSales Order NumberSalesLine.SalesIdSalesIdBase
Item NumberItem NumberSalesLine.ItemIdItemIdSmall
Item groupItem GroupInventItemGroupItem.ItemGroupIdItemGroupId
Sales PriceSales PriceSalesLine.UnitPriceSalesPrice
Open QtyOrder Line QuantitySalesLine.QtyOrderedInventQty
CustAccountCust AccountSalesline.CustAccountCustAccount
CustNameCustNameCustTable.Name()Name
Date RequestedShip date requestedSalesLine.ShippingDateRequestedSalesShippingDateRequested
Order StatusSales Order Line StatusSalesLine.SalesStatusSalesStatus
  
Now create temp table in Visual studio
  
Now double click on table in solution explorer and open it designer mode in center of screen.
Update its Table Type property to InMemory.
Now create each field and update its extended data type as we created in previous step.
  
Now next step is report parameters. For this purpose we use Data contract class. In current report we required only one parameter base on Extended Data Type CustAccount. At simplest our data contract class will be look like as follow.

[

DataContractAttribute

]

public class AlSaleOrderDC

{

CustAccount custAccount;

 

[

DataMemberAttribute('CustomerAccount')

]

public CustAccount ParmCustAccount(CustAccount _custAccount=custAccount)

{

custAccount = _custAccount;

return custAccount;

 

}

 

}

Now we required to write some logic which you above mention class as its data contract and populate the custom temp table we build in pervious step.
[

SRSReportParameterAttribute(classStr(AlSaleOrderDC))

]

public class AlSaleOrderPD extends SRSReportDataProviderBase

{

AlSaleOrderDC dc;

ALSalesOrderTemp salesOrderTemp;

 

[

SRSReportDataSetAttribute('tempSalesOrderDS')

]

public ALSalesOrderTemp gettempSalesOrderDS()

{

select * from salesOrderTemp;

return salesOrderTemp;

}

 

/// </summary>

public void processReport()

{

CustAccount _CustAccount;

SalesLine _SalesLine;

dc =this.parmDataContract();

 

_CustAccount = dc.ParmCustAccount();

 

while select * from _SalesLine where _SalesLine.CustAccount == _CustAccount

{

salesOrderTemp.SalesId = _SalesLine.SalesId;

salesOrderTemp.CustAccount = _SalesLine.CustAccount;

salesOrderTemp.CustName = CustTable::find(   _SalesLine.CustAccount).name();

salesOrderTemp.SalesPrice = _SalesLine.PriceUnit;

salesOrderTemp.ItemId = _SalesLine.ItemId;

salesOrderTemp.ItemGroupId = InventItemGroupItem::findByItemIdLegalEntity( _SalesLine.ItemId).ItemGroupId;

salesOrderTemp.OpenQty = _SalesLine.QtyOrdered;

salesOrderTemp.DateRequested = _SalesLine.ShippingDateRequested;

salesOrderTemp.SalesStatus = _SalesLine.SalesStatus;

salesOrderTemp.insert();
 

}
 

} 

}
At this step build the solution, never forget to check the project Sychronized database on build set to true.
Now Add new report in project
Double click on Report in solution and open in designer screen. And right click on datasets and create new Data Set.
From new data set update the following properties. Data Source Type to Report Data provider. Change Name to DSSales Order
Click on Query and from dialog select the data provider class we create some above screen.

After Selecting Class, click on next button. From next dialog select field for usage in report.

Now right click on designer node of report and click on precision design.
In precision design, we can create report designer by our need.

New designer will be added, rename it and double click to open it. from left pane, toolbar to drag to table to add in designer.

From Report Data drop dataset fields on table columns. Add new column in table.
When all fields are added in report. Right click outside the report body and click on report property.

And select report print layout. Our target is A4 page. so set it from report properties.

Now right click on report body and check its with. Its width must be under 6 because at run time 1 inch on both side must be skip.
After that right click on report in solution explorer and click on deploy
After the add new display menu Item update the its following properties

Here there is one advancement, In 2012 there is very difficult to debug report. But in Dynamics 365 for operation is its very simple. Just put break point and set report as starting object and run the project.


Now new browser window you find similar form.
Now Customer appear because by default it opens in “DAT” legal entity. Now update the url as follow.

select parameter from customer. click on ok
And my break open hit
So it is very here to debug report.


So our report logic fine, but one step is pending Which is report should be run from Client with particular module.
So I decide to add this report in sales and marketing module and under sale order report . For this purpose we have to create a new menu extension.

New menu extension added in solution explorer.
Expand menu extension in designer and drag display menu in required menu
From property update “Display In Content Area” to true to create show crumb bar.
Now save and compile again the project.
And open client url in Browser
Login and goes in Sales module For identification purpose, I set the label of display menu as “Ali Sale Order”.
Click on it find report parameter dialog

So our report is running successfully.

Tuesday, 19 December 2017

Axapta 7 Introduction

Introduction to  Dynamics 365 



  • The first step is to log into Visual Studio as an Adminstrator
Dev1
Once within Visual Studio, go to the AX7 menu and select Model Management and ‘Create model’. A model is not exactly the same as a layer, but it might be a useful analogy to think of it as being similar to a layer in the sense, it is a way to isolate and manage your customizations
Dev2
Create the model in the form that appears and fill out the fields as appropriate

Dev3
Click ‘Next’ and select ‘Existing package’ and choose ‘Application Suite’

Dev4
Press ‘Next’ – accept the default values suggested and press ‘Finish’


Dev5
In the ‘Save’ screen that appears, select the project DynamicsAX7 and give it a name of your choice and press ‘OK’
Dev6
Notice in the Solution Explorer to the right in the screen a new Project has been created in your newly created Model

Dev7
On the Solution Explorer go to the project and rightclick and select ‘Properties’
Dev8
In here note that the project is in the new Model just created and that the customizations will be saved in the ‘USR’ layer
Dev9
Also, it’s a good idea to specify which company you wish to execute the code in, here ‘DEMF’
Press OK
 Now to add ‘Runnable Class(Job) to the Project do the following. On the project rightclick and select ‘Add’ and then select ‘New Item’
Dev10

In the screen shat appears select ‘Code’ and then ‘Runable Class(Job)’
Dev11
And press ‘Add’
Note that now in the Solution explorer that the Runable Class has been added
Dev12
Doubleclick on the ‘Runable class’ and enter the code editor window and give your job a meaningful name and write your code like you would do any X++ job as known in previous versions
dev12B
Save the project(Ctrl + S) and to compile the job, select ‘Build’ and ‘Build Solution’ from the toolbar
Dev13b

Wait until the output window reports that the build has completed
Dev14


Now in order to run the job, you need to set it as a ‘Startup Object’
Go to the Solution Explorer and on the Job, righclick and select ‘set as Startup Object’
Dev15
Now to execute the job, select ‘Debug’ in the toolbar and then ‘Start without Debugging’
Dev16
And note the output in the browser, Make sure it is Synchronized with Data Base.
DevFinalOutput
And that’s it Axaptians...🔺 !


Sunday, 3 December 2017

TYPES OF QUERY RANGES


BASIC Query Classes 

Query
QueryBuildDataSource
QueryBuildRange
QueryBuildFieldList
QueryBuildLink
QueryBuildDynaLink
   CustTable            custTable;
   Query                query      = new Query();
   QueryRun             qr         = new queryRun(query);
   QueryBuildDataSource qbds  = qr.query().addDataSource(tableNum(CustTable));
   QueryBuildRange      qbrAccN    = qbds.addRange(fieldNum(CustTable,AccountNum));
   QueryBuildRange      qbrCountry = qbds.addRange(fieldNum(CustTable,Country));
   QueryBuildFieldList  qbfl       = qbds.fields();
   ;
   qbrAccN.value('4000..4050');
   qbrAccN.status(RangeStatus::Locked);
   qbrCountry.value('CA..NO');
   qbfl.addField(fieldNum(CustTable,CreditMax),SelectionField::Sum);
   qbfl.addField(fieldnum(CustTable,RecId),SelectionField::Count);
   qbds.addSortField(fieldnum(CustTable,Country));
   qbds.addSortField(fieldNum(CustTable,Currency));
   qbds.orderMode(OrderMode::GroupBy);
   if (qr.prompt())
   {
       while (qr.next())
       {
           custTable = qr.get(tableNum(CustTable));
           print strfmt("%1 %2 %3 (%4 records)",custTable.Country,custTable.Currency,
                    num2str(custTable.CreditMax,10,2,0,0),custTable.RecId);
       }
   }
   pause;
}
*****************************************************************

Query “join table A with table B and table A with table C”

QueryBuildDataSource qbds,qbds1,qbds2;
qbds = query.addDataSource(tableNum(CustTable));
qbds1 = qbds.addDataSource(tableNum(CustTrans));
qbds2 = qbds1.addDataSource(tableNum(CustLedgerTransTypeMapping));
qbds1.addLink(fieldNum(CustTrans,AccountNum),fieldNum(CustTable,AccountNum));
qbds2.addLink(fieldNum(CustTrans,TransType),fieldNum(CustLedgerTransTypeMapping,CustSettleTransType));
info(query.toString());
*********************************************************************************************************************************
JOIN QUERY WITH X++
qbds1 = query.addDataSource(tablenum(Table1));

qbds2 = qbds1.addDataSource(tablenum(Table2));
qbds2.relations(true);

qbds3 = qbds2.addDataSource(tablenum(Table3));
qbds3.relations(true); 

qbds4 = qbds3.addDataSource(tablenum(Table4));
qbds4.relations(true);

qbds5 = qbds4.addDataSource(tablenum(Table5));
qbds5.relations(true);

qbds6 = qbds5.addDataSource(tablenum(Table6));
qbds6.relations(true);
qbds6.joinMode(JoinMode::ExistsJoin);
*****************************************************************************************************************************
JOINS IN X++ With EXAMPLE: 
    Query                                    query = new Query();
    QueryBuildDataSource     queryBuildDSSalesTable;
    QueryBuildDataSource     queryBuildDSSalesLine;
    QueryBuildRange              queryBuildRange;
    QueryRun                            queryRun;
    SalesLine                            salesLine;
    ;
    
 // Setup the primary datasource.
    queryBuildDSSalesTable  = query.addDataSource(tablenum(SalesTable));
    queryBuildDSSalesTable.addRange(fieldnum(SalesTable,SalesId)).value('XXXXXX');
// Setup the secondary (joined) datasource.
 queryBuildDSSalesLine   = queryBuildDSSalesTable.addDataSource(tablenum(SalesLine));
 queryBuildDSSalesLine.joinMode(JoinMode::InnerJoin);
 queryBuildDSSalesLine.relations(true);
 queryRun = new QueryRun(query);
    while(queryRun.next())
    {
        salesLine   = queryRun.get(tablenum(SalesLine));
        info(strfmt('%1',salesLine.SalesId));
    }
***************************************************************************************************************
In the example below, we construct a query and add a single datasource.
The range is then added, using the DataAreaId field on each table. Any field can be used, but using an unusual one such as DataAreaId helps remind a casual reader of the code that it's not a normal range.

query = new Query();
dsInventTable = query.addDataSource(tableNum(InventTable));
 
// Add our range
queryBuildRange = dsInventTable.addRange(fieldNum(InventTable, DataAreaId));
Given the above, the following are valid range specifications:

 

Simple criteria

Find the record where ItemId is B-R14. Take note of the single quotes and parenthesis surrounding the entire expression.
queryBuildRange.value(strFmt('(ItemId == "%1")', queryValue("B-R14")));
Find records where the ItemType is Service. Note the use of any2int().
queryBuildRange.value(strFmt('(ItemType == %1)', any2int(ItemType::Service)));
Find records where the ItemType is Service or the ItemId is B-R14. Note the nesting of the parenthesis in this example.
queryBuildRange.value(strFmt('((ItemType == %1) || (ItemId == "%2"))', 
    any2int(ItemType::Service),
    queryValue("B-R14")));
Find records where the modified date is after 1st January 2000. Note the use of Date2StrXpp() to format the date correctly.
queryBuildRange.value(strFmt('(ModifiedDate > %1)', Date2StrXpp(01012000)));
Find records where the Field is blank (null) or an empty string. For more see Sys::Query Docs
qbrStatement = this.query().dataSourceName("BankAccountTrans2").addRange(fieldnum(BankAccountTrans,AccountStatement));
//qbrStatement.value("!?*");//this is the old way that may not work in future versions of AX
qbrStatement.value(sysquery::valueEmptyString());//this is the new way 

 

Complex criteria with combined AND and OR clauses

Find all records where the ItemType is Service, or both the ItemType is Item and the ProjCategoryId is Spares. This is not possible to achieve using the standard range syntax.
Note also that in this example, we are using the fieldStr() method to specify our actual field names and again, that we have nested our parenthesis for each sub-expression.
queryBuildRange.value(strFmt('((%1 == %2) || ((%1 == %3) && (%4 == "%5")))',
    fieldStr(InventTable, ItemType),
    any2int(ItemType::Service),
    any2int(ItemType::Item),
    fieldStr(InventTable, ProjCategoryId),
    queryValue("Spares")));

 

WHERE clauses referencing fields from multiple tables

For this example below, we construct a query consisting of two joined datasources (using an Exists join). Note that we specify the datasource names when adding the datasources to the query.
The ranges are then added, using the DataAreaId field on each table as described in the earlier example.
query = new Query();
dsInventTable = query.addDataSource(tableNum(InventTable), tableStr(InventTable));
dsInventItemBarCode = dsInventTable.addDataSource(tableNum(InventItemBarCode), tableStr(InventItemBarCode));
dsInventItemBarCode.relations(true);
dsInventItemBarCode.joinMode(JoinMode::ExistsJoin);
 
// Add our two ranges
queryBuildRange1 = dsInventTable.addRange(fieldNum(InventTable, DataAreaId));
queryBuildRange2 = dsInventItemBarCode.addRange(fieldNum(InventItemBarCode, DataAreaId));
Find all records where a bar code record exists for an item and was modified later than the item was modified.
In this example, we are using the range on the BarCode table. Therefore the unqualified ModifiedDate reference will relate to InventItemBarCode.ModifiedDate. The other field is a fully-qualified one, using the DatasourceName.FieldName syntax.
queryBuildRange2.value(strFmt('(ModifiedDate > InventTable.ModifiedDate)'));
Note that if we had added our InventTable datasource using the following code
dsInventTable = query.addDataSource(tableNum(InventTable), "InventTableCustomName"); // Note that we are manually specifying a different datasource name 
then the query range would need to appear as follows
queryBuildRange2.value(strFmt('(ModifiedDate > InventTableCustomName.ModifiedDate)'));

 

Conditional joins

We will modify our previous example slightly, to remove the automatic addition of relations for the join.
query = new Query();
dsInventTable = query.addDataSource(tableNum(InventTable), "InventTable");
dsInventItemBarCode = dsInventTable.addDataSource(tableNum(InventItemBarCode), "InventItemBarCode");
dsInventItemBarCode.joinMode(JoinMode::ExistsJoin);
 
// Add our two ranges
queryBuildRange1 = dsInventTable.addRange(fieldNum(InventTable, DataAreaId));
queryBuildRange2 = dsInventItemBarCode.addRange(fieldNum(InventItemBarCode, DataAreaId));
We can now use the query expression to specify whatever we like as the join criteria.
Find all records where either the ItemType is Service, or the ItemType is Item and a barcode exists. The join criteria is only applied in the second half of the expression, so all Service items will appear irrespective of whether they have a bar code. Again, this is not possible to achieve using the standard query ranges.
queryBuildRange2.value(strFmt('((%1.%2 == %3) || ((%1.%2 == %4) && (%1.%5 == %6)))',
    query.dataSourceTable(tableNum(InventTable)).name(), // InventTable %1
    fieldStr(InventTable, ItemType), // ItemType %2
    any2int(ItemType::Service), // %3
    any2int(ItemType::Item), // %4
    fieldStr(InventTable, ItemId), // ItemId %5
    fieldStr(InventItemBarCode, ItemId))); // %6 
Using the techniques above, it is possible to create queries with almost as much flexibility as using SQL statements directly.
Filter on array fields
queryBuildRange.value(strFmt('((%1.%2 == "%4") || (%1.%3 == "%5"))', 
    queryBuildDataSource.name(),
    fieldid2name(tablenum(<table>), fieldid2ext(fieldnum(<table>, Dimension), Dimensions::code2ArrayIdx(SysDimension::Center))), 
    fieldid2name(tablenum(<table>), fieldid2ext(fieldnum(<table>, Dimension), Dimensions::code2ArrayIdx(SysDimension::Purpose))), 
    "some dim2 value", 
    "some dim3 value"));
Note: you must always specify the datasource name if you use Query Expression syntax to filter on array fields. See also Limitations section at the bottom of the page.

 

Using wildcards and comma-separated range values

Again, the previous example here was using standard syntax, not the special syntax using expressions. It's not possible to modify the above examples to work with wildcards.
The above statement applies to AX versions < 5.0
AX 5.0 introduced solution to wildcards - while you still cannot directly use wildcards in ranges, now it supports the 'LIKE' keyword.
(AccountNum LIKE "*AA*" || Name LIKE "*AA*")


Query With Enum:
qbds.addRange(fieldNum(EIRTenderRecordTable, Status))
        .value(queryValue(EIRStatus::Active));
qbds.addRange(fieldNum(EIRTenderRecordTable, StartDate))
        .value(SysQuery::range(_requisitionDate, dateNull()));
qbds.addRange(fieldNum(EIRTenderRecordTable, EndDate))
        .value(SysQuery::range(dateNull(), _requisitionDate));

8 Common Errors in Dual-write

502 Bad Gateway : when the target application is not ready to handle the incoming request  401 Unauthorized / 403 Forbidden: This error occ...