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

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.

Related

How do I attach data to custom fields on INTranCost during release of POReceiptEntry?

I need to attach custom data into new fields added to INTranCost when the PO Receipt occurs.
Following the breadcrumbs, it seems that POReceiptEntry -> Release Action eventually calls INDocumentRelease.ReleaseDoc that eventually creates INTranCost. I tried extending both POReceiptEntry and INDocumentRelease to add an event for INTranCost_RowInserted to publish a PXTrace message, but the trace doesn't appear, telling me that I'm not hitting the event that I expected. (Which explains why the real business logic I need included didn't fire.)
protected virtual void _(Events.RowInserted<INTranCost> e)
{
PXTrace.WriteInformation("This is it!");
}
Of course, I want to put real code in this spot, but I am just trying to make sure I'm hitting the event properly. This works on pretty much everything else I've done, including attaching similar data to INTranExt fields. I cannot get it to work for INTranCost so that I can add to INTranCostExt. At this point, I can't determine if it is location (which graph extension) or a special methodology required for this special case.
I also tried overriding events and putting a breakpoint on the code, but it's like I'm not even on the same process. (Yes, I checked that I am connected to the right Acumatica instance and that I have no errors.)
What event in which graph is required to capture the creation in INTranCost for a PO Receipt to update custom fields in INTranCostExt?
Using Request Profiler, I was able to determine that I was close but not deep enough. While the INTranCost object to insert was built in INDocumentRelease FILE, the actual insert was processed in INReleaseProcess graph in that same file.
I only need to execute this "push" from the data captured on the POLine when the INTranCost record is created, and LineNbr is a key field and therefore never updated after it is set. I need to be sure that I have enough data to make the connection back, and the primary key links me back to the INTran easily. That subsequently gets back to the POReceiptLine to the POLine where the data is maintained that needs the "current value" to be captured when the transaction is posted. Since I need to update the DAC Extension, I need to use an event that will allow an existing DAC.Update to apply my values. Therefore, I added an event handler on INTranCost_LineNbr_FieldUpdated since that value should not be "updated" after it is set initially.
Code that accomplished the task:
public class INReleaseProcess_Extension : PXGraphExtension<INReleaseProcess>
{
public override void Initialize()
{
base.Initialize();
}
protected virtual void _(Events.FieldUpdated<INTranCost.lineNbr> e)
{
INTranCost row = (INTranCost) e.Row;
INTran tran = PXSelect<INTran,
Where<INTran.docType, Equal<Required<INTran.docType>>,
And<INTran.refNbr, Equal<Required<INTran.refNbr>>,
And<INTran.lineNbr, Equal<Required<INTran.lineNbr>>
>>>>
.SelectSingleBound(Base, null, row.DocType, row.RefNbr, (int?) e.NewValue);
if (tran?.POReceiptType != null && tran?.POReceiptNbr != null)
{
PXResultset<POReceiptLine> Results = PXSelectJoin<POReceiptLine,
InnerJoin<POLine, On<POLine.orderType, Equal<POReceiptLine.pOType>,
And<POLine.orderNbr, Equal<POReceiptLine.pONbr>,
And<POLine.lineNbr, Equal<POReceiptLine.pOLineNbr>>>>,
InnerJoin<POOrder, On<POOrder.orderType, Equal<POLine.orderType>,
And<POOrder.orderNbr, Equal<POLine.orderNbr>>>>>,
Where<POReceiptLine.receiptType, Equal<Required<POReceiptLine.receiptType>>,
And<POReceiptLine.receiptNbr, Equal<Required<POReceiptLine.receiptNbr>>,
And<POReceiptLine.lineNbr, Equal<Required<POReceiptLine.lineNbr>>>>>>.
SelectSingleBound(Base, null, tran.POReceiptType, tran.POReceiptNbr, tran.POReceiptLineNbr);
if (Results != null)
{
foreach (PXResult<POReceiptLine, POLine, POOrder> result in Results)
{
POReceiptLine receipt = result;
POLine line = result;
POOrder order = result;
POLineExt pOLineExt = PXCache<POLine>.GetExtension<POLineExt>(line);
INTranCostExt iNTranCostExt = PXCache<INTranCost>.GetExtension<INTranCostExt>(row);
if (pOLineExt != null && iNTranCostExt != null)
{
Base.Caches[typeof(INTranCost)].SetValueExt<INTranCostExt.usrField>(row, pOLineExt.UsrField);
}
}
}
}
}
}

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 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

Refresh Stock Items Screen

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.

Resources