Refresh Stock Items Screen - acumatica

I have a problem in Acumatica. I have created a trigger on InventoryItem table to insert the inserted records to my customized table.
The problem is, whenever I try to save new stock items in Acumatica, it does not reflect the correct last-saved data of the stock item. The details in the general settings tab are incorrect. I need to close the screen and reopen to be able to see the correct data.
Can someone please help me with regards to how can I get a refreshed stock item screen immediately after saving. Or is there a bug in Acumatica whenever there is a customized trigger?

I haven't checked the issue you mentioned about not refreshing the information, but if you need to forcefully refresh the screen after saving the record
Override persist
call the base action
create a new instance of graph
search for the record to set as current of the header cache
throw new redirect required exception
so the code might look as below [Might require Modification]
[PXOverride]
public void Persist(Action persit)
{
persit();// this will call base Persist();
InventoryItemMaint grp = PXGraph.CreateInstance<InventoryItemMaint>();
InventoryItem inv = PXSelect<InventoryItem, Where<InventoryItem.inventoryCD, Equal<Required<InventoryItem.inventoryCD>>>>.Select(grp, this.Base.Item.Current.InventoryCD.Trim());
if (inv != null && inv.InventoryID.HasValue)
{
grp.Item.Current = grp.Item.Search<InventoryItem.inventoryID>(inv.InventoryID);
throw new PXRedirectRequiredException(grp, "Reloading Item");
}
}
If you dont want the whole screen to be refreshed, instead of throwing the exception you can just refresh the required view method suggested by other user(Yura Zaletskyy) on this post.

Let's say your grid is binded to view PayRollsDetails. Then you can use following code to refresh your grid:
PayRollsDetails.View.Cache.Clear();
PayRollsDetails.View.Cache.ClearQueryCache();

You can try the select method.
For example: this is the item setting data view in Acumatica source code (you can use the explore source code page)
[PXViewName(Messages.InventoryItem)]
public PXSelect<InventoryItem, Where<InventoryItem.inventoryID, Equal<Current<InventoryItem.inventoryID>>>> ItemSettings;
Through customization (I assume you use AEF), add select method for this data view
[PXViewName(Messages.InventoryItem)]
public PXSelect<InventoryItem, Where<InventoryItem.inventoryID, Equal<Current<InventoryItem.inventoryID>>>> ItemSettings;
protected virtual IEnumerable itemSettings()
{
return new PXSelect<InventoryItem, Where<InventoryItem.inventoryID, Equal<Current<InventoryItem.inventoryID>>>>(Base).Select();
}
Sometimes I use sql store procedure to insert data to my table, the select method is helpful for reloading screen with inserted data.

Related

How do I clear the input of a Smart Panel dialog (PXFilter)?

I have created a smart panel in a custom screen to ask for user input that is used to facilitate moving stock from normal inventory into an isolation area. My original smart panel example that I always use is the Copy Order in the SOOrderEntry graph (SO301000). In this case, I need to do a bit of validation, and the user may very well decide to close the smart panel and update the document in the screen before reopening the smart panel again. If the user clicks the cancel button, I need the smart panel to reset back to defaults every time it is opened.
I thought this might be handled in the ASPX screen definition, but I can't find the right setting for the form itself. I use AutoRefresh on selectors to refresh every time they are opened, but I need the form itself to do the same and refresh back to default every time it is opened. The desired behavior DOES occur automatically when I navigate to another record of the graph's primary DAC, but I cannot seem to force the smart panel to refresh automatically every time it is opened. I looked at the various options for the form in ASPX, but I overlooked it if it is there.
Similarly to CopyOrder on SOOrderEntry, here is my code sample from my graph.
public PXFilter<StockParamFilter> stockparamfilter;
#region AddFromStock
public PXAction<MyTag> addFromStock;
[PXUIField(DisplayName = Messages.AddFromStock, MapEnableRights = PXCacheRights.Insert, MapViewRights = PXCacheRights.Insert)]
[PXButton]
protected virtual IEnumerable AddFromStock(PXAdapter adapter)
{
MyTag tag = Tags.Current;
if (tag?.TranRefNbr != null)
{
throw new PXException(Messages.TagAlreadyReceived);
}
MyTagEntry graph = PXGraph.CreateInstance<MyTagEntry>();
WebDialogResult dialogResult = stockparamfilter.AskExt(setStockStateFilter, true);
if (dialogResult == WebDialogResult.OK || (IsContractBasedAPI && dialogResult == WebDialogResult.Yes))
{
// My Business Logic Here
}
return adapter.Get();
}
#endregion
#region CheckStockParams (OK Button in Smart Panel)
public PXAction<MyTag> checkStockParams;
[PXUIField(DisplayName = "OK", MapEnableRights = PXCacheRights.Select, MapViewRights = PXCacheRights.Select)]
[PXLookupButton]
public virtual IEnumerable CheckStockParams(PXAdapter adapter)
{
return adapter.Get();
}
#endregion
#region setStockStateFilter
private void setStockStateFilter(PXGraph aGraph, string ViewName)
{
checkStockStateFilter();
}
#endregion
#region checkStockStateFilter
protected virtual void checkStockStateFilter()
{
// My Business Logic Here to set bool enableStockParams = ???
checkStockParams.SetEnabled(enableStockParams);
}
#endregion
This seems like something I did in the past, but I cannot seem to locate the code. I think it is related to stockparamfilter being a PXFilter instead of a PXSelect (or SelectFrom).
I have tried stockparamfilter.ClearDialog() with no luck. I have tried stockparamfilter.RequestRefresh() with no luck. I even tried stockparamfilter.DeleteCurrent() which seemed to work when I hit Cancel, but then my code did not execute when I hit OK. I also seemed to get the desired results when I used stockparamfilter.Cache.SetDefaultExt<StockParamFilter.locationID>(filter); on every field, until I hit OK which did nothing. It's like every time I try to manipulate the filter, I break the smart panel without any errors in the trace. In fact, here is the list of what I tried unsuccessfully:
StockParamFilter filter = stockparamfilter.Current;
stockparamfilter.View.Clear();
stockparamfilter.View.RequestRefresh();
stockparamfilter.Cache.Clear();
stockparamfilter.View.RequestRefresh();
stockparamfilter.View.RequestFiltersReset();
stockparamfilter.DeleteCurrent();
stockparamfilter.ClearDialog();
stockparamfilter.Cache.SetDefaultExt<StockParamFilter.locationID>(filter);
stockparamfilter.Cache.SetDefaultExt<StockParamFilter.toLocationID>(filter);
stockparamfilter.Cache.SetDefaultExt<StockParamFilter.qty>(filter);
stockparamfilter.Cache.SetDefaultExt<StockParamFilter.lotSerialNbr>(filter);
stockparamfilter.Cache.SetDefaultExt<StockParamFilter.origRefNbr>(filter);
What is the ASPX code or C# Code that will let me reset the smart panel to defaults?
A big thanks to Hughes Beausejour at Acumatica for the offline assist. Posting solution for anyone else that may have this issue.
First, it is important to understand that AskExt generates 2 passes of the code. The first pass prompts the smart panel. Upon response to the smart panel, the code executes again, but in this second context skips the ask. With that in mind, the reason for my code not working became clear, as Hughes explained to me.
To execute the code when the form is initialized, that code must be executed before the ask occurs. Otherwise, the form is presented and then the initializing code is executed too late. Additionally, it must be conditioned such that it only fires when the smart panel was not given an OK response by the user. (Not realizing the code executes twice, I was unaware that I was resetting the fields on both passes. When I could get the form to reset, the subsequent processing would fail becuase I was resetting it on that pass as well.) Following that code, the AskExt can be used to present the form along with the normal processing of the user response.
My code, to show the working example, is as follows:
StockParamFilter filter = stockparamfilter.Current;
// If the user response is anything except an affirmative, default the fields
if (!(stockparamfilter.View.Answer == WebDialogResult.OK || (IsContractBasedAPI && stockparamfilter.View.Answer == WebDialogResult.Yes)))
{
stockparamfilter.Cache.SetDefaultExt<StockParamFilter.locationID>(filter);
stockparamfilter.Cache.SetDefaultExt<StockParamFilter.toLocationID>(filter);
stockparamfilter.Cache.SetDefaultExt<StockParamFilter.qty>(filter);
stockparamfilter.Cache.SetDefaultExt<StockParamFilter.lotSerialNbr>(filter);
stockparamfilter.Cache.SetDefaultExt<StockParamFilter.origRefNbr>(filter);
}
// Present the Smart Panel Dialog (happens only on the 1st pass - AskExt causes the code to execute twice)
WebDialogResult dialogResult = stockparamfilter.AskExt(setStockStateFilter, true);
// If the response was affirmative, execute the business logic
if (dialogResult == WebDialogResult.OK || (IsContractBasedAPI && dialogResult == WebDialogResult.Yes))
{
// Do Business Logic Based On User Response In Smart Panel
}

How to solve "Changes cannot be saved to the database from the event handler"?

In POOrderEntry as a POLine is created or deleted, I need to push a reference back to a custom DAC that originates the PO Line. For instance, if the PO Line is deleted, my custom DAC has the reference removed in Events.RowDeleted via:
using (PXTransactionScope ts = new PXTransactionScope())
{
Base.Caches[typeof(MyDAC)].SetValueExt<MyDAC.pOType>(row, null);
Base.Caches[typeof(MyDAC)].SetValueExt<MyDAC.pONbr>(row, null);
Base.Caches[typeof(MyDAC)].SetValueExt<MyDAC.pOLineNbr>(row, null);
Base.Caches[typeof(MyDAC)].Update(row);
Base.Caches[typeof(MyDAC)].Persist(PXDBOperation.Update);
ts.Complete(Base);
}
I have tried to allow the normal Persist to save the values, but it doesn't unless I call Persist (last line of my example above). The result is an error via Acuminator of "Changes cannot be saved to the database from the event handler". As I look at this, I wonder if it should be in an Long Operation instead of a Transaction Scope, but the error from Acuminator tells me I'm doing this wrong. What is the proper way to achieve my update back to "MyDAC" for each PO Line?
I have also tried initializing a graph instance for MyDAC's graph, but I get a warning about creating a PXGraph in an event handler so I can't "legally" call the graph where MyDAC is maintained.
My code compiles and functions as desired, but the error from Acuminator tells me there must be a more proper way to accomplish this.
You can add a view to the graph extension.
Then in the row deleted you will use your view.Update(row) to update your custom dac.
During the base graph persist your records will commit as long as there are no other errors found in other events.
The way you have it now commits your changes with a chance the row that was being deleted is never deleted.
Also with this change there is no need to use PXTransactionScope.
An example might look something like this...
public class POOrderEntryExtension : PXGraphExtension<POOrderEntry>
{
public PXSelect<MyDac> MyView;
protected virtual void _(Events.RowDeleted<POLine> e)
{
//get your row to update from e.Row
var myRow = PXSelect...
myRow.pOType = null;
myRow.pONbr = null;
myRow.pOLineNbr = null;
MyView.Update(myRow);
}
}

How do I save value in user field added to INItemPlan?

After using a custom "Tag" table to create a Sales Order destined to be fulfilled via PO, I need to attach my custom "Tag ID" to the INItemPlan record for future use in the purchasing process. While I was able to do this successfully in other places, repeating the same methodology is not working now.
In the code, the INItemPlanExt object is retrieved for the INItemPlan object. A value is assigned to the UsrTagID field, and the data can be retrieved from the cache following the Persist.
The code below shows the lookup from the SOLine to the SOLineSplit(s) to the INItemPlan record that is intended to handle purchase via the PO Create graph later. For testing purposes, it sets the value, persists the data, and then retrieves it again. The trace shows that the value appears to be written and retrievable, but the database does not show the value in the UsrTagID field when looking directly into SQL after the fact. I don't see where the code might reset the record in the database as this code is executed as the last call in the "Create SO" action that was added to the menu.
(A) Is there an error in the first section shown in how the UsrTagID field is retrieved and set/saved?
(B) Is there a better way to store the Tag ID on the INItemPlan (database table) record?
(C) Is there something I should look for that might be resetting the data elsewhere? (Although I may have missed something, I didn't find anything unexpected in event handlers.)
Version is: Acumatica 2018R1 Build 18.114.0018
public static void StoreSoTagID(SOOrderEntry graph, int? tagID, string orderType, string orderNbr, int? lineNbr)
{
PXResultset<SOLine> Results = PXSelectJoin<SOLine,
InnerJoin<SOOrder, On<SOOrder.orderNbr, Equal<SOLine.orderNbr>,
And<SOOrder.orderType, Equal<SOLine.orderType>>>,
InnerJoin<SOLineSplit, On<SOLineSplit.orderType, Equal<SOLine.orderType>,
And<SOLineSplit.orderNbr, Equal<SOLine.orderNbr>,
And<SOLineSplit.lineNbr, Equal<SOLine.lineNbr>>>>,
InnerJoin<INItemPlan, On<INItemPlan.planID, Equal<SOLineSplit.planID>>
>>>,
Where<SOLine.orderType, Equal<Required<SOLine.orderType>>,
And<SOLine.orderNbr, Equal<Required<SOLine.orderNbr>>,
And<SOLine.lineNbr, Equal<Required<SOLine.lineNbr>>>>>>
.Select(graph, orderType, orderNbr, lineNbr);
foreach (PXResult<SOLine, SOOrder, SOLineSplit, INItemPlan> result in Results)
{
INItemPlan plan = result;
INItemPlanExt planExt = PXCache<INItemPlan>.GetExtension<INItemPlanExt>(plan);
planExt.UsrTagID = tagID;
graph.Caches[typeof(INItemPlanExt)].Update(planExt);
graph.Caches[typeof(INItemPlanExt)].Persist(PXDBOperation.Update);
PXTrace.WriteInformation("Setting: {0} {1}", plan.PlanID, planExt.UsrTagID);
}
Results = PXSelectJoin<SOLine,
InnerJoin<SOOrder, On<SOOrder.orderNbr, Equal<SOLine.orderNbr>,
And<SOOrder.orderType, Equal<SOLine.orderType>>>,
InnerJoin<SOLineSplit, On<SOLineSplit.orderType, Equal<SOLine.orderType>,
And<SOLineSplit.orderNbr, Equal<SOLine.orderNbr>,
And<SOLineSplit.lineNbr, Equal<SOLine.lineNbr>>>>,
InnerJoin<INItemPlan, On<INItemPlan.planID, Equal<SOLineSplit.planID>>
>>>,
Where<SOLine.orderType, Equal<Required<SOLine.orderType>>,
And<SOLine.orderNbr, Equal<Required<SOLine.orderNbr>>,
And<SOLine.lineNbr, Equal<Required<SOLine.lineNbr>>>>>>
.Select(graph, orderType, orderNbr, lineNbr);
foreach (PXResult<SOLine, SOOrder, SOLineSplit, INItemPlan> result in Results)
{
INItemPlan plan = result;
INItemPlanExt planExt = PXCache<INItemPlan>.GetExtension<INItemPlanExt>(plan);
PXTrace.WriteInformation("Poll DB: {0} {1}", plan.PlanID, planExt.UsrTagID);
}
}
While I have had a problem in the past where I made the DAC definition PXString instead of PXDBString, I did verifiy the DAC to ensure that I didn't repeat the error this time.
The solution is in the comments, so pulling the comments from HB_ACUMATICA out into a solution for others to find easily...
graph.Caches[typeof(INItemPlan)].Update(plan);
graph.Caches[typeof(INItemPlan)].Persist(plan, PXDBOperation.Update);
When working with the cache, do not reference the Ext DAC. Work with the base cache. Acumatica knows how to connect the extended cache data for you. A combination of referencing the plan object in the Persist and also referencing the base DAC instead of the Ext DAC resulted in my data being written to the database table as desired.

How to automatically create Note record in Acumatica?

I've noticed that whenever an AR Invoice gets saved, a record gets created in the Note table with the new invoice's note ID. Can you tell me how that is being accomplished? I'd like to get one of my screens to do the same thing. I guess there must be some kind of attribute on the either the DAC or the graph but I can't find it. I have the PXNote attribute on the NoteID column in my DAC but it does not cause a Note record to be automatically created.
Thanks for your help.
To have Note record automatically created when a new parent record gets saved, one should invoke the static PXNoteAttribute.GetNoteID<Field>(PXCache cache, object data) method when the parent record is inserted in the cache.
For example, to have Note record automatically created when a new Stock Item gets saved, you should subscribe to RowInserted handler for the InventoryItem DAC and call PXNoteAttribute.GetNoteID<Field>(...):
public class InventoryItemMaintExt : PXGraphExtension<InventoryItemMaint>
{
public void InventoryItem_RowInserted(PXCache sender, PXRowInsertedEventArgs e)
{
var noteCache = Base.Caches[typeof(Note)];
var oldDirty = noteCache.IsDirty;
PXNoteAttribute.GetNoteID<InventoryItem.noteID>(sender, e.Row);
noteCache.IsDirty = oldDirty;
}
}
The code snippet above can be incorporated into almost any custom BLC with a couple simple changes to replace InventoryItem with a custom DAC.

Error #153 Another process has added 'Note' record. Your changes will be lost(SalesOrderEntry)

In the Sales Order page, I created a custom button which purpose is to save and refresh the page. Currently it saves fine and processes the new order to an order number but when I try to add an item/edit or perform an action in the drop down menu I receive the error message.
Here's my code:
public PXAction<SOOrder> SRefresh;
[PXUIField(DisplayName = "S RefreshT")]
[PXButton(CommitChanges = true)]
protected virtual IEnumerable sRefresh(PXAdapter adapter)
{
SOOrderEntry graph = PXGraph.CreateInstance<SOOrderEntry>();
Base.Actions.PressSave();
SOLine sLine = PXSelect<SOLine, Where<SOLine.orderNbr, Equal<Required<SOLine.orderNbr>>>>.Select(graph, this.Base.Document.Current.OrderNbr);
if (sLine != null && sLine.InventoryID.HasValue)
{
graph.Document.Current = graph.Document.Search<SOLine.orderNbr>(sLine.OrderNbr);
throw new PXRedirectRequiredException(graph, null);
}
return adapter.Get();
}
I've also tried using graph.Persist() as said in the manual instead of Action.PressSave(); with no success.
I appreciate any input you guys may have, Thank you
Since you're working with the current sales order, you don't need to create a new instance of the sales order entery graph and redirect your user. You can work with the Base object and run all your logic on it.
Base.Document.Current contains a reference to the current SOOrder, and Base.Transactions contains the list of SOLine of this document. Another problem I also found in your code is that you're calling Document.Search<SOline.orderNbr>; it should be SOOrder.orerNbr since you're searching inside the Document view, which contains sales orders, and not lines. In this case, it's not even necessary to search, Base.Document.Current will already be set to the order you're looking at.
I strongly recommend completing the standard Acumatica developer trainings - T100, T200, T300; this stuff is all covered and will get you productive quickly

Resources