1

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.

  • If there are parameters that are requested for the report, the report will not run even when passing the parameters to the PXReportRequiredException. It will just open the entry screen for the report where the parameters can be entered before you can manually run the report, which defeats the whole purpose of the process screen.

  • If no parameters are requested, the report will run with the default parameter values defined on the Report Definitions screen. However, if I try to use the PXReportRequiredException.CombineReport method when multiple reports need to be combined, it only generates a blank single-paged report instead of combining multiple reports together.

  • I've even gone as far as to attempt to edit the RMDataSource record where the default values for the parameters come from before generating the report. This worked for generating a report for a single branch but obviously did not work for multiple b/c it would attempt to use the same parameters for all reports.

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.

kchron
  • 29
  • 3
  • Would it fit your business use case that instead of redirecting to it just exports the combined reports as a pdf? I have the combined report generation. – Joshua Van Hoesen Feb 28 '23 at 19:33
  • @JoshuaVanHoesen Yes, this would also be acceptable. I was mainly just confused as to why the financial reports behave differently than reports created in the report designer when trying to generate them in the code. – kchron Feb 28 '23 at 19:40
  • After some digging it appears Acumatica checks if its an ARM report and utilizes different methdology for generation and presentation. I've figure out the generation and combination of reports but not yet how to display the combined report as a redirect page. – Joshua Van Hoesen Feb 28 '23 at 20:53

1 Answers1

0

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'.

Joshua Van Hoesen
  • 1,684
  • 15
  • 30