Skip to main content
Solved

Not able to overrde "OnPersisted" method in a drived class from EPApprovalList


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

Hello all,

I have a need to override “OnPersisted” method (actually I don’t to be called at all) in a drived class from EPApprovalList (a few level of inheritences) but no matter what I do it still gets called. First I tried to override the method itself. The override method in my custom class gets hit when I trace. In my override I just have one command which is “return” but still the code in the base class somehow gets executed (I am guessing because it is in fact an event handler so the base code is already executed when gets to the override). So I decided the remove the handler when class is initialized. I took two approaches to remove the handler. the codes seems to be executed successfuly as when I trace I do not receive any error and it seems to dp what I want but again I am not sure why still “OnPersisted” methods gets executed. Long story short how I can prevent this method from being executed. Here is the related portion of the drived class I have.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using HCL.Common;
using PX.Common;
using PX.Data;
using PX.Data.BQL.Fluent;
using PX.Data.EP;
using PX.Objects.CS;
using PX.Objects.EP;
using PX.SM;

namespace HCL.ApprovalWorkflow
{
    #region EPApprovalAutomation Class
    public class HCLEPApprovalAutomation<SourceAssign, Approved, Rejected, Hold, SetupApproval> : EPApprovalAutomation<SourceAssign, Approved, Rejected, Hold, SetupApproval>
        where SourceAssign : class, IAssign, IBqlTable, new()
        where Approved : class, IBqlField
        where Rejected : class, IBqlField
        where Hold : class, IBqlField
        where SetupApproval : class, IBqlTable, new()
    {
        public HCLEPApprovalAutomation(PXGraph graph, Delegate @delegate) : base(graph, @delegate) { }
        public HCLEPApprovalAutomation(PXGraph graph) : base(graph) { }

        public static bool IsActive() => true;

		#region Ctor
        [PXOverride]
        protected override void Init(PXGraph graph)
        {
            base.Init(graph);
            RemovedHandler(graph);
        }

        private void RemovedHandler(PXGraph graph)
        {
            dynamic docGraph = graph;
            dynamic approval = docGraph.Approval;

            MethodInfo mi;
            Type type = approval.GetType();

            mi = type.GetMethod("OnPersisted", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
            docGraph.RowPersisted.RemoveHandler(typeof(SourceAssign), (PXRowPersisted)mi.CreateDelegate(typeof(PXRowPersisted), approval));
			// I tried this too but did not work either.
            //graph.RowPersisted.RemoveHandler<SourceAssign>(OnPersisted);
        }
		#endregion

        #region OnPersisted
		// I tried to override the OnPersisted first so it never is triggered in the base class but not sure why it is triggering. This method gets hit when I trace.
        //[PXOverride]
        //protected override void OnPersisted(PXCache cache, PXRowPersistedEventArgs e)
        //{
        //    return;
        //}
        #endregion
	}
}

@andriitkachenko @NicholasBova52 

Best answer by andriitkachenko

@aaghaei oh, that complicates things.

Your extension replaces standard Approval. However, by that time it already added the OnPersisted event to the graph, so your RemovedHandler removes the other instance of OnPersisted (OnPersisted added by ARInvoiceEntry.Approval isn’t the same as OnPersisted added by your HCLEPApprovalAutomation through parent EPApprovalAutomation).

I achieved the result you seek in this way:

    public class TestExtensionApproval : PXGraphExtension<ARInvoiceEntry_ApprovalWorkflow, ARInvoiceEntry_Workflow, ARInvoiceEntry>
    {
        public virtual void _(Events.RowPersisted<ARInvoice> e, PXRowPersisted baseMethod)
        {
            if(!(e.Row is ARInvoice row))
                return;

            // if you need to do something in Persisted - you can place it here
        }
    }

graph.RowPersisted.AddHandler<SourceAssign>(new PXRowPersisted(this.OnPersisted)); is another way of adding the event. When you declare events in your graph extension, the system scans for those events and adds them using AddHandler<> under the hood.

So if you create an event with baseMethod in the signature (public virtual void _(Events.RowPersisted<ARInvoice> e, PXRowPersisted baseMethod) and never call baseMethod?.Invoke(e.Cache, e.Args); - the email won’t be sent.

You could as make validations like this:

        public virtual void _(Events.RowPersisted<ARInvoice> e, PXRowPersisted baseMethod)
        {
            if (!(e.Row is ARInvoice row))
                return;

            // this condition is copied from OnPersisted - it is not executed if this condition is true, so this branch of logic is safe to execute
            // you can also add a validation of EPApproval.Status != "P"
            if (e.TranStatus != PXTranStatus.Completed || e.Operation == PXDBOperation.Delete)
            {
                baseMethod?.Invoke(e.Cache, e.Args);
            }

            // if you need to do something in Persisted - you can place it here
        }

 

If you are cautious about suppressing the entire RowPersisted pipeline like that or want to keep everything in the view class, here’s the variant with Reflection:

using PX.Data;
using PX.Data.EP;
using PX.Objects.AR;
using PX.Objects.EP;
using System;
using System.Reflection;

namespace HCL.ApprovalWorkflow
{
    // Acuminator disable once PX1016 ExtensionDoesNotDeclareIsActiveMethod extension should be constantly active
    public class TestExtensionApproval : PXGraphExtension<ARInvoiceEntry_ApprovalWorkflow, ARInvoiceEntry_Workflow, ARInvoiceEntry>
    {
        [PXViewName(PX.Objects.EP.Messages.Approval)]
        public HCLEPApprovalAutomation<ARInvoice, ARRegister.approved, ARRegister.rejected, ARRegister.hold, ARSetupApproval> Approval;
    }

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

        #region Ctor
        protected override void Init(PXGraph graph)
        {
            base.Init(graph);
            graph.RowPersisted.RemoveHandler<SourceAssign>(base.OnPersisted); // remove OnPersisted added by base.Init(graph)
            RemoveOriginalViewHandler(graph); // remove OnPersisted added by ARInvoiceEntry.Approval view
        }

        private void RemoveOriginalViewHandler(PXGraph graph)
        {
            var originalApprovalView = graph
                .GetType()
                .GetField("Approval");

            if (originalApprovalView == null)
                return;

            var viewObject = originalApprovalView
                .GetValue(graph);

            if (viewObject == null)
                return;

            var onPersisted = viewObject
                .GetType()
                .GetMethod("OnPersisted", BindingFlags.Instance | BindingFlags.NonPublic);

            if (onPersisted == null)
                return;

            var eventToRemove = Delegate.CreateDelegate(typeof(PXRowPersisted), viewObject, onPersisted) as PXRowPersisted;

            graph.RowPersisted.RemoveHandler<SourceAssign>(eventToRemove);
        }
        #endregion
    }
    #endregion
}

 Both variants don’t create an email in my local environment.

View original
Did this topic help you find an answer to your question?

8 replies

andriitkachenko
Jr Varsity I
Forum|alt.badge.img+5

Hi @aaghaei 

Firstly, you don’t need [PXOverride] here. It’s required for PXGraphExtension classes, which do not inherit graphs directly, so standard .Net method overriding doesn’t work for them.

Your HCLEPApprovalAutomation inherits EPApprovalAutomation, so you have access to the protected methods without additional code.

Here are two variants that worked as expected for me:

  1. Without OnPersisted overridden:
using System;
using PX.Data;
using PX.Data.EP;
using PX.Objects.EP;

namespace HCL.ApprovalWorkflow
{
    #region EPApprovalAutomation Class
    public class HCLEPApprovalAutomation<SourceAssign, Approved, Rejected, Hold, SetupApproval> : EPApprovalAutomation<SourceAssign, Approved, Rejected, Hold, SetupApproval>
        where SourceAssign : class, IAssign, IBqlTable, new()
        where Approved : class, IBqlField
        where Rejected : class, IBqlField
        where Hold : class, IBqlField
        where SetupApproval : class, IBqlTable, new()
    {
        public HCLEPApprovalAutomation(PXGraph graph, Delegate @delegate) : base(graph, @delegate) { }
        public HCLEPApprovalAutomation(PXGraph graph) : base(graph) { }

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

            // if you override OnPersisted - it won't work
            graph.RowPersisted.RemoveHandler<SourceAssign>(base.OnPersisted);
        }
        #endregion
    }
    #endregion
}
  1. With OnPersisted overridden:
using System;
using PX.Data;
using PX.Data.EP;
using PX.Objects.EP;

namespace HCL.ApprovalWorkflow
{
    #region EPApprovalAutomation Class
    public class HCLEPApprovalAutomation<SourceAssign, Approved, Rejected, Hold, SetupApproval> : EPApprovalAutomation<SourceAssign, Approved, Rejected, Hold, SetupApproval>
        where SourceAssign : class, IAssign, IBqlTable, new()
        where Approved : class, IBqlField
        where Rejected : class, IBqlField
        where Hold : class, IBqlField
        where SetupApproval : class, IBqlTable, new()
    {
        public HCLEPApprovalAutomation(PXGraph graph, Delegate @delegate) : base(graph, @delegate) { }
        public HCLEPApprovalAutomation(PXGraph graph) : base(graph) { }

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

            graph.RowPersisted.RemoveHandler<SourceAssign>(OnPersisted);
        }
        #endregion

        #region OnPersisted
        protected override void OnPersisted(PXCache cache, PXRowPersistedEventArgs e)
        {
            // this method won't be called
            return;
        }
        #endregion
    }
    #endregion
}

 

I want also to point out that if you override the OnPersisted method, even if it is triggered - you already replace the base logic with a simple return;. So the base logic of SourceAssign.RowPersisted wouldn’t work anyway.


aaghaei
Captain II
Forum|alt.badge.img+9
  • Author
  • Captain II
  • 1180 replies
  • July 31, 2024

Thank you @andriitkachenko 

I applied the suggested changes but I still am experiencing the same issue I had. See the below snippet from the code which is basically what you suggested.

 

As possobly you are aware the OnPersisted method is responsible for sending the notifications out. I do not want it to be triggered as I want to handle the notifications differently. Somehow the override of the OnPersisted doesn’t work as it is still sending notifications out. I tried to Remove a document from hold, then approve and see if the notification is generated and as you see eventhough the OnPersisted handler is removed still it is generating notifications. See below:

 

For clarification I do not have any Business Event that you may think it is triggering these notifications.

I think because the OnPersisted infact is an Event Handler, whatever I do it runs after the base event is fired. 

Any though?


andriitkachenko
Jr Varsity I
Forum|alt.badge.img+5

Hi @aaghaei 

I’ve checked it yesterday, and double-checked it now - in both variants I’ve given, OnPersisted isn’t triggered.

Here’s the excerpt from the graph I’ve used to test the view:

    public class TestGraph : PXGraph<TestGraph, TestDac>, IGraphWithInitialization
    {
        public HCLEPApprovalAutomation<TestDac, TestDac.approved, TestDac.rejected, TestDac.hold, TestSetup> Approval;
        // rest of the views

        public void Initialize()
        {
            // my code
        }

        // rest of the event handlers
    }

The approval process works as expected, OnPersisted isn’t triggered, new email isn’t created (my approval has NotificationID and status Pending - which are conditions to start generating email).

Please make sure you’ve replaced your Approval view with your new HCLEPApprovalAutomation class. If you did - there’s a chance there’s other logic sending the email.


aaghaei
Captain II
Forum|alt.badge.img+9
  • Author
  • Captain II
  • 1180 replies
  • August 1, 2024

Thank you again @andriitkachenko 

Mine in fact is not a new graph and custom DAC. I am overriding Acumatica’s existing Graphs. I am initializing my class override in the graph extension as you pointed regarding “Approval”. Here is the snippet from one of my graphs.

And the ApprovalAutomation Class override 

I am 100% sure I do not have any business event or other method that initiates the notification. I am not sure how OnPersisted gets fired. If you can spare a few minutes I can send a Teams call invite anytme that works for you.


andriitkachenko
Jr Varsity I
Forum|alt.badge.img+5

@aaghaei oh, that complicates things.

Your extension replaces standard Approval. However, by that time it already added the OnPersisted event to the graph, so your RemovedHandler removes the other instance of OnPersisted (OnPersisted added by ARInvoiceEntry.Approval isn’t the same as OnPersisted added by your HCLEPApprovalAutomation through parent EPApprovalAutomation).

I achieved the result you seek in this way:

    public class TestExtensionApproval : PXGraphExtension<ARInvoiceEntry_ApprovalWorkflow, ARInvoiceEntry_Workflow, ARInvoiceEntry>
    {
        public virtual void _(Events.RowPersisted<ARInvoice> e, PXRowPersisted baseMethod)
        {
            if(!(e.Row is ARInvoice row))
                return;

            // if you need to do something in Persisted - you can place it here
        }
    }

graph.RowPersisted.AddHandler<SourceAssign>(new PXRowPersisted(this.OnPersisted)); is another way of adding the event. When you declare events in your graph extension, the system scans for those events and adds them using AddHandler<> under the hood.

So if you create an event with baseMethod in the signature (public virtual void _(Events.RowPersisted<ARInvoice> e, PXRowPersisted baseMethod) and never call baseMethod?.Invoke(e.Cache, e.Args); - the email won’t be sent.

You could as make validations like this:

        public virtual void _(Events.RowPersisted<ARInvoice> e, PXRowPersisted baseMethod)
        {
            if (!(e.Row is ARInvoice row))
                return;

            // this condition is copied from OnPersisted - it is not executed if this condition is true, so this branch of logic is safe to execute
            // you can also add a validation of EPApproval.Status != "P"
            if (e.TranStatus != PXTranStatus.Completed || e.Operation == PXDBOperation.Delete)
            {
                baseMethod?.Invoke(e.Cache, e.Args);
            }

            // if you need to do something in Persisted - you can place it here
        }

 

If you are cautious about suppressing the entire RowPersisted pipeline like that or want to keep everything in the view class, here’s the variant with Reflection:

using PX.Data;
using PX.Data.EP;
using PX.Objects.AR;
using PX.Objects.EP;
using System;
using System.Reflection;

namespace HCL.ApprovalWorkflow
{
    // Acuminator disable once PX1016 ExtensionDoesNotDeclareIsActiveMethod extension should be constantly active
    public class TestExtensionApproval : PXGraphExtension<ARInvoiceEntry_ApprovalWorkflow, ARInvoiceEntry_Workflow, ARInvoiceEntry>
    {
        [PXViewName(PX.Objects.EP.Messages.Approval)]
        public HCLEPApprovalAutomation<ARInvoice, ARRegister.approved, ARRegister.rejected, ARRegister.hold, ARSetupApproval> Approval;
    }

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

        #region Ctor
        protected override void Init(PXGraph graph)
        {
            base.Init(graph);
            graph.RowPersisted.RemoveHandler<SourceAssign>(base.OnPersisted); // remove OnPersisted added by base.Init(graph)
            RemoveOriginalViewHandler(graph); // remove OnPersisted added by ARInvoiceEntry.Approval view
        }

        private void RemoveOriginalViewHandler(PXGraph graph)
        {
            var originalApprovalView = graph
                .GetType()
                .GetField("Approval");

            if (originalApprovalView == null)
                return;

            var viewObject = originalApprovalView
                .GetValue(graph);

            if (viewObject == null)
                return;

            var onPersisted = viewObject
                .GetType()
                .GetMethod("OnPersisted", BindingFlags.Instance | BindingFlags.NonPublic);

            if (onPersisted == null)
                return;

            var eventToRemove = Delegate.CreateDelegate(typeof(PXRowPersisted), viewObject, onPersisted) as PXRowPersisted;

            graph.RowPersisted.RemoveHandler<SourceAssign>(eventToRemove);
        }
        #endregion
    }
    #endregion
}

 Both variants don’t create an email in my local environment.


aaghaei
Captain II
Forum|alt.badge.img+9
  • Author
  • Captain II
  • 1180 replies
  • August 2, 2024

Thank you very much @andriitkachenko 

I opted to go with the second option as I have multiple screens utilizing this class and I would like to avoid code duplication in graphs and also as you pointed out do not risk RowPersisted event handlers suppression. 

I would never thought in a million year that two instances of “OnPersisted” are created as it logically should created duplicate notification in Acumatica’s base code too!

Thank you again 👍👍.


andriitkachenko
Jr Varsity I
Forum|alt.badge.img+5

@aaghaei it actually does create a second notification - you just suppressed it immediately in your new Approval view. So all your efforts to suppress the original notification were affecting the one you’ve created yourself by adding this view to the Graph Extension.

If you want to test it, remove all the logic to unsubscribe from the OnSelected event, and you will see that the graph now generates 2 emails instead of 1.


aaghaei
Captain II
Forum|alt.badge.img+9
  • Author
  • Captain II
  • 1180 replies
  • August 5, 2024

@andriitkachenko Thank you for confirming. Cheers pal


Reply


Cookie policy

We use cookies to enhance and personalize your experience. If you accept you agree to our full cookie policy. Learn more about our cookies.

 
Cookie settings