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