One of the common requirements that one may have for the inquiry form is to have Totals calculated for some column in the grid.
It may seem a simple task, but there are some unexpected obstacles that make it hard to implement the feature properly.
Straightforward Solution
We can implement the requirement with the help of a filter delegate.
Assume that we have the filter view and the grid view.
public PXFilter<FilterDAC> Filter; public PXSelectReadonly<ResultsDAC> Records;
Add a field for accumulated result to the filter DAC.
public partial class FilterDAC: IBqlTable { ... public abstract class total : PX.Data.IBqlField { } [PXDecimal()] [PXDefault(TypeCode.Decimal, "0.0")] [PXUIField(DisplayName = "Total", Enabled = false)] public virtual decimal? Total { get; set; } ... }
In the filter delegate get records from the grid view. For example:
protected virtual IEnumerable filter() { FilterDAC filter = Filter.Current; if(filter != null) { int startRow=0; int totalRows=0; filter.Total = 0; //reset total to zero foreach(ResultDAC row in Records.Select()) { filter.Total += row.Amount; } } yield return filter; }
This code works, but it has several issues:
- It does not consider user defined filter for the grid
- Filter is not refreshed when something changed in the grid
- It executes select that may return significant amount of rows and therefore affects performance in a bad way
Implementing User-defined Filters
To solve the first issue, we can extract user-defined filters from grid view and send them to the select we use to calculate totals. Example:
protected virtual IEnumerable filter() { FilterDAC filter = Filter.Current; if(filter != null) { filter.Total = 0; //reset total to zero foreach(ResultDAC row in Records.View.Select(new[] { filter }, //pass filter context to the grid view delegate null, null, null, null, Records.View.GetExternalFilters(), //Get grid user filters ref startRow, 0, //get all records without regard to paging ref totalRows)) { filter.Total+= row.Amount; } } yield return filter; }
Once this implemented, the second issue becomes vividly visible. After applying grid filter, it is required to change something in the filter to refresh it. Otherwise Total value stays the same.
Implementing Filter Refresh
To refresh the header after filter was applied to the grid, one can use Repaint Controls behavior defined in aspx:
The property is located in the Grid -> Callback Commands ->Refresh. One can specify controls that should be refreshed after grid refresh here.
<px:PXGrid ...> <CallbackCommands> <Refresh RepaintControlsIDs="form" /> </CallbackCommands>
Moving Totals Calculations to SQL Server
In Acumatica ERP, standard grid selects have built-in optimization: the system selects only those records that are visible on the current page. However, to calculate totals we have to select all records. That may have a negative performance impact. Better approach here is to get already aggregated data from the SQL server. To do that you add aggregation to the exiting BQL select. This will allow you to calculate totals on SQL level keeping amount of records retrieved from itl as low as possible.
protected virtual IEnumerable filter() { FilterDAC filter = Filter.Current; if(filter != null) { int startRow=0; int totalRows=0; var cmd = Records.View.BqlSelect.AggregateNew<Aggregate<Sum<ResultDAC.Amount>>>();// add aggregation to the existing BQL foreach (ResultDAC row in new PXView(this, false, cmd).Select( new[] { filter }, //pass filter context to the grid view delegate null, null, null, null, Records.View.GetExternalFilters(), //Get grid user filters ref startRow, 0, ref totalRows)) { filter.Total = row.Amount; } } yield return filter; }
Dmitry,
I used your technique and it works nicely, but only if I specify a filter. How can I get the total to be calculated even if there are no filters specified.
For calculating grid total in inquiry form, we must use fieldselecting of filter form.
And In this fieldselecting event, write the dynamic Bql query to get the filtered record which you get the same record in grid.
Event =====FilterDAC_TotalScheduled_FieldSelecting(Cache, e.row)
For calculating grid total in inquiry form, we must use fieldselecting of filter form.
And In this fieldselecting event, write the dynamic Bql query to get the filtered record which you get the same record in grid.
Event =====FilterDAC_TotalScheduled_FieldSelecting(Cache, e.row)
protected void MasterTable_TotalAmount_FieldSelecting(PXCache sender,
PXFieldSelectingEventArgs e)
{
MasterTable filter = e.Row as MasterTable;
decimal val = 0m;
if (filter != null)
{
BqlCommand queryTotal = new PXSelectJoinGroupBy<ARTran,
InnerJoin<PMTran,
On<PMTran.aRRefNbr, Equal,
And<PMTran.refLineNbr, Equal>>,
InnerJoin<Contract,
On<Contract.contractID, Equal>>>,
Aggregate<Sum>>(this).View.BqlSelect;
FilterTransactions(ref queryTotal, filter);/// This for filtering dynamically
PXResult total = new PXView(this, true, queryTotal)
.SelectSingle() as PXResult;
if (total != null)
{
val = ((ARTran)total).CuryExtPrice.GetValueOrDefault();
}
}
e.ReturnValue = val;
}
We are trying to fix an inquiry form to only have two decimal places. How do we do this?
If you are in 2019R1 version open the Companies form (CS101500). You can specify the decimal precision by changing the number specified in the Price / Cost Decimal box. This box is located underneath the Miscellaneous Settings on the Companies form. Hope this helps.
Starting from 2019R1, you can also use LINQ2BQL instead of Records.View.BqlSelect.AggregateNew to perform the aggregation on the SQL side.