acumaticaacumatica-kb

Acumatica - Open Combined Financial Reports w/ Parameters from Code


I'm trying to write a process screen in Acumatica ERP 2021R2 (version 21.210.0030) that will generate a financial report for each selected branch in the grid, combine them, and open the combined report when all of them are created.

I know how to do this with reports that are created in the report designer, but this will not work with the financial reports that are created with the Report Definitions screen.

Here is the process delegate that I'm trying to use (which behaves as I have described above):

    public static void GenerateReports(List<BranchBAccount> branches, RPBranchProcessFilter filter)
    {
        RPBranchReportProcess dummyGraph = PXGraph.CreateInstance<RPBranchReportProcess>();

        Ledger ledger = Ledger.PK.Find(dummyGraph, filter.LedgerID);

        Dictionary<string, string> parameters = 
            new Dictionary<string, string>
            { 
                ["LedgerID"] = ledger.LedgerCD,
                ["StartPeriod"] = filter.FromFinPeriodID,
                ["EndPeriod"] = filter.ToFinPeriodID
            };

        PXReportRequiredException reportEx = null;

        foreach (BranchBAccount branch in branches)
        {
            parameters["StartBranch"] = branch.BranchBranchCD;
            parameters["EndBranch"] = branch.BranchBranchCD;

            reportEx = PXReportRequiredException.CombineReport(reportEx, "RM000007", parameters, false);
        }

        if(reportEx != null)
        {
            reportEx.Mode = PXBaseRedirectException.WindowMode.New;
            reportEx.SeparateWindows = false;
            throw reportEx;
        }
    }

I've been able to find absolutely no documentation on handling financial reports from the code, so any help would be appreciated.


Solution

  • After some digging it appears Acumatica checks the report name references an ARM report and utilizes different methdology for generation and presentation if so.

    To generate the ARM report I had to write the following Extension method.

    using PX.Common;
    using PX.CS;
    using PX.Data;
    using PX.Data.Reports;
    using PX.Reports;
    using PX.Reports.ARm;
    using PX.Reports.ARm.Data;
    using PX.Reports.Controls;
    using System.Collections.Generic;
    
    namespace StackOverflow
    {
        static class ARMReportExtension
        {
            public static Report RenderARMReport(this IReportLoaderService reportLoader, string reportName, IDictionary<string, string> reportParams)
            {
                reportLoader.ThrowOnNull("reportLoader", null);
    
                //Load ARM report from sitemap URL
                Report report = reportLoader.LoadReport(reportName, null);
    
                //Retrieve report schema URL from report returned from sitemap URL
                string schemaUrl = report.SchemaUrl;
    
                RMReportReader ds = PXGraph.CreateInstance<RMReportReader>();
    
                //Check that report is from ARM
                if (!PXSiteMap.IsARmReport(schemaUrl))
                {
                    ds.ReportCode = ((!string.IsNullOrEmpty(schemaUrl) && schemaUrl.LastIndexOf('.') != -1) ? schemaUrl.Substring(0, schemaUrl.LastIndexOf('.')) : schemaUrl);
                }
    
                //Retrieve ARM report definition
                ARmReport armReport = ds.GetReport();
                if (armReport != null)
                {
                    //Create standard report from ARM Report
                    report = ARmProcessor.CreateReport(armReport);
                    SoapNavigator nav = new SoapNavigator(new PXGraph());
                    report.DataSource = nav;
                    report.ApplyRules(report);
    
                    //Initialize paramters
                    if (reportParams != null)
                    {
                        reportLoader.InitDefaultReportParameters(report, reportParams);
                        ARmProcessor.CopyParameters(report, armReport);
                    }
    
                    ARmReportNode aRmReportNode = ARmProcessor.ProcessReport(ds, armReport);
                    ARmProcessor.Render(aRmReportNode.ActiveNode, report);
                    report.Name = reportName;
                }
                //Returns report
                return report;
            }
        }
    }
    

    To test I implemented a processing page that iterates through financial periods and runs a series of ARM reports that are then combined and exported as a single .pdf file.

    using PX.Data;
    using PX.Objects.GL;
    using PX.Objects.GL.FinPeriods.TableDefinition;
    using PX.Reports;
    using PX.Reports.Controls;
    using PX.Reports.Data;
    using PX.SM;
    using System.Collections.Generic;
    
    using MailMessage = PX.Reports.Mail.Message;
    
    namespace StackOverflow
    {
        public class FinancialReportProc : PXGraph<FinancialReportProc>
        {
            #region Constructor
            public FinancialReportProc()
            {
                FinPeriods.SetProcessDelegate(GenerateAcumaticaReports);
            }
            #endregion
    
            #region Properties
    
            [InjectDependency]
            protected IReportLoaderService ReportLoader { get; set; }
    
            [InjectDependency]
            protected IReportDataBinder ReportDataBinder { get; set; }
            #endregion
    
            #region Views
    
            public PXCancel<FinPeriod> Cancel;
    
            [PXFilterable]
            public PXProcessing<FinPeriod, Where<FinPeriod.active,Equal<True>>> FinPeriods;
    
            #endregion
    
            #region Methods
            public static void GenerateAcumaticaReports(List<FinPeriod> periods)
            {
                FinancialReportProc graph = PXGraph.CreateInstance<FinancialReportProc>();
    
                Report report = null;
                Dictionary<string, string> parameters;
    
                //Iterate through records to have reports run against
                foreach (FinPeriod period in periods)
                {
                    //Create paramters for report
                    parameters = new Dictionary<string, string>
                    {
                        ["StartPeriod"] = FinPeriodIDAttribute.FormatPeriod(period.FinPeriodID)
                    };
    
                    //If the report is null it means a 'Root' report has not been rendered yet and needs to be.
                    //If report is NOT null 'Root' has been established and additional reports are added as siblings.
                    if (report != null)
                    {
                        report.SiblingReports.Add
                        (
                            graph.ReportLoader.RenderARMReport("RM000033", parameters)
                        );
                    }
                    else
                    {
                        report = graph.ReportLoader.RenderARMReport("RM000033", parameters);
                    }
                }
    
                //Generate report node from combined reports.
                ReportNode reportNode = graph.ReportDataBinder.ProcessReportDataBinding(report);
    
                //Generate combined PDF Report and create Acumatica file reference for export.
                FileInfo fileInfo = new FileInfo("CombinedReport.pdf", null, MailMessage.GenerateReport(reportNode, RenderType.FilterPdf)[0]);
    
                //Export combined .pdf report
                throw new PXRedirectToFileException(fileInfo, true);
            }
            #endregion
        }
    }
    

    Note : Parameter names for ARM reports do not necesarilly line up to field names seen on screen i.e. Ledger was actually 'BookCode'.