I need to set Visible state of the Primary Contact->Email field on the Customers screen (General tab) to hidden.
In 2020R2 it's not clear to me how these Views are implemented. They have the same names, but seem to work differently than previous versions of Acumatica. In the version I'm upgrading from, 2019R1, the Primary Contact section doesn't exist.
I've created this event handler to hide the email field. But, it hides ALL the email fields: Both instances on the General tab, and also on the Billing and Shipping tabs. I only want to hide the field in the Primary Contact grouping.
protected virtual void _(Events.RowSelected<Contact> e)
{
PXUIFieldAttribute.SetVisible<Contact.eMail>(e.Cache, e.Row, false);
}
Is there a way to target just the Primary Contact email field with an event handler? When that field is inspected, the view name is: PrimaryContactCurrent. This view doesn't seem to exist in CodeRepository\CustomerMaint.cs, so I'm not sure how to target it with an event handler.
In 20R2 the logic for Contacts managements is located in separate graph extension CRPrimaryContactGraphExt and the PrimaryContactCurrent is defined there.
However, I don't think that's what you need. You are on the right track with the RowSelected event. You just need to add a condition to hide only needed field.
protected virtual void _(Events.RowSelected<Contact> e)
{
if(e.Row?.ContactID==Base.BAccount.Current?.PrimaryContactID)
PXUIFieldAttribute.SetVisible<Contact.eMail>(e.Cache, e.Row, false);
}
Related
I'm using Acumatica 2020R1 and I'm adding side panels. Is there anyway, for example on the PO form (PO301000), that I could link a side panel to update when clicking on the Document Details?
I actually discovered a way to do this on Acumatica 2020R1. Here's how I did it:
Edit: I did this for the SOOrderEntry screen, creating a side panel that looks at the SOOrder but uses parameters from the SOLine. This method will work for any two tables with a master-detail relationship by replacing SOOrder & SOLine accordingly.
Part A:
Create virtual fields on the SOOrder DAC that correlate to the SOLine Parameters you need
Update these as lines are selecting/updated using event handlers
pass these fields as the params to your side panel actions as you normally would
While It seems like this alone would work, the side panel actually doesn’t refresh when a new SOLine is selected. We’re going to have to get creative to resolve this issue.
Part B:
Create an additional virtual field on the SOOrder DAC to keep track of which side panel was last opened (I will explain how to track this later). I use a string field to do so. This is especially important if you have more than one side panel on your screen.
Next you want to define a standard graph action that will update this field. Make it Visible = false, since you don’t actually want this button to show. We will call if from behind the scenes later. This is the method I use:
public PXAction<SOOrder> SidePanelCallback;
[PXButton]
[PXUIField(DisplayName = "SidePanelCallback", Visible = false)]
public virtual IEnumerable sidePanelCallback(PXAdapter adapter)
{
SOOrder doc = Base.Document.Current;
if (doc == null) return adapter.Get();
var docExt = doc.GetExtension<SOOrderExt>();
docExt.UsrSidePanelDetailParamsRefresh = adapter.CommandArguments;
return adapter.Get();
}
*Note: docExt.UsrSidePanelDetailParamRefresh is the variable I for the previous step
Now you need a way to trigger your code when your side panel actions are called. To do this we’ll use the following JavaScript
function commandResult(ds, context) {
var ds = px_alls['ds'];
switch (context.command)
{
case ('JustAction'):
ds.executeCallback('GraphAction', 'parameter value');
break;
}
}
You want to replace JustAction with the internal name of your side panel action. it should look like this: NavigateToLayer$DB000028
where DB000028 is the target ScreenID for your sidepanel. If you want to make sure your actions follow the same naming convention, you can check it by adding breakpoints and checking with the debugger.
You should replace GraphAction with the name of the graph action you created in the previous step. In our case SidePanelCallback
Then finally for the parameter value pass whatever you’d like to use to identify the opened side panel. This will be stored in UsrSidePanelDetailParamRefresh, or your equivalent.
You can create a case statement for each side panel you want this functionality for, so you only have to do all this once per graph!
Then finally, attach your script to the client event ‘commandPerformed’ on your data source.
Next we will define another graph action to do the actual refresh of the side panel. Mine looks like this:
public PXAction<SOOrder> SidePanelRefresh;
[PXButton]
[PXUIField(DisplayName = "SidePanelRefresh", Visible = false)]
public virtual IEnumerable sidePanelRefresh(PXAdapter adapter)
{
SOOrder doc = Base.Document.Current;
if (doc == null) return adapter.Get();
var docExt = doc.GetExtension<SOOrderExt>();
Dictionary<string, string> parameters = new Dictionary<string, string>();
string[] args = adapter.CommandArguments.Split('*');
switch (docExt.UsrSidePanelDetailParamsRefresh)
{
case ("Component Inventory"):
parameters.Add("Kit", args[0]);
parameters.Add("SiteID", args[1]);
throw new PXRedirectToGIRequiredException("Component Inventory", parameters) { Mode = PXBaseRedirectException.WindowMode.Layer };
}
return adapter.Get();
}
Note that for my use case the parameters I am passing cannot contain the character ‘*’, so I use it as a delimiter
And again, you can define as many case statements as you need for each of your dynamic side panels.
And now finally we need javascript again to call this action. Using event handlers are too slow for this type of refresh, they will always pass the old SOLine parameters. If you know a way to avoid javascript here, I’d love to hear it, as doing callbacks like this does have a heavy overhead. Because of this, be careful where you attach this script to, I actually attach it to the grid’s RowSelectorClick client event rather than AfterRowChange for this reason, and train my users to select rows in this way (by clicking the arrow to the left of each row) so we don’t impact the rest of the company for the minority that use this feature. I’d recommend this if you're working on a high traffic screen such as SO301000.
Anyway, here’s the javascript:
function Side_Panel_Refresh() {
px_alls["ds"].executeCallback("SidePanelRefresh", this.func.caller.arguments[2].row.getCell("InventoryID").getValue() + "*" + this.func.caller.arguments[2].row.getCell("SiteID").getValue());
}
SidePanelRefresh: Name of our graph action from the previous step.
InventoryID & SiteID are my parameters.
And again, using ‘*’ as a delimiter.
Anyway, I hope this helps anyone looking to make dynamic side panels. If anyone has improvements on this method I’d love to hear constructive feedback.
Happy development!
It is a new feature. Side panel were restricted to generic inquiry. In version 2020 you still need a generic inquiry to configure the side panel but they can be viewed in data entry forms too.
The feature is implemented with wizards so it's pretty limited in what it can do. Currently you are limited to the choices available in navigation parameter. It seems like it's not possible to add details entity there. Maybe there's a way but I'm not aware of it.
Check documentation reference link for User Interface: Side Panels for Data Entry Forms (content copied below):
https://help-2020r1.acumatica.com/Help?ScreenId=ShowWiki&pageid=a11aa71b-5f18-4c8f-9c69-350f2c7a89f4
Adding a Side Panel to the Data Entry Form
A customizer can add multiple side panel navigation paths to a data entry form.
To add a side panel, the customizer navigates to the Customization Projects (SM204505) form and creates a new project.
In the Customization Project Editor, the customizer selects the screen for which the side panel is planned to be added.
On the Screens > Form ID > Actions, the customizer adds an action of the Navigation:
Side panel type (1), specifies general settings (2), adds destination screen (3), and specifies parameters (4), if needed (for instance, in the screenshot below, Business Account was selected as a parameter so that the side panel displayed the cases of the specific customer selected on the data entry form).
Example of a side panel on the Customers form
To apply the changes, the customizer must publish the created customization project.
I'm trying to make a custom action update a custom field on the current record. Eventually I need to work through all the deatils to collect some data, but for now I just need to click the button and have it update the current record. More or less, I think I fail to understand how to get the data that would be in a row level event like this protected void SOOrder_RowSelected(PXCache cache, PXRowSelectedEventArgs e)
public PXAction<PX.Objects.SO.SOOrder> LookupShipping;
[PXButton(CommitChanges = true)]
[PXUIField(DisplayName = "Lookup Shipping Rates")]
protected void lookupShipping()
{
SOOrder TheRow = Base.Document.Current;
}
Thanks in advance.
If you are trying to update custom fields...
First, you will want to connect to the DAC Extension via:
SOOrderExt sOOrderExt = PXCache<SOOrder>.GetExtension<SOOrderExt>(TheRow);
Then you need to update the field value. Assuming you are in a Graph Extension, you will need to utilize "Base" to access the cache.
Base.Caches[typeof(SOOrder)].SetValueExt<SOOrderExt.usrCustomField>(TheRow, InsertValueHere);
Once you update all field values, you need to actually update the cache itself.
Base.Caches[typeof(SOOrder)].Update(sOOrderExt);
And don't forget to save the record, assuming this button should be a one-stop shop.
Save.Press();
If you are trying to simply update the values in the existing view, you can do it very easily.
TheRow.FieldName = InsertValueHere;
Document.Current.Update(TheRow);
Save.Press();
You can see some good alternatives to updating values in HB_Acumatica's answer to where I asked something similar...
What is the proper way to update values of DAC's retrieved via PXResultset?
I use the RowSelecting event, in order to perform a BQL query. I choose this event, since adding a BQL in RowSelected event is not advisable. My purpose is to assign a non-DB bound field (a boolean), which is used to enable/disable a field. During RowSelected event, the value is read, and a particular field is enabled/disabled, based on that value.
While using the debugger, I notice RowSelecting event does not fire when the form is first opened. Cancel button causes event to fire. Then I notice the api documentation...RowSelected & FieldSelecting events happen during sequence of events - display of record. RowSelecting is not mentioned.
My goal is to disable a field based on some BQL. What is the best way to perform the BQL and disable the field? Should I use RowSelected? Documentation says to avoid it. In my case, I refer to SO invoice entry form...specifically SOInvoice DAC.
You can extend the DAC in the Graph to add a PXUIEnabled Attribute to do this.
I updated my example to include a non-databound field that controls enabling and disabling another field.
In the SOInvoiceExt DAC Extension I have...
public class SOInvoiceExt : PXCacheExtension<PX.Objects.SO.SOInvoice>
{
#region UsrExtRefNbrDisabled
[PXBool]
[PXUIField(DisplayName = "ExtRefNbr Disabled?")]
public virtual bool? UsrExtRefNbrDisabled { get; set; }
public abstract class usrExtRefNbrDisabled : PX.Data.BQL.BqlBool.Field<usrExtRefNbrDisabled> { }
#endregion
}
Then I added the new custom field to the screen. Ensure that you set CommitChanges to True.
Then in the Graph Extension, I merged the PXUIEnabled attribute with the CachedAttached event
[PXUIEnabled(typeof(Where<SOInvoiceExt.usrExtRefNbrDisabled, NotEqual<True>>))]
[PXMergeAttributes(Method = MergeMethod.Merge)]
protected virtual void SOInvoice_ExtRefNbr_CacheAttached(PXCache cache)
{ }
I was able to check/uncheck the box and it enabled/disabled the field.
Here is an old blog post on the subject: https://asiablog.acumatica.com/2016/11/pxuienabled-and-pxuirequired-attributes.html
Probably the best way to do this would be to make your NonDB field a calculated field and then setEnabled from RowSelected.
https://www.acumatica.com/blog/using-the-pxformula-attribute-to-simplify-your-code/
Otherwise, create a BQl query in FieldDefaulting.
I'm sure this is a simple solution, but I am just learning a lot of this and also and not a developer. Know enough to get lost. Apologies for the newb question and thanks for the help ahead of time.
I am creating custom fields that are visible and editable on the BAccount form but don't want to show the fields in that column if the BAccount type isn't Customer.
Any guidance would be great.
Navigate to Business Accounts page CR303000. Use the Inspect Element feature in customization menu (top right) to find out the name of that screen graph (aka BLC/business logic controller) and the customer type field:
In Acumatica Customization Project Editor create a BusinessAccountMaint graph extension for that screen or use the shortcut from inspect element to create it:
In the graph extension you can put the logic to hide the custom fields when BAccount type is different than customer. By convention RowSelected is an appropriate event handler for those type of visibility validations:
using PX.Data;
namespace PX.Objects.CR
{
public class BusinessAccountMaint_Extension : PXGraphExtension<BusinessAccountMaint>
{
public void BAccount_RowSelected(PXCache sender, PXRowSelectedEventArgs e)
{
BAccount account = e.Row as BAccount;
if (account != null)
{
PXUIFieldAttribute.SetVisible<BAccount.status>(sender, account, account.Type == BAccountType.CustomerType);
}
}
}
}
Consider following T100 training which is about making simple customizations: https://openuni.acumatica.com/courses/development/t100-introduction-to-acumatica-framework/
I would like to filter the records shown on the Projects screen. I've been given the directive to see if limiting the Selector for projects (re-writing the PXSelector for the ProjectID?) would also limit the records that show up on the screen, i.e., a user would not be able to navigate to records that aren't displayed by the Selector. I don't think that's the case, as the screen's view is not limited by what is chosen by the Selector - but I wanted to verify this.
Also - as far as limiting the records that show up in the Selector (possibly re-writing the Selector/BQL using a where clause?) - I looked at the source DAC, and for the life of me I can't figure it out. There is a PXSelector on the ContractID, which doesn't use the SubstituteKey that I'm familiar with, and the ContractCD also has several attributes with which I'm unfamiliar - namely the PXRestrictor AND the PXDimensionSelector.
Bottom line:
1.) What's the best way to limit the records for Project shown in the screen's Selector? Can I just add to the PXRestrictor attribute?
2.) Would limiting the Selector's results also limit what the user can navigate to on the screen using the navigation buttons?
Whenever you need to restrict access to primary records on a data entry screen, it's always required to customize both the lookup DAC key field and the primary data view. By design, in Acumatica key fields in DAC and the primary data view are completely independent, that is why it's required to modify both pieces to achieve the desired result.
For example, to deny access to canceled projects on the Projects screen, you should add PXRestrictorAttribute to PMProject's ContractCD field and also re-declare Project primary data view:
public class ProjectEntryExt : PXGraphExtension<ProjectEntry>
{
public class cancelled : Constant<string>
{
public cancelled() : base(ProjectStatus.Cancelled) {; }
}
[PXViewName(Messages.Project)]
public PXSelect<PMProject, Where<PMProject.baseType, Equal<PMProject.ProjectBaseType>,
And<PMProject.nonProject, Equal<False>, And<PMProject.isTemplate, Equal<False>,
And<PMProject.status, NotEqual<cancelled>,
And<Match<Current<AccessInfo.userName>>>>>>>> Project;
[PXMergeAttributes(Method = MergeMethod.Append)]
[PXRestrictor(typeof(Where<PMProject.status, NotEqual<cancelled>>),
"Given Project/Contract '{0}' is cancelled", typeof(PMProject.contractCD))]
public void PMProject_ContractCD_CacheAttached(PXCache sender) { }
}