I didn't want to go through the hassle of setting up a blog just to post this tutorial, so I am posting it in the Forum instead...
Sending reports with Print Management is a nice feature; however, the report is sent as an attachment to a blank email. I was asked by others in my company to find a way to send these reports using an AX email template and dynamic subject lines. I will outline the changes I needed to make to allow this to work. (Note: This may not be the best way, but it does work)
Step 1: Set up Print Destination to Handle the New Options
In \Classes\PrintDestinationSettings
Update the classDeclaration method to include the following (idNumber is not necessary if you do not need a dynamic subject line)
boolean sendWithTemplate;
str templateType;
str idNumber;
// Pack/unpack information
#define.version (1)
#define.packVersion (1)
#localmacro.currentList
version,
packedPrintMedium,
printMediumType,
printerName,
printerPageSettings,
landscape,
printerStatus,
printerType,
printerWhere,
printerComment,
printAllPages,
fromPage,
toPage,
numberOfCopies,
emailTo,
emailCc,
emailSubject,
emailAttachmentFileFormat,
emailAttachmentImageFileFormat,
sendWithTemplate,
templateType,
idNumber,
fileName,
fileFormat,
imageFileFormat,
overwriteFile,
printToArchive,
overridePageSettings,
overwriteFileIsSet
#endmacro
Because of the extra 3 (or 2) extra fields we are packing in the class declaration the unpack method needs to be updated (Note: if you are not using IdNumber omit the insert at line 22)
public boolean unpack(container _pack)
{
boolean ret;
int version;
container packedPrintMedium;
if (typeOf(conPeek(_pack, 1)) == Types::Integer)
{
version = conPeek(_pack, 1);
if (version == #packVersion)
{
// This to to handle print destination settings that were created prior to the update
if(conLen(_pack) < 28)
{
_pack = conIns(_pack,20,false);
_pack = conIns(_pack,21,"");
_pack = conIns(_pack,22,"");
}
[#currentList] = _pack;
// Need to use packedPrintMedium to create the print medium via the classFactory
this.printMedium(new SRSPrintMediumScreen());
ret = true;
}
}
return ret;
}
If using dynamic subject line the new method will need to be updated as well
public void new(container _c = conNull(), str _idNumber = "")
{
if (_c != conNull())
{
if(conLen(_c) == 29 && _idNumber != "")
{
_c = conPoke(_c, 22, _idNumber);
this.idNumber(_idNumber);
}
this.unpack(_c);
}
else
{ …}
Now new methods need to be created to retrieve the new variables…
Send with Template
[DataMemberAttribute]
public boolean sendWithTemplate(boolean _value = sendWithTemplate)
{
sendWithTemplate = _value;
return sendWithTemplate;
}
Template Type
[DataMemberAttribute]
public str templateType(str _value = templateType)
{
templateType = _value;
return templateType;
}
IdNumber
public str idNumber(str _value = idNumber)
{
idNumber = _value;
return idNumber;
}
Step 2: Update the form to give the user the option to Send with Template
In \Forms\SRSPrintDestinationSettingsForm create a check box for the Send with Template option and an Editable String for Template Type. Both of these new fields need the Auto Declaration set to “YES”. To “DataSources” add “SysEmailTable” and in TemplateType properties set DataSource to ”SysEmailTable” and DataField to “Description”.
Override the lookup method for Template type, this will give the user a drop down menu of all available templates to select from (\Forms\SRSPrintDestinationSettingsForm\TemplateType:lookup)
public void lookup()
{
Query query = new Query();
QueryBuildDataSource queryBuildDataSource;
QueryBuildRange queryBuildRange;
SysTableLookup sysTableLookup = SysTableLookup::newParameters(tableNum(sysEmailTable), this);
sysTableLookup.addLookupField(fieldNum(SysEmailTable, EmailId));
sysTableLookup.addLookupField(fieldNum(SysEmailTable, Description));
queryBuildDataSource = query.addDataSource(tableNum(SysEmailTable));
queryBuildRange = queryBuildDataSource.addRange(fieldNum(SysEmailTable, EmailId));
sysTableLookup.parmQuery(query);
sysTableLookup.performFormLookup();
mailSubject.text(SysEmailMessageTable::find(TemplateType.text(),'en-us').subject);
//super();
}

In \Forms\SRSPrintDestinationSettingsForm\init add the following in the print to email settings (add at line 31 on my version)
SendTemplate.value(printSettings.sendWithTemplate());
if(printSettings.sendWithTemplate())
{
TemplateType.visible(true);
TemplateType.text(printSettings.TemplateType());
}
\Forms\SRSPrintDestinationSettingsForm\closeOK add the following to the print to email settings (add at line 46 in my version)
printSettings.sendWithTemplate(SendTemplate.checked());
printSettings.templateType(SendTemplate.checked() ? TemplateType.text():"");
Now your print destination settings form should look like this…

The subject line is pulled from the subject line in the SysEmailMessageTable during lookup of the email description.
Step 3: Sending the Email through X++
First I will show how to handle the sending the email without the dynamic subject line (a lot of changes are needed to make that work so I will cover that all together at the end), for sending with a template we need to make 3 simple changes.
\Classes\SRSPrintDestinationSettings\parmEMailContract
Prior to the return add the following if statement
if(emailContract.parmBody() == "" && this.templateType() != "")
{
emailContract.parmBody(SysEmailMessage::stringExpand(SysEmailMessageTable::find(templateType,
'en-us').Mail));
}
\Classes\SysINetMail\sendEMail
server static void sendEMail(
SysEmailId _emailId,
LanguageId _language,
str _emailAddr,
Map _mappings = null,
str _from = '',
FilenameOpen _attachmentFilename = '',
boolean _isHTML = false)
{
#help
SysINetMail sysINetMail;
SysEmailTable table = SysEmailTable::find(_emailId);
SysEmailMessageTable message = SysEmailMessageTable::find(_emailId, _language);
str messageBody;
str htmlText;
COMDispFunction comWrite;
COMVariant text;
COM ctrl;
COM document;
COM body;
boolean isHTML = _isHTML;
;
if (!message)
{
// Message not found for this language.
message = SysEmailMessageTable::find(_emailId, table.DefaultLanguage);
}
if(isHTML==false)
{
isHTML = (message.LayoutType == SysEmailLayoutType::StaticLayout);
messageBody = SysEmailMessage::stringExpand(message.Mail, _mappings);
}
…
}
\Classes\SrsReportRunMailer\emailReport
Add the highlighted updates to the elseif(inetMailer != null) statement
else if (inetMailer != null)
{
result = inetMailer.sendMailAttach(_emailContract.parmTo(),
_emailContract.parmCc(),
_emailContract.parmSubject(),
_emailContract.parmBody(),
false, /* do not show dialog*/
_attachmentPath,
'',
true);
}
Now you can send the report attached to a template!!!
Step 4: Sending Email with Dynamic Subject Line
First thing be sure your template has the variable you want to use wrapped in ‘%’ so AX will recognize it as dynamic.
Here I am using the Purchase Order Id number as my dynamic item in the subject line. (see the Print Destination Settings Form Subject line %IdNumber%)
In \Classes\Print Mgmt\classDeclaration add
In \Classes\Print Mgmt\buildEffectiveSettings add field idNumber to the following (line 27)
PrintMgmtNodeInstance::mergeIntoEffectiveSettings(_nodeInstance, childInstance, idNumber);
In \Classes\Print Mgmt\getNodeInstances add the highlighted parts (*NOTE: this is for a PO, if you are using it for something else you will need to change the table id to match what you are using*)
while (currentInstance != null && currentInstance.parmNodeDefinition().isValidDocumentType(documentType))
{
//Added to push PO Number to Template for Emails
if(referencedTable.TableId == tableNum(PurchTable))
{
purchTable = referencedTable;
idNumber = purchTable.PurchId;
}
else if(hierarchyContext.parmReferencedTableBuffer().TableId == tableNum(PurchTable))
{
purchTable = hierarchyContext.parmReferencedTableBuffer();
idNumber = purchTable.PurchId;
}
else
{
idNumber = "";
}
// We have the instance we need, so add to it
setupDocument = PrintMgmtSetupDoc::construct(currentInstance,
documentType, _languageId, null, idNumber);
An extra field is being added to that call so we must change the arguments for the call. Be sure to set the default to empty so it will not break calls made to it from elsewhere
\Classes\PrintMgmtSetupDoc\construct
public static PrintMgmtSetupDoc construct(PrintMgmtNodeInstance _nodeInstance, PrintMgmtDocumentType _docType, LanguageId _langId, PrintMgmtSetupFolder _parent = null, str _idNumber = "")
{
…
while select RecId from setup
order by PriorityId
where
setup.DocumentType == _docType
&& setup.NodeType == _nodeInstance.parmNodeDefinition().getNodeType()
&& setup.ReferencedRecId == recId
&& setup.ReferencedTableId == tableId
{
doc.addInstance(PrintMgmtSetupDocInstance::constructFromRec(doc, setup.RecId, _langId, _idNumber));
}
}
Again we added a parameter, so go to \Classes\PrintMgmtSetupDocInstance\constructFromRec
public static PrintMgmtSetupDocInstance constructFromRec(PrintMgmtSetupDoc _parent, RefRecId _recId, LanguageId _langId, str _idNumber = "")
{
…
PrintMgmtSetupSettings::newSettingsFromRecs(docInstance, _langId, _idNumber);
return docInstance;
}
\Classes\PrintMgmtSetupDocInstance\shallowCopy
public PrintMgmtSetupDocInstance shallowCopy(PrintMgmtSetupDoc _newParent, str _idNumber = "")
{
return PrintMgmtSetupDocInstance::constructFromRec(_newParent, recId, langId, _idNumber);
}
\Classes\PrintMgmtSetupSettings\newSettingsFromRec
public static void newSettingsFromRecs(PrintMgmtSetupDocInstance _parent, LanguageId _langId, str _idNumber = "")
{
…
if(rec.QueryPacked == conNull()) // Default Setting?
{
if(defaultFound) // duplicate default settings are not allowed!
{
throw error(Error::wrongUseOfFunction(funcName()));
}
PrintMgmtSetupSettings::newDefaultFromRec(_parent, rec.RecId, _langId, _idNumber);
defaultFound = true;
}
…
}
\Classes\PrintMgmtSetupSettings\newDefaultFromRec
public static PrintMgmtSetupSettingsDefault newDefaultFromRec(PrintMgmtSetupDocInstance _parent, RefRecId _recId, LanguageId _langId, str _idNumber = ""){
PrintMgmtSettings rec;
;
select firstonly rec where rec.RecId == _recId;
if(conLen(rec.PrintJobSettings) == 29 && _idNumber != "")
{
ttsBegin;
rec.PrintJobSettings = conPoke(rec.PrintJobSettings, 22, _idNumber);
ttsCommit;
}
return PrintMgmtSetupSettingsDefault::constructFromRec(_parent, rec, _langId, _idNumber);
}
\Classes\PrintMgmtSetupSettingsDefault\constructFromRec
public static PrintMgmtSetupSettings constructFromRec(PrintMgmtSetupDocInstance _parent, PrintMgmtSettings _printMgmtSettings, LanguageId _langId, str _idNumber = "")
{
…
obj.init(_parent, _printMgmtSettings, _langId, _idNumber);
…
}
\Classes\PrintMgmtSetupSettingsDefault\init
protected void init(PrintMgmtSetupDocInstance _parent, PrintMgmtSettings _printMgmtSettings, LanguageId _langId, str _idNumber = "")
{
;
if(conLen(_printMgmtSettings.PrintJobSettings) == 29 && conPeek(_printMgmtSettings.PrintJobSettings, 22) != _idNumber && _idNumber != "")
{
_printMgmtSettings.PrintJobSettings = conPoke(_printMgmtSettings.PrintJobSettings,22, _idNumber);
}
…
}
\Classes\FormLetter\isClientOutput
public server static boolean isClientOutput(container _packedSettings, str _idNumber = "")
{
return new SRSPrintDestinationSettings(_packedSettings, _idNumber).printMediumType() == SRSPrintMediumType::Screen;
}
\Classes\FormLetterSErviceController\checkClientOutputPrintManagement (again if you are using something other than the PurchTable you will need to change it here)
while select PrintJobSettings from printMgmtSettings
where
printMgmtSettings.Description == ''
join PrintType from printMgmtDocInstance
where printMgmtDocInstance.RecId == printMgmtSettings.ParentId
&& printMgmtDocInstance.ReferencedRecId == 0
&& printMgmtDocInstance.ReferencedTableId == 0
&& printMgmtDocInstance.DocumentType == this.printMgmtDocumentType()
&& printMgmtDocInstance.NodeType == this.printMgmtNodeType()
{
if(strContains(callerFormName, "PurchTable"))
{
purchTable = this.getContract().parmCallerTable();
if (FormLetter::isClientOutput(printMgmtSettings.PrintJobSettings, purchTable.PurchId ))
{
printerSetupErrorText += strFmt("@SYS118704", printMgmtDocInstance.PrintType) + '\n';
onClient = true;
}
}
else
{
if (FormLetter::isClientOutput(printMgmtSettings.PrintJobSettings))
{
printerSetupErrorText += strFmt("@SYS118704", printMgmtDocInstance.PrintType) + '\n';
onClient = true;
}
}
}
\Classes\PurchPurchaseOrderController\initFormLetterReport (starting at line 23 in my version)
if (purchPurchOrderJournalPrint)
{
formLetterReport.parmDefaultCopyPrintJobSettings(new SRSPrintDestinationSettings(purchPurchOrderJournalPrint.parmPrinterSettingsFormLetterCopy()));
formLetterReport.parmDefaultOriginalPrintJobSettings(new SRSPrintDestinationSettings(purchPurchOrderJournalPrint.parmPrinterSettingsFormLetter()));
formLetterReport.parmUsePrintMgmtDestinations(purchPurchOrderJournalPrint.parmUsePrintManagement());
}
Now we can get to the point of sending the email with the template and dynamic subject line!
Go back to \Classes\SRSPrintDestinationSettings\parmEMailContract that we edited in Step 3
public SrsReportEMailDataContract parmEMailContract(SrsReportEMailDataContract _emailContract = emailContract)
{
//added
Map mappings = new Map(Types::String, Types::String);
mappings.insert('IdNumber', idNumber);
//end added
…
if(emailContract.parmBody() == "" && this.templateType() != "")
{
if(strContains(this.emailSubject(), "%"))
{
emailContract.parmSubject(SysEmailMessage::stringExpand(SysEmailMessageTable::find(templateType,
'en-us').Subject, mappings));
}
emailContract.parmBody(SysEmailMessage::stringExpand(SysEmailMessageTable::find(templateType,
'en-us').Mail));
}
return emailContract;
}
Now emails will send with the template and a dynamic subject line!!! It is a lot of work (at least for the dynamic subject line), but it will make the end users very happy.
Hope this was helpful!
-Jessi