Skip to main content
Answer

No Matter how override a method, the original method still is being executed.

  • May 21, 2025
  • 14 replies
  • 237 views

aaghaei
Captain II
Forum|alt.badge.img+10

Hello all,

I am trying to override two methods “ClearRelatedApproval” and “RowUpdated” as shown below and my intention is to prevent original methods from being executed.

public class HCLEPApprovalAutomation<SourceAssign, Approved, Rejected, Hold, Released, SetupApproval> : EPApprovalAutomation<SourceAssign, Approved, Rejected, Hold, SetupApproval>
where SourceAssign : class, IApprovable, IAssign, IBqlTable, new()
where Approved : class, IBqlField
where Rejected : class, IBqlField
where Hold : class, IBqlField
where Released : class, IBqlField
where SetupApproval : class, IAssignedMap, IBqlTable, new()
{
public HCLEPApprovalAutomation(PXGraph graph, Delegate @delegate) : base(graph, @delegate) { }
public HCLEPApprovalAutomation(PXGraph graph) : base(graph) { }

public static bool IsActive() => true;

[PXOverride] //shouldn't be needed really
public override void ClearRelatedApprovals(SourceAssign doc)
{
// Do my stuff
}

[PXOverride] // Shouldn't be needed really
protected override void RowUpdated(PXCache cache, PXRowUpdatedEventArgs e)
{
// Do my stuff
}
}

Then I call the custom class in APInvoiceEntry graph extension to override the “Approval” as follow:

public class HCLAPInvoiceEntryApprovalWorkflow : PXGraphExtension<APInvoiceEntry_ApprovalWorkflow, APInvoiceEntry_Workflow, APInvoiceEntry>
{
public static bool IsActive() => true;

[PXCopyPasteHiddenView]
[PXViewName(EPMessages.Approval)]
public HCLEPApprovalAutomation<APInvoice, APInvoice.approved, APInvoice.rejected, APInvoice.hold, APInvoice.released, APSetupApproval> Approval;
}

When I try the above, my code is executed without any error but still the original methods also executed. So I tried removing the “RowUpdated” event handler delegate that executes the other method in the base class. This is what I did.

	protected override void Init(PXGraph graph)
{
base.Init(graph);

// Remove Class Event Handler
graph.RowUpdated.RemoveHandler<SourceAssign>(base.RowUpdated);

// Remove Approval View Event Handler
PXRowUpdated rowUpdated = (PXRowUpdated)Delegate.CreateDelegate(typeof(PXRowUpdated), approvalObject, graph.GetType().GetField("Approval").GetValue(graph).GetType().GetMethod("RowUpdated", BindingFlags.Instance | BindingFlags.NonPublic));
graph.RowUpdated.RemoveHandler<SourceAssign>(rowUpdated);
}

The above code also is executed without any error but still these two original methods are executed too. Not sure what I am missing.

 

@andriitkachenko that will be great if you can help me out.

Best answer by andriitkachenko

Here’s what I meant:

        public HCLEPApprovalAutomation(PXGraph graph, Delegate @delegate) : base(graph, @delegate)
{
RemoveEvents();
}

public HCLEPApprovalAutomation(PXGraph graph) : base(graph)
{
RemoveEvents();
}

protected void RemoveEvents()
{
// Remove Class Event Handler
_Graph.RowUpdated.RemoveHandler<SourceAssign>(base.RowUpdated);

RemoveEventInit(); // find InitLastEvents method from original Approval view and unsubscribe it from graph
RemoveRowUpdated(_Graph);

// techincally shouldn't be required - consider it plan B
// that you can try if RemoveEventInit() above doesn't help
_Graph.Initialized += RemoveRowUpdated;
}

private void RemoveRowUpdated(PXGraph graph)
{
// Remove Approval View Event Handler
var originalApprovalView = graph
.GetType()
.GetField("Approval");

if (originalApprovalView == null)
return;

var approvalObject = originalApprovalView
.GetValue(graph);

if (approvalObject == null)
return;

var rowUpdated = approvalObject
.GetType()
.GetMethod("RowUpdated", BindingFlags.Instance | BindingFlags.NonPublic);

if (rowUpdated == null)
return;

PXRowUpdated eventDelegate = (PXRowUpdated)Delegate.CreateDelegate(typeof(PXRowUpdated), approvalObject, rowUpdated);
graph.RowUpdated.RemoveHandler<SourceAssign>(eventDelegate);
}

private void RemoveEventInit()
{
// Remove Approval View Event Handler
var originalApprovalView = _Graph
.GetType()
.GetField("Approval");

if (originalApprovalView == null)
return;

var approvalObject = originalApprovalView
.GetValue(_Graph);

if (approvalObject == null)
return;

var initLastEvents = approvalObject
.GetType()
.GetMethod("InitLastEvents", BindingFlags.Instance | BindingFlags.NonPublic);

if (initLastEvents == null)
return;

var eventDelegate = (PXGraphInitializedDelegate)Delegate.CreateDelegate(typeof(PXGraphInitializedDelegate), approvalObject, initLastEvents);
_Graph.Initialized -= eventDelegate;
}

I didn’t test it though - it will compile, but can’t guarantee results. But that’s what I would try as a next step

14 replies

Forum|alt.badge.img+8
  • Captain II
  • May 21, 2025

Have you tried extending only the base graph rather than workflow & base?


aaghaei
Captain II
Forum|alt.badge.img+10
  • Author
  • Captain II
  • May 21, 2025

Hi ​@aiwan,

Thanks for the response. The order of extensions execution is important in the work I do so I need to override some behavior of fields set by Acumatica Workflow or Approval extensions. I have not tested your suggestion but I need them in my extension anyways. In addition some action buttons like “Approve” and “Reject” are part of the Approval Extension which I need them.


Forum|alt.badge.img+8
  • Captain II
  • May 21, 2025

If this is anything like the PO Approvals workflow, it’s a private class.

 

the view should be declared in the main graph and your graph extension should override the view either way, whether the other graph extensions are extended or not. And I’m pretty sure referencing the view to get your fields from the approval workflow would work too.
 

When I was attempting this for the PO screen, I ended up creating a condition in the customisation editor that controlled visibility of the buttons, but I’m not sure if that’s your goal here.

 

also, if you create a new RowUpdated and don’t invoke the base handler, it will override it and not invoke the base handler.

 

hope that helps!


aaghaei
Captain II
Forum|alt.badge.img+10
  • Author
  • Captain II
  • May 21, 2025

Thanks ​@aiwan 

I am guessing we are getting a little bit side tracked as the issue I don’t believe is related but here are clarifications re your comments:

  1. I am not sure what do you mean by “It” but the “EPApprovalAutomation” is a public class and both of the methods I am overriding are virtual.
  1. The Approval view is declared in the main graph but workflows do override the availability of fields and I want to have some fields still editable while document is pending approval so I must execute my code after other extensions are executed. That is why I need to define the hierarchy of graph executions when I am extending on APInvoiceEntry.
  1. I have managed to control the visibility of the Approval buttons so if a person is not authorized approver the approval buttons are not visible. This is not an issue.
  1. I believe I explained in the original post that somehow platform still executes the base methods even if “override” those two virtual methods and leave the body of the method empty. I am not calling the base methods at all in my overrides but still they are executed.

I had this issue with “OnPersisted” method of the same class last year but when I removed that method which is in fact an event handler in my graph initialization, I achieved the desired result but not sure why “RowUpdated” which is in fact another Event Handler anyway is executed that subsequently executes the ClearRelatedApprovals


darylbowman
Captain II
Forum|alt.badge.img+15

I’m not sure what version of Acumatica you’re using, but in 24 R2, the Approval view is defined as 

[PXViewName("Approval")]
public EPApprovalAutomationWithoutHoldDefaulting<APInvoice, APRegister.approved, APRegister.rejected, APInvoice.hold, APSetupApproval> Approval;

which is one more derived layer above EPApprovalAutomation. Also, since the Approval view is defined in APInvoiceEntry, I don’t think you’d want to redefine it in the _ApprovalWorkflow graph extension.

 

 


andriitkachenko
Jr Varsity III
Forum|alt.badge.img+6

Hi.

This is actually close to this question, but for AP module instead of AR:

@aiwan ​@darylbowman that’s why ​@aaghaei says it’s okay to have those extensions in the class inheritance declaration.

What’s different from the answer however - he now needs to suppress other methods (only RowUpdated actually, because ClearRelatedApprovals is used in it, so it won’t be used once RowUpdated doesn’t trigger)

I’m not sure why exactly the previous solution isn’t working for you - unfortunately, I’m in a tight schedule right now, so I can’t spend much time investigating it.

What I’ve already seen however - the RowUpdated is added a bit differently than  RowPersisted.

It’s being subscribed to the graph’s event in the InitLastEvents method, which is called from Initialize → ctor 

    public EPApprovalAutomation(PXGraph graph, Delegate @delegate)
: base(graph, @delegate)
{
Initialize(graph);
}

public EPApprovalAutomation(PXGraph graph)
: base(graph)
{
Initialize(graph);
}

private void Initialize(PXGraph graph)
{
// unrelated code here
// ...
graph.Initialized += InitLastEvents;
}

private void InitLastEvents(PXGraph graph)
{
graph.RowUpdated.AddHandler<SourceAssign>(RowUpdated);
}

I tried to add the logic you’ve place into Init method inside the HCLEPApprovalAutomation(PXGraph graph, Delegate @delegate) and HCLEPApprovalAutomation(PXGraph graph):

        public HCLEPApprovalAutomation(PXGraph graph, Delegate @delegate) : base(graph, @delegate)
{
RemoveEvents();
}

public HCLEPApprovalAutomation(PXGraph graph) : base(graph)
{
RemoveEvents();
}

protected void RemoveEvents()
{
// Remove Class Event Handler
_Graph.RowUpdated.RemoveHandler<SourceAssign>(base.RowUpdated);

// Remove Approval View Event Handler
var originalApprovalView = _Graph
.GetType()
.GetField("Approval");

if (originalApprovalView == null)
return;

var approvalObject = originalApprovalView
.GetValue(_Graph);

if (approvalObject == null)
return;

var rowUpdated = approvalObject
.GetType()
.GetMethod("RowUpdated", BindingFlags.Instance | BindingFlags.NonPublic);

if (rowUpdated == null)
return;

PXRowUpdated eventDelegate = (PXRowUpdated)Delegate.CreateDelegate(typeof(PXRowUpdated), approvalObject, rowUpdated);
_Graph.RowUpdated.RemoveHandler<SourceAssign>(eventDelegate);
}

But that didn’t help.

So what can be happening - you’re removing the event, but the constructor is called again later on, and the event is re-added to the graph.

If there’s a moment when original view instance is created again, but you view instance isn’t (yet) - the event can be triggered at that time.

According to the summary of the Intialized:

    //
// Summary:
// Occurs at the end of graph constructor.
public event PXGraphInitializedDelegate Initialized;

So it can be called even after your child class ctor and Init worked (which would explain why my addition of the removal logic into ctor above didn’t help).

You might want to try to remove that event from graph.Initialized in the child class ctors - that might help, but I can’t try it out myself at the moment.

You can also try to tinker with removing the event when Approve/Reject button is clicked - override them, call RemoveEvents from there, then call baseDelegate.


darylbowman
Captain II
Forum|alt.badge.img+15

@andriitkachenko - All well and good, but these events should definitely be overide-able in a derived class if done properly.


andriitkachenko
Jr Varsity III
Forum|alt.badge.img+6

@darylbowman agreed, but here’s the catch - ​@aaghaei want to suppress only RowUpdated that is coming from the EPApprovalAutomation view. If you override RowUpdated on the graph extension level, you receive entire RowUpdated pipeline in it, including events from other extensions/customizations, base graph, etc. If you suppress it all, this might be an overkill. Hence, all these shenanigans with finding the exact method to unsubscribe


darylbowman
Captain II
Forum|alt.badge.img+15

Hence my belief that the correct override would be more effective just left blank.


aaghaei
Captain II
Forum|alt.badge.img+10
  • Author
  • Captain II
  • May 22, 2025

Thank you ​@darylbowman and ​@andriitkachenko 

@darylbowman for sake of testing, I tried extending just the base graph but the result is the same. You are right the AP Approval is from a derived class from EPApprovalAutomation but the derived class is only overriding the Hold Field Defaulting event with an empty body that I am overriding that too as well as another class on top of both of these for Reserved docs. So I intentionally am deriving from EPApprovalAutomation.

@andriitkachenko you are right you helped me out with the post you referenced above. The difference between OnPersisted (class EPApprovalList) and RowUpdated (class EPApprovalAutomation) is their base class and how they are added. 

I have tried tis too but no luck

public HCLEPApprovalAutomation(PXGraph graph, Delegate @delegate) : base(graph, @delegate)
{
Initialize(graph);
}
public HCLEPApprovalAutomation(PXGraph graph) : base(graph)
{
Initialize(graph);
}

private void Initialize(PXGraph graph)
{
RemoveHandlers(graph);
graph.Initialized += InitLastEvents;
}

private void RemoveHandlers(PXGraph graph)
{
graph.RowPersisted.RemoveHandler<SourceAssign>(base.OnPersisted);
graph.RowUpdated.RemoveHandler<SourceAssign>(base.RowUpdated);

FieldInfo approvalFieldInfo = graph.GetType().GetField("Approval");

if (approvalFieldInfo != null)
{
object approvalObject = approvalFieldInfo.GetValue(graph);

if (approvalObject != null)
{
Type approvalType = approvalObject.GetType();

if (approvalType != null)
{
MethodInfo approvalMethodInfo = null;

approvalMethodInfo = approvalType.GetMethod("OnPersisted", BindingFlags.Instance | BindingFlags.NonPublic);

if (approvalMethodInfo != null)
{
PXRowPersisted onPersisted = (PXRowPersisted)Delegate.CreateDelegate(typeof(PXRowPersisted), approvalObject, approvalMethodInfo);
graph.RowPersisted.RemoveHandler<SourceAssign>(onPersisted);
}

approvalMethodInfo = approvalType.GetMethod("RowUpdated", BindingFlags.Instance | BindingFlags.NonPublic);

if (approvalMethodInfo != null)
{
PXRowUpdated rowUpdated = (PXRowUpdated)Delegate.CreateDelegate(typeof(PXRowUpdated), approvalObject, approvalMethodInfo);
graph.RowUpdated.RemoveHandler<SourceAssign>(rowUpdated);
}
}
}
}
}

private void InitLastEvents(PXGraph graph)
{
graph.RowUpdated.AddHandler<SourceAssign>(this.RowUpdated);
}

The OnPersisted is working as expected but RowUpdated is not.

 

I believe what I do overrides the base class correctly but the events are attached to the base Approval view are what cause problem and I am unsure how to get rid of them.


andriitkachenko
Jr Varsity III
Forum|alt.badge.img+6
private void Initialize(PXGraph graph)
{
RemoveHandlers(graph);
graph.Initialized += InitLastEvents;
}

This part is actually making it double the pain: you added InitLastEvents again, which adds the event the second time, now with your “approval”.

What you might try also is to subscribe RemoveHandlers to the Initialized, as it matches the expected signature:

private void Initialize(PXGraph graph)
{
graph.Initialized += RemoveHandlers;
}

(Note that I removed InitLastEvents from the Initialize at all)

This way your RemoveHandlers might counter act on InitLastEvents at the right time

My only gripe with such solution - you don’t control order in which they will be fired, so there’s a chance InitiLastEvents will be called AFTER your RemoveHandlers, rendering it pointless.

What I’d recommend trying - getting InitLastEvents from Approval view, like we were doing before with events, and unsubscribe it from graph.Initialized:

private void Initialize(PXGraph graph)
{
var originalInitToRemove = FindOriginalInit(graph);
graph.Initialized -= originalInitToRemove;
graph.Initialized += RemoveHandlers;
}

This might work, as we would remove InitLastEvents from subscription completely (assuming, again, that constructors will be fired in an ideal order)


aaghaei
Captain II
Forum|alt.badge.img+10
  • Author
  • Captain II
  • May 22, 2025

Thank you ​@andriitkachenko  I am not sure if I understood you correctly but if the below is what you meant, still the base class RowUpdated is triggered.

private void Initialize(PXGraph graph)
{
//var originalInitToRemove = FindOriginalInit(graph);
graph.Initialized -= RemoveViewHandlers;
graph.Initialized += RemoveClassHandlers;
}

private void RemoveViewHandlers(PXGraph graph)
{
FieldInfo approvalFieldInfo = graph.GetType().GetField("Approval");

if (approvalFieldInfo != null)
{
object approvalObject = approvalFieldInfo.GetValue(graph);

if (approvalObject != null)
{
Type approvalType = approvalObject.GetType();

if (approvalType != null)
{
MethodInfo approvalMethodInfo = null;

approvalMethodInfo = approvalType.GetMethod("RowUpdated", BindingFlags.Instance | BindingFlags.NonPublic);

if (approvalMethodInfo != null)
{
PXRowUpdated rowUpdated = (PXRowUpdated)Delegate.CreateDelegate(typeof(PXRowUpdated), approvalObject, approvalMethodInfo);
graph.RowUpdated.RemoveHandler<SourceAssign>(rowUpdated);
}

approvalMethodInfo = approvalType.GetMethod("OnPersisted", BindingFlags.Instance | BindingFlags.NonPublic);

if (approvalMethodInfo != null)
{
PXRowPersisted onPersisted = (PXRowPersisted)Delegate.CreateDelegate(typeof(PXRowPersisted), approvalObject, approvalMethodInfo);
graph.RowPersisted.RemoveHandler<SourceAssign>(onPersisted);
}
}
}
}
}


private void RemoveClassHandlers(PXGraph graph)
{
graph.RowUpdated.RemoveHandler<SourceAssign>(base.RowUpdated);
graph.RowPersisted.RemoveHandler<SourceAssign>(base.OnPersisted);
}

 


andriitkachenko
Jr Varsity III
Forum|alt.badge.img+6

Here’s what I meant:

        public HCLEPApprovalAutomation(PXGraph graph, Delegate @delegate) : base(graph, @delegate)
{
RemoveEvents();
}

public HCLEPApprovalAutomation(PXGraph graph) : base(graph)
{
RemoveEvents();
}

protected void RemoveEvents()
{
// Remove Class Event Handler
_Graph.RowUpdated.RemoveHandler<SourceAssign>(base.RowUpdated);

RemoveEventInit(); // find InitLastEvents method from original Approval view and unsubscribe it from graph
RemoveRowUpdated(_Graph);

// techincally shouldn't be required - consider it plan B
// that you can try if RemoveEventInit() above doesn't help
_Graph.Initialized += RemoveRowUpdated;
}

private void RemoveRowUpdated(PXGraph graph)
{
// Remove Approval View Event Handler
var originalApprovalView = graph
.GetType()
.GetField("Approval");

if (originalApprovalView == null)
return;

var approvalObject = originalApprovalView
.GetValue(graph);

if (approvalObject == null)
return;

var rowUpdated = approvalObject
.GetType()
.GetMethod("RowUpdated", BindingFlags.Instance | BindingFlags.NonPublic);

if (rowUpdated == null)
return;

PXRowUpdated eventDelegate = (PXRowUpdated)Delegate.CreateDelegate(typeof(PXRowUpdated), approvalObject, rowUpdated);
graph.RowUpdated.RemoveHandler<SourceAssign>(eventDelegate);
}

private void RemoveEventInit()
{
// Remove Approval View Event Handler
var originalApprovalView = _Graph
.GetType()
.GetField("Approval");

if (originalApprovalView == null)
return;

var approvalObject = originalApprovalView
.GetValue(_Graph);

if (approvalObject == null)
return;

var initLastEvents = approvalObject
.GetType()
.GetMethod("InitLastEvents", BindingFlags.Instance | BindingFlags.NonPublic);

if (initLastEvents == null)
return;

var eventDelegate = (PXGraphInitializedDelegate)Delegate.CreateDelegate(typeof(PXGraphInitializedDelegate), approvalObject, initLastEvents);
_Graph.Initialized -= eventDelegate;
}

I didn’t test it though - it will compile, but can’t guarantee results. But that’s what I would try as a next step


aaghaei
Captain II
Forum|alt.badge.img+10
  • Author
  • Captain II
  • May 23, 2025

Thank you very much ​@andriitkachenko 

It is functioning as expected now with Plan B line of code included not without it. Much appreciated. Let see if something else breaks 😀