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

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:

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!

Multi-Select selector is not selecting more than one item from the selector field

I am trying to create a multi-select selector field where more than one item needs to be selected from the selector, but I am only able to select one item and when I try to select more than one the item does not show on the selector field and when saved only the first item selected is saved.
Selector List:
Selector List
Item Selected:
item selected
DAC code snippet
[PXDBString(255)]
[PXUIField(DisplayName = "Module")]
[PXSelector(
typeof(ModulesTable.id),
DescriptionField = typeof(ModulesTable.description), ValidateValue = false)]
public virtual string UsrModule { get; set; }
public abstract class usrModule : PX.Data.BQL.BqlString.Field<usrModule> { }
#endregion
ASPX code snippet for the selector:
<px:PXMultiSelector runat="server" DataField="UsrModule" ID="CstPXSelector5" CommitChanges="True" />
In the source code change the PXDBString attribue from
PXDBString(255) to PXDBString(255,IsUnicode = true).
Add the ValuesSeparator="," and AllowCustomItems="True" properties for the PXMultiSelector control:
<px:PXMultiSelector ValuesSeparator="," AllowCustomItems="True" AutoRefresh="True" DataSourceID="ds" runat="server" ID="edFieldID" DataField="FieldID" CommitChanges="True" />
Change PXSelector attribute by this:
[PXSelector(typeof(Search<ModulesTable.id>), DescriptionField = typeof(ModulesTable.description), ValidateValue = false)]

How come PXUIField attribute disables text edit boxes on a processing page in 2019 R1?

I'm trying to setup a processing page that will update a SOShipment document lines with new data entered on the PXFilteredProcessing page. When declaring a PXString (virtual field), if I add the PXUIfield attribute, the field becomes read only.
Here is the DAC declaration showing the actual problem. The page I use is a simple PXFilteredProcessing page with a completely custom page that was made in Visual Studio.
#region StockRow
public abstract class stockRow : IBqlField { }
[PXString()]
[PXUIField(Enabled = true)]
public virtual String StockRow { get; set; }
#endregion
#region StockFlag
public abstract class stockFlag : IBqlField { }
[PXString]
public virtual String StockFlag { get; set; }
#endregion
The page has the fields defined as follow :
<px:PXTextEdit ID="edStockRow" runat="server"
DataField="StockRow" Enabled ="true" >
</px:PXTextEdit>
<px:PXTextEdit ID="edStockFlag" runat="server"
DataField="StockFlag" Enabled ="true">
</px:PXTextEdit>
<px:PXGridColumn DataField="StockRow" Width="200px" >
</px:PXGridColumn>
<px:PXGridColumn DataField="StockFlag" Width="200px">
</px:PXGridColumn>
Should the PXUIField really make the field become read only or is there something I am not getting?
PS: I know I can re-enable the field on the RowSelected even, I'm mostly looking for an explanation as to why this is happening.
if I add the PXUIfield attribute, the field becomes read only
Are you sure that this is the operation making the field read-only?
Typically all processing screen detail fields are disabled except the Selected column. I believe this is a behavior introduced by the use of PXProcessing type data view. Going against that behavior will likely not yield the desired result.
If the screen needs detail fields to be editable (except Selected column) I would advise not to create a processing screen. Using a PXSelect data view instead will provide correct behavior for the editable fields.

PXSelector on DAC field causing column filter issues

I have a DAC which has a FK1 to Table1ID from table1 and FK2 to Table2ID from table2. I have added a PXSelector on these fields to show the friendly name instead of the ID number. When user filters on Table2ID and goes to add a new record on the screen and tabs from column to column to enter the information, Table1ID column value disappears. User can add the value back to the column but I'm trying to figure out why is disappearing. If I remove the PXSelector from the Table2ID filed everything works fine. PXSelector is a simple select with a check for if IsActive is true.
<px:PXGridColumn DataField="AdministrationRouteID" DisplayMode="Text" Width="100px" CommitChanges="True" />
<px:PXGridColumn DataField="MedicationID" DisplayMode="Text" Width="200px" CommitChanges="True" />
public abstract class administrationRouteID : PX.Data.IBqlField
{
}
protected int? _AdministrationRouteID;
[PXDBInt()]
[PXDefault()]
[PXSelector(typeof(Search<SsAhAdministrationRoute.administrationRouteID,
Where<SsAhAdministrationRoute.isActive, Equal<True>>>), DescriptionField = typeof(SsAhAdministrationRoute.name))]
[PXUIField(DisplayName = "Administration Route")]
public virtual int? AdministrationRouteID
{
get
{
return this._AdministrationRouteID;
}
set
{
this._AdministrationRouteID = value;
}
}
public abstract class medicationID : PX.Data.IBqlField
{
}
protected int? _MedicationID;
[PXDBInt()]
[PXDefault()]
[PXSelector(typeof(Search<SsAhMedication.medicationId,
Where<SsAhMedication.isActive, Equal<True>>>), DescriptionField = typeof(SsAhMedication.name))]
[PXUIField(DisplayName = "Medication Name")]
public virtual int? MedicationID
{
get
{
return this._MedicationID;
}
set
{
this._MedicationID = value;
}
}
Check that CommitChanges="true" is applied only to GridColumns needing that functionality. Try removing CommitChanges="true" from the selector columns if not needed. SyncPosition="true" on the grid may also help, particularly when hopping from line to line during data entry.
It sounds like you just need to indicate your CD field on your selector using the SubstituteKey property. The example below assume SsAhAdministrationRoute.administrationRouteCD is the CD user friendly value related to the SsAhAdministrationRoute.administrationRouteID ID key value.
[PXSelector(typeof(Search<SsAhAdministrationRoute.administrationRouteID,
Where<SsAhAdministrationRoute.isActive, Equal<True>>>),
DescriptionField = typeof(SsAhAdministrationRoute.name),
SubstituteKey = typeof(SsAhAdministrationRoute.administrationRouteCD))]

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" />

Resources