Cannot trigger cancel button action after processing results returned - acumatica

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();
}

Related

Acumatica CROpportunityExt data not saving

Good day
I have a new field inside the CROpportunity Extenstion called usrGrossProfit.
During CROpportunity's RowSelected it works out the values as needed. The problem I am having is that the users are using the create Quote button on the form and because of this never saves using the save button, The system does it for them. I have found that because of this the usrGrossProfit value is not saved.
Is there a way to force a save/Persist inside the RowSelected function?
protected void CROpportunity_RowSelected(PXCache cache, PXRowSelectedEventArgs e)
{
try
{
var row = (CROpportunity)e.Row;
if (row == null) return;
CROpportunityExt SOE = PXCache<CROpportunity>.GetExtension<CROpportunityExt>(row);
int total = 0;
decimal TotalSales = 0;
decimal TotalCost = 0;
foreach (CROpportunityProducts item in this.Base.Products.Select())
{
total++;
CROpportunityProductsExt2 itemExt = PXCache<CROpportunityProducts>.GetExtension<CROpportunityProductsExt2>(item);
TotalCost += (decimal)itemExt.UsrCostPrice.Value * item.Qty.Value;
TotalSales += (decimal)itemExt.UsrSellingprice * item.Qty.Value;
}
SOE.UsrGrossProfit = TotalSales - TotalCost;
// I added this just to try and see if it helps
cache.SetValueExt<CROpportunityExt.usrGrossProfit>(row, (decimal)(TotalSales - TotalCost));
// we are not allowed to press the save button in the event Handler
//this.Base.Save.Press();
}
catch (Exception ex)
{
PXTrace.WriteError(ex);
}
}
I have also tried to override the CreateQuote Function but this doesn't work
public delegate IEnumerable CreateQuoteDelegate(PXAdapter adapter);
[PXOverride]
public IEnumerable CreateQuote(PXAdapter adapter, CreateQuoteDelegate baseMethod)
{
this.Base.Persist();
return baseMethod(adapter);
}
I have also made a business event to open and save the Opportunity also with no luck.
No, you shouldn't save on row selected even if it was allowed. This is because row selected event gets fired several times and you don't want to be saving each time.
If you want to save on your CreateQuote override, try this:
Base.Save.PressButton(adapter)
Perhaps a better option, might be to force the user so that it's the user himself who saves. For example, you could check the state and throw an error in your override instead of saving.
if (Opportunity.Current != null && Opportunity.Cache.GetStatus(Opportunity.Current) == PXEntryStatus.Inserted)
{
throw new PXException("Please save before proceeding");
}

Loading popup value to InventoryID field

with code from Load value from popup to InventoryID field question am able to set the InventoyID from popup to the InventoryID field but the default behavior of Acumatica is missing, like after InventoryID is set from poup, am selecting Item Class then a pop up is firing and asking for confirmation, generally this happen only when we update Item Class for already created Item.
[PXButton]
[PXUIField(DisplayName = "Generate")]
public IEnumerable GenerateInv(PXAdapter adapter)
{
string inventoryCD = "ACUMATICA";
InventoryItem item = PXCache<InventoryItem>.CreateCopy(Base.Item.Current);
OrderedDictionary keys = new OrderedDictionary(StringComparer.OrdinalIgnoreCase)
{
{
typeof(InventoryItem.inventoryCD).Name,
adapter.View.Cache.GetValue(adapter.View.Cache.Current,
typeof(InventoryItem.inventoryCD).Name)
}
};
OrderedDictionary vals = new OrderedDictionary(StringComparer.OrdinalIgnoreCase)
{
{
typeof(InventoryItem.inventoryCD).Name, inventoryCD
}
};
adapter.View.Cache.Update(keys, vals);
if (adapter.Searches != null && adapter.Searches.Length > 0)
{
adapter.Searches[0] = inventoryCD;
}
return adapter.Get();
}
You can try to skip the message window by overriding the Field Verifying on the stock item page for InventoryItem.ItemClassID. You should use some condition to indicate you are running your customer process so the standard message will appear for normal use on the stock items page.
public virtual void InventoryItem_ItemClassID_FieldVerifying(PXCache cache, PXFieldVerifyingEventArgs e, PXFieldVerifying del)
{
try
{
del?.Invoke(cache, e);
}
catch (PXDialogRequiredException)
{
var someSkipMessageWindowCondition = true;
if (someSkipMessageWindowCondition)
{
return;
}
throw;
}
}
Because of the use of the private property doResetDefaultsOnItemClassChange in the Base graph, the process will function as if the message box was answered YES to default the field values from the new item class. If you do not want the values to change using the new class you will need to cancel the field defaultings for the fields found in InventoryItem_ItemClassID_FieldUpdated in the Base graph. This will point out which fields are being updated when doResetDefaultsOnItemClassChange == true.
A better answer to your question would be automatically answering NO which I am unsure how to do. Would be nice to know how do perform that function if someone else knows how to make that call.

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();
}

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.

How to reselect item in Telerik's RadGrid?

I have 2 Telerik's rad grids. First one is master and second one is detail. I can delete rows from both grids independently by pressing "Delete" button on toolbar above each grid. I also have "Refresh" buttons in toolbar of both grids.
The problem is with detail grid. When I delete item(s) the grid doesn't refresh. Calling Rebind method doesn't help. The only thing that helps is to press "Refresh" button in toolbar of master grid and select the row in master grid by mouse that was previously selected. After that I can see refreshed detail grid.
So, I don't need to press "Refresh" button in toolbar of master grid and select the row in master grid by mouse. I can refresh the master grid programmatically and only want to reselect the item that was originally selected also programmatically. I've tried this:
item.Selected = true;
But, it only visually selects the item in master grid and doesn't refresh the detail grid.
So, how to select the item in master grid programmatically in order to get the same effect as selecting it by mouse?
Thank you in advance.
I've just realised that your probably using different DataSource for both grids, but pointing to the same database, right? My example below uses the same datasource for both grids. However I made on a detail view versus a normal view by making some columns not visible. Maybe this strategy could fix your issue?
My first thought was to try implement the SelectionChanged event, or if not that, the SelectionChanging event. Put a refresh in there you see. But I didn't end up doing it that way.
I wrote a small program as below. It saves the edits to disk with any row change as long as its not a remove (I had trouble saving remove edits when the button was clicked it gave a null pointer exception on the remove command). It also saves changes just before closing the program (so that any delete rows are also saved then). I did find that the deleteOne and deleteTwo buttons (that delete from the first or second grid, respectively) do in fact cause the deletion to occur in both grids. So a possibility is you could use the radGridView1.Rows.Remove(row) or RemoveAt(i) command if that works in your situation?
Another possibility is that if refresh isn't working you could set the DataSource to null and then set it to the data source again, after deleting the row. This is a bit drastic but if it's the only thing that works? I'm talking about the data source for both grids.
My code is below:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using Telerik.WinControls;
using Telerik.WinControls.Data;
using Telerik.WinControls.UI;
namespace RadControlsWinFormsApp1
{
public partial class RadForm1 : Telerik.WinControls.UI.RadForm
{
public RadForm1()
{
InitializeComponent();
}
private void RadForm1_Load(object sender, EventArgs e)
{
// TODO: This line of code loads data into the 'testdbDataSet.Customers' table. You can move, or remove it, as needed.
this.customersTableAdapter.Fill(this.testdbDataSet.Customers);
radGridView1.Columns["Address"].IsVisible = false;
}
private void radGridView1_RowsChanged(object sender, Telerik.WinControls.UI.GridViewCollectionChangedEventArgs e)
{
// if removing don't update, because if my delete button is pressed this
// will otherwise cause all sorts of problems and freezes the grid
if (e.Action != NotifyCollectionChangedAction.Remove)
{
try
{
customersTableAdapter.Update(testdbDataSet);
}
catch (DBConcurrencyException ex)
{
// unable to save right now, don't worry about it
}
}
radGridView2.Refresh();
}
private void butDeleteOne_Click(object sender, EventArgs e)
{
bool haveRemoved = false;
for (int i = 0; i < radGridView1.Rows.Count && !haveRemoved; ++i)
{
GridViewRowInfo row = radGridView1.Rows[i];
if (row.IsSelected)
{
haveRemoved = true;
radGridView1.Rows.RemoveAt(i);
}
}
}
private void butDeleteTwo_Click(object sender, EventArgs e)
{
bool haveRemoved = false;
for (int i = 0; i < radGridView2.Rows.Count && !haveRemoved; ++i)
{
GridViewRowInfo row = radGridView2.Rows[i];
if (row.IsSelected)
{
haveRemoved = true;
radGridView2.Rows.RemoveAt(i);
}
}
}
private void radGridView2_RowsChanged(object sender, GridViewCollectionChangedEventArgs e)
{
// if removing don't update, because if my delete button is pressed this
// will otherwise cause all sorts of problems and freezes the grid
if (e.Action != NotifyCollectionChangedAction.Remove)
{
try
{
customersTableAdapter.Update(testdbDataSet);
}
catch (DBConcurrencyException ex)
{
// unable to save right now, don't worry about it
}
}
radGridView1.Refresh();
}
private void RadForm1_FormClosing(object sender, FormClosingEventArgs e)
{
// ensure all data is saved back into database on close
customersTableAdapter.Update(testdbDataSet);
}
//private void radGridView1_CellEndEdit(object sender, Telerik.WinControls.UI.GridViewCellEventArgs e)
//{
//}
}
}

Resources