Acumatica Processing Screen with Parameter Doesn't Refresh - acumatica

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.

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.

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.

Related

Dialog box for new action on SO301000 screen, cannot add more than one item to the grid

I am trying to add a dialog box to the SO301000 screen, this dialog box (I think its also considered a smart panel but the difference is lost on me) is just supposed to show a list of orders that a customer has made.
What I have working:
1: I am able to pull all of the orders that a customer has made.
2: I am able to open/close the dialog box after clicking the action.
3: An order IS able to be put into grid.
What doesn't work:
1: I am unable to get more than one order into the grid.
I have no need to edit the orders in this grid, I just want to puke out quick information.
public class SOOrderEntry_Extension : PXGraphExtension<SOOrderEntry>
{
public PXFilter<MSCustomerOrderDac> MSCustomerViewForm;
public PXFilter<MSCustomerOrderDac> MSCustomerOrderViews; //Issue.
public PXAction<SOOrder> ViewCustomerOrders;
[PXUIField(DisplayName = "View Custoemr", MapEnableRights = PXCacheRights.Select, MapViewRights = PXCacheRights.Select)]
[PXButton(Category = "Other")]
[PXUIEnabled(typeof(Where<SOOrder.customerID.IsNotNull>))]
protected virtual IEnumerable viewCustomerOrders(PXAdapter adapter)
{
MSCustomerOrderViews.AllowInsert=true;
PXSelectBase<SOOrder> orders =
new PXSelectReadonly<SOOrder,
Where<SOOrder.customerID, Equal<Current<SOOrder.customerID>>>>(Base);
int linenumber = 0;
foreach (SOOrder order in orders.Select())
{
MSCustomerOrderDac newOrder = new MSCustomerOrderDac();
newOrder.OrderNumber = order.OrderNbr;
newOrder.LineNbr = linenumber++;
newOrder = MSCustomerOrderViews.Insert(newOrder);
}
if (MSCustomerViewForm.AskExt(true) != WebDialogResult.OK) //need this to show the form
{}
return adapter.Get();
}
[PXVirtual]
[Serializable]
public class MSCustomerOrderDac : IBqlTable
{
#region OrderNumber
[PXString]
[PXUIField(DisplayName = "Order Number")]
public virtual String OrderNumber { get; set; }
public abstract class orderNumber : PX.Data.BQL.BqlString.Field<orderNumber> { }
#endregion
[PXInt(IsKey = true)]
public virtual Int32? LineNbr { get; set; }
public abstract class lineNbr : PX.Data.BQL.BqlInt.Field<lineNbr> { }
}
}
This is the whole of my code, I also tried breaking the loop and adding more than 1 items manually but that made no difference.
I also found this thread on the community forums: https://community.acumatica.com/customizations-187/dialog-with-grid-with-in-memory-dac-from-action-button-8578
However I think he and I were having different issues.
Also, I have just noticed that the order that it is pushing is aways the same one.
The following code snippits will give you the desired functionality.
Create a graph extension that has an declared data view that will retrieve the information desired, in this case SOOrders related to the Customer.
public class SOOrderEntryExtension : PXGraphExtension<SOOrderEntry>
{
public PXSelectReadonly<SOOrder2, Where<SOOrder2.customerID, Equal<Current<SOOrder.customerID>>>> RelatedOrders;
public PXAction<SOOrder> ViewCustomerOrders;
[PXUIField(DisplayName = "View Customer Orders", MapEnableRights = PXCacheRights.Update, MapViewRights = PXCacheRights.Update)]
[PXButton]
protected virtual void viewCustomerOrders()
{
RelatedOrders.AskExt(true);
}
protected virtual void __(Events.RowSelected<SOOrder> e)
{
if(e.Row is SOOrder row)
{
ViewCustomerOrders.SetVisible(true);
ViewCustomerOrders.SetEnabled(true);
}
}
}
If multiple views are declared on a graph / extension that utilize the same DAC issues may occur. To overcome this issue a derived DAC needs to be created [SOOrder2] with the key / search criteria fields redeclared which will allow the framework to properly segregate the cache types.
[Serializable]
[PXHidden]
public class SOOrder2 : SOOrder
{
#region DocType
public new abstract class orderType : BqlString.Field<orderType>
{
}
#endregion
#region RefNbr
public new abstract class orderNbr : BqlString.Field<orderNbr>
{
}
#endregion
#region CustomerID
public new abstract class customerID : BqlInt.Field<customerID>
{
}
#endregion
}
The smart panel that your view utilizes can be found below :
<px:PXSmartPanel runat="server" ID="CustomerRelatedOrderPnl" Height="550px" Width="950px" CaptionVisible="True" Caption="Related Orders" Key="RelatedOrders" AutoCallBack-Target="CustomerRelatedOrderGrid" AutoCallBack-Command="Refresh">
<px:PXGrid runat="server" ID="CustomerRelatedOrderGrid" SyncPosition="True" Height="100%" SkinID="Inquire" Width="100%" DataSourceID="ds" NoteIndicator="False">
<AutoSize Enabled="True" />
<Levels>
<px:PXGridLevel DataMember="RelatedOrders">
<Columns>
<px:PXGridColumn DataField="OrderType" Width="140" />
<px:PXGridColumn DataField="OrderNbr" Width="140" />
<px:PXGridColumn DataField="CustomerID" Width="140" />
<px:PXGridColumn DataField="Status" Width="140" />
<px:PXGridColumn DataField="OrderDate" Width="140" />
<px:PXGridColumn DataField="CuryOrderTotal" Width="140" />
</Columns>
</px:PXGridLevel>
</Levels>
</px:PXGrid>
<px:PXPanel runat="server" ID="CustomerRelatedOrderButtonPnl">
<px:PXButton runat="server" DialogResult="OK" Text="Ok" CommandSourceID="ds" ID="CustomerRelatedOrderOK" />
</px:PXPanel>
</px:PXSmartPanel>
The results of this code can be seen here :
Not an answer, but I don't have enough reputation to comment.
Some extra information :
A PXFilter View can only have 1 instance of the DAC on the Graph.
You don't need to create a PXSelectBase instance unless you're going to be modifying the query based on conditions, or unless you're going to pass it somewhere else, such as into a PXFieldScope. You can simply do this (Using the proper separate DAC to prevent caching issues) :
foreach
(
SOOrder2 soOrder in PXSelectReadonly
<
SOOrder2,
Where
<
SOOrder2.customerID, Equal<Current<SOOrder.customerID>>
>
>.Select(Base)
)
{
...
}
By performing your Select and Insert inside the Action Delegate rather than having a View like in Josh's answer, or having a View Delegate, your logic will insert records every single time someone pushes the button, even if they already exist in the Cache.
You shouldn't need to check the result of AskExt.
I'm not 100% sure about this : If User A has SOOrderEntry open on an SOOrder and sits there for a bit, then User B creates a new SOOrder for the same Customer, if User A opens the popup, then it could be possible that Acumatica may not run the query again, and the new SOOrder may not pop up. If you experience this behavior, then you would have to Clear the View before calling AskExt. Acumatica should then run the query again, and the new SOOrder should appear.
Acumatica may try to persist your View, so you should put a PXVirtual Attribute on it, following the instructions in its summary tags.
You may want to add conditions to your query to exclude the current record from the results, unless it is your desire to include the current record in your popup.
Since #Joshua Van Hoesen gave such a detailed answer, I am going to post the solution I came to for completeness's sake and if anyone wants to see potentially another way to do it.
public class SOOrderEntry_Extension : PXGraphExtension<SOOrderEntry>
// It might be best practice to move these to their own file, but I think its better to keep it all together.
{
// This is just for the form so the cache for the grid doesnt get over written.
public PXFilter<MSCustomerOrderDac> MSCustomerViewForm;
// This is the for the grid, we have to use PXSelect because it is able to grab more than one item.
// Since our DAC is virtual we must mark it as such AND create a delagate.
[PXVirtualDAC]
public PXSelect<MSCustomerOrderDac> MSCustomerOrderViews;
// This delegate overrides the database call
// first letter in lower case (Your view has it Upper case sicne hte names must be the same)
public IEnumerable mSCustomerOrderViews()
{
// This just grabs the current view and ensures we dont go to the db, theoretically we could include anything.
return MSCustomerOrderViews.Cache.Cached;
}
// This is our action, were need the map rights so we can interact with things.
public PXAction<SOOrder> ViewCustomerOrders;
[PXUIField(DisplayName = "View Customer Order History", MapEnableRights = PXCacheRights.Select, MapViewRights = PXCacheRights.Select)]
[PXButton(Category = "Other",CommitChanges =true)]
[PXUIEnabled(typeof(Where<SOOrder.customerID.IsNotNull>))]
// This is the code that runs for the action that fills the grid.
protected virtual IEnumerable viewCustomerOrders(PXAdapter adapter)
{
PXSelectBase<SOOrder> orders =
new PXSelectReadonly<SOOrder,
Where<SOOrder.customerID, Equal<Current<SOOrder.customerID>>>>(Base);
int linenumber = 0;
foreach (SOOrder order in orders.Select())
{
linenumber++;
MSCustomerOrderViews.Current = MSCustomerOrderViews.Insert(new MSCustomerOrderDac() { LineNbr = linenumber });
var newOrder = MSCustomerOrderViews.Current;
newOrder.OrderNumber = order.OrderNbr;
MSCustomerOrderViews.Update(newOrder);
}
if (MSCustomerViewForm.AskExt(true) != WebDialogResult.OK) {} // This is how we get the form to show up, we want it after the loop so it is filled once it pops up.
return adapter.Get();
}
// This is our virtual DAC. When we create a Virtual DAC we HAVE to have a key column, in this case it is the line number.
[PXVirtual]
[Serializable]
[PXHidden]
public class MSCustomerOrderDac : IBqlTable
{
#region OrderNumber
[PXString]
[PXUIField(DisplayName = "Order Number")]
public virtual String OrderNumber { get; set; }
public abstract class orderNumber : PX.Data.BQL.BqlString.Field<orderNumber> { }
#endregion
#region LineNumber
[PXInt(IsKey = true)]
public virtual Int32? LineNbr { get; set; }
public abstract class lineNbr : PX.Data.BQL.BqlInt.Field<lineNbr> { }
#endregion
}
}
The comments are just for future me.
If anyone knows potential performance hits that could happen on mine please let me know!

How to get the default value for the Transaction Period and Financial Period in Acumatica

I have a customization form that I need to fill “Transaction Period” and company’s “Financial Period” by defult from a date field and the selected branch but I can not make it work. I reviewed a couple Acumatica’s owen screens and I’m doing exctly the same (at least I think so) but these two fields are not filled by default. Any help appreciated. Here is my little Graph, partial DAC and Partial ASPX.
namespace MyCustom
}
// Graph
public class PMCashflowProjectionEntry : PXGraph<PMCashflowProjectionEntry, PMCashflowProjection>
{
[PXViewName("Projects Cashflow Projections")]
public PXSelect<PMCashflowProjection> CashflowProjections;
public PXSelect<PMCashflowProjectionSchedule, Where<PMCashflowProjectionSchedule.projectID, Equal<Current<PMCashflowProjection.projectID>>>> CashflowProjectionSchedules;
}
//Partial Header DAC
public class PMCashflowProjection : IBqlTable
{
#region BranchID
public abstract class branchID : PX.Data.BQL.BqlInt.Field<branchID> { }
protected Int32? _BranchID;
[Branch()]
[PXForeignReference(typeof(Field<branchID>.IsRelatedTo<Branch.branchID>))]
[PXUIField(DisplayName = "Branch", Visibility = PXUIVisibility.SelectorVisible, Required = true, Enabled = true, Visible = true)]
public virtual Int32? BranchID
{
get
{
return this._BranchID;
}
set
{
this._BranchID = value;
}
}
#endregion
}
//Partial Detail DAC
public class PMCashflowProjectionSchedule : IBqlTable
{
#region Date
public abstract class date : PX.Data.BQL.BqlDateTime.Field<date> { }
protected DateTime? _Date;
[PXDBDate()]
[PXDefault(typeof(AccessInfo.businessDate), PersistingCheck = PXPersistingCheck.Nothing)]
[PXUIField(DisplayName = "Date", Visibility = PXUIVisibility.SelectorVisible, Required = true, Enabled = true, Visible = true, IsReadOnly = false)]
public virtual DateTime? Date
{
get
{
return this._Date;
}
set
{
this._Date = value;
}
}
#endregion
#region TranPeriodID
public abstract class tranPeriodID : PX.Data.BQL.BqlString.Field<tranPeriodID> { }
protected String _TranPeriodID;
[PXDefault()]
[PeriodID(
searchType: null,
sourceType: typeof(date),
defaultType: null,
redefaultOnDateChanged: true)]
[PXUIField(DisplayName = "Tran. Period", Visibility = PXUIVisibility.SelectorVisible, Required = true, Enabled = true, Visible = true)]
public virtual String TranPeriodID
{
get
{
return this._TranPeriodID;
}
set
{
this._TranPeriodID = value;
}
}
#endregion
#region FinPeriodID
public abstract class finPeriodID : PX.Data.BQL.BqlString.Field<finPeriodID> { }
protected String _FinPeriodID;
[PXDefault()]
[FinPeriodID(
sourceType: typeof(date),
branchSourceType: typeof(PMCashflowProjection.branchID),
masterFinPeriodIDType: typeof(tranPeriodID),
headerMasterFinPeriodIDType: typeof(tranPeriodID),
redefaultOnDateChanged: true)]
[PXUIField(DisplayName = "Fin. Period", Visibility = PXUIVisibility.SelectorVisible, Required = true, Enabled = true, Visible = true)]
public virtual String FinPeriodID
{
get
{
return this._FinPeriodID;
}
set
{
this._FinPeriodID = value;
}
}
#endregion
}
}
Partial ASPX Example
//Form
<px:PXSegmentMask runat="server" ID="edBranchID" DataField="BranchID" CommitChanges="True" AutoRefresh="True" />
//Grid Clumns
<px:PXGridColumn DataField="Date" Width="80" CommitChanges="True" ></px:PXGridColumn>
<px:PXGridColumn DataField="TranPeriodID" Width="80" CommitChanges="True" ></px:PXGridColumn>
<px:PXGridColumn DataField="FinPeriodID" Width="80" CommitChanges="True" ></px:PXGridColumn>
// Grid Row Template
<px:PXDateTimeEdit runat="server" ID="edDate" DataField="Date" CommitChanges="True" AutoRefresh="True" ></px:PXDateTimeEdit>
<px:PXMaskEdit runat="server" ID="edTranPeriodID" DataField="TranPeriodID" CommitChanges="True" Size="s" ></px:PXMaskEdit>
<px:PXMaskEdit runat="server" ID="edFinPeriodID" DataField="FinPeriodID" CommitChanges="True" Size="s" ></px:PXMaskEdit>
The work of PeriodId attribute and its descendants looks kind of tricky, as they share their cache. Without doing this customization and debugging, the rule of a thumb is to mimic the declaration of multiple out-of-the box DACs using these periods. I see two differencies of this declaration from standard which might play a role:
FinPeriodId generally precedes the TranPeriodId declaration. It looks important because the source field used for the periodid defaulting comes from the specification for the attribute on the FinPeriodId field
In the sourceType declaration, the DAC name is generally specified. So consider using the following syntax:
[FinPeriodID(
sourceType: typeof(PMCashflowProjectionSchedule.date),
You could use PXDefault attribute to set a default value on page load and if you would like to allow the users to change the default value the field can be made as a Selector type with PXSelector attribute.
an example below for PXDefault attribute getting default value from the financial periods and current business date :
[PXUIField(DisplayName = "Fin. Period", Required = true)]
[FinPeriodIDFormatting()]
[PXDefault(typeof(SearchFor<MasterFinPeriod.finPeriodID>
.Where<MasterFinPeriod.endDate.IsGreaterEqual<AccessInfo.businessDate.FromCurrent>
.And<MasterFinPeriod.startDate.IsLessEqual<AccessInfo.businessDate.FromCurrent>>>),
PersistingCheck = PXPersistingCheck.Nothing)]
[PXSelector(typeof(SearchFor<MasterFinPeriod.finPeriodID>.In<SelectFrom<MasterFinPeriod>
.Where<MasterFinPeriod.endDate.IsLessEqual<AccessInfo.businessDate.FromCurrent>
.And<MasterFinPeriod.finPeriodID.IsNotNull>>>
.OrderBy<MasterFinPeriod.finPeriodID.Desc>>))]
FinPeriodIDFormatting() attribute formats to MM-YYYY
<px:PXSelector CommitChanges="True" runat="server" ID="edTranPeriodID" DataField="TranPeriodID" ></px:PXSelector>
<px:PXSelector CommitChanges="True" runat="server" ID="edFinPeriodID" DataField="FinPeriodID" ></px:PXSelector>

How to place a 'Related Entity' lookup on a field

In the Activity / Task screen (cr306020), there is a 'Related Entity' field with a PXSelector lookup as well as a pencil for opening the screen of the related entity:
I'd like to know if there is a way to do this for a custom field. I've looked at the source code for the field (it's EPActivity.Source in the DAC), but I see nothing that puts these attributes on that field. No PXSelector or anything similar.
The example below shows how to add the Related Entity field on the Opportunities (CR304000) screen. Please be aware, PXRefNoteSelector control used in this sample is not currently supported by the Layout Editor in Acumatica Customization Manager. I used Opportunities to simplify and shorten the example. Unfortunately, for now you can only add the Related Entity field on a custom screen.
Now let's move forward to the sample:
Implement extension for the CROpportunity DAC to declare the database bound UsrRefNoteID and unbound RelatedEntity fields. Related Entity's NoteID will be stored in UsrRefNoteID, and RelatedEntity will be used to display Related Entity's user-friendly description:
public class CROpportunityExt : PXCacheExtension<CROpportunity>
{
#region UsrRefNoteID
public abstract class usrRefNoteID : IBqlField { }
protected Guid? _UsrRefNoteID;
[PXDBGuid]
[PXParent(typeof(Select<CRActivityStatistics,
Where<CRActivityStatistics.noteID, Equal<Current<CROpportunityExt.usrRefNoteID>>>>), LeaveChildren = true)]
public Guid? UsrRefNoteID
{
get
{
return _UsrRefNoteID;
}
set
{
_UsrRefNoteID = value;
}
}
#endregion
#region Source
public abstract class relatedEntity : IBqlField { }
[PXString(IsUnicode = true)]
[PXUIField(DisplayName = "Related Entity Description", Enabled = false)]
[PXFormula(typeof(EntityDescription<CROpportunityExt.usrRefNoteID>))]
public string RelatedEntity { get; set; }
#endregion
}
Create extension for the OpportunityMaint BLC to decorate its primary Opportunity data view with PXRefNoteSelectorAttribute. The PXRefNoteSelectorAttribute is required for the Edit (pencil) and Lookup buttons to work on your custom Related Entity field:
public class OpportunityMaintExt : PXGraphExtension<OpportunityMaint>
{
[PXCopyPasteHiddenFields(typeof(CROpportunity.resolution))]
[PXViewName(Messages.Opportunity)]
[PXRefNoteSelector(typeof(CROpportunity), typeof(CROpportunityExt.usrRefNoteID))]
public PXSelect<CROpportunity> Opportunity;
}
On Aspx page, add PXRefNoteSelector control with the DataField property set to RelatedEntity and NoteIDDataField to UsrRefNoteID.
For the EditButton, LookupButton and LookupPanel tags, use the primary data view name decorated with the PXRefNoteSelector attribute (Opportunity in the code snippet below)
<pxa:PXRefNoteSelector ID="edRefEntity" runat="server" DataField="RelatedEntity" NoteIDDataField="UsrRefNoteID"
MaxValue="0" MinValue="0" ValueType="Guid" CommitChanges="true">
<EditButton CommandName="Opportunity$Navigate_ByRefNote" CommandSourceID="ds" />
<LookupButton CommandName="Opportunity$Select_RefNote" CommandSourceID="ds" />
<LookupPanel DataMember="Opportunity$RefNoteView" DataSourceID="ds" TypeDataField="Type" IDDataField="NoteID" />
</pxa:PXRefNoteSelector>
Hide 3 actions generated by the PXRefNoteSelector attribute from the form toolbar. Use the same primary data view name decorated with the PXRefNoteSelector attribute (Opportunity in the code snippet below) as in the step above:
<CallbackCommands>
...
<px:PXDSCallbackCommand Name="Opportunity$Navigate_ByRefNote" Visible="False" />
<px:PXDSCallbackCommand Name="Opportunity$Select_RefNote" Visible="False" />
<px:PXDSCallbackCommand Name="Opportunity$Attach_RefNote" Visible="False" />
</CallbackCommands>
You might also need to implement your own EntityDescription operator, since at the time this example was created, it had internal access modifier and was not available outside of the PX.Objects.dll:
public class EntityDescription<RefNoteID> : BqlFormulaEvaluator<RefNoteID>, IBqlOperand
where RefNoteID : IBqlField
{
public override object Evaluate(PXCache cache, object item, Dictionary<Type, object> pars)
{
Guid? refNoteID = (Guid?)pars[typeof(RefNoteID)];
return new EntityHelper(cache.Graph).GetEntityDescription(refNoteID, item.GetType());
}
}
And finally... screenshot of the customized Opportunities screen with a brand-new Related Entity field:

Showing smartpanel after field value changes - grid remains empty

I have the need in one of my customization to show a popup directly after the user modifies the value of one of the controls (in this case, a custom field in the SOLine of the Sales Order Entry screen). This popup shows some additional values in a grid that the user must select before completing the row.
Using the standard process a SmartPanel was added to the screen.
If I call this from an action / PXLookupButton, the popup shows and the grid is populated correctly.
If I move this to either the "FieldUpdated" or "RowSelected" event, the smartpanel is displayed however the grid is always empty. Once more, if I then click on the button the grid stays empty till I cancel the modifications and re-enter using only the button.
I tried calling the action's press method in these events as well but the same result occurs.
Watching SQL profiler and the debugger events I can see that the BQL statement is being executed and returning the correct rows it's just not displaying in the smartpanel's grid.
Is it possible to handle this type of request? I'm assuming I need to either move this to a different method and/or pass some additional values but haven't found the right combination.
This holds true on Acumatica 5.3 / 6.1
Any input would be appreciated.
RowUpdated handler allowed me to achieve requested behavior and show SmartPanel after field value change.
Example below relies on custom unbound Trigger Dialog field declared for the SOLine DAC. When a user checks or uncheckes Trigger Dialog flag, the system will show Item Quantity dialog to update Quantity for selected SOLine record:
public class SOLineExt : PXCacheExtension<SOLine>
{
#region TriggerDialog
public abstract class triggerDialog : PX.Data.IBqlField
{
}
[PXBool]
[PXUIField(DisplayName = "Trigger Dialog")]
public virtual bool? TriggerDialog { get; set; }
#endregion
}
Very basic SmartPanel declaration in Aspx:
<px:PXSmartPanel runat="server" ID="CstSmartPanel2" Key="SOLineParam" Caption="Item Quantity" AutoRepaint="True"
CaptionVisible="True" AcceptButtonID="CstButton6" AutoReload="true" >
<px:PXFormView runat="server" ID="CstFormView3" DataMember="SOLineParam" SkinID="Transparent" >
<Template>
<px:PXLayoutRule runat="server" StartColumn="True" />
<px:PXNumberEdit runat="server" ID="CstPXNumberEdit10" DataField="OrderQty" />
</Template>
</px:PXFormView>
<px:PXLayoutRule runat="server" StartRow="True" />
<px:PXPanel runat="server" ID="CstPanel5" SkinID="Buttons">
<px:PXButton runat="server" ID="CstButton6" DialogResult="OK" CommandName="ChangeOk" CommandSourceID="ds" />
<px:PXButton runat="server" ID="CstButton7" DialogResult="Cancel" Text="Cancel" />
</px:PXPanel>
</px:PXSmartPanel>
Accomplished with the SOOrderEntry BLC extension subscribing to RowUpdated handler for the SOLine DAC to show Item Quantity dialog to a user:
public class SOOrderEntryExt : PXGraphExtension<SOOrderEntry>
{
[Serializable]
public class SOLineParams : IBqlTable
{
#region OrderQty
public abstract class orderQty : PX.Data.IBqlField
{
}
[PXDBDecimal]
[PXDefault(TypeCode.Decimal, "0.0")]
[PXUIField(DisplayName = "Quantity")]
public virtual decimal? OrderQty { get; set; }
#endregion
}
public PXFilter<SOLineParams> SOLineParam;
public PXAction<SOOrder> ChangeOk;
[PXUIField(DisplayName = "OK")]
[PXButton(CommitChanges = true)]
protected void changeOk()
{
var lineParams = SOLineParam.Current;
Base.Transactions.Cache.SetValue<SOLine.orderQty>(Base.Transactions.Current, lineParams.OrderQty);
SOLineParam.Cache.Clear();
}
public void SOLine_RowUpdated(PXCache sender, PXRowUpdatedEventArgs e)
{
if (!sender.ObjectsEqual<SOLineExt.triggerDialog>(e.Row, e.OldRow) && e.ExternalCall == true)
{
SOLineParam.AskExt();
}
}
}
Another part of the extension class is ChangeOk action invoked by SmartPanel to update Quantity for selected record in the Document Details grid. To hide ChangeOk action from screen toolbar, it's also necessary to add the following command into PXDataSource.CallbackCommands collection:
<px:PXDSCallbackCommand Name="ChangeOk" Visible="False" />

usercontrol in asp.net

i have one page and 2 user control in it and first user control have dropdownlist and second user control have another dropdown list , when we select dropdownlist of first user control than should be filled another dropdown list of second user control.... how can we achieve it ...please explaing in detain
thanks in advance...
I would expose the child DropDownList's OnSelectedItemChanged event AND the actual DropDownList at the top public level for the user control.
This would allow you to catch the OnSelectedItemChanged event in the Page and set the value of the second user control.
Let me know if you want some sample code.
Ok, so first the user control
<%# Control Language="C#" AutoEventWireup="true" CodeBehind="SampleUserControl.ascx.cs" Inherits="WebApplication1.UserControls.SampleUserControl" %>
<asp:DropDownList runat="server" ID="DdlTest" AutoPostBack="true">
<asp:ListItem Text="Sampe 1" />
<asp:ListItem Text="Sampe 2" />
</asp:DropDownList>
now the file behind that
public partial class SampleUserControl : System.Web.UI.UserControl
{
public DropDownList InternalDropDownList
{
get { return DdlTest; }
}
protected void Page_Load(object sender, EventArgs e)
{
}
}
okay, lets go to the actual .aspx
<form id="form1" runat="server">
<div>
<uc1:SampleUserControl ID="SampleUserControl1" runat="server" />
<uc1:SampleUserControl ID="SampleUserControl2" runat="server" />
</div>
</form>
and the code behind that
public partial class WebForm1 : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
SampleUserControl1.InternalDropDownList.SelectedIndexChanged += InternalDropDownList_SelectedIndexChanged;
}
void InternalDropDownList_SelectedIndexChanged(object sender, EventArgs e)
{
SampleUserControl2.InternalDropDownList.SelectedValue = SampleUserControl1.InternalDropDownList.SelectedValue;
}
}

Resources