I currently have custom RowPersisted events on SOLine and POLine in Acumatica. Basically I need to make sure that custom Vendor cost field in SO and unit price field in PO update each other for all the linked PO's and SO's, when user saves them in Acumatica. So I have something like this in POLine_RowPersisted:
soRecExt.UsrVendorCost = line.CuryUnitCost;
SOOrderEntry graph = PXGraph.CreateInstance<SOOrderEntry>();
graph.CurrentDocument.Current = soOrd;
var result = graph.Transactions.Select();
graph.Transactions.Update(soRec);
graph.Actions.PressSave();
And something like this in SOLine_RowPersisted:
poRec.CuryUnitCost = lineExt.UsrVendorCost;
POOrderEntry graph = PXGraph.CreateInstance<POOrderEntry>();
graph.CurrentDocument.Current = poOrd;
var result = graph.Transactions.Select();
graph.Transactions.Update(poRec);
graph.Actions.PressSave();
So unfortunately when one is updated the whole thing enters infinite loop. I have tried something like this:
POOrderEntry_Extension graphExt = graph.GetExtension<POOrderEntry_Extension>();
graphExt.RowPersisted.RemoveHandler<SOOrderEntry_Extension>(graphExt.POLine_RowPersisted);
However, there is no RowPersisted on graph extension. My events are set to public. Can someone help please?
The events are registered and triggered by the Base graph so you have to remove them on base graph.
I believe what you're trying to accomplish is more along the lines of:
POOrderEntry_Extension graphExt = graph.GetExtension<POOrderEntry_Extension>();
graphExt.Base.RowPersisted.RemoveHandler<POOrderEntry>(graphExt.POLine_RowPersisted);
Where 'graph' is of type POOrderEntry then using 'graph' is equivalent to 'graphExt.Base'.
Related
Apologies if this question has been answered elsewhere, I have had trouble finding any resources on this.
The scenario is this. I have created a custom field in the Tax Preferences screen called Usrapikey.
This value holds an api key for a call that gets done in some custom business logic.
The custom business logic however occurs on the Taxes screen.
So within the event handler I need to access that API key value from the other screen. I have tried instantiating graphs and using linq and bql but to no avail.
below is what I have currently and my error is: No overload for method 'GetExtension' takes 1 arguments
If I am going about this the wrong way please let me know if there is a more civilized way to do this
protected virtual void _(Events.FieldUpdated<TaxRev, TaxRev.startDate> e)
{
var setup = PXGraph.CreateInstance<TXSetupMaint>();
var TXSetupEX = setup.GetExtension<PX.Objects.TX.TXSetupExt>(setup);
var rateObj = GetValues(e.Row.TaxID, TXSetupEX.Usrapikey);
decimal rate;
var tryRate = (Decimal.TryParse(rateObj.rate.combined_rate, out rate));
row.TaxRate = (decimal)rate * (decimal)100;
row.TaxBucketID = 1;
}
Many Thanks!
Well, acumatica support got back. It seems if you want to access the base page use:
TXSetup txsetup = PXSetup<TXSetup>.Select(Base);
To get the extension use:
TXSetupExt rowExt = PXCache<TXSetup>.GetExtension<TXSetupExt>(txsetup);
Then you can access the extension fields like so:
var foo = rowExt.Usrfield;
I want to get all the attributes from my "Actual Item Inventry" (From Stock Items Form) so i have:
PXResultset<CSAnswers> res = PXSelectJoin<CSAnswers,
InnerJoin<InventoryItem,
On<CSAnswers.refNoteID, Equal<Current<InventoryItem.noteID>>>
>
>.Select(new PXGraph());
But, this returns me 0 rows.
Where is my error?
UPDATED:
My loop is like this:
foreach (PXResult<CSAnswers> record in res)
{
CSAnswers answers = (CSAnswers)record;
string refnoteid = answers.RefNoteID.ToString();
string value = answers.Value;
}
... but i can not go inside foreach.
Sorry for the English.
You should use an initialized graph rather than just "new PXGraph()" for the select. This can be as simple as "this" or "Base" depending on where this code is located. There are times that it is ok to initialize a new graph instance, but also times that it is not ok. Not knowing the context of your code sample, let's assume that "this" and "Base" were insufficient, and you need to initialize a new graph. If you need to work within another graph instance, this is how your code would look.
InventoryItemMaint graph = PXGraph<InventoryItemMaint>.CreateInstance<InventoryItemMaint>();
PXResultset<CSAnswers> res = PXSelectJoin<CSAnswers,
InnerJoin<InventoryItem, On<CSAnswers.refNoteID, Equal<Current<InventoryItem.noteID>>>>>
.Select(graph);
foreach (PXResult<CSAnswers> record in res)
{
CSAnswers answers = (CSAnswers)record;
string refnoteid = answers.RefNoteID.ToString();
string value = answers.Value;
}
However, since you should be initializing graph within a graph or graph extension, you should be able to use:
.Select(this) // To use the current graph containing this logic
or
.Select(Base) // To use the base graph that is being extended if in a graph extension
Since you are referring to:
Current<InventoryItem.noteID>
...but are using "new PXGraph()" then there is no "InventoryItem" to be in the current data cache of the generic base object PXGraph. Hence the need to reference a fully defined graph.
Another syntax for specifying exactly what value you want to pass in is to use a parameter like this:
var myNoteIdVariable = ...
InventoryItemMaint graph = PXGraph<InventoryItemMaint>.CreateInstance<InventoryItemMaint>();
PXResultset<CSAnswers> res = PXSelectJoin<CSAnswers,
InnerJoin<InventoryItem, On<CSAnswers.refNoteID, Equal<Required<InventoryItem.noteID>>>>>
.Select(graph, myNoteIdVariable);
foreach (PXResult<CSAnswers> record in res)
{
CSAnswers answers = (CSAnswers)record;
string refnoteid = answers.RefNoteID.ToString();
string value = answers.Value;
}
Notice the "Required" and the extra value in the Select() section. A quick and easy way to check if you have a value for your parameter is to use PXTrace to write to the Trace that you can check after refreshing the screen and performing whatever action would execute your code:
PXTrace.WriteInformation(myNoteIdVariable.ToString());
...to see if there is a value in myNoteIdVariable to retrieve a result set. Place that outside of the foreach block or you will only get a value in the trace when you actually get records... which is not happening in your case.
If you want to get deep into what SQL statements are being generated and executed, look for Request Profiler in the menus and enable SQL logging while you run a test. Then come back to check the results. (Remember to disable the SQL logging when done or you can generate a lot of unnecessary data.)
I have created a custom table and made it available on the Customers screen called 'Serial Tracking'. The purpose of this screen is to track serialised items that each customer is in possession of (regardless of who the item was purchased from).
I would like a record automatically added to the table on shipment release. I have attempted to customise the Release method of SoShipmentEntry but am having trouble getting all the required data together as well as the best way to structure the code.
The custom table DAC is
AUSerialTrack
Not necessarily the answer to your question but to long for a comment.
As an alternative, what if you set your Serials tab view to the Ship Line Split table without dealing with a custom table. You could get the information you needed with something like this: (need to convert to your BQL view for your serials tab)
SELECT [ship].[CustomerID],
[ship].[ShipmentNbr],
[split].[InventoryID],
[split].[LotSerialNbr]
FROM [dbo].[SOShipLineSplit] split
INNER JOIN [dbo].[SOShipLine] line
ON [line].[CompanyID] = [split].[CompanyID]
AND [line].[ShipmentNbr] = [split].[ShipmentNbr]
AND [line].[LineNbr] = [split].[LineNbr]
INNER JOIN [dbo].[SOShipment] ship
ON [ship].[CompanyID] = [split].[CompanyID]
AND [ship].[ShipmentNbr] = [split].[ShipmentNbr]
INNER JOIN [dbo].[InventoryItem] i
ON [i].[CompanyID] = [split].[CompanyID]
AND [i].[InventoryID] = [split].[InventoryID]
INNER JOIN [dbo].[INLotSerClass] c
ON [c].[CompanyID] = [i].[CompanyID]
AND [c].[LotSerClassID] = [i].[LotSerClassID]
WHERE [c].[LotSerTrack] = 'S'
AND [ship].[Confirmed] = 1;
Then when the user goes to the tab its always the current results. No custom code to fill in a custom table so easier for upgrades/customization maintenance.
We have a business requirement to set the SO return COST to the original cost issued without invoicing if possible. We determined that Sales Orders are necessary to track issuing materials to our client, and we are cost driven rather than price driven. We use FIFO costing, but SO return orders do not seem to return at the original COST unless invoiced (which we also don't do in a traditional manner).
I found that setting the unit/ext cost on the SO Shipment Line directly in the database before Confirm Shipment and Update IN appears to provide the results desired. Applying a custom menu option to streamline and strongly control the return, I cloned nearby code as a base. The section between the === is where I set the unit/ext cost. The PXTrace shows the expected value, but it is coming out as $0 on the shipment record. I thought I might need "docgraph.Update(sOShipmentLine)" to save it, but that's not accessible in this scope.
using (var ts = new PXTransactionScope())
{
PXTimeStampScope.SetRecordComesFirst(typeof(SOOrder), true);
//Reminder - SOShipmentEntry docgraph = PXGraph.CreateInstance<SOShipmentEntry>();
docgraph.CreateShipment(order, SiteID, filter.ShipDate, adapter.MassProcess, SOOperation.Receipt, created, adapter.QuickProcessFlow);
PXTrace.WriteError("Setting Cost");
//Set Cost on Shipment to Cost On SO Line
PXResultset<SOShipment> results =
PXSelectJoin<SOShipment,
InnerJoin <SOShipLine, On<SOShipLine.shipmentNbr, Equal<SOShipment.shipmentNbr>>,
InnerJoin <SOLine, On<SOLine.orderType, Equal<SOShipLine.origOrderType>,
And<SOLine.orderNbr, Equal<SOShipLine.origOrderNbr>, And<SOLine.lineNbr, Equal<SOShipLine.origLineNbr>>>>
>>,
Where<SOShipment.shipmentNbr, Equal<Required<SOShipment.shipmentNbr>>>>
.Select(docgraph, docgraph.Document.Current.ShipmentNbr);
PXTrace.WriteError("Shipment {0} - Records {1}", docgraph.Document.Current.ShipmentNbr, results.Count);
foreach (PXResult<SOShipment, SOShipLine, SOLine> record in results)
{
SOShipment shipment = (SOShipment)record;
SOShipLine shipmentLine = (SOShipLine)record;
SOLine sOLine = (SOLine)record;
==============================================
shipmentLine.UnitCost = GetReturnUnitCost(sOLine.OrigOrderType, sOLine.OrigOrderNbr, sOLine.OrigLineNbr, sOLine.CuryInfoID);
shipmentLine.ExtCost = shipmentLine.Qty * shipmentLine.UnitCost;
PXTrace.WriteError(string.Format("{0} {1}-{2} = {3} / {4}", shipmentLine.LineType, shipmentLine.ShipmentNbr, shipmentLine.LineNbr, shipmentLine.Qty, shipmentLine.UnitCost));
==============================================
}
PXAutomation.CompleteSimple(docgraph.Document.View);
var items = new List<object> { order };
PXAutomation.RemovePersisted(docgraph, typeof(SOOrder), items);
PXAutomation.RemoveProcessing(docgraph, typeof(SOOrder), items);
ts.Complete();
}
Still on the learning curve so I'm expecting the solution is likely simple and obvious to someone more experienced.
There's three phase to it:
Changing the value
Updating the cache
Persisting the cache
I think you are changing the value but not persisting it. The reason why it works after invoking Confirm Shipment or Update IN action is probably that these actions will persist all changes by calling the graph Save action.
To change a field value in a data view you would do:
DACRecord.Field = value;
DataView.Update(DACRecord);
The particularity of your example is that the request is not bound to a data view.
When you have a loose BQL request you can do the same operation with a cache object. In your example the Caches context is available from docGraph:
DACRecord.Field = value;
graph.Caches[typeof(DACType)].Update(DACRecord);
graph.Caches[typeof(DACType)].Persist(DACRecord, PXDBOperation.Update);
Update and Persist are often omitted because in many scenarios they will be called later on by other framework mechanism. For example if you were to do only Update on a UI field, the record won't be persisted until the user clicks on the save button.
Updating value on UI is a bit different than updating in cache.
The recommended approach for UI fields is to use SetValue:
cache.SetValue<DAC.DacField>(DACRecord, fieldValue);
Or use SetValueExt when you want to trigger the framework events like FieldUpdated when changing the field value:
cache.SetValueExt<DAC.DacField>(DACRecord, fieldValue);
You'll still have to update and persist the changes in cache for these too if you want the changes to stick without requiring the user to manually save the document.
i am using the Graph library in order to save my data correctly.
I was wondering, if there's a way to update an existing Entity, without duplicate the entity 2 times: user will be allowed to update just two values of the entity.
Also, i would like to know if there's a proper save method
I give you an example
let person = Entity(type: "Person")
person["name"] = //not editable
person["work"] = //editable
person["age"] = //editable
graph.sync() //or something like graph.update
Writing this, i am just creating a new entity, which is not what i want. Maybe, i have to search for the entity, delete that every time, and insert the new one?Hope not.
Thank you for any help you could give!
yes, you can update Entities.
You need to search for the one you want to update first. For example:
let graph = Graph()
let search = Search<Entity>(graph: graph).for(types: "Person")
for entity in search.sync() {
// do something
}
Once you have the entity you want to update, you can set any property, group, or tag as per usual.
entity["name"] = "Daniel"
From there you need to call graph.sync() or graph.async() so that all is saved.
Thats it :)