The Problem
One of the improvements that we had to do for one of our clients was the possibility to select multiple invoices from the grid control and then print them all into one PDF, instead of the default action of creating a separate PDF file per invoice.
In our particular case, the print destination settings are maintained for all the invoices and confirmed on the press of the OK button.
The Solution
To perform the operation, we had to extend the system SRSReportRunController class and to create an additional method “runFromArgs(Args _args)” where we replicated all the steps that are performed when the report is run, with the purpose of getting the rendered report bytes for the selected invoices. The next step was to put the data into a container and use the merging method where the container is being read as a stream and afterwards written to a file and sent to the user.
In this process, we used the PDFsharp library (PDRSharp.dll) which needed to be added as a reference to the project.
public static void runFromArgs(Args _args)
{
if (!_args || _args.dataset() != tableNum(InvoiceTable))
{
throw error(“@SYS22996”);
}
InvoiceTable invoice = _args.record() as InvoiceTable;
Map selectedRecordMap = Map::create(Helpers::getFormRecordOrderedByField(FormDataUtil::getFormDataSource(invoice), fieldNum(InvoiceTable,InvoiceNum)));
MapEnumerator mapEnum = selectedRecordMap.getEnumerator();
boolean firstReport = true;
boolean printMediumFile;
container con;
str mergedFileName;
boolean proceed = true;
SRSPrintMediumType firstMediumType;
while (mapEnum.moveNext())
{
PdfSharp.Pdf.PdfDocument initialDocument;
invoice = mapEnum.currentValue();
ExtendedInvoiceReportController controller = new ExtendedInvoiceReportController();
controller.parmReportName(invoice.PSOInvoiceLayoutTable().ReportName);
_args.record(invoice);
controller.parmArgs(_args);
controller.parmShowDialog(firstReport==true);
SrsReportDataContract reportContract = controller.parmReportContract();
SRSReportExecutionInfo executionInfo;
guid reportRunId = newGuid();
executionInfo = new SRSReportExecutionInfo();
executionInfo.parmReportRunId(reportRunId);
reportContract.parmReportExecutionInfo(executionInfo);
reportContract.parmReportServerConfig(SRSConfiguration::getDefaultServerConfiguration());
SRSPrintDestinationSettings printSettings;
printSettings = reportContract.parmPrintSettings();
if(firstReport)
{
controller.prompt();
}
else
{
printSettings.printMediumType(firstMediumType);
printSettings.fileFormat(SRSReportFileFormat::PDF);
printSettings.overwriteFile(true);
}
if(controller.dialogCanceled && firstReport == true)
{
proceed = false;
firstReport = false;
}
if(proceed)
{
if(printSettings.printMediumType() == SRSPrintMediumType::File)
{
firstMediumType = printSettings.printMediumType();
controller.runReport();
Microsoft.Dynamics.AX.Framework.Reporting.Shared.ReportingService.ParameterValue[] parameterValueArray;
Map reportParametersMap;
rsReportRunPrinter reportRunPrinter;
SrsReportRunService service = new SrsReportRunService();
service.getReportDataContract(invoice.InvoiceLayoutTable().ReportName);
service.preRunReport(reportContract);
reportParametersMap = service.createParamMapFromContract(reportContract);
parameterValueArray = SrsReportRunUtil::getParameterValueArray(reportParametersMap);
reportRunPrinter = new SrsReportRunPrinter(reportContract, parameterValueArray);
SRSReportFileFormat fileFormat = printSettings.fileFormat();
System.Byte[] reportBytes;
SRSProxy proxy = reportRunPrinter.parmSrsProxy();
reportBytes = proxy.renderReportToByteArray(reportContract.parmReportPath(),
parameterValueArray,
fileFormat,
printSettings.deviceInfo());
str content = System.Convert::ToBase64String(reportBytes);
if(firstReport)
{
mergedFileName = printSettings.parmFileName();
}
con += content;
firstReport = false;
printMediumFile = true;
}
else
{
irstMediumType = printSettings.printMediumType();
controller.runOperation();
firstReport = false;
}
}
}
if(printMediumFile)
{
Helper::mergePDFs(con, mergedFileName);
}
}
The Method
Further on, the actual method for merging of the PDFs is as follows:
static PdfSharp.Pdf.PdfDocument mergePDFs(container _con, str _mergedName)
{
PdfSharp.Pdf.PdfDocument outPutPDFDocument = new PdfSharp.Pdf.PdfDocument();
PdfSharp.Pdf.PdfDocument inputPDFDocument = new PdfSharp.Pdf.PdfDocument();
PdfSharp.Pdf.PdfPages pdfPages;
container con = _con;
int i, j, pageCount;
FileName pdfFile;
InteropPermission permission;
str errorMessage;
System.IO.MemoryStream mergedStream;
str tempDir;
str mergedName = ‘INVOICES_’ + _mergedName;
try
{
permission = new InteropPermission(InteropKind::ClrInterop);
permission.assert();
for (i = 1; i <= conLen(con); i++)
{
System.Byte[] reportBytes;
System.IO.MemoryStream stream;
reportBytes = System.Convert::FromBase64String(conPeek(con,i));
stream = new System.IO.MemoryStream(reportBytes);
inputPDFDocument = PdfSharp.Pdf.IO.PdfReader::Open(stream,PdfSharp.Pdf.IO.PdfDocumentOpenMode::Import);
outputPDFDocument.set_Version(inputPDFDocument.get_Version());
pageCount = inputPDFDocument.get_PageCount();
pdfPages = inputPDFDocument.get_Pages();
if (pageCount > 0)
{
for (j = 0 ; j < pageCount; j++)
{
outputPDFDocument.AddPage(pdfPages.get_Item(j));
}
}
}
new InteropPermission(InteropKind::ClrInterop).assert();
tempDir = ‘C:\\tempDir’;
System.IO.Directory::CreateDirectory(tempDir);
CodeAccessPermission::revertAssert();
outputPDFDocument.Save(tempDir + ‘\\’ + mergedName);
System.Byte[] outBytes = System.IO.File::ReadAllBytes(tempDir + ‘\\’ + mergedName);
mergedStream = new System.IO.MemoryStream(outBytes);
File::SendFileToUser(mergedStream, mergedName);
CodeAccessPermission::revertAssert();
new InteropPermission(InteropKind::ClrInterop).assert();
PSOHelpers::clearDirectory(tempDir);
System.IO.Directory::Delete(tempDir);
CodeAccessPermission::revertAssert();
}
catch(Exception::CLRError)
{
errorMessage = AifUtil::getClrErrorMessage();
CodeAccessPermission::revertAssert();
throw error(errorMessage);
}
return outputPDFDocument;
}
static void clearDirectory(Filename _folderName)
{
System.String[] files;
int filecount;
int i;
if (WinAPI::folderExists(_folderName))
{
files = System.IO.Directory::GetFiles(_folderName);
filecount = files.get_Length();
for(i=0; i < filecount; i++)
{
System.IO.File::Delete(files.get_Item(i));
}
}
}
Side Effects
As a side effect, we faced the problem that extra PDF files were being created for all the invoice reports that were additionally going to be sent to the client along with the merged file, which would be pretty annoying for the client.
We managed to resolve that problem with the use of a post handler for the “onToFile” method from the SRSPrintDestinationSettingsDelegates class, where the method was set to not return a value in a set condition.
[PostHandlerFor(classStr(SRSPrintDestinationSettingsDelegates), methodStr(SRSPrintDestinationSettingsDelegates, onToFile))]
public static void SRSPrintDestinationSettingsDelegates_Post_onToFile(XppPrePostArgs args)
{
ProjectHoursContract projectHoursContract;
SrsReportDataContract dataContract;
dataContract = args.getArgNum(2);
projectHoursContract = dataContract.parmRdpContract() as ProjectHoursContract;
if(!projectHoursContract)
{
args.setReturnValue(false);
}
}
related posts
November 29, 2023
How to use the method runFromArgs() ? From where exactly we’ll make the call to this method?