When setting up the Print Management settings for reports in Accounts Receivable and Accounts Payable module in Dynamics 365 for Finance and Operations, you can specify in the To and Cc email addresses fields so called email tokens. For example, you can use the Primary contact and Contact purpose email tokens for a customer (Accounts Receivable) or a vendor (Accounts Payable) when emailing, e.g. an invoice or a purchase order.
Let’s say that you want to email each invoice to all those customer’s contacts which are marked with a special Contact purpose: Invoice. You will then simply select Invoice as Purpose on the Print destination settings form in the Print Management setup, as it is shown on the picture below. So, each time when an invoice is emailed using Print management, the emails with the attached invoice document will be sent to all the corresponding customer’s contacts with the Invoice purpose. Similarly, you can also use the Primary contact email token, in which case invoices will be sent to the customer’s primary contact email.
What if a customer has no primary contact nor any contact of type Email address that the Invoice purpose is assigned to? As you can see on the pictures below, resolving of these two email tokens results in warnings, and no email will be sent.
This built-in behavior can be changed if you are using Docentric AX Free Edition.
Let’s say that we want to do it in the following way: if for a printing invoice and the corresponding customer no email contact with Invoice purpose cannot be found (or primary contact email), instead of stopping the report execution we will send the problematic invoice to a specific internal email account (e.g. info@docentric.com). We will also change the email subject to describe the problem so that we know when such an email is received to take the appropriate action (e.g. to resend the email to the valid customer email addresses). We can still have the warnings about missing customer contact information, or we can easily remove them or transform them into some kind of logging.
In order to achieve this, we will introduce a new Docentric DSP class, DocSalesInvoiceWithCustomPurposePlaceholderDSP, which will handle these special requirements in its overrideReportRunSettings() method.
Learn in detail on how to define custom placeholders with Docentric >>
First, we are going to define two new placeholders (@InvoicePurpose@ and @PrimaryEmail@) which will be used instead of the built-in ones (@Invoice@ and @Primary@). This way we are going to avoid the built-in warnings. These two email tokens are defined as the attributes on the overrideReportRunSettings() method. Next, we will implement the above described logic in the same method in the following way.
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 |
[DocPlaceholderAttribute('InvoicePurpose', 'INV - Contact Purpose'), DocPlaceholderAttribute('PrimaryEmail', 'INV - Primary Email')] public DocPlaceholderManager overrideReportRunSettings(DocReportRunContext _reportRunContext, boolean _replaceStandardPlaceholders = true) { // List of defined custom placeholders for the report: #define.InvoicePurpose('InvoicePurpose') #define.PrimaryEmail('PrimaryEmail') #define.BuiltInInvoicePurpose('Invoice') DocPlaceholderManager placeholderMng = super(_reportRunContext, _replaceStandardPlaceholders); if (_reportRunContext.parmPrintDestination() == DocPrintDestination::Email) { // -- Placeholder @InvoicePurpose@ str emailTo = _reportRunContext.emailPrintDestSettings().parmEmailTo(); str emailCc = _reportRunContext.emailPrintDestSettings().parmEmailFrom(); str emailBcc = _reportRunContext.emailPrintDestSettings().parmEmailBcc(); if (DocPlaceholderManager::findPlaceholder(emailTo, #InvoicePurpose) || DocPlaceholderManager::findPlaceholder(emailCc, #InvoicePurpose) || DocPlaceholderManager::findPlaceholder(emailBcc, #InvoicePurpose)) { // NOTE: Make sure that the custTable is a global class variable fetched in the onSelectedRdpTableRecord() method. // Find all the customer's contacts with the 'Invoice' contact purpose. str purposeEmailList = DocSalesInvoiceWithCustomPurposePlaceholderDSP::constructEmailsFromPurpose(custTable, #BuiltInInvoicePurpose); if (!purposeEmailList) { // We don't want to throw an exception but to send to a control email account // that the emailing of the current invoice failed. _reportRunContext.emailPrintDestSettings().parmEmailSubject( strFmt('***ERROR FOR %1 ::INVOICE PURPOSE NOT FOUND:: *** ', placeholder_invoiceId) + _reportRunContext.emailPrintDestSettings().parmEmailSubject()); _reportRunContext.emailPrintDestSettings().parmEmailTo('info@docentric.com'); // Show the warning if needed. DocGlobalHelper::handleWarning( strFmt('Invoice contact purpose not found for the customer %1, %2', custTable.AccountNum, custTable.name())); } else { // Replace the placeholder. placeholderMng.replacePlaceholderInCurrentPrintDest(#InvoicePurpose, purposeEmailList); } } // -- Placeholder @PrimaryEmail@ if (DocPlaceholderManager::findPlaceholder(emailTo, #PrimaryEmail) || DocPlaceholderManager::findPlaceholder(emailCc, #PrimaryEmail) || DocPlaceholderManager::findPlaceholder(emailBcc, #PrimaryEmail)) { // NOTE: Make sure that the custTable is a global class variable fetched in the onSelectedRdpTableRecord() method. if (!custTable.email()) { // We don't want to throw an exception but to send to a control email account // that the emailing of the current invoice failed. _reportRunContext.emailPrintDestSettings().parmEmailSubject( strFmt('***ERROR FOR %1 ::PRIMARY EMAIL NOT FOUND:: *** ', placeholder_invoiceId) + _reportRunContext.emailPrintDestSettings().parmEmailSubject()); _reportRunContext.emailPrintDestSettings().parmEmailTo('info@docentric.com'); // Show the warning if needed. DocGlobalHelper::handleWarning( strFmt('Primary email not found for the customer %1, %2', custTable.AccountNum, custTable.name())); } else { // Replace the placeholder. placeholderMng.replacePlaceholderInCurrentPrintDest(#PrimaryEmail, custTable.email()); } } } return placeholderMng; } |
After that, to apply the new functionality we need to assign the new DocSalesInvoiceWithCustomPurposePlaceholderDSP DSP class to the Sales invoice report in Docentric report setup, as it shown on the picture below.
Afterwards, we also need to change the Print Management setup for the Sales invoice report to use these two new placeholders: @InvoicePurpose@ and @PrimaryEmail@ instead of the built-in @Invoice@ and @@.
When we now email an invoice by using Print Management we will get the different warnings (that could also be easily removed).
We will also get an error email in our internal inbox.
In fact, you can adjust these emailing rules as you please. You can even cancel sending such an email (without the valid To email addresses field) by subscribing to the DocReportRunDelegates.printToEmailBegin delegate or some other delegates defined in the report execution pipeline.
Additionally, with Docentric AX Free Edition you can define and edit HTML body, subject and attachment name with custom placeholders, send an email with multiple attachments and also use Bcc email addresses. Learn more >>
Resolving customer contact purpose
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public static str constructEmailsFromPurpose(CustTable _custTable, str _purpose) { container custContactInfoPurposeEmailsCon = DirParty::electronicAddressLocatorsByRole(_custTable.Party, LogisticsElectronicAddressMethodType::Email, _purpose); str custContactInfoPurposeEmailsStr = ''; if (custContactInfoPurposeEmailsCon != conNull()) { custContactInfoPurposeEmailsStr = conPeek(custContactInfoPurposeEmailsCon, 1); for (int i = 2; i <= conLen(custContactInfoPurposeEmailsCon); i++) { custContactInfoPurposeEmailsStr = custContactInfoPurposeEmailsStr + ',' + conPeek(custContactInfoPurposeEmailsCon, i); } } return custContactInfoPurposeEmailsStr; } |
Resolving customer primary email
custTable.email()
Similarly, when printing a report from Accounts Payable module, e.g. Purchase order you can use the custom @PO@ (Purchase order purpose) and @PrimaryEmail@ tokens.
Resolving vendor contact purpose
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public static str constructEmailsFromPurpose(VendTable _vendTable, str _purpose) { container vendContactInfoPurposeEmailsCon = DirParty::electronicAddressLocatorsByRole(_vendTable.Party, LogisticsElectronicAddressMethodType::Email, _purpose); str vendContactInfoPurposeEmailsStr = ''; if (vendContactInfoPurposeEmailsCon != conNull()) { vendContactInfoPurposeEmailsStr = conPeek(vendContactInfoPurposeEmailsCon, 1); for (int i = 2; i <= conLen(vendContactInfoPurposeEmailsCon); i++) { vendContactInfoPurposeEmailsStr = vendContactInfoPurposeEmailsStr + ',' + conPeek(vendContactInfoPurposeEmailsCon, i); } } return vendContactInfoPurposeEmailsStr; } |
Resolving vendor primary email
vendTable.email()