For Instance, We Created Two Tables.
- Student
- Student Attendance
Then Create a relation as per your requirement. I am using Normal relations.
Student
For Instance, We Created Two Tables.
Let’s say that we want to develop a new report that will show the data from a selected sales agreement but instead of using the existing SSRS report for Sales agreement confirmation (AgreementConfirmation.Report), we will create a completely new one from scratch, which will look like a real-world agreement document. We will name this report as Sales agreement document.
Sales agreement document should contain the data from the source sales agreement’s header, a list of sold items but also a list of the customer’s contacts.
The report doesn’t have any visible parameters since it should always run in the context of a single sales agreement. Thus, the only parameter (which is hidden) is RecId of the currently selected sales agreement.
The requirements regarding printing the report: a user can preview the report by clicking a menu item placed directly on the Sales agreement form. Additional menu item that opens the Print destination settings form should also be introduced in order to enable emailing the report or sending it to a particular printer.
We are going to create the following artifacts:
For this report the data are divided into three data sets: Header data, Lines data and Customer contacts data, so we need to create three temporary tables.
Create temporary tables by clicking Add > New Items > Dynamics 365 Items > Data Model > Table:
Add the following three temporary tables:
Set the TableType property to InMemory for each table. This way the tables will act as temporary tables.
We will now add all needed fields to our temporary tables.
In order to add a new class, go to Add > New Items > Dynamics 365 Items > Code > Class.
We are going to create three classes here at once:
A Controller class orchestrates the report execution. In our case, the controller will handle whether the report dialog form should open, set up the SSRS report design and the value of the hidden parameter. Each controller class should extend SrsReportRunController (SrsPrintMgmtController or SrsPrintMgmtFormLetterController for Print Management reports).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | class DocAgreementDocumentController extends SrsReportRunController { public static DocAgreementDocumentController construct() { return new DocAgreementDocumentController(); } public static void main(Args _args) { DocAgreementDocumentController docAgreementDocumentController = DocAgreementDocumentController::construct(); docAgreementDocumentController.parmReportName(ssrsReportStr(DocAgreementDocument, Report)); docAgreementDocumentController.parmArgs(_args); docAgreementDocumentController.parmDialogCaption("Sales agreement document"); docAgreementDocumentController.startOperation(); } protected void prePromptModifyContract() { DocAgreementDocumentContract contract = this.parmReportContract().parmRdpContract(); // Set the hidden parameter value. contract.parmSalesAgreementRecId(args.record().RecId); // Set the report design name. this.parmReportContract().parmReportName(ssrsReportStr(DocAgreementDocument, Report)); boolean isPreview = args.menuItemName() == menuItemOutputStr(DocAgreementDocumentPreview); // Set the target print destination to Screen if we are previewing the report. if (isPreview) { this.parmReportContract().parmPrintSettings().printMediumType(SRSPrintMediumType::Screen); } // Don't show the report dialog if the report menu item is DocAgreementDocumentPreview // and don't save to SysLastValue either. this.parmShowDialog(!isPreview); this.parmLoadFromSysLastValue(!isPreview); } } |
A Data Contract class defines and stores the values of the report parameters. In our case, we will have one hidden parameter: Sales agreement’s RecId. Note that each Data Provider class is decorated with the DataContractAttribute attribute, and each of the methods representing a particular report parameter is decorated with the DataMemberAttribute attribute.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | [DataContractAttribute] public class DocAgreementDocumentContract { RecId salesAgreementRecId; [ DataMemberAttribute('SalesAgreementRecId'), SysOperationLabelAttribute(literalStr("@SYS190134")), SysOperationControlVisibilityAttribute(false) ] public RecId parmSalesAgreementRecId(RecId _salesAgreementRecId = salesAgreementRecId) { salesAgreementRecId = _salesAgreementRecId; return salesAgreementRecId; } } |
A Data Provider class should extend SRSReportDataProviderBase (or SrsReportDataProviderPreProcess for pre-processed reports) and implement at least the processReport() method. Each Data Provider class is decorated with the SRSReportParameterAttribute attribute that points to a particular Data Contract class, and optionally with the SRSReportQueryAttribute attribute that points to a dynamic query if such exists. Additionally, for each of the data sets a method decorated with the SRSReportDataSetAttribute attribute should be implemented.
The processReport() method is called by Reporting Services to fetch the report data and fill the temporary tables. The same temporary tables should be exposed via methods decorated with the SRSReportDataSetAttribute attribute, for example:
[SRSReportDataSetAttribute(tablestr(DocAgreementHeader))]
public DocAgreementHeader getDocAgreementHeader()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | [SRSReportParameterAttribute(classStr(DocAgreementDocumentContract))] class DocAgreementDocumentDP extends SRSReportDataProviderBase { // Contains Agreement header data. DocAgreementHeader docAgreementHeader; // Contains Agreement lines data. DocAgreementLine docAgreementLine; // Contains Agreement customer contact data. DocAgreementContact docAgreementContact; [SRSReportDataSetAttribute(tablestr(DocAgreementHeader))] public DocAgreementHeader getDocAgreementHeader() { select docAgreementHeader; return docAgreementHeader; } [SRSReportDataSetAttribute(tablestr(DocAgreementLine))] public DocAgreementLine getDocAgreementLine() { select docAgreementLine; return docAgreementLine; } [SRSReportDataSetAttribute(tablestr(DocAgreementContact))] public DocAgreementContact getDocAgreementContact() { select docAgreementContact; return docAgreementContact; } /// <summary> /// Process report data. /// </summary> public void processReport() { DocAgreementDocumentContract contract; SalesAgreementHeader salesAgreementHeader; contract = this.parmDataContract() as DocAgreementDocumentContract; // Get sales agreement header record from the contract parameter salesAgreementHeader = SalesAgreementHeader::find(contract.parmSalesAgreementRecId()); if (salesAgreementHeader) { // Populate sales agreement header this.populateSalesAgreementHeader(salesAgreementHeader); // Populate sales agreement line this.populateSalesAgreementLine(salesAgreementHeader); // Populate sales agreement customer contacts this.populateSalesAgreementCustomerContact(salesAgreementHeader); } } ... } |
To add a new SSRS report, go to Add > New Items > Dynamics 365 Items > Reports > Report.
Now add a dataset to the report and set its Data Source Type property to Report Data Provider, then click the small button available on the Query property. The list of all DP classes available in the AOT will be shown; locate and select our DocAgreementDocumentDP class > DocAgreementTable. Set the properties as shown on the image below.
Now repeat the same procedure for two more data sets – for sales agreement lines and customer contacts.
Add a new precision design; a standard SSRS designer will open. You can see the Report Items toolbar from where you can drag and drop the controls onto the design surface.
Now we will design the report.
You can use placeholders by clicking on Expression or selecting and right-clicking it; in both cases Placeholder window will open.
This is how it looks when an expression uses a placeholder:
And this is the same expression without any placeholder:
Create a new extension for the SalesAgreement form for adding two new menu items. First of them (Preview document) will be used for opening the report on Screen and the second one (Print document) should open the report dialog form, where a user can select the target print destination.
Navigate to Accounts receivable > Orders > Sales agreements and open any sales agreement. Click Preview document to see the result.
This report took us around 12-15 hours to develop. The most time we spent designing the report.
Below is what we found to be the main drawbacks:
This Sales Agreement (this "Agreement") is entered as of the & Fields!EffectiveDate.Value.