What's the best way to override the project allocation process - acumatica

Regarding the 'Run Allocations by Projects' process - I have a customization where I'd like to add conditions to the Allocation process so that it doesn't execute unless those conditions are met. I've added a checkbox user field to the 'Allocation Rules' screen (PM207500), and I'd like that field to be used (in combination with other criteria) to determine whether or not to actually execute the Allocation for that PMTask row.
I've overriden the PMAllocator.Execute method as follows, but it doesn't seem to be working properly. Here is the code I've come up with in an extension of the PMAllocator graph:
[PXOverride]
public virtual void Execute(List<PMTask> tasks)
{
Base.PreselectAccountGroups();
if (Base.PreSelectTasksTransactions(tasks))
{
foreach (PMTask task in tasks)
{
//Get the allocation id for the task...
var pmalloc = (PMAllocation)PXSelect<PMAllocation,
Where<PMAllocation.allocationID, Equal<Required<PMAllocation.allocationID>>>>.Select(Base, task.AllocationID);
//if (pmalloc == null) return;
//Get the cache extension / user field...
var pmallocext = PXCache<PMAllocation>.GetExtension<PMAllocationExt>(pmalloc);
if (pmallocext.UsrRunAfterProjectCompletion == true)
{
//Get the project...
var pmproj = (PMProject)PXSelect<PMProject,
Where<PMProject.contractID, Equal<Required<PMProject.contractID>>>>.Select(Base, task.ProjectID);
if (pmproj.ExpireDate > DateTime.Today || pmproj.ExpireDate == null)
{
//do nothing..
}
else
{
Base.Execute(task, false);
}
}
else
{
Base.Execute(task, false);
}
}
}
}
But I'm not sure if this is the correct way to do it. It seems to be adding an extra allocation line. I'm not really even sure if the base execute method is NOT being called if I don't explicitly call it here.
Can someone point out the best way of accomplishing this?

When you use just [PXOverride] - base Execute(...) method will be called before yours.
To replace the base method you should specify additional parameter - a delegate. In your case it can be like that:
public delegate void ExecuteDelegate(List<PMTask> tasks);
[PXOverride]
public virtual void Execute(List<PMTask> tasks, ExecuteDelegate BaseExecute)
{
... your code
}

Related

While deleting the customer, how can i set null value for the custom field for the assigned contacts in Acumatica

I have tried multiple ways, but getting Another process error in the default version of Acumatica 19.106.0020
On top of it i have a customized code on both customer and contact screen, my requirement to clear the value of the custom field that is created in contact table when customer is deleting from the screen AR303000 i need to set null value of the custom field for the deleted contact from the customer.
i have tried by setting value on Customer_RowDeleting event but continuously getting Another process error, below is the screenshot error
Below is the code that i was tried
protected virtual void Customer_RowDeleting(PXCache sender, PXRowDeletingEventArgs e, PXRowDeleting BaseEvent)
{
BaseEvent?.Invoke(sender, e);
Customer rows = e.Row as Customer;
if (rows == null)
return;
if (Base.BAccount.Cache.GetStatus(Base.BAccount.Current) == PXEntryStatus.Deleted)
{
foreach (Contact BACT in PXSelectReadonly<Contact,
Where<Contact.bAccountID, Equal<Required<Contact.bAccountID>>,
And<Contact.contactType, NotEqual<ContactTypesAttribute.bAccountProperty>>>>.Select(Base, rows.BAccountID))
{
ContactMaint congraph = PXGraph.CreateInstance<ContactMaint>();
Contact CTData = PXSelectReadonly<Contact,
Where<Contact.contactID, Equal<Required<Contact.contactID>>>>.Select(Base, BACT.ContactID);
if (CTData != null)
{
congraph.Contact.Current = CTData;
if (congraph.Contact.Current != null)
{
congraph.Contact.SetValueExt<ContactExt.usrKWBAccountId>(congraph.Contact.Current, null);
congraph.Contact.Update(congraph.Contact.Current);
congraph.Save.Press();
}
}
}
}
}
Thanks in advance.
Hi Chris, please find the attached image here
I don't recommend to create graphs during RowDeleting event. If you have Acuminator installed, you will see a warning about creating graphs in event handlers.
Instead, call your custom code during the Persist method. Persist method is called during Delete operation. After the Base persist is finished, your custom code can perform it's work. Try something like this
public class CustomerMaint_Extension : PXGraphExtension<CustomerMaint>
{
public delegate void PersistDelegate();
[PXOverride]
public void Persist(PersistDelegate baseMethod)
{
Customer currentCustomer = Base.CurrentCustomer.Current; //get the customer record before it's deleted, i.e. Customer.bAccountID
baseMethod(); //let the base delete process happen first
if (Base.CurrentCustomer.Cache.GetStatus(currentCustomer) == PXEntryStatus.Deleted)
{
using (PXTransactionScope ts = new PXTransactionScope())
{
//here is where you add your code to delete other records
ts.Complete(); //be sure to complete the transaction scope
}
}
}
}
Also you might want to unpublish other customization packages, and see if the error continues without those packages. That is one way to determine the source of the error...by process of elimination.

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
// ...
});
}
}
}

xsp.extlib.convstate returns null

I have an Xpage application that uses the extension library where the xsp.extlib.convstate is 'null' for one of three users until they manually refresh page. All three users access application via RDP using Citrix and internet options are the same for all three. Trying to figure out why this would be happening. The application is only on one 9.0.1 server.
From the looks of the source code, if there hasn't been a conversationState initialised yet, the conversationState would not be initialised until either:
after the Render Response phase (in the phase listener: com.ibm.xsp.extlib.component.layout.impl.ApplicationPhaseListener)
#SuppressWarnings("unchecked") // $NON-NLS-1$
public void afterPhase(PhaseEvent event) {
if(event.getPhaseId()==PhaseId.RENDER_RESPONSE) {
// After the render phase, we save the conversion state
ConversationState.saveInSession(event.getFacesContext());
}
}
in the setParent method of the UIApplicationLayout, and this seems to be guarded by a 'isRestoringState' condition, which means I don't think this would run on the first view of a page as there wouldn't be any state to restore.
#Override
public void setParent(UIComponent parent) {
super.setParent(parent);
if( null == parent ){ // removing parent
return;
}
// TODO should move this initialization to initBeforeContents instead
FacesContextEx context = (FacesContextEx) getFacesContext();
if(null != context && !context.isRestoringState()) {
ConversationState cs = ConversationState.get(context, FacesUtil.getViewRoot(this), true);
// Initialize the conversation state
// Set the current navigation path to the UserBean
ApplicationConfiguration conf = findConfiguration();
if(conf!=null) {
String navPath = conf.getNavigationPath();
if(StringUtil.isEmpty(navPath)) {
// If there isn't a navigation path that is defined, the use the default one
if(StringUtil.isEmpty(cs.getNavigationPath())) {
navPath = conf.getDefaultNavigationPath();
}
}
if(StringUtil.isNotEmpty(navPath)) {
cs.setNavigationPath(navPath);
}
}
}
}
So this might explain why it wouldn't be initialised until the 2nd page view.
You could try forcing an initialisation of the ConversationState before you try to use it, maybe in beforePageLoad, by calling one of the com.ibm.xsp.extlib.component.layout.ConversationState's get() methods.
Note the boolean parameter tells the method to create the ConversationState if it does not exist.
I don't do much ServerSide Javascript but I guess this works? The sentiment is correct.
#{javascript: com.ibm.xsp.extlib.component.layout.ConversationState.get(facesContext, true); }
If you are doing it in java then:
ConversationState.get(FacesContext.getInstance(), true);
Does this sound like an explanation of why you are seeing your behaviour?

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.

Update UI using Parallel Task

I have method
public override void InitializeRow(object sender, InitializeRowEventArgs e)
{
if (!e.ReInitialize)
Task.Factory.StartNew(() =>
{
AfterInitializeRow(sender, e);
});
}
public override void AfterInitializeRow(object sender, InitializeRowEventArgs e)
{
foreach (UltraGridColumn ugc in e.Row.Band.Columns)
{
if (IsNumeric(ugc.Key))
{
e.Row.Cells[ugc].DroppedDown = true;
e.Row.Cells[ugc].ValueList = “Some value”;
e.Row.Cells[ugc].SetValue(e.Row.Cells[ugc.Key].Value, false);
e.Row.Cells[ugc].Style = Infragistics.Win.UltraWinGrid.ColumnStyle.DropDownList;
}
}
}
But its Giving error at e.Row.Cells[ugc].DroppedDown = true;
I learned that only Main thread can update the UI.
But is it possible that while updating the DroppedDown only it switch to main thread. Bcoz more than 1000’s rows are initialized in this way making the load of Grid very slow. So I want to do some kind of parallelism in this process.
In any function in your Form or UserControl, you can use the following type of code:
public void SetText(string text)
{
if (InvokeRequired)
{
BeginInvoke(new Action<string>(SetText), text);
}
else
{
label1.Text = text;
}
}
label1 would be the control to update in this case.
This will make sure that you invoke the function on the UI-thread.
You should still be careful with syncrhonization, though, but simply updating your UI from another thread can be easily done like that.
The answer to this question is that you shouldn't be using threading in the InitialzieRow event to set or even access properties on the grid or its related objects.
What you should do instead is look for ways to optimize what you are doing in this method first. For example why are you setting the value of a cell to the value it already has, this line of code should be able to be removed without impacting behavior.
Also all of the logic provided is only based on the column key so if the column has a consistent set of values, you could set the ValueList on the column in InitializeLayout instead of using InitializeRow.

Resources