How to override a protected method using PXProtectedAccess? - acumatica

I need to override the Service Contract Schedule's Next Execution Date when a Service Order is generated. This automatically calculated depending on the occurrence setup. The calculation doesn't fit my requirements though so I need to override it.
Looking at the source I found the method that I need to override. Its protected unfortunately.
I found this gem but I'm not getting such luck. Not sure what am I missing here ? My code as follows :
[PXProtectedAccess]
public abstract class ServiceContractInqExt1 : PXGraphExtension<ServiceContractInq>
{
[PXProtectedAccess]
protected abstract void UpdateGeneratedSchedule(int scheduleID, DateTime? toDate, DateTime? lastGeneratedElementDate, FSSchedule fsScheduleRow);
}
public class ServiceContractInqExt2: PXGraphExtension<ServiceContractInqExt1,ServiceContractInq>
{
#region Event Handlers
public static bool IsActive()
{
return true;
}
public delegate void UpdateGeneratedScheduleDelegate(int scheduleID, DateTime? toDate, DateTime? lastGeneratedElementDate, FSSchedule fsScheduleRow);
[PXOverride]
public virtual void UpdateGeneratedSchedule(int scheduleID, DateTime? toDate, DateTime? lastGeneratedElementDate, FSSchedule fsScheduleRow,UpdateGeneratedScheduleDelegate baseMethod)
{
PXTrace.WriteInformation("HIT !");//this is not firing !
baseMethod(scheduleID, toDate, lastGeneratedElementDate, fsScheduleRow);
}
#endregion
}
EDIT :
Following the answer below.
using PX.Data;
using PX.Objects.CR;
using PX.Objects.CS;
using PX.Objects.FS.Scheduler;
using PX.SM;
using System;
using System.Collections;
using System.Collections.Generic;
using PX.Objects.AR;
using PX.Objects;
using PX.Objects.FS;
namespace PX.Objects.FS
{
[PXProtectedAccess]
public abstract class AAServiceContractInqExtension : PXGraphExtension<ServiceContractInq>
{
[PXProtectedAccess]
protected void UpdateGeneratedSchedule(int scheduleID, DateTime? toDate, DateTime? lastGeneratedElementDate, FSSchedule fsScheduleRow)
{
PXTrace.WriteInformation("HIT");
}
}
}
And simply loading the page is raising an error.
My Acumatica build/version

Unfortunately you will be unable to override the method UpdateGeneratedSchedule due to the fact that it is not marked as virtual so even with access to the ContractGenerationEnqBase graph we cannot modify its functionality.
With that in mind I successfully implemented the following solution
public class ServiceContractInqExtension : PXGraphExtension<ServiceContractInq>
{
public delegate void ProcessServiceContractDelegate(PXCache cache, FSContractSchedule fsScheduleRow, DateTime? fromDate, DateTime? toDate);
[PXOverride]
public virtual void processServiceContract(PXCache cache, FSContractSchedule fsScheduleRow, DateTime? fromDate, DateTime? toDate, ProcessServiceContractDelegate del)
{
del?.Invoke(cache, fsScheduleRow, fromDate, toDate);
UpdateGeneratedSchedule(fsScheduleRow.ScheduleID, new DateTime(2023, 3, 11));
}
protected void UpdateGeneratedSchedule(int? scheduleID, DateTime nextExecutionDate)
{
PXUpdate<Set<FSSchedule.nextExecutionDate, Required<FSSchedule.nextExecutionDate>>, FSSchedule,
Where<
FSSchedule.scheduleID, Equal<Required<FSSchedule.scheduleID>>>>
.Update(Base, nextExecutionDate, scheduleID);
}
}
Once the processing method has been completed the custom UpdateGeneratedSchedule will utilize PXUpdate to set the desired field value. PXUpdate being the same call used by the method you originally wanted to override.
Here we can see one of the schedules before processing
After our custom code has been called we can see that the 'Next Execution Date' is the value provided to our custom method.

Related

Acumatica calling ApplyState method in Scan and Receive screen

Good day
I am trying to call the ApplyState(state) function. This is a protected function inside PX.Objects.IN.INScanReceive.
I am reading a QR code and the value has a lot/serial number and expiry date. The idea is to set the 2 values during the ProcessItemBarcode function call. The problem is when calling the base method the state is set and the "screens state" wants me to scan the lot/serial.
What I need to do is call the ProcessConfirm() or ApplyState(state). But calling the functions inside ProcessItemBarcode is where I stuck:
using PX.Common;
using PX.Data;
using WMSBase = PX.Objects.IN.WarehouseManagementSystemGraph<PX.Objects.IN.INScanReceive, PX.Objects.IN.INScanReceiveHost, PX.Objects.IN.INRegister, PX.Objects.IN.INScanReceive.Header>;
using PX.Objects.IN;
namespace MyCustomPackage.Graph.Extension
{
public class INScanReceiveHostExtCustomPackage2 : PXGraphExtension<INScanReceive, INScanReceiveHost>
{
public static bool IsActive() => true;
#region Overrides ProcessItemBarcode
//ProcessItemBarcode
public delegate void ProcessItemBarcodeDelegate(string barcode);
[PXOverride]
public virtual void ProcessItemBarcode(string barcode, ProcessItemBarcodeDelegate baseMethod)
{
//..//logic go change barcode
baseMethod?.Invoke(barcode);
// Option one call ApplyState()
Base.ApplyState()
// Option2 one call ProcessLotSerialBarcode
// I dont know how to send the delegate
ProcessLotSerialBarcode(barcode, );
}
#endregion
#region Overrides ProcessLotSerialBarcode
//ProcessLotSerialBarcode
public delegate void ProcessLotSerialBarcodeDelegate(string barcode);
[PXOverride]
public virtual void ProcessLotSerialBarcode(string barcode, ProcessLotSerialBarcodeDelegate baseMethod)
{
baseMethod?.Invoke(barcode);
}
#endregion
[PXProtectedAccess]
public abstract class INScanReceiveHostExtProtectedAccess : PXGraphExtension<INScanReceiveHostExtCustomPackage, INScanReceive, INScanReceiveHost>
{
[PXProtectedAccess(typeof(INScanReceive))]
protected abstract void ProcessItemBarcode(string barcode);
[PXProtectedAccess(typeof(INScanReceive))]
protected abstract void ApplyState(string state);
[PXProtectedAccess(typeof(INScanReceive))]
protected abstract void ProcessLotSerialBarcode(string barcode);
}
}
}
This is not the preferred method but when I can't figure out another option I will copy the source from Acumatica, e.g. Static methods and private ones. I will make note in my code that they were copied so I can make sure to duplication the process when versions change. You can then call those methods locally. Not elegant but works for me :)

Acumatica override method - Scan and Receive screen

Good day
I am looking for a way to override the function ProcessItemBarcode(string Barcode) inside public class INScanReceive : WMSBase
The idea is to extend the INScanReceiveHost graph and manipulate the barcode before it is processed. This is to change how the Scan and Receive page scan'sa barcodes so that I can read and manipulate QR codes
namespace PX.Objects.IN
{
// Acuminator disable once PX1016 ExtensionDoesNotDeclareIsActiveMethod extension should be constantly active
public class INScanReceiveHost_Extension : PXGraphExtension<INScanReceive>
{
#region Event Handlers
[PXOverride]
public virtual void ProcessItemBarcode(string barcode)
{
//change barcode
Base.ProcessItemBarcode.Invoke(barcode);
}
#endregion
}
}
But It telling me i can't access the function?
Update 2021/06/15
Thanks Sean Prouty for your help so far. I am getting very close to a solution.
I have a follow-up question.
I have overridden the ProcessItemBarcode function and ProcessLotSerialBarcode
Question: Can I call ProcessLotSerialBarcode from ProcessItemBarcode
because I have the location from the QR code I can set it from the first scan:
#region Overrides ProcessItemBarcode
//ProcessItemBarcode
public delegate void ProcessItemBarcodeDelegate(string barcode);
[PXOverride]
public virtual void ProcessItemBarcode(string barcode, ProcessItemBarcodeDelegate baseMethod)
{
try
{
string inventoryBC = barcode;
//...//get InvetoryID using Barcode
baseMethod?.Invoke(inventoryBC);
//how do you call the ProcessLotSerialBarcode function?
ProcessLotSerialBarcode(barcode, ProcessLotSerialBarcodeDelegate);
}
catch (Exception ex)
{//TODO: check if not a QR code
PXTrace.WriteError("ProcessItemBarcode Override: " + ex.Message);
baseMethod?.Invoke(barcode);
}
}
#endregion
#region Overrides ProcessLotSerialBarcode
//ProcessLotSerialBarcode
public delegate void ProcessLotSerialBarcodeDelegate(string barcode);
[PXOverride]
public virtual void ProcessLotSerialBarcode(string barcode, ProcessLotSerialBarcodeDelegate baseMethod)
{
try
{
string inventoryBC = "LOgic";
baseMethod?.Invoke(inventoryBC);
}
catch (Exception)
{
//TODO: check if not a QR code
baseMethod?.Invoke(barcode);
}
}
#endregion
[PXProtectedAccess]
public abstract class INScanReceiveHostExtProtectedAccess : PXGraphExtension<INScanReceiveHostExtCustomPackage, INScanReceive, INScanReceiveHost>
{
[PXProtectedAccess(typeof(INScanReceive))]
protected abstract void ProcessItemBarcode(string barcode);
[PXProtectedAccess(typeof(INScanReceive))]
protected abstract void ApplyState(string state);
[PXProtectedAccess(typeof(INScanReceive))]
protected abstract void ProcessLotSerialBarcode(string barcode);
}
I was also thinking of setting the state from the first method but then I need to call
Base.ApplyState(INScanIssue.ScanStates.Confirm);
Then I can set the Header and just keep resetting the scanner to the Confirm state. What do you think?
Because the method you are trying to override is protected, and not public, you will need to override the logic in a different way using the PXProtectedAccess attribute and an abstract graph extension.
namespace MyCustomPackage.Graph.Extension
{
public class INScanReceiveHostExtCustomPackage : PXGraphExtension<INScanReceive, INScanReceiveHost>
{
public static bool IsActive() => true;
#region Overrides
public delegate void ProcessItemBarcodeDelegate(string barcode);
[PXOverride]
public virtual void ProcessItemBarcode(string barcode, ProcessItemBarcodeDelegate baseMethod)
{
PXTrace.WriteInformation("Running abstract override");
baseMethod?.Invoke(barcode);
}
#endregion
}
[PXProtectedAccess]
public abstract class INScanReceiveHostExtProtectedAccess : PXGraphExtension<INScanReceiveHostExtCustomPackage, INScanReceive, INScanReceiveHost>
{
[PXProtectedAccess(typeof(INScanReceive))]
protected abstract void ProcessItemBarcode(string barcode);
}
}
I didn't have a good way to test this unfortunately, so you may need to tweak the type that is being passed to the PXProtectedAccess attribute above the abstract method. If this doesn't work, try passing the INScanReceiveHost type to the attribute and see if that works for you.

How do I get a hook into the Acumatica AR Document release?

gone through a variety of the articles, and none seem to "Work". In particular, 2021r1, not getting ANY of the PXTrace statement in the output
Challenge:
Static method of the graph directly called from ARPaymentEntry, and 4-5 overloads (From .\App_Data\CodeRepository\PX.Objects\AR\ARDocumentRelease.cs)
Eventually ends up calling a single method within that graph, starting with "public static void ReleaseDoc("
No clear place to add a delegate, though that seems the "most correct" method (E.g. question 37262565, comment from cbetabeta) - Yet the initialize event doesn't appear to be firing (possibly JIT optimization? Direct call into static method doesn't really need the class to be instantiated, I'd guess)
Also need a complete solution - e.g. Must handle the call from Payment Entry as well as from AR Document Release process
Sample Code:
using PX.Data;
using PX.Objects.AR;
using PX.Objects.GL;
using System.Collections;
using System.Collections.Generic;
using static PX.Objects.AR.ARDocumentRelease;
namespace Test.GraphExtensions
{
public class ARDocumentReleaseTestABC : PXGraphExtension<ARDocumentRelease>
{
// Tries include:
// https://html.developreference.com/article/11055300/How+to+customize+the+Process+button+on+the+AP505200+screen.+Acumatica
// https://stackoverflow.com/questions/36784480/customize-release-ap-document-in-acumatica-system
// https://stackoverflow.com/questions/37262565/how-can-i-execute-code-from-the-release-release-all-buttons-in-the-release-ar
// https://living-sun.com/es/acumatica/2179-extend-arpaymententry-release-action-acumatica.html
#region IsActive - Turn off if no setup record
public static bool IsActive()
{
return true;
}
#endregion IsActive - Turn off if no setup record
public virtual void BalancedARDocument_RowSelected(PXCache sender, PXRowSelectedEventArgs e)
{
PXTrace.WriteVerbose(string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0}", "dc1703c7-f8b7-4ce1-b838-d51475f4d477"));
}
public override void Initialize()
{
PXTrace.WriteVerbose(string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0}", "8ecc4f83-9ac8-4bb6-bad7-ac2aabc5b58e"));
}
public static void ReleaseDocRBRR(ARRegister ardoc, bool isAborted)
{
PXTrace.WriteVerbose(string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0} {1} {2}", "20ae0d5e-44eb-42f2-ad15-0b9e307d2a86", isAborted, (ardoc == null)));
}
[PXUIField(DisplayName = "Release", MapEnableRights = PXCacheRights.Update, MapViewRights = PXCacheRights.Update)]
[PXProcessButton]
// [PXOverride]
public virtual IEnumerable Release(PXAdapter adapter)
{
PXTrace.WriteVerbose(string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0}", "0ccff0b7-7702-4083-b717-17b031e27be5"));
List<ARRegister> list = new List<ARRegister>();
return list;
}
}
}
The proper place to override is either ARReleaseProcess.OnBeforeRelease if you need your code to fire before the release code or ARReleaseProcess.ReleaseDocProc.
Also, if you just need to run your code after the release, it is recommended that you add your code to onreleasecomplete Delegate in ARReleaseProcess.ReleaseDocProc.
Also, if you just need to run your code after the document release and you do not necessarily need to have it run exactly in the same transaction, please consider adding a separate process that picks up released documents and processes them further. You can either schedule that process ti run in the backgroud every minute or put a trigger with Business Events.
I think its the namespace that is the issue:
namespace PX.Objects.AR
{
public class ARDocumentRelease_Extension : PXGraphExtension<ARDocumentRelease>
{
#region Event Handlers
public override void Initialize()
{
PXTrace.WriteVerbose(string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0}", "8ecc4f83-9ac8-4bb6-bad7-ac2aabc5b58e"));
}
#endregion
}
}
Seems I had a variety of issues, in particular was an extension to the wrong graph. Hooking up with Dmitrii's answer, I indicate at least some of the events that are hit, so others can use in the future
Code modified per #dmitrii-naumov's answer:
using PX.Data;
using PX.Objects.AR;
using PX.Objects.GL;
using System.Collections.Generic;
namespace Test.ABCD
{
public class ARDocumentReleaseTestABCD : PXGraphExtension<ARReleaseProcess>
{
#region IsActive - Turn off if no setup record
public static bool IsActive()
{
return true;
}
#endregion IsActive - Turn off if no setup record
public override void Initialize()
{
// Hit
PXTrace.WriteVerbose(string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0}", "8ecc4f83-9ac8-4bb6-bad7-ac2aabc5b58e"));
}
public delegate List<ARRegister> ReleaseDocProcDelegate(JournalEntry je, ARRegister ardoc, List<Batch> pmBatchList, ARDocumentRelease.ARMassProcessReleaseTransactionScopeDelegate onreleasecomplete);
[PXOverride]
public List<ARRegister> ReleaseDocProc(JournalEntry je, ARRegister ardoc, List<Batch> pmBatchList, ARDocumentRelease.ARMassProcessReleaseTransactionScopeDelegate onreleasecomplete, ReleaseDocProcDelegate del)
{
// Hit
PXTrace.WriteVerbose(string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0}", "8f153b0b-dd57-4893-aa8c-d29ea69528e4"));
PXTrace.WriteVerbose(string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0}", "9b1caa30-5abf-4f21-8c45-a55c4047441a"));
return null;
}
[PXOverride]
public virtual ARRegister OnBeforeRelease(ARRegister ardoc)
{
// Hit
PXTrace.WriteVerbose(string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0}", "741a01ad-5a37-40a8-ad10-a1fcd6659f7e"));
PXTrace.WriteVerbose(string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0}", "9b1caa30-5abf-4f21-8c45-a55c4047441a"));
return ardoc;
}
}
}
This version implements OnReleaseComplete, from code Dmitrii provided to me (I'll take the blame for mistakes, but he got the event hooked up correctly, so do get the OnComplete event displayed in the log):
public class ARDocumentReleaseTESTABC : PXGraphExtension<ARReleaseProcess>
{
#region IsActive
public static bool IsActive()
{
return true;
}
#endregion IsActive
public void OnReleaseComplete(ARRegister doc)
{
///code
PXTrace.WriteVerbose(string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0}", "7d22cef8-c14d-4111-bcc5-405be006ea6b"));
}
#region ReleaseDocProc
public delegate List<ARRegister> ReleaseDocProcDelegate(JournalEntry je, ARRegister ardoc, List<Batch> pmBatchList, ARDocumentRelease.ARMassProcessReleaseTransactionScopeDelegate onreleasecomplete);
[PXOverride]
public List<ARRegister> ReleaseDocProc(JournalEntry je, ARRegister ardoc, List<Batch> pmBatchList, ARDocumentRelease.ARMassProcessReleaseTransactionScopeDelegate onreleasecomplete, ReleaseDocProcDelegate del)
{
PXTrace.WriteVerbose(string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0}", "d508b521-976c-452b-9765-57f532a0a513"));
onreleasecomplete += OnReleaseComplete;
PXTrace.WriteVerbose(string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0}", "309e2e6b-6fb8-4c75-ab8a-40e56c173562"));
return del(je, ardoc, pmBatchList, onreleasecomplete);
}
#endregion
}

Unable to use protected method override in Acumatica Graph Extension

I'm attempting to create a graph extension to modify the behavior of one of the mobile scan screens, and I found in developer release notes for 2020 R1 the ability to use the [PXProtectedAccess] attribute to utilize protected members of a Graph from the extension, even though it does not directly inherit from it.
However, in order to utilize this, the Graph Extension class needs to be abstract and Acumatica no longer seems to recognize it when I do so. I'm sure that I am missing a crucial piece here but I can't figure out what it is based on documentation. EDIT: I was missing the [PXProtectedAccess] attribute on the class itself.
Now I am seeing something else when I try to actually call the abstract method. It throws Unable to cast object of type 'Wrapper.PX.Objects.IN.Cst_INScanIssueHost' to type 'INScanIssueHostDynamicInterface'. when I attempt to call any one of these protected members. I'm not sure what INScanIssueHostDynamicInterface refers to or how to resolve the type conflicts here.
Here is an excerpt of the code I'm using:
[PXProtectedAccess]
public abstract class INScanIssue_Extension : PXGraphExtension<INScanIssue, INScanIssueHost>
{
[PXProtectedAccess]
protected abstract void ReportError(string errorMsg, params object[] args);
public delegate void ProcessConfirmDelegate();
[PXOverride]
public virtual void ProcessConfirm(ProcessConfirmDelegate baseMethod)
{
ReportError("TEST");
}
}
I think you are on the right path. Your graphExtension should be abstract. Also please note that on your extension you use protected member of the graph extension by specifying the parameter of the attribute, as shown below:
public class MyGraph : PXGraph<MyGraph>
{
protected void Bar() { }
}
public class MyExt : PXGraphExtension<MyGraph>
{
protected void Foo() { }
}
[PXProtectedAccess]
public abstract class MySecondLevelExt : PXGraphExtension<MyExt, MyGraph>
{
[PXProtectedAccess]
protected abstract void Bar();
[PXProtectedAccess(typeof(MyExt))]
protected abstract void Foo();
}
So in your case, I think you can try to add that parameter to the ProctectedAccess attribute for those members that from INScanIssue(or overriden there ):
namespace PX.Objects.IN
{
[PXProtectedAccess]
public abstract class INScanIssue_Extension : PXGraphExtension<INScanIssue,
INScanIssueHost>
{
public static bool IsActive()
{
return true;
}
# region Protected Access
*[PXProtectedAccess(typeof(INScanIssue))]*
protected abstract void ClearHeaderInfo(bool redirect = false);
[PXProtectedAccess]
protected abstract void SetScanState(string state, string message = null, params object[] args);
[PXProtectedAccess(typeof(INScanIssue))]
protected abstract bool PromptLocationForEveryLine { get; }
........................................
Use the abstract extension only to access the protected members, then add a second level extension, that calls the exposed members from your first level extension. And I don't think you need to apply the attribute on the extension.
public abstract class INScanIssueProtectedAccessExt : PXGraphExtension<INScanIssue, INScanIssueHost>
{
[PXProtectedAccess]
public abstract void ReportError(string errorMsg, params object[] args);
}
public class INScanIssue_Extension : PXGraphExtension<INScanIssueProtectedAccessExt, INScanIssue, INScanIssueHost>
{
public delegate void ProcessConfirmDelegate();
[PXOverride]
public virtual void ProcessConfirm(ProcessConfirmDelegate baseMethod)
{
this.Base2.ReportError("TEST");
}
}

Acumatica Report: How to populate Vendor Location as parameter?

I have multiple branches/Locations for Vendors and need it as parameter but when I tried to populate the lookup it returns empty.
Report Parameter
Location View name I used:
=Report.GetFieldSchema('Location.LocationCD')
The result
You can reuse an existing DAC but for more control you may create a Parameter DAC
using PX.Data;
using PX.Objects.CS;
using PX.Objects.GL;
using PX.Objects.CR;
using System;
namespace PX.Objects.AP
{
public class APVendorParameters : IBqlTable
{
#region ActiveVendorID
public abstract class activeVendorID : PX.Data.IBqlField { }
[Vendor]
[PXRestrictor(typeof(Where<Vendor.status, NotEqual<BAccount.status.inactive>>),
Messages.VendorIsInStatus,
typeof(Vendor.status))]
public virtual int? ActiveVendorID { get; set; }
#endregion
#region VendorLocationID
public abstract class vendorLocationID : PX.Data.IBqlField { }
[PXDBInt]
[PXSelector(typeof(Search<Location.locationID, Where<Location.bAccountID, Equal<Optional<activeVendorID>>>>), SubstituteKey = typeof(Location.locationCD), DescriptionField = typeof(Location.locationCD))]
[PXUIField(DisplayName = "Location")]
public virtual Int32? VendorLocationID { get; set; }
#endregion
}
}
In report add 2 parameters:
VendorID (string) ViewName=Report.GetFieldSchema('APVendorParameters.ActiveVendorID')
LocationID (string) ViewName=Report.GetFieldSchema('APVendorParameters.VendorLocationID,VendorID')
The secret stais in
,VendorID')
syntax

Resources