Monday, November 05, 2012

Enable SalesInvoice report on Enterprise Portal

Continuing the series of articles in SSRS, here I explain how to print the salesInvoice (or any other custom report) in Enterprise Portal.

Despite Packing Slip, this report is not supported in the standard Enterprise portal installation (you don't have the button to print an invoice on the EP invoices list page)

That's because Sales Invoice use a report controller that has not been implemented for the use in EP.

The solution I propose here is a little tricky, but it works pretty well :)

The key is to modify the class EPDocuGet that manages the download of reports or attachments from EP.

In that class you can write some code to print the SalesInvoice PDF to a temporary directory (how to do that is explained in a post before) and then write out the data in HTTP response of the Web request

So in the method run you can do something like:


 void run(Args args = null)  
 {  
   IISResponse       response = new IISResponse();  
 ;  
   // Transition to a server function to avoid  
   // Security problems  
   if (args)  
   {  
     if (args.record().TableId == tableNum(DocuRef))  
     {  
       EPDocuGet::runDocument(args.record().data(), response);  
     }  
     // Begin change  
     // Description:  
     else if (args.record().TableId == tableNum(CustInvoiceJour))  
     {  
       EPDocuGet::DWrunReportSalesInvoice(args.record().data(), response);  
     }  
     // End  
     else  
     {  
       EPDocuGet::runReport(args.record().data(), response);  
     }  
   }  
 }  

So if you got a CustInvoiceJour record you run your custom code. The method DWrunReportSalesInvoice is a copy of the runDocument method, a little bit modified:

 #define.BUFFER_SIZE(4096)  
 #File  
 client static void DWrunReportSalesInvoice(Common callerRecord, IISResponse response)  
 {  
   Query          query = new Query();  
   QueryBuildDataSource  qbds;  
   QueryRun        queryRun;  
   //EPSendDocument     document;  
   BinData         binData;  
   str           fileName;  
   int           fileOffset;  
   DictTable        table = new DictTable(callerRecord.TableId);  
   str           tempFileName;  
   boolean         emptyReport;  
   CustInvoiceJour     custInvoiceJour = callerRecord;  
   ;  
   if (EPDocuGet::hasTableAccess(callerRecord.TableId))  
   {  
     qbds = query.addDataSource(callerRecord.TableId);  
     if (callerRecord && callerRecord.TableId != tableNum(DocuRef))  
     {  
       // Requery the record to test record level security  
       qbds.addRange(table.fieldName2Id('RecId')).value(SysQuery::value(callerRecord.RecId));  
       query.recordLevelSecurity(true);  
       queryRun = new QueryRun(query);  
       if (queryRun.next())  
       {  
         //document = new EPSendDocument(callerRecord);  
         tempFileName = System.IO.Path::GetTempFileName();  
         EPDocuGet::DWPrintInvoicePdf(callerRecord, tempFileName);  
         infolog.clear();  
         //document.parmOriginal(true);  
         //document.parmFileName(tempFileName);  
         // Make document will run the report and send it to a PDF file with  
         // the path specified in tempFileName  
         //document.makeDocument();  
         fileName = custInvoiceJour.InvoiceId + ".pdf";  
         // remove all ';' from the filename. These are treated as delimiters by Ie  
         fileName = strReplace(fileName, ';', '_');  
         emptyReport = (WinAPI::fileSize(tempFileName) == 0);  
         response.clear();  
         binData = new BinData();  
         if (emptyReport)  
         {  
           response.contentType('application/Octet-Stream');  
           response.addHeader('Content-Disposition', 'attachment;filename="' + fileName + #txt + '"');  
           response.writeTxt(strFmt("@SYS58533", fileName));  
         }  
         else  
         {  
           response.contentType('application/pdf');  
           response.addHeader('Content-Disposition', 'attachment;filename="' + fileName + #pdf + '"');  
           // Loop over the stored file and chunk it down to the client  
           fileOffset = 0;  
           // Assert permission and get the temp filename  
           new FileIOPermission(tempFileName, #io_read).assert();  
           while(true)  
           {  
             // BP Deviation Documented  
             if (!binData.loadFile(tempFileName, fileOffset, #BUFFER_SIZE))  
             {  
               break;  
             }  
             fileOffset += #BUFFER_SIZE;  
             response.binaryWrite(binData.getVariant());  
           }  
           CodeAccessPermission::revertAssert();  
         }  
         binData = null;  
         WinAPI::deleteFile(tempFileName);  
       }  
     }  
   }  
 }  

And here is the method to print the invoice in PDF:

 client static void DWPrintInvoicePdf(Common _record, Filename filename)  
 {  
   args args = new args();  
   CustInvoiceJour custInvoiceJour = _record;  
   SRSPrintDestinationSettings printSettings;  
   SalesInvoiceController controller = new SalesInvoiceController();  
   str pdfPath = filename;  
   args.record(_record);  
   //pdfPath = WinAPI::getTempPath() + custInvoiceJour.InvoiceId + ".pdf";  
   // imposta nome report  
   controller.parmReportName(ssrsReportStr(SalesInvoice, Report));  
   // get print settings from contract  
   printSettings = controller.parmReportContract().parmPrintSettings();  
   // set print medium  
   printSettings.printMediumType(SRSPrintMediumType::File);  
   printSettings.fileFormat(SRSReportFileFormat::PDF);  
   printSettings.overwriteFile(true);  
   printSettings.fileName(pdfPath);  
   // forzo che non vengano cambiati i parametri di stampa che gli passo io  
   controller.DEVparmLockDestinationProperties(true);  
   // suppress the parameter dialog  
   controller.parmShowDialog(false);  
   controller.parmArgs(args);  
   // start operation  
   controller.startOperation();  
 }  


At this point, all you have to do is to create an Output Menu Item with the value "EPDocuGet" in the property "WebMenuItemName" and add this menu item to the EPCustInvoiceListPage form or whatever form you like.

12 comments:

Divya said...

I tried this method and it had the following problem. when I create the output menuitem with Epdocuget as the webmenuitemname and added it to the EPCustinvoicelistpage , it says no object set on the output menuitem I created when I click it. Can you please advise what I am missing?

Ludovico Mattiuzzo said...

If you run it from inside Ax it's normal if you get an error message, this will work only from EP.

For the blank page, make sure your PDF is created correctly!

You could try creating a job that run the method EPdocuGet.DWrunReportSalesInvoice, so you can debug it and check if the PDF is created correctly.

You can even check the event viewer on the server that host the EP installation to see what errors are logged here!

Yered Castro said...

And... what contain "DEVparmLockDestinationProperties"? I try run my report but always show in screen

Ludovico Mattiuzzo said...

In that class you can write some code to print the SalesInvoice PDF to a temporary directory (how to do that is explained in a post before)

http://daxldsoft.blogspot.it/2012/07/print-salesinvoice-packingslip-or.html

Yered Castro said...

Thank you very much, your blog is extraordinarily helpful!

Unknown said...

Hello!

Is this a complete solution?
Because i have some problems...

When i click the button i added to the list page in EP i only get a blank page(I use IE). I try to run a job in AX 2012 where i Call EPDocuGet::DWPrintInvoicePdf(CustInvoiceJour, "\\\\space\\felles\\SalesInvoiceReport\\test.pdf") but i get an error.

there are some folders created in the Directory, but no pdf-files. what Method is responsible for creating the pdf, and what Method is responsible for showing it in EP?

best regards
Kent

Ludovico Mattiuzzo said...

Hi Kent, If the PDF is not created I think that you miss the part with the LockDestinationProperties.

There is a link on the article that explain how to do that:

http://daxldsoft.blogspot.it/2012/07/print-salesinvoice-packingslip-or.html

So, make sure the PDF is created correctly, and then everything should work.

BR

Unknown said...

i tried running a job with code

select CustInvoiceJour where CustInvoiceJour.InvoiceId == "4484509";
EPDocuGet::DWPrintInvoicePdf(CustInvoiceJour, "c:\\EPSalesInvoicePDFtmp_"+custInvoiceJour.InvoiceId+".pdf");

and it created the PDF just fine.

I however still get a blank screen at downloaddocument.aspx after clicking the button in custTableListPage in EP...

Any ideas?
Thanks for your time.
regards

Ludovico Mattiuzzo said...

Kent, you should try to debug to understand where the problem lies.

If you can't debug from EP just put some throw error("some text"); around the method DWrunReportSalesInvoice to see what is going wrong.

That way should be easy to detect the error.

getout332 said...
This comment has been removed by the author.
getout332 said...

We've just found a solution the blank page problem. This was related to security and permission problems in our case. First, try running EP with the Microsoft Dynamics AX system admin role, and using the button. If that solves the problem then try adding permissions to your desired role, so that the methods involved in creating this report are allowed to be executed. This solved our blank page problem and got the button working.

Unknown said...

Hey, this is working really good, but i was wondering if there's any way to get multiselection into the downloadDocument.aspx?

I have enabled for multiSelection in the listpage where i use the EPDocuget menu item and i can mark several records on the listpage running on EP, but the downloadDocument.aspx
only extract a single record in this code:
IAxaptaRecordAdapter record = tableContext.DataKey.GetRecord(session)

How can i get all of the marked records and maybe send them as a string in doc.runDownload(record) back to AX 2012 before i send them the documents?

I need this to make the user able to select multiple documents that i can stitch together to a single bigger PDF and send to the user...