Let’s say that we want to print purchase orders with all corresponding product specifications appended at the end of output document generated in PDF format. We will assume that product specifications are PDF documents stored as Attachments (Document management) of built-in Products in D365FO. In this article we will provide an X++ solution for achieving this scenario in D365FO, which is based on Docentric’s capability of merging PDF documents.
This solution has the following limitations:
- Output report format has to be PDF.
- Only Product Attachments in PDF format will be appended to the Purchase order report. If there are no such Attachments for a product item from purchase order lines, no Attachment will be appended for this product.
- Resulting report with the appended Product Attachments can’t be sent to the Printer print destination, because it requires documents in EMF format, and converting PDF to EMF format isn’t currently supported. All other Docentric print destinations are supported (Screen, File, Print archive, Email and also Printer with the Print as PDF option turned on).
Setting up Product Attachments
In order to be able to test the solution, we first need to add PDF product specifications as Attachments to some products in D365FO. Open Modules > Product information management > Products and attach two PDF specifications to two different products.
We prepared two PDF documents and attached them to the products with item number A0001 and A0002 respectively.
The Coding Part
Custom code is required to implement our scenario. You can use the code below right away, build it and everything will just work!
[SubscribesTo(classStr(DocReportRunDelegates), delegateStr(DocReportRunDelegates, generateReportContent))]
public static void DocReportRunDelegates_generateReportContent(DocPrintReportSettings _printReportSettings,
DocPrintedReport_printedReport, DocEventHandlerResult _result)
if (_printReportSettings.parmReportId() != ssrsReportStr(PurchPurchaseOrder, Report))
if (_printedReport.parmDocentricOutputFileFormat() != DocOutputFileFormat::PDF)
if (_printedReport.parmSrsOutputFileFormat() != SRSReportFileFormat::PDF)
List attachmentsList = new List(Types::AnyType);
MemoryStream reportMs = _printedReport.getReportContentMemoryStream();
// Get the PurchTable record, based on the VendPurchOrderJour information
purchTable = VendPurchOrderJour::findRecId(
// Or you can get it this way:
//purchTable = PurchTable::find(_printReportSettings.parmArchiveContract().parmSourceTableId());
// Iterate through the products related to the purchase order lines.
// This way we will include each product only once, if some of them are repeating on multiple PO lines.
while select RecId, TableId, DisplayProductNumber from product
exists join inventTable where inventTable.Product == product.RecId
exists join purchLine where purchLine.ItemId == inventTable.ItemId &&
purchLine.PurchId == purchTable.PurchId
// Fetch Attachments for the current product.
while select docuRef where
docuRef.RefTableId == product.TableId &&
docuRef.RefRecId == product.RecId
exists join docuType
where docuType.TypeId == docuRef.TypeId &&
(docutype.TypeGroup == DocuTypeGroup::File ||
docuType.TypeGroup == DocuTypeGroup::Image ||
docuType.TypeGroup == DocuTypeGroup::Document)
DocuValue docuValue = docuRef.docuValue();
if (docuValue.FileType != 'pdf') // Only PDF attachments are allowed.
// Append each PDF Attachment to attachmentsList.
info("Attachment for item id: " + product.DisplayProductNumber + " --> " + docuRef.Name);
using (Stream docuRefStream = DocumentManagement::getAttachmentStream(docuRef))
using (MemoryStream attachmentMs = new MemoryStream())
strFmt("Error while fetching Attachment (%1)", docuRef.Name);
strFmt("Error while fetching Attachment (%1)", docuRef.Name);
// Add the generated report to the attachmentsList.
// Create the merged PDF file from the documents stored in the list attachmentsList.
using (MemoryStream mergedPDFStream = DocDocumentHelper::mergePdfDocuments(attachmentsList))
// Uncomment the next line to download the resulting PDF file to the browser
The Code Explanation
We have to introduce a new class (we created DocMergePOReportWithItemAttachmentsPDF in our example) in the model where our reporting customizations are made.
This class contains a delegate method (DocReportRunDelegates_generateReportContent) which is executed whenever the report content is generated using any Docentric print destination, no matter if you are using a SSRS report design or a Docentric template. First some checks are performed, to make sure that the printing report is Purchase order, and that it is printed in the PDF output format.
Next, the PurchTable table record is fetched using the VendPurchOrderJour journal record. Having the purchase order record, we can now iterate through all the products that the purchase order lines point to. Next, another iteration takes place to collect all PDF attachments of the current product. At the end of the method, all the attachments are merged with the printed report, which is then sent to the selected print destination.
The image below shows what we get if we select Screen print destination.
Once we close the viewer form, we see the info messages, telling us what Attachments were appended to the printed purchase order.
There is more
Part of Docentric product is also customization of built-in Attachments, which enables us to mark each of Product Attachments with a Category or special Tags, so we can distinct product specifications from other Attachments, when selecting them in X++ from the DocuRef table.
Also, if you want more flexibility in terms of design (e.g. you want to place product specifications anywhere in the generated documents, for example in a table cell immediately after the purchase order line that contains that product), or if you want to support direct printing to network printers (with this solution only Print as PDF is supported for the Printer print destination), you will need to attach product specifications not as PDF documents but in MS Word format.
This way, you will be able to embed product specifications directly in the report data source, and afterwards to use them as Subdocuments from within Docentric Designer.
You can see that it doesn’t take much effort to append PDF Attachments to existing reports, by subscribing to Docentric delegate generateReportContent located in the DocReportRunDelegates class.
In our case we used the Purchase order report, but similar approach can be taken for any other report. You can also select Attachments for some other source tables – like purchase order lines, for example. In that case, you would only need to modify that part of the X++ code which selects the corresponding records from Attachments, i.e. the DocuRef table.