1
votes

Issue Summary:

The processing logic on my new screen is working but the page doesn't show any feedback to the user (e.g. Timer doesn't show, No Red/Green checkboxes, checkboxes aren't disabled)

Issue Detail:

I'm creating a processing screen that requires separate information from the user to be used by the processing delegate. The business logic works, but the user experience isn't like other processing screens. When you click Process there is nothing shown to the user. Normally the page refreshes the grid by disabling and showing only the selected items being processed, in addition to the long operation timer being added (then replaced by a green check or red x based on whether the process was successful or failed). When I click process all the entire grid's selected column is checked, but again nothing else changes (e.g. no timer, status, nor is the grid disabled). Ultimately both process and process all perform the business logic but the user doesn't see anything to indicate said success/failure.

The screen shows all customer locations because the process is updating some statistics that we are keeping for each location based on orders that exist for each location.

My Graph

public class CalculateLocationStatsProcess : PXGraph<CalculateLocationStatsProcess>
{
    public PXCancel<LocationStatsFilter> Cancel;

    public PXFilter<LocationStatsFilter> filterLocStat;
    public PXFilteredProcessingJoin<Location,
        LocationStatsFilter,
        InnerJoin<Customer, On<Customer.bAccountID, Equal<Location.bAccountID>>>,
        Where<True, Equal<True>>,
        OrderBy<Asc<Customer.acctCD, Asc<Location.locationCD>>>> processLocations;

    public CalculateLocationStatsProcess()
    {
    }

    public virtual void LocationStatsFilter_RowSelected(PXCache sender, PXRowSelectedEventArgs e)
    {
        processLocations.SetProcessDelegate(
            delegate (List<Location> list)
            {
                var newList = new List<Location>();
                foreach (Location locLp in list)
                {
                    newList.Add(locLp);
                }
                CalculateLocationStatsProcess.updateLocationStats(newList, filterLocStat.Current);
            }
        );
    }

    public static void updateLocationStats(List<Location> locations, LocationStatsFilter filter)
    {
        var graph = new PXGraph();
        var locStats = new StatsHelper(graph, filter.TargetDate);

        bool erred = false;
        for (int iLp = 0; iLp < locations.Count; iLp++)
        {
            Location locationLp = locations[iLp];
            PXProcessing<Location>.SetCurrentItem(locationLp);
            try
            {
                locStats.setCommStats(locationLp);
            }
            catch (Exception ex)
            {
                erred = true;
                PXProcessing<Location>.SetError(iLp, ex.Message);
            }
        }

        locStats.StatCache.Persist(PXDBOperation.Insert);
        locStats.StatCache.Persist(PXDBOperation.Update);

        if (erred)
        {
            throw new PXException("Location(s) failed during recalculation process. View individual lines to see their specific error.");
        }
    }
}

My Page

<%@ Page Language="C#" MasterPageFile="~/MasterPages/FormDetail.master" AutoEventWireup="true" ValidateRequest="false" CodeFile="SO509503.aspx.cs" Inherits="Page_SO509503" Title="Calculate Location Stats" %>
<%@ MasterType VirtualPath="~/MasterPages/FormDetail.master" %>

<asp:Content ID="cont1" ContentPlaceHolderID="phDS" runat="Server">
    <px:PXDataSource ID="ds" runat="server" Visible="True" Width="100%" PrimaryView="filterLocStat" TypeName="exampleNS.CalculateLocationStatsProcess">
        <CallbackCommands>
        </CallbackCommands>
    </px:PXDataSource>
</asp:Content>
<asp:Content ID="cont2" ContentPlaceHolderID="phF" runat="Server">
    <px:PXFormView ID="formFilter" runat="server" DataMember="filterLocStat" DataSourceID="ds" Style="z-index: 100" Width="100%" >
        <Template>
            <px:PXLayoutRule runat="server" ID="PXLayoutRule1" ControlSize="M" LabelsWidth="M" StartRow="true" />
            <px:PXDateTimeEdit runat="server" ID="edTargetDate" DataField="TargetDate" />
        </Template>
    </px:PXFormView>
</asp:Content>
<asp:Content ID="cont3" ContentPlaceHolderID="phG" runat="Server">
    <px:PXGrid ID="gridLocations" runat="server" 
        AdjustPageSize="Auto" AllowPaging="True" AllowSearch="true" DataSourceID="ds" FilesIndicator="false" 
        Height="400px" NoteIndicator="false" SkinID="Inquire" Style="z-index: 100" SyncPosition="true" Width="100%" >
        <AutoSize Container="Window" Enabled="True" MinHeight="200" />
        <Levels>
            <px:PXGridLevel DataMember="processLocations">
                <Columns>
                    <px:PXGridColumn DataField="Selected" Type="CheckBox" AllowCheckAll="true" Width="40px" />
                    <px:PXGridColumn DataField="Customer__AcctCD" Width="125px" />
                    <px:PXGridColumn DataField="LocationCD" Width="75px" />
                    <px:PXGridColumn DataField="Descr" Width="175px" />
                </Columns>
            </px:PXGridLevel>
        </Levels>
    </px:PXGrid>
</asp:Content>

My Filter DAC

public class LocationStatsFilter : IBqlTable
{
    #region TargetDate
    public abstract class targetDate : IBqlField { }
    [PXDate()]
    [PXUIField(DisplayName = "Target Month and Year")]
    public virtual DateTime? TargetDate { get; set; }
    #endregion
}

My Location Extension DAC

[PXTable(typeof(Location.locationID), typeof(Location.bAccountID), IsOptional = false)]
public class LocationExt : PXCacheExtension<Location>
{
    #region Selected
    public abstract class selected : IBqlField { }
    [PXBool()]
    [PXDefault(false)]
    [PXUIField(DisplayName = "Selected")]
    public virtual bool? Selected { get; set; }
    #endregion
    #region DateFirstService
    public abstract class dateFirstService : IBqlField { }
    [PXDBDate()]
    [PXUIField(DisplayName = "Date of First Service")]
    public virtual DateTime? DateFirstService { get; set; }
    #endregion
}

I modelled my solution after several processing screens that I found, but I've looked at so many I couldn't say which ones I used as examples. I've moved the SetProcessDelegate call between the RowSelected event and the Constructor with no luck. I've attempted making the updateLocationStats function static versus not static (using the existing graph instead) with no success.

UPDATE:

  • Calling the updateLocationStats method directly instead of creating a copy of the List didn't change the result.
  • Adding PXFilterable to the PXFilteredProcessingJoin didn't change the result
  • Removing the calls to locStats (creation, setCommStats(locationLp), Persist) didn't change the result
  • Added missing Location DAC
  • Attempted moving Selected from LocationExt DAC to a new LocationAlt : Location DAC with no change in result.
  • Added DataType="Boolean" to the selected field in the page. No change in behavior
  • Added BatchUpdate="True" to the PXGrid tag. No change in behavior.
  • Added PXProcessing.SetProcessed(); after locStats.setCommStats(locationLp); no change in behavior.

Alternate Test

public class LocationAlt : Location
{
    #region Selected
    public abstract class selected : IBqlField { }
    [PXBool()]
    [PXDefault(false)]
    [PXUIField(DisplayName = "Selected")]
    public virtual bool? Selected { get; set; }
    #endregion
}

public class CalculateLocationStatsProcess : PXGraph<CalculateLocationStatsProcess>
{
    public PXCancel<LocationStatsFilter> Cancel;

    public PXFilter<LocationStatsFilter> filterLocStat;
    [PXFilterable()]
    public PXFilteredProcessingJoin<LocationAlt,
        LocationStatsFilter,
        InnerJoin<Customer, On<Customer.bAccountID, Equal<LocationAlt.bAccountID>>>,
        Where<True, Equal<True>>,
        OrderBy<Asc<Customer.acctCD, Asc<LocationAlt.locationCD>>>> processLocations;

    public CalculateLocationStatsProcess()
    {
    }

    public virtual void LocationStatsFilter_RowSelected(PXCache sender, PXRowSelectedEventArgs e)
    {
        processLocations.SetProcessDelegate(
            delegate (List<LocationAlt> list)
            {
                CalculateLocationStatsProcess.updateLocationStats(list, filterLocStat.Current);
            }
        );
    }

    public static void updateLocationStats(List<LocationAlt> locations, LocationStatsFilter filter)
    {
        var graph = new PXGraph();
        var locStats = new CommStatsHelper(graph, filter.TargetDate);

        bool erred = false;
        for (int iLp = 0; iLp < locations.Count; iLp++)
        {
            LocationAlt locationLp = locations[iLp];
            PXProcessing<LocationAlt>.SetCurrentItem(locationLp);
            try
            {
                locStats.setCommStats(locationLp);
            }
            catch (Exception ex)
            {
                erred = true;
                PXProcessing<LocationAlt>.SetError(iLp, ex.Message);
            }
        }

        locStats.StatCache.Persist(PXDBOperation.Insert);
        locStats.StatCache.Persist(PXDBOperation.Update);

        if (erred)
        {
            throw new PXException("Location(s) failed during recalculation process. View individual lines to see their specific error.");
        }
    }
}

Note that the alternate test didn't work either.

2
I noticed that on your Persist you are using DataView. Did you try using graph global cache directly. Ex: "graph.Persist();" also, can you provide details about the StatsHelper() and setCommStats() methods? (is not clear if you are persising on the Same graph declared on your static method or if 'locStats' belongs to another graph.cbetabeta
@cbetabeta I understand the concern but even if I comment out all 4 lines that have locStats in them, the behavior of the page is the same. It doesn't put a timer, it doesn't put the Green/Red status indicators, and doesn't disable/shrink the grid.yc9nyw08vf
As an FYI, StatsHelper is a class that takes a graph as a parameter (stores it as a property and then uses it for any selects/persists it needs to do). The StatCache is just a read only property to get graph.Caches[typeof(LocationStat)]yc9nyw08vf
I think you should approach all your issues separately. For instance the timer could be hidden because you're not doing any significant work in the delegate. Try putting a Thread.Sleep in there instead of commenting the actual process.Hugues Beauséjour
There's many things that could go wrong in the code which is not in the question but we don't know since we don't see it. A more direct approach would eliminate the ambiguity. For example, don't create a list copy 'var newList' for the processed records, use the delegate 'List<Location> list' directly. Try modifying and saving the records directly, ex: list[0].Active = false; sender.Caches[typeof(Location)].Update(list[0]); sender.Caches[typeof(Location)].Persist(PXDBOperation.Update);.Hugues Beauséjour

2 Answers

2
votes

The problem being experienced is because the originating graph instance (that is the instance of the graph for which you are in when you first enter the screen) isn't maintaining scope to the execution graph instance (that is the instance of the graph which is doing the logic).

For Acumatica to put the processing status logic (e.g. Timer, Green/Red Status circles) it appears the originating graph needs an link to the graph performing the actions. This appears to be handled for you just fine when you point SetProcessDelegate to a method delegate.

In the issue here, you are creating an ad-hoc delegate (by using the delegate keyword in the constructor/rowselected event). The variable declaration (not instantiation) needs to be outside of the delegate.

Corrected Setting of Delegate

CalculateLocationStatsProcess graph = null;
processLocations.SetProcessDelegate((List<Location> list) => 
    {
        graph = PXGraph.CreateInstance<CalculateLocationStatsProcess>();
        CalculateLocationStatsProcess.updateLocationStats(list, filterLocStat.Current);
    }
);

Note the first line is before the SetProcessDelegate call. Even though the graph isn't instantiated outside the delegate, the graph pointer is created. Thus it appears when assigned a link is made and the UI updates as desired.

Additional Notes

  • The selected field still exists as part of the extension
  • I left the function static but it did work as desired if converted to non static, you simply change CalculateLocationStatsProcess.updateLocationStats(list, filterLocStat.Current); to graph.updateLocationStats(list, filterLocStat.Current);
  • The delegate declaration can occur in either the Constructor public CalculateLocationStatsProcess() or in the Filter's Row Selected Event Handler public virtual void LocationStatsFilter_RowSelected(PXCache sender, PXRowSelectedEventArgs e) both worked for me.
  • BatchUpdate="true" isn't necessary.
  • DataType="Boolean" isn't necessary.

Lastly, the linked Customer__AcctCD clears when the process is finished (the join doesn't appear to keep the fields populated) so some code for displaying the AcctCD on the graph will need changing. Since this wasn't the focus of the question presented, I do not plan to include that code here.

0
votes

I believe you are missing the Selected data field in your DAC:

#region Selected
public abstract class selected : IBqlField
{
}
protected bool? _Selected = false;
[PXBool]
[PXDefault(false)]
[PXUIField(DisplayName = "Selected")]
public bool? Selected
{
    get
    {
        return _Selected;
    }
    set
    {
        _Selected = value;
    }
}
#endregion

Instead of using Location directly, create a DAC extension for Location (LocationStat?) where you will add the Selected field. Don't use Location in your processing screen, use the extension containing the selected field.