Let’s say that we want to send eInvoices from Dynamics 365 for Finance and Operations to our customers. In addition to the invoice data in XML, we also want to send along the invoice document in PDF format and some other related documents (e.g. specifications).
The built-in D365FO functionality supports eInvoice formats from the Electronic reporting framework. You can set up these ER formats in Accounts receivable parameters.
In this article we will use OIOUBL Project Invoice for Norway (used also for Denmark and Austria).
We will first show how to embed multiple Attachments from the Project invoice journal into the output XML file by using the standard D365FO, where one of them is the Project invoice document generated in PDF format during the posting process.
Next, we will explain how to combine the invoice document and the rest of the journal’s Attachments by merging them into a single PDF document, before it gets embedded into the output eInvoice XML file. This will require some custom code.
Embed report PDF and additional Attachments with standard D365FO
On the Customers form under the Invoice and delivery tab you can enable eInvoice and eInvoice attachment for a specific customer.
Set the eInvoice option to Yes to enable electronic invoices to be generated. It will trigger the eInvoice XML generation when posting an invoice.
Set the eInvoice attachment option to Yes to embed the invoice document generated in PDF format into the electronic invoice. It will create an invoice PDF document attached to the journal record, which is later embedded into the outgoing XML file.
The standard schema for OIOUBL Project Invoice XML already supports multiple attachments. Let’s explain how this works.
On the Invoice proposal you can add attachments on two entities: header and transactions. Click on one of the fields of the desired section (below marked in red or blue) to add an attachment on the right entity (header or transaction).
During posting all Attachments from Invoice proposal are copied to Invoice journal (ProjProposalJour → ProjInvoiceJour and ProjProposalOnAcc → ProjInvoiceOnAcc).
The generated invoice document is attached to ProjInvoiceJour (with Restriction = External).
Electronic reporting job (named as Send the eInvoice XML), which generates the XML with all external attachments from ProjInvoiceJour, is created in Organization administration > Electronic reporting > Electronic reporting jobs with Status set to Waiting. After the job is processed and its Status is changed to Finished, the XML file will be created and attached to the Electronic reporting job record and will contain all external Attachment files from ProjInvoiceJour.
You can see that the resulting XML file contains all (external only) Attachment files. The last of them is the Project invoice PDF document.
If it is executed before the document is attached, this attachment will not be included in the XML. However, if you resend the eInvoice, the XML will be created again with all attachments.
This is a bug and we have submitted it to Microsoft. It has been fixed in version 10.0.16. A similar bug for Sales invoice has been previously reported and fixed in 10.0.9.
Embed report PDF merged with additional Attachments
The standard solution described above works fine, but it has some drawbacks. We have no control over the order of the attachments and there is no information which is the main attachment and what is the purpose of the others. Multiple attachments are also not well supported by the recipient’s software.
A more reliable approach is to combine all attachments into one PDF. This way, the eInvoice XML contains only one embedded PDF and we can control its structure. This gives more confidence that the recipient will see and understand everything correctly.
Let's first describe how this custom solution works and then we will explain the code behind it.
The solution merges all PDF Attachments from ProjInvoiceJour into a single Attachment, which is later embedded into the XML. The steps before posting are the same as already described above for standard.
However, after posting ProjInvoiceJour contains only one merged Attachment that includes (in listed order): invoice document + external ProjInvoiceJour attachments + external ProjInvoiceOnAcc attachments (optional). Original Attachments from ProjInvoiceJour are deleted after merging (to avoid embedding them twice in the XML).
The XML now contains only one merged attachment.
Below is the sample code that implements the described scenario with one possible structure of a merged document. It can be further customized for a more specific use case.
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 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
using System.IO; [ExtensionOf(classStr(DocumentAttachmentProjectInvoice))] final class DocumentAttachmentProjectInvoice_MergeAttachments_Extension { public void run() { next run(); this.mergeAttachments(journal); } // merge all external attachments in one and delete others on ProjInvoiceJour void mergeAttachments(ProjInvoiceJour _projInvoiceJour) { List attachmentsList = new List(Types::AnyType); DocuRef docuRef, docuRefInvoice = null; DocuValue docuValue; boolean attachments = false; void addStream(DocuRef _docuRef, boolean _addStart = false) { if ((docuRef.Restriction != DocuRestriction::External) || (docuValue.FileType != 'PDF')) // add warning if this is not allowed return; System.IO.Stream stream = DocumentManagement::getAttachmentStream(_docuRef); Debug::assert(stream != null); using (MemoryStream attachmentMs = new MemoryStream()) { stream.CopyTo(attachmentMs); if (_addStart) attachmentsList.addStart(attachmentMs.ToArray()); else { attachmentsList.addEnd(attachmentMs.ToArray()); attachments = true; } } } ; if (!_projInvoiceJour.RecId) return; ttsbegin; // merge external PDF attachments from Invoice journal docuRef = DocumentManagement::findAttachmentsForCommon(_projInvoiceJour); while (docuRef) { docuValue = docuRef.docuValue(); if (docuRef.Name == 'ProjInvoice.Report') docuRefInvoice = docuRef.data(); addStream(docuRef, docuRef.RecId == docuRefInvoice.RecId); next docuRef; } // merge with external PDF attachments from Invoice transactions (ProjInvoiceOnAcc) ProjInvoiceOnAcc projInvoiceOnAcc; while select projInvoiceOnAcc where projInvoiceOnAcc.ProjInvoiceId == _projInvoiceJour.ProjInvoiceId && projInvoiceOnAcc.InvoiceDate == _projInvoiceJour.InvoiceDate { DocuRefSearch docuRefSearch = DocuRefSearch::newCommon(projInvoiceOnAcc); // similar to DocumentManagement::findAttachmentsForCommon while (docuRefSearch.next()) { docuRef = docuRefSearch.docuRef(); docuValue = docuRef.docuValue(); addStream(docuRef); } } if (!attachments) return; using (MemoryStream mergedPDFStream = DocDocumentHelper::mergePdfDocuments(attachmentsList)) { // replace file on existing DocuRef (DocumentManagement::attachFileToDocuRef clears some fields) docuRefInvoice.selectForUpdate(true); if (!docuRefInvoice.docuAction().attachFile(docuRefInvoice, docuRefInvoice.filename(), null, mergedPDFStream)) throw error("@ApplicationFoundation:Docu_ErrorWhileAttaching"); } // delete all journal attachments except eInvoice docuRef = DocumentManagement::findAttachmentsForCommon(_projInvoiceJour); while (docuRef) { if (docuRef.RecId != docuRefInvoice.RecId) { docuRef.selectForUpdate(true); docuRef.delete(); } next docuRef; } ttscommit; } } |
Summary
eInvoice XML can include PDF attachments for invoice visualization (printable invoice) and additional specifications of your electronic invoice. The standard D365FO supports one approach, while additional requirements can be covered with custom code presented in this article. The article presents various options that you can further customize for your business requirements.