While Acumatica provides a tremendous amount of functionality built in, each of us have business rules that require changing standard functionality from time to time. Sometimes this means creating new event handlers, and sometimes it means changing the existing ones. Likewise, sometimes it means overriding existing methods that are not event handlers. In this post, we’ll look at how to adjust Acumatica’s standard code to “do it my way”.
Example #1 – Override Standard Method
In this example, we will override the Persist method. To do so, we need to define a delegate for Persist, mark it as PXOverride, and call the original (base) method for Persist. Depending on the method that you override, you may have a reason to never call the base method, so simply don’t. You still need to define the delegate, but just don’t make the call shown here as baseMethod().
An example of why you may override Persist is that you may need to validate some complex business rules before allowing the data to be saved to the database. If validation fails, you would notify the user and prevent the save. If it is successful, you may need to initialize another graph and perform some follow-on activity after the base Persist method is called.
#region PersistDelegate
public delegate void PersistDelegate();
[PXOverride]
public void Persist(PersistDelegate baseMethod)
{
// Insert Code to Execute BEFORE the Base Method
baseMethod();
// Insert Code to Execute AFTER the Base Method
}
#endregion
Example #2 – Override Event Handler to Inject Code
In this example, our business case requires us to insert behavior in the middle of the standard code. Unfortunately, this means that we will have to review the standard portion of this code at least with every major upgrade. Fortunately, the touch point is relatively small in this case. Larger code blocks with more business logic expose the code to more breaking changes between versions.
The point at which we need to place our code is in the CreateProc method. Unfortunately for us, that method is marked static, meaning we cannot override it. The same problem would occur if it were marked private. To overcome this problem, we move up to the code block that calls this method which is the event handler POCreateFilter_RowSelected. Since the logic is so simple in CreateProc, we will combine these 2 methods to simplify the code and eliminate the impact of the CreateProc being defined as static. We can do that because this is the only case of CreateProc that we need to modify. Note that if CreateProc is called from any other method, this enhancement may not be thorough enough for your purpose.
Standard Code
#region CreateProc
public static void CreateProc(
List<POFixedDemand> list, DateTime? orderDate,
bool extSort)
{
PXRedirectRequiredException poredirect =
CreatePOOrders(list, orderDate, extSort);
if (poredirect != null)
throw poredirect;
}
#endregion
protected virtual void POCreateFilter_RowSelected
(PXCache sender, PXRowSelectedEventArgs e)
{
POCreateFilter filter = Filter.Current;
if (filter == null) return;
FixedDemand.SetProcessDelegate(delegate(List<POFixedDemand> list)
{
CreateProc(list, filter.PurchDate, filter.OrderNbr != null);
});
TimeSpan span;
Exception message;
PXLongRunStatus status = PXLongOperation.GetStatus
(this.UID, out span, out message);
PXUIFieldAttribute.SetVisible<POLine.orderNbr>
(Caches[typeof(POLine)], null,
(status == PXLongRunStatus.Completed ||
status == PXLongRunStatus.Aborted));
PXUIFieldAttribute.SetVisible<POCreateFilter.orderTotal>
(sender, null, filter.VendorID != null);
}
Override
#region OVERRIDE - POCreateFilter_RowSelected
protected virtual void POCreateFilter_RowSelected
(PXCache sender, PXRowSelectedEventArgs e, PXRowSelected baseMethod)
{
POCreateFilter filter = Base.Filter.Current;
if (filter == null) return;
Base.FixedDemand.SetProcessDelegate(delegate (List<POFixedDemand> list)
{
PXRedirectRequiredException poredirect =
CreatePOOrders(list, filter.PurchDate,
filter.OrderNbr != null);
// INSERT CUSTOM CODE HERE
if (poredirect != null)
throw poredirect;
});
TimeSpan span;
Exception message;
PXLongRunStatus status = PXLongOperation.GetStatus
(Base.UID, out span, out message);
PXUIFieldAttribute.SetVisible<POLine.orderNbr>
(Base.Caches[typeof(POLine)], null,
(status == PXLongRunStatus.Completed ||
status == PXLongRunStatus.Aborted));
PXUIFieldAttribute.SetVisible<POCreateFilter.orderTotal>
(sender, null, filter.VendorID != null);
}
#endregion
Since this is an event handler, notice that we do not create a delegate or use PXOverride. As we will see in a moment, event handlers have predefined delegates that we can use, but as a method, we must create the proper delegate to capture the method’s signature for the override. If we simply needed to inject our code before or after the standard code of the method instead of injecting into the middle, we could use the override to specify our custom code and then execute baseMethod() passing in the appropriate parameters to execute the standard code before or after our custom code.
Acumatica event handlers include predefined delegates for each event, so overriding the event handler is as easy as adding the delegate to the signature as shown below. Then simply use the delegate to hand off to the base event handler.
#region OVERRIDE - POCreateFilter_RowSelected
protected virtual void POCreateFilter_RowSelected
(PXCache sender, PXRowSelectedEventArgs e, PXRowSelected baseMethod)
{
// Insert Custom Code Here to Execute BEFORE Standard Code
baseMethod(sender, e);
// Insert Custom Code Here to Execute AFTER Standard Code
}
#endregion
To override an event handler, use the Row_Field_Event nomenclature and specify the PX event delegate that corresponds to your event. i.e. RowSelected would be PXRowSelected, FieldUpdated would be PXFieldUpdated, etc.
Bonus Example – Override View
If you need to override a data view instead of an event or method, create a graph extension and simply specify a data view by the same name as used in the base graph. When Acumatica creates the composite graph, the view defined in the extension will override the base view.
using PX.Data;
namespace PX.Objects.PO
{
public class POOrderEntry_Extension : PXGraphExtension<POOrderEntry>
{
[PXViewName(Messages.POOrder)]
public PXSelectJoin<POOrder,
LeftJoinSingleTable<Vendor, On<Vendor.bAccountID,
Equal<POOrder.vendorID>>>,
Where<Vendor.bAccountID, IsNull,
Or<Match<Vendor, Current<AccessInfo.userName>>>>>
Document;
}
}
This example is purely hypothetical, but it removes the order type from the Where clause. When Acumatica looks for the view, it will no longer apply the order type to the Select.