How to Add Additional Data to a Docentric SSRS Report
If you want to add some additional data to an existing SSRS report, you can do it in the standard way: add new fields to the built-in SSRS report RDP table(s), and implement business logic within the built-in SSRS report data provider class (e.g. PurchPurchaseOrderDP for the Purchase order report).
The newly added fields will be visible immediately in the regenerated report DDSP (Data Source Package) file, so you can use them to customize your report further with Docentric designer.
With this approach Docentric AX Framework always uses the default DSP class for Docentric SSRS reports (DocDataSourceProviderSrsReporting), which automatically builds the DDSP file from report RDP tables filled by SSRS data provider class.
We encourage you to use this standard approach for a particular report if you plan to switch between Docentric and SSRS designs in the same AX project. Sometimes this can be a better approach in terms of performance as well.
Docentric approach
The alternative way of adding additional data to an existing SSRS report is to add them without modifying the built-in SSRS report artifacts (RDP tables, DP class) through the code in a custom Docentric DSP class for this report. This class extends DocDataSourceProviderSrsReporting, a base DSP class for Docentric SSRS reports, which is used for all Docentric SSRS reports by default. In this tutorial you will learn about this technique.
For example, you can easily add a sub-line for each line of a quote, or any other entity. Otherwise, you would need to create and fill an additional RDP table. You can also structure additional (and existing) data into meaningful groups, exclude unnecessary data, etc.
A custom Docentric DSP class for an SSRS report, which is registered to run as Docentric report, can be created with the help of the wizard, and afterwards your task will be to append additional data needed for the report customization through the X++ code.
In this custom Docentric DSP class we will need to extend/override certain methods. Methods to be extended/overridden depend on the type of the underlying SSRS report:
For data provider-based reports extend/override one or both of these methods:
- addDataFieldsForRdpTableRecord()
- generateXmlDataSource()
Different scenarios for data provider-based reports are described below in this manual.
For query-based reports extend/override one or both of these methods:
- addAdditionalDataFieldsForQueryRecord()
- onSelectedQueryRecord()
Extending the base addDataFieldsForRdpTableRecord() method
This method is called each time when a RDP table record is selected and added to a report data source being built by Docentric AX Framework. If you override or extend this method with a custom DSP class for your report, you will get a chance to include additional data records and fields by selecting related data from AX tables likewise to exclude unnecessary fields from the currently adding data record.
Override this method (without calling super()) in order to exclude particular fields from the currently adding record of an RDP (report data provider) table, and also to add additional data to this record.
Extend this method (by calling super() first) in order to add additional data to the currently adding record, or to change names/labels of particular fields or adding record itself.
Scenario: Add additional data fields
Extend the addDataFieldsForRdpTableRecord() method in order to add some additional data fields to the currently adding record of a report RDP table. Let’s say that we want to add to Purchase packing slip header some additional data, e.g. Purchase status of the corresponding purchase order.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
protected void addDataFieldsForRdpTableRecord(DocXmlRecord _addingRecord, Common _rdpTableRecord, TableName _rdpTableName) { PurchTable purchTable; PurchPackingSlipHeaderTmp purchPackingSlipHeader; super(_addingRecord, _rdpTableRecord, _rdpTableName); if (_rdpTableName == tableStr(PurchPackingSlipHeaderTmp)) { // Add a new calculated field from the related purchase table. purchPackingSlipHeader = _rdpTableRecord; purchTable = purchTable::find(purchPackingSlipHeader.PurchId); _addingRecord.addCalculatedFieldFromEnum( 'PurchaseStatus_NewField', enumStr(PurchStatus), purchTable.PurchStatus); } } |
Scenario: Exclude unnecessary data fields
Override the addDataFieldsForRdpTableRecord() method in order to exclude some unnecessary data fields, for the sake of better design experience and also performance. For example, we will remove all company related data from Purchase packing slip header since we already have them in the GeneralData data section at our disposal.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
protected void addDataFieldsForRdpTableRecord(DocXmlRecord _addingRecord, Common _rdpTableRecord, TableName _rdpTableName) { // super(_addingRecord, _rdpTableRecord, _rdpTableName); - don't call super() if (_rdpTableName == tableStr(PurchPackingSlipHeaderTmp)) { // Add all fields from the current table buffer except those related to the company. _addingRecord.addAllFieldsExcept([ fieldStr(PurchPackingSlipHeaderTmp, CompanyAddress), fieldStr(PurchPackingSlipHeaderTmp, CompanyEmail), fieldStr(PurchPackingSlipHeaderTmp, CompanyLogo), fieldStr(PurchPackingSlipHeaderTmp, CompanyName), fieldStr(PurchPackingSlipHeaderTmp, CompanyPhone), fieldStr(PurchPackingSlipHeaderTmp, CompanyTeleFax) ]); } } |
Scenario: Rename data records and fields
Extend the addDataFieldsForRdpTableRecord() method in order to rename the adding record or particular data fields. Usually we want to do this if the built-in names are not enough descriptive, e.g. for end users. In the following example we renamed the PurchPackingSlipTmp RDP table representing Purchase packing slip line to the more meaningful name - PurchPackingSlipLine.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
protected void addDataFieldsForRdpTableRecord(DocXmlRecord _addingRecord, Common _rdpTableRecord, TableName _rdpTableName) { DocXmlField field; super(_addingRecord, _rdpTableRecord, _rdpTableName); if (_rdpTableName == tableStr(PurchPackingSlipTmp)) { // Rename the record PurchPackingSlipTmp -> PurchPackingSlipLine. _addingRecord.setRecordName('PurchPackingSlipLine'); // Change the record label. _addingRecord.setRecordLabelId(literalStr("@SYS340636")); // Rename the field ExternalItemNum -> ExternalItemNum_NewName. field = _addingRecord.fields().lookup(fieldStr(PurchPackingSlipTmp, ExternalItemNum)); field.setFieldName('ExternalItemNum_NewName'); // Change the field label. field.setFieldLabelId(literalStr("@SYS54845")); } } |
Scenario: Add additional data records
Extend the addDataFieldsForRdpTableRecord() method in order to add additional data record to the currently adding record of the report RDP table as a child record. We don’t have to introduce a new RDP table for that purpose. All it takes is to use Docentric AX APIs such as the addChildRecord() and addChildCalculatedRecord() methods to specify needed data.
Here we are adding to each Purchase packing slip line record the related Purchase order record as a child, including the Title fields and the Default dimension field, and another child record that represents some calculated data.
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 |
protected void addDataFieldsForRdpTableRecord(DocXmlRecord _addingRecord, Common _rdpTableRecord, TableName _rdpTableName) { PurchTable purchTable; DocXmlRecord childRecord; PurchPackingSlipTmp purchPackingSlipLine; super(_addingRecord, _rdpTableRecord, _rdpTableName); if (_rdpTableName == tableStr(PurchPackingSlipTmp)) { // Add additional child record based on a table buffer. purchPackingSlipLine = _rdpTableRecord; purchTable = purchTable::find(purchPackingSlipLine.PurchId); _addingRecord.addChildRecord(purchTable) .addTitleFields() .addField(fieldStr(PurchTable, DefaultDimension)); // Add another child record that is calculated. childRecord = _addingRecord.addChildCalculatedRecord('SomeAdditionalData'); childRecord.addCalculatedField('NewField_Str', 'abc'); childRecord.addCalculatedField('NewField_Int', 123); childRecord.addCalculatedField('NewField_Date', systemDateGet()); } } |
You can check the result for previous scenarios here:
Learn more about Docentric AX APIs >>
Download Resources for Purchase Order Product Receipt Example >>
Extending the base generateXmlDataSource() method
This method is called after a report data source is built by using built-in SSRS RDP classes and tables by Docentric AX Framework. If you extend this method with a custom DSP class for your report, you will get a chance to append additional data records and fields by selecting related data from AX tables. However, you will not be able to change, e.g. exclude unnecessary fields from the report data source. On the other hand, if you override this method, you will be completely in charge how your report data source will look like.
Extend this method (by calling super() first) in order to append additional data to the report data source. For Print Management reports extend this method in the combination with the onSelectedRdpTableRecord() method.
Override this method (without calling super()) when you develop a new custom SSRS report from scratch. Override this method also when you need to change the structure/relations of the report RDP tables, or to exclude unnecessary fields or to include additional data (fields or tables) to each record of a report RDP table in the more performant way.
Implement the generateXmlDataSource() method instead of the addDataFieldsForRdpTableRecord() method when:
- Develop a new custom SSRS report from scratch that does not have (and not need to have) RDP tables defined.
- Append some additional data to a report data source for Non Print Management and other non pre-processed reports.
- To append additional data to a report data source for Print Management and other pre-processed reports use this method in the combination with the onSelectedRdpTableRecord() method.
- Want to gain more control and completely reshape the report data source.
- Want to achieve the same thing as with the addDataFieldsForRdpTableRecord() method but in the more performant way.
Scenario: New custom SSRS report developed from scratch
Override (re-implement) the whole generateXmlDataSource() method in order to define/add all data needed for a new custom SSRS report developed from scratch that has no RDP tables defined.
Let’s say that we need to develop a new report showing all purchase orders that are invoiced in the given period. We need to create a report menu item and also a dummy SSRS provider with no X++ logic. No temporary tables are needed. We only have to create a report data contract class because of the report parameters. After the wizard generates a custom Docentric DSP class for our new report, we need to override its generateXmlDataSource() method in order to define the report data by using Docentric AX APIs.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
protected void generateXmlDataSource(DocXmlRecordBuilder _recordBuilder) { PurchTable purchTable; DocDummySSSRContract reportContract = this.getSrsRdpContract(); TransDate fromDate = reportContract.parmFromDate(); TransDate toDate = reportContract.parmToDate(); // Add all purch orders that are invoiced in the given interval. while select purchTable where purchTable.PurchStatus == PurchStatus::Invoiced && (fromDate == dateNull() || purchTable.DeliveryDate >= fromDate) && (toDate == dateNull() || purchTable.DeliveryDate <= toDate) { _recordBuilder.addRecordWithAllFields(purchTable); _recordBuilder.addDisplayMethod(tableMethodStr(PurchTable, amountInvoiced)); } // Show the data source (i.e. the MainData data section) to check if everything is OK. //info(_recordBuilder.exportToXmlStr()); } |
The data source for our new report we defined above looks like the following:
Walkthrough of New Report From Scratch Example >>
Download Resources for New Report From Scratch Example >>
Scenario: Append additional data to a report data source for Non Print Management reports
Extend the generateXmlDataSource() method in order to append some additional data to a report data source for Non Print Management and other non pre-processed reports. We can access to the running instance of the report RDP class and retrieve the RDP table buffers containing the current report data by using the getSrsRdpClassRunningInstance() method. We can also access the report RDP data contract and retrieve the report parameters by using the getSrsRdpContract() method.
Let's say that we want to append the info about maximum number of workers of all existing departments to the Human resources -> Reports -> Workers -> Number of workers report.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
protected void generateXmlDataSource(DocXmlRecordBuilder _recordBuilder) { HcmNumberOfWorkersDP rdp = this.getSrsRdpClassRunningInstance(); HcmNumberOfWorkersTmp rdpTable = rdp.getDepartmentDetails(); // NOTE: This way we can access the report contract and read the report parameters. HcmNumberOfWorkersContract rdpDataContract = this.getSrsRdpContract(); int maxNumberOfWorkers; // In the base implementation of the method the report data set(s) are built // by using the built-in SSRS RDP class and tables. super(_recordBuilder); // Calculate maximum number of workers of all departments // excluding the Grand Total line. select maxOf(NumberOfWorkers) from rdpTable where rdpTable.DepartmentName != ''; maxNumberOfWorkers = rdpTable.NumberOfWorkers; // Append this info as an additional data field to the report data source. _recordBuilder.addCalculatedField('MaxNumberOfWorkers', maxNumberOfWorkers); // Show the data source (i.e. the MainData data section) to check if everything is OK. //info(_recordBuilder.exportToXmlStr()); } |
Download resources for this example and try to do it by yourself >>
Scenario: Append additional data to a report data source for Print Management reports
You can extend the generateXmlDataSource() method in order to append some additional data to a report data source for Print Management and other pre-processed reports but with no direct access to the report RDP tables through the getSrsRdpClassRunningInstance() method, as above. We would use the SrsReportPreProcessedDetails table to retrieve pre-processed report data - please see the next scenario.
However, we can use the generateXmlDataSource() method in the combination with the onSelectedRdpTableRecord() method which is invoked each time when a RDP table record is selected during the building of report data source by the base DSP class for Docentric SSRS reports. Thus, we need to implement the onSelectedRdpTableRecord() method in order to store particular key values in global class variables and then use them in the generateXmlDataSource() method to select the related data we want to append to the report data source.
Let's see how it works on the Purchase order report example, when we want to append, e.g. the related vendor's VAT number.
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 |
protected void onSelectedRdpTableRecord(Common _rdpTableRecord, TableName _rdpTableName) { PurchPurchaseOrderHeader purchPurchaseOrderHeader; // Build the report data source by using the built-in SSRS RDP classes and tables. super(_rdpTableRecord, _rdpTableName); // Store PurchId into a class global variable to use it in this.generateXmlDataSource() // method in order to add additional data related to the printing purchase order. if (tableStr(PurchPurchaseOrderHeader) == _rdpTableName) { purchPurchaseOrderHeader = _rdpTableRecord; purchId = purchPurchaseOrderHeader.PurchId; } } protected void generateXmlDataSource(DocXmlRecordBuilder _recordBuilder) { PurchTable purchTable; // Build the report data source by using the built-in SSRS RDP classes and tables. super(_recordBuilder); // Append some additional data. // Use PurchId stored in the onSelectedRdpTableRecord() method to locate the printing purchase order. purchTable = PurchTable::find(purchId); _recordBuilder.addCalculatedField('VendorVAT', VendTable::find(purchTable.OrderAccount).VATNum, literalStr("@SYS4082676")); // Show the data source (i.e. the MainData data section) to check if everything is OK. //info(_recordBuilder.exportToXmlStr()); } |
See in the walkthrough exactly how to do it >>
Download Resources for Purchase Order Example >>
Scenario: Reshape report data source
Override (re-implement) the whole generateXmlDataSource() method when you need to re-structure the existing report data sets, or to add additional data to each record of a report data set in the more performant way than using the addDataFieldsForRdpTableRecord() method.
Re-implementing the generateXmlDataSource() method for a Print Management (or a pre-processed) SSRS report:
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 |
protected void generateXmlDataSource(DocXmlRecordBuilder _recordBuilder) { PurchPurchaseOrderHeader purchOrderHeader; PurchPurchaseOrderTmp purchOrderLine; // NOTE: This way we can access the report contract and read the report parameters. PurchPurchaseOrderContract purchOrderDc = this.getSrsRdpContract(); SrsReportPreProcessedDetails srsReportPreProcessedDetails; PurchTable purchTable; DocXmlRecord purchTableAdditionalData; // Retrieve data from pre-processed report RDP tables using the srsReportPreProcessedDetails table. select firstOnly purchOrderHeader exists join CreatedTransactionId from srsReportPreProcessedDetails where srsReportPreProcessedDetails.RecId == preProcessedId && purchOrderHeader.CreatedTransactionId == srsReportPreProcessedDetails.CreatedTransactionId; purchOrderHeaderRecord = _recordBuilder.addRecordWithAllFields(purchOrderHeader); // Rename the purchOrderHeader record. purchOrderHeaderRecord.setRecordName('PurchaseOrder'); purchOrderHeaderRecord.setRecordLabelId(literalStr("@SYS16574")); // Re-structure the existing report data sets by adding purchase lines as child data records. while select purchOrderLine exists join CreatedTransactionId from srsReportPreProcessedDetails where srsReportPreProcessedDetails.RecId == preProcessedId && purchOrderLine.CreatedTransactionId == srsReportPreProcessedDetails.CreatedTransactionId { purchOrderLineRecord = _recordBuilder.addRecordWithAllFields(purchOrderLine); // Rename the purchOrderLine record. purchOrderLineRecord.setRecordName('PurchaseOrderLines'); purchOrderLineRecord.setRecordLabelId(literalStr("@SYS9664")); _recordBuilder.goToParentRecord(); } // Show the data source (i.e. the MainData data section) to check if everything is OK. //info(_recordBuilder.exportToXmlStr()); } |
Re-implementing the generateXmlDataSource() method for a non Print Management (or a non pre-processed) SSRS report:
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 |
protected void generateXmlDataSource(DocXmlRecordBuilder _recordBuilder) { HcmBirthdayDP hcmBirthdayRdp = this.getSrsRdpClassRunningInstance(); HcmBirthdayTmp hcmBirthdayDs = hcmBirthdayRdp.getHcmBirthdayTmp(); HcmBirthdayContract hcmBirthdayDc = this.getSrsRdpContract(); // Use RDP data contract to access the report parameters: TransDate parameterToDate = hcmBirthdayDc.parmToDate(); date newFieldBirthdayDate; while select hcmBirthdayDs { // Add all existing fields from the report data set. _recordBuilder.addRecordWithAllFields(hcmBirthdayDs); // Add the additional BirthdayDate field. newFieldBirthdayDate = mkdate(dayOfMth(hcmBirthdayDs.BirthDate), mthOfYr(hcmBirthdayDs.BirthDate), year(parameterToDate)); _recordBuilder.addCalculatedField('BirthdayDate', newFieldBirthdayDate); } // Show the data source (i.e. the MainData data section) to check if everything is OK. //info(_recordBuilder.exportToXmlStr()); } |
See in the walkthrough exactly how to do it >>
Download Resources for Birthdays Example >>
Download Resources for Reshaped Purchase Order Example >>
See also
Learn more about Docentric AX APIs >>
SSRS Report Examples >>
How to Develop a New SSRS Report >>