How to customize the Process button on the AP505200 screen. Acumatica - acumatica

What method should I use to customize the "process" button on the AP 505200 screen.
I need to record records in a certain table when I press the Process button
Many thanks in advance!

The method is mapped to the Process/Process All actions using the SetProcessDelegate method of the processing data view.
First locate the Graph of the AP505200 screen (APReleaseChecks):
In the Source Code screen search for APReleaseChecks graph and find the SetProcessDelegate method:
APPaymentList.SetProcessDelegate(list => ReleasePayments(list, action));
The process delegate ReleasePayments method is static so you can't easily override it.
Try using SetProcessDelegate to call your own process delegate that will in turn call the base one.
public class APReleaseChecks_Extension : PXGraphExtension<APReleaseChecks>
{
public virtual void ReleaseChecksFilter_RowSelected(PXCache sender, PXRowSelectedEventArgs e)
{
ReleaseChecksFilter filter = e.Row as ReleaseChecksFilter;
if (filter != null)
{
Base.APPaymentList.SetProcessDelegate(delegate (List<APPayment> list)
{
// Do processing on list items before base process delegate
// [...]
// Call base process delegate
APReleaseChecks.ReleasePayments(list, filter.Action);
// Do processing on list items after base process delegate
// [...]
});
}
}
}

Related

Cannot trigger cancel button action after processing results returned

Within the Acumatica 19.201.0070 framework I have created a custom processing page that utilizes PXFilteredProcessing with the old style processing UI public override bool IsProcessing => false; I have defined a cancel button (below) that will clear the graph and set some values of the processing filter.
public PXCancel<NPMasterSubGeneratorFilter> Cancel;
[PXCancelButton()]
protected virtual IEnumerable cancel(PXAdapter adapter)
{
NPMasterSubGeneratorFilter row = Filter.Current;
if (row != null)
{
this.Clear();
Filter.SetValueExt<NPMasterSubGeneratorFilter.segmentID>(Filter.Current, row.SegmentID);
if (!(row.NewSegment ?? false)) Filter.SetValueExt<NPMasterSubGeneratorFilter.segmentValue>(Filter.Current, row.SegmentValue);
}
return adapter.Get();
}
This works perfectly fine except for a single use case, after processing results are shown if the user then presses the cancel button the corresponding action is never hit. ( My fellow office devs state that core Acumatica processing pages seem to operate the same. )
Setting of the processing delegate is within the filter RowSelected event.
GeneratedSubs.SetProcessDelegate(list => CreateSubaccounts(list, row));
I have implemented a few iterations of my processing method but the current is below.
protected virtual void CreateSubaccounts(List<NPGeneratedSub> subs, NPMasterSubGeneratorFilter filter)
{
if (filter.NewSegment ?? false)
{
try
{
SegmentMaint segGraph = PXGraph.CreateInstance<SegmentMaint>();
segGraph.Segment.Update(segGraph.Segment.Search<Segment.dimensionID, Segment.segmentID>(AADimension.Subaccount, filter.SegmentID.Value));
SegmentValue value = segGraph.Values.Insert(new SegmentValue() { Value = filter.SegmentValue, Descr = filter.Description });
segGraph.Actions.PressSave();
}
catch
{
throw new PXOperationCompletedSingleErrorException(NonProfitPlusMessages.SegmentValueCannotCreate);
}
}
SubAccountMaint subGraph = PXGraph.CreateInstance<SubAccountMaint>();
NPSubAccountMaintExtension subGraphExt = subGraph.GetExtension<NPSubAccountMaintExtension>();
subGraphExt.save.ConfirmSaving = false;
Sub newSub;
bool errored = false;
foreach (NPGeneratedSub sub in subs)
{
PXProcessing<NPGeneratedSub>.SetCurrentItem(sub);
try
{
newSub = subGraph.SubRecords.Insert(new Sub() { SubCD = sub.SubCD, Description = sub.Description });
subGraph.Save.Press();
subGraph.Clear();
PXProcessing<NPGeneratedSub>.SetProcessed();
}
catch (Exception e)
{
PXProcessing<NPGeneratedSub>.SetError(e);
errored = true;
}
}
if (errored)
{
throw new PXOperationCompletedWithErrorException();
}
}
What needs to be adjusted to allow the buttons action to be triggered on press after processing results have been returned?
After stepping through the javascript I discovered that it wasn't sending a request to the server when you click the cancel button on this screen after processing. The reason is because SuppressActions is getting set to true on the Cancel PXToolBarButton. I compared what I was seeing on this screen to what was happening on screens that work correctly and realized that Acumatica is supposed to set SuppressActions to true on the Schedule drop down PXToolBarButton but for some reason, on this screen, it is incorrectly setting it to true on whatever button is after the Schedule drop down button.
I looked through the code in PX.Web.UI and it looks like they set SuppressActions to true when a drop down button is disabled and PXProcessing adds a FieldSelecting event to the Schedule button which disables the button after you click process. However, I didn't notice any obvious issues as to why the code would be setting it on the wrong PXToolBarButton so someone will likely need to debug the code and see what's going on (we are unable to debug code in PX.Web.UI.dll).
I tried commenting out the other grids in the aspx file that aren't related to the PXProcessing view and this resolved the issue. So my guess would be that having multiple grids on the PXProcessing screen somehow causes a bug where it sets SuppressActions on the wrong PXToolBarButton. However, since the multiple grids are a business requirement, removing them is not a solution. Instead, I would suggest moving all buttons that are after the schedule button to be before the schedule button. To do this, just declare the PXActions before the PXFilteredProcessing view in the graph.
Please try this
Override IsDirty property
Use PXAction instead of PXCancel
Add PXUIField attribute with enable rights
action name should start from lowercase letter
delegate name should start from uppercase letter
see code below
public override bool IsDirty => false;
public override bool IsProcessing
{
get { return false;}
set { }
}
public PXAction<NPMasterSubGeneratorFilter> cancel;
[PXUIField(MapEnableRights = PXCacheRights.Select)]
[PXCancelButton]
protected virtual IEnumerable Cancel(PXAdapter adapter)
{
NPMasterSubGeneratorFilter row = Filter.Current;
if (row != null)
{
this.Clear();
Filter.SetValueExt<NPMasterSubGeneratorFilter.segmentID>(Filter.Current, row.SegmentID);
if (!(row.NewSegment ?? false)) Filter.SetValueExt<NPMasterSubGeneratorFilter.segmentValue>(Filter.Current, row.SegmentValue);
}
return adapter.Get();
}

How to add condition for process buttons on Recognize Input VAT TX503500

I have to add a description field that is mandatory, so that the action of processing can be carried out, however I am a little confused, due to the fact that the field is in the filter area, to be copied later in the descriptions to be processed.
How can I customize the actions Process, ProcessAll?
I don't find these actions in Override Methods
thanks for helping me, I'm really new to this
sorry if my english is not so good
Basically, the Process/Process All actions are mapped to one method which is using the SetProcessDelegate method of the processing data view.
What you need to do is for first locating to the Graph(ProcessInputSVAT) of the Recognize Input VAT screen(TX503500). After opening the source code for that graph you can see that it's derived from the ProcessSVATBase class. And when you'll enter that class you'll see the mentioned SetProcessDelegate function called by Data View:
protected virtual void SVATTaxFilter_RowSelected(PXCache sender, PXRowSelectedEventArgs e)
{
SVATTaxFilter filter = (SVATTaxFilter)e.Row;
if (filter == null)
{
return;
}
this.SVATDocuments.SetProcessDelegate(delegate(List<SVATConversionHistExt> list)
{
ProcessSVATBase.ProcessPendingVATProc(list, filter);
});
}
So we've figured out which graph extension we should create. Now, it's necessary to override the RowSelected event of the SVATTaxFilter DAC in the extension graph.
public class ProcessSVATBaseExt : PXGraphExtension<ProcessSVATBase>
{
public virtual void SVATTaxFilter_RowSelected(PXCache sender, PXRowSelectedEventArgs e)
{
SVATTaxFilter filter = e.Row as SVATTaxFilter;
if (filter != null)
{
Base.SVATDocuments.SetProcessDelegate(delegate (List<SVATConversionHistExt> list)
{
// Here you can manage the list items and then call the base method
// ...
ProcessSVATBase.ProcessPendingVATProc(list, filter); // the base method
// Here you can manage the list items after the base method
// ...
});
}
}
}

Acumatica scheduling an action

I am trying to create a routine to import orders from our EDI service into Acumatica. I have created a skeleton action:
public PXAction<EDOrderReviewFilter> GetOrders;
[PXProcessButton()]
[PXUIField(DisplayName = "Get Orders")]
protected virtual void getOrders()
{
EDOrderReview graph = PXGraph.CreateInstance<EDOrderReview>();
graph.Filter.Current.ReviewType = "A";
throw new PXRedirectRequiredException(graph, false, "Review");
}
I can finish the code to retrieve the orders and insert the sales orders, but I cannot get this to schedule. The order review graph display would not be included in the automated retrieval. The schedule appears to only allow scheduling of Process All even though the documentation says it should be a picklist of the actions in the graph. Can anyone help? Is there a better way to schedule the order retrieval? The current thought is to check every 15 minutes and import all new orders.
=============New information============================================
I am having trouble now making the calling graph show the spinning timer while orders are being retrieved. In the code below the EDGetOrders.cs is the new processing page that simply retrieves the orders. This will eventually be hidden and scheduled. EDOrderReview.cs is the original graph that allows review and adjustment of imported orders where I would like to have a button that will initiate an order retrieve and show some feedback that the process is running and then show some indication that it is finished. Using the PressButton method processes the retrieve synchronously and then the screen refreshes via the last three lines. The LongOperation method starts the process asynchronously and immediately redraws the screen. Am I using the LongOperation correctly?
// EDGetOrders.cs Separate graph to simply retrieve the orders
// Action to retrieve orders
public PXAction<EDGetOrderFilter> GetOrders;
[PXProcessButton()]
[PXUIField(DisplayName = "")]
protected virtual void getOrders()
{
getEDIOrders();
}
// This function performs all the work and works fine
public void getEDIOrders()
{
...
}
// This function is called on Process All and works fine and shows the spinning timer
public void ProcessOrder(List<EDIGetOrder> list, string type)
{
SOOrderEntry soOrderGraph = PXGraph.CreateInstance<SOOrderEntry>();
bool errorOccured = false;
string statusText = "";
foreach (EDIGetOrder ediOrder in list)
{
PXProcessing<EDIGetOrder>.SetCurrentItem(ediOrder);
getOrders();
statusText = "Orders Retrieved";
}
if (errorOccured)
throw new PXOperationCompletedWithErrorException(statusText);
else
throw new PXOperationCompletedException(statusText);
}
//EDOrderReview.cs Original graph I want to call getOrders from and show the spinning timer
//Action to create button
public PXAction<EDOrderReviewFilter> GetOrders;
[PXProcessButton()]
[PXUIField(DisplayName = "Get Orders")]
protected virtual void getOrders()
{
EDGetOrders getOrders = PXGraph.CreateInstance<EDGetOrders>();
//getOrders.GetOrders.PressButton();
PXLongOperation.StartOperation(this, delegate () { goGetOrders(); });
//Redraw the screen with the new orders
EDOrderReview graph = PXGraph.CreateInstance<EDOrderReview>();
graph.Filter.Current.ReviewType = "A";
throw new PXRedirectRequiredException(graph, false, "Review");
}
public static void goGetOrders()
{
EDGetOrders getOrders = PXGraph.CreateInstance<EDGetOrders>();
getOrders.getEDIOrders();
}
Unfortunately, the current documentation doesn't match with the actual behavior of the Automation Schedules screen. In reality, the Action Name field always stays disabled and can only show the Process All option. Hopefully, this explains why it won't be possible to schedule order retrieval from your current processing page.
An alternative solution would be to create a stand-alone processing screen just to retrieve orders from external EDI service, which you can schedule to run the Process All action every 15 minutes. You can hide this new processing screen from users by placing it in the Hidden folder of the SiteMap.
For sure, you can still keep the Get Orders button on your current processing screen and, if you implement your method to retrieve orders from external EDI service as static, it should be possible to invoke the same method from both your current and new processing screens.
Update to answer the New Information section:
You should throw PXRedirectRequiredException to show EDOrderReview after the GetEDIOrders operation is over:
public PXAction<EDOrderReviewFilter> GetOrders;
[PXProcessButton()]
[PXUIField(DisplayName = "Get Orders")]
protected virtual void getOrders()
{
EDGetOrders getOrders = PXGraph.CreateInstance<EDGetOrders>();
//getOrders.GetOrders.PressButton();
PXLongOperation.StartOperation(this, delegate ()
{
goGetOrders();
//Redraw the screen with the new orders
EDOrderReview graph = PXGraph.CreateInstance<EDOrderReview>();
graph.Filter.Current.ReviewType = "A";
throw new PXRedirectRequiredException(graph, false, "Review");
});
}
public static void goGetOrders()
{
EDGetOrders getOrders = PXGraph.CreateInstance<EDGetOrders>();
getOrders.getEDIOrders();
}

Intercept process on Run Project Billing screen

We're using the Run Project Billing screen to create records in AR / Invoice and Memo.
In the Invoice & Memo screen, we need the process to populate the header Customer Ord. number, along with a user field that has been added to the grid section on the 'Document Details' tab. At the moment, the process is not doing this.
I'd like to intercept the processing action on the screen using a technique I'm familiar with, namely using an 'AddHandler':
[PXOverride]
protected virtual IEnumerable Items (PXAdapter adapter)
{
PXGraph.InstanceCreated.AddHandler<BillingProcess>((graph) =>
{
graph.RowInserting.AddHandler<BillingProcess.ProjectsList>((sender, e) =>
{
//Custom logic goes here
});
});
return Base.action.Press(adapter);
}
I see no Base.Actions that remotely resembles 'Bill' or 'Bill All'.
This is obviously not exactly the code I need, but I would think this is the general place to start.
After reviewing the source business logic, I don't see any 'Bill' or 'Bill All' Actions - or any 'Actions' at all (baffling). I see an IEnumerable method called 'items', so that's what I started with above.
Is this the correct way to go about this?
Update: 2/14/2017
Using the answer provided re: the overridden method InsertTransaction(...) I've tried to set our ARTran user field (which is required) using the following logic:
PMProject pmproj = PXSelect<PMProject, Where<PMProject.contractID, Equal<Required<PMProject.contractID>>>>.Select(Base, tran.ProjectID);
if (pmproj == null) return;
PMProjectExt pmprojext = PXCache<PMProject>.GetExtension<PMProjectExt>(pmproj);
if (pmprojext == null) return;
ARTranExt tranext = PXCache<ARTran>.GetExtension<ARTranExt>(tran);
if (tranext == null) return;
tranext.UsrContractID = pmprojext.UsrContractID;
Even though this sets the user field to the correct value, it still gives me an error that the required field is empty when the process finishes. My limited knowledge prevents me from understanding why.
On the Run Project Billing screen, captions of Process and Process All buttons were changed to Bill and Bill All respectively in BLC constructor.
Process delegate is set for Items data view within the BillingFilter_RowSelected handler:
public class BillingProcess : PXGraph<BillingProcess>
{
...
public BillingProcess()
{
Items.SetProcessCaption(PM.Messages.ProcBill);
Items.SetProcessAllCaption(PM.Messages.ProcBillAll);
}
...
protected virtual void BillingFilter_RowSelected(PXCache cache, PXRowSelectedEventArgs e)
{
BillingFilter filter = Filter.Current;
Items.SetProcessDelegate<PMBillEngine>(
delegate (PMBillEngine engine, ProjectsList item)
{
if (!engine.Bill(item.ProjectID, filter.InvoiceDate, filter.InvFinPeriodID))
{
throw new PXSetPropertyException(Warnings.NothingToBill, PXErrorLevel.RowWarning);
}
});
}
...
}
As code snippet above confirms, all records in the AR Invoice and Memos screen are created by instance of the PMBillEngine class. Below is code snippet showing how to override InsertNewInvoiceDocument and InsertTransaction methods within the PMBillEngine BLC extension:
public class PMBillEngineExt : PXGraphExtension<PMBillEngine>
{
public delegate ARInvoice InsertNewInvoiceDocumentDel(string finPeriod, string docType, Customer customer,
PMProject project, DateTime billingDate, string docDesc);
[PXOverride]
public ARInvoice InsertNewInvoiceDocument(string finPeriod, string docType, Customer customer, PMProject project,
DateTime billingDate, string docDesc, InsertNewInvoiceDocumentDel del)
{
var result = del(finPeriod, docType, customer, project, billingDate, docDesc);
// custom logic goes here
return result;
}
[PXOverride]
public void InsertTransaction(ARTran tran, string subCD, string note, Guid[] files)
{
// the system will automatically invoke base method prior to the customized one
// custom logic goes here
}
}
Run Project Billing process invokes InsertNewInvoiceDocument method to create new record on the AR Invoice and Memos screen and InsertTransaction method to add new invoice transaction.
One important thing to mention: overridden InsertNewInvoiceDocument and InsertTransaction methods will be invoked when a user launches Run Project Billing operation either from the processing Run Project Billing screen or from the data entry Projects screen.
For more information on how to override virtual BLC methods, see Help -> Customization -> Customizing Business Logic -> Graph -> To Override a Virtual Method available in every Acumatica ERP 6.1 website

How can I execute code from the Release / Release All buttons in the Release AR Documents screen

I've got a customization to the Invoice & Memo screen where I execute some custom code (web service calls) when the Release action is activated. This works fine - I knew how to replace the PXAction code and proceeded from there. Now I want to use the Release AR Documents processing screen to do the same thing, but I'm having trouble understanding where / what to override, or where to place my code.
I see the ARDocumentRelease graph constructor with the SetProcessDelegate in the source code, but I'm not sure how to proceed - whether this is where I need to be looking or not. I need to execute my code for each line being released, using the RefNbr in my code.
Since it's an static method, you can't override it. Also, you can't do like it's done in the T300, because you are in processing graph and you can't override the release button with your own. I was able to achieve it by passing callback for each AR document that have been processed.
You can call the Initialize method of the ARDocumentRelease graph to override the logic like you said. After you just have to call ReleaseDoc that uses a callback parameter instead of using the default one.
Here's the code that I came with:
public class ARDocumentRelease_Extension : PXGraphExtension<ARDocumentRelease>
{
public override void Initialize()
{
ARSetup setup = Base.arsetup.Current;
Base.ARDocumentList.SetProcessDelegate(
delegate (List<BalancedARDocument> list)
{
List<ARRegister> newlist = new List<ARRegister>(list.Count);
foreach (BalancedARDocument doc in list)
{
newlist.Add(doc);
}
AddAdditionalLogicToRelease(newlist);
}
);
Base.ARDocumentList.SetProcessCaption("Release");
Base.ARDocumentList.SetProcessAllCaption("Release All");
}
public delegate void PostPorcessing(ARRegister ardoc, bool isAborted);
private void AddAdditionalLogicToRelease(List<ARRegister> newlist)
{
ARDocumentRelease.ReleaseDoc(newlist, true, null, delegate(ARRegister ardoc, bool isAborted) {
//Add your logic to handle each document
//Test to check if it was not aborted
});
}
}
Please note that you must always call static methods from within long running process and create necessary objects there.
Processing delegate logic is implemented as long running process which creates worker thread to execute the processing logic.
You have AddAdditionalLogicToRelease() method which requires object instance in order to call and will fail during thread context switches and hence the issue. So, you must have create object instance inside the thread context and then call instance method.
In general, method that gets called from long running processes are declared static and required objects/graphs are created inside this static method to do some work. See below example how to properly override ARDocumentRelease graph for this purpose:
public class ARDocumentRelease_Extension : PXGraphExtension<ARDocumentRelease>
{
public override void Initialize()
{
Base.ARDocumentList.SetProcessDelegate(
delegate (List<BalancedARDocument> list)
{
List<ARRegister> newlist = new List<ARRegister>(list.Count);
foreach (BalancedARDocument doc in list)
{
newlist.Add(doc);
}
// use override that allows to specify onsuccess routine
ARDocumentRelease.ReleaseDoc(newlist, true, null, (ardoc, isAborted) =>
{
//Custom code here, such as create your GL
});
}
);
}
}
I think it's the function
public static void ReleaseDoc(List<ARRegister> list, bool isMassProcess, List<Batch> externalPostList, ARMassProcessDelegate onsuccess)
under ARDocumentRelease businesss logic.

Resources