I have the following code:
protected void SOLine_UsrMargin_FieldUpdated(PXCache cache, PXFieldUpdatedEventArgs e)
{
var salesOrderEntry = (SOLine)e.Row;
if (salesOrderEntry == null)
return;
decimal? lastCost = GetLastCost(salesOrderEntry.InventoryID);
decimal margin = (decimal)cache.GetValue<SOLineExt.usrMargin (salesOrderEntry); <-- Line 47
.
.
.
}
This code works fine when the user is adding/editing a Sales Order. They are getting the following Exception after they have save the Sales Order, create a Shipment and go to confirm it:
Object reference not set to an instance of an object.
Stack Trace:
at PX.Objects.SO.SOOrderEntry_Extension.SOLine_UsrMargin_FieldUpdated(PXCache cache, PXFieldUpdatedEventArgs e) in g:\projects\xxx\Automatica\Projects\XXX\WebSite\App_RuntimeCode\SOOrderEntry.cs:line 47
at PX.Data.PXCache.OnFieldUpdated(String name, Object row, Object oldValue, Boolean externalCall)
at PX.Data.PXCache`1.a(TNode A_0, TNode A_1, TNode A_2)
This is running on Acumatica Version 6.10.1122
If you want to get the value of your custom field it will be better for you to change your code to something like this
SOLineExt rowExt = PXCache<SOLine>.GetExtension<SOLineExt>(salesOrderEntry);
decimal margin=0;// or set any default value for margin for calculation if rowExt==null
if(rowExt!=null)
{
margin = rowExt.usrMargin;
}
Also I will suggest you to rename salesOrderEntry which is the instance of current row to something like currentRow, so that your code become more readable.
Related
I was trying to update OperationCD in AMProdOper with my ProcessStepCD. But it gives me error when I call prodDetailMaint.Save.Press();
Error: Another process has added the 'Note' record. Your changes will be lost.
at PX.Data.PXCache`1.PersistInserted(Object row, Boolean bypassInterceptor)
at PX.Data.PXNoteAttribute.RowPersisting(PXCache sender, PXRowPersistingEventArgs e)
at PX.Data.PXCache.OnRowPersisting(Object item, PXDBOperation operation)
at PX.Data.PXCache`1.PersistInserted(Object row, Boolean bypassInterceptor)
at PX.Data.PXCache`1.Persist(PXDBOperation operation)
at PX.Data.PXGraph.Persist()
at PX.Objects.AM.ProdDetail.Persist() in D:\Bld5\AC-FULLCOMMON64-JOB1\code_repo\WebSites\Pure\PX.Objects.AM\AM\ProdDetail.cs:line 310
at PX.Data.PXSave`1.d__2.MoveNext()
at PX.Data.PXAction`1.d__38.MoveNext()
at PX.Data.PXAction`1.d__38.MoveNext()
at PX.Data.PXAction`1.PressImpl(Boolean internalCall, Boolean externalCall)
It was working fine in previous release but start giving error when we move to Acumatica 22r1 release.
Please help me out
Parent Function:-
ProdDetail prodDetailMaint = PXGraph.CreateInstance<ProdDetail>();
prodDetailMaint.ProdItemRecords.Current = prodItem;
<SomeDeleteCodeFromIfnotExist> //however it's not executing
<CalledChildFunction(prodItem, formula, fg, prodDetailMaint)>;
prodDetailMaint.Save.Press();
ChildFunction:-
PXResultset<AMProdOper> prodOperResultSet = prodDetailMaint.ProdOperRecords.Select();
foreach(EWPMBatchProcessStep batchStep in this.BatchProcessStep.Select()) {
AMProdOper prodOper = FindAMOper(prodOperResultSet, amProdItem, batchStep.AMProdOperID);
if(prodOper.OperationCD != batchStep.BatchProcessStepCD) {
prodOper.OperationCD = batchStep.BatchProcessStepCD;
}
prodDetailMaint.ProdOperRecords.Update(prodOper);
I had added a custome field "UsrWgtIndex" on Allocation panel in Purchase Receipts. And its value is the sum of the other custom field "UsrWgtPerUnit".
But strange thing happened. The value of UsrWgtIndex keeps the same when I open diffrent allocation panel. It is always the value of first row of transations.
My code is below, and I'm really confused about this. In logic, the code will sum each row of transations, and assign each row of the "UsrWgtIndex". But it's always the value of the first row.
Anyone can help on this? Thanks a lot!
namespace PX.Objects.PO {
public class POReceiptEntry_Extension: PXGraphExtension < POReceiptEntry > {
#region Event Handlers
decimal totalCgt = 0 M,
tempTotal = 0 M;
protected void POReceiptLine_RowSelected(PXCache cache, PXRowSelectedEventArgs e) {
POReceiptLine poRLine = (POReceiptLine) e.Row;
if (poRLine != null) {
totalCgt = 0 M;
foreach(POReceiptLineSplit line in Base.splits.Select()) {
POReceiptLineSplitExt poReceiptLineSplitExt = line.GetExtension < POReceiptLineSplitExt > ();
var recentQty = poReceiptLineSplitExt.UsrWgtPerUnit;
var temp = Convert.ToDecimal(recentQty);
totalCgt = totalCgt + temp;
};
var cgt = Convert.ToDecimal(totalCgt);
if (totalCgt != null) {
cache.SetValue < POReceiptLineExt.usrTotalWgt > (poRLine, cgt);
//This line is setting the value of usrWgtIndex
cache.SetValue < POReceiptLineExt.usrWgtIndex > (poRLine, cgt);
};
}
}
}
}
More detail update:
The customer field “usrWgtIndex” belongs to the data class “POReceiptLine”. But I make its control on the Allocations Panel.
I had made a test: set value to the field “UnassignedQty” on allocations panel with the same value I give to “usrWgtIndex”. It works correctly. Or I changed the other field in POReceiptLine data class with same value in the same time, It works fine again.
3.It seems that if I change a custom field on allocations panel, this strange thing would happen…
More Detail Update2:
I didn't add the "UsrWgtIndex" on the DAC LotSerOptions. I added it on the POReceiptLine. Because when I add the custom field on LotSerOptions, I can't assign its value with setValueEXT methord, it seems there is no DAC named "LotSerOptionsExt".
So I just put "UsrWgtIndex" on DAC POReceiptLine, and assign its value with
cache.SetValue<POReceiptLineExt.usrWgtIndex>(poRLine, cgt);
The 'splits' DataView depends on 'Current' POReceiptLine:
PXSelect<POReceiptLineSplit, Where<POReceiptLineSplit.receiptNbr, Equal<Current<POReceiptLine.receiptNbr>>,
And<POReceiptLineSplit.lineNbr, Equal<Current<POReceiptLine.lineNbr>>,
And<Where<POLineType.goodsForInventory, Equal<Current<POReceiptLine.lineType>>,
Or<POLineType.goodsForSalesOrder, Equal<Current<POReceiptLine.lineType>>,
Or<POLineType.goodsForDropShip, Equal<Current<POReceiptLine.lineType>>>>>>>>> splits;
The issue is that 'Current' POReceiptLine doesn't change when user clicks (select) another POReceiptLine in 'transactions' grid. Setting the grid 'SyncPosition' to true in your customization project should ensure 'Current' value is properly set when users change record selection in the grid:
Below is my code to insert whatever value is entered into my UsrWLAmt field into my BudgetGrid representing the history of the fields values.
I want to raise a warning prompting the user to enter a value into the details field in the BudgetGrid History
protected void PMProject_UsrWLAmt_FieldUpdated(PXCache cache, PXFieldUpdatedEventArgs e, PXFieldUpdated InvokeBaseHandler)
{
if(InvokeBaseHandler != null)
InvokeBaseHandler(cache, e);
var row = (PMProject)e.Row;
PMProject con = Base.Project.Current;
PX.Objects.PM.ProjectExt item = con.GetExtension<PX.Objects.PM.ProjectExt>();
if (item.UsrWLAmt > 0)
{
atcBudgetHis bud = new atcBudgetHis();
bud.CreatedDateTime = DateTime.Now;
bud.Value = item.UsrWLAmt;
BudgetGrid.Insert(bud);
// to attach the exception object to the field
BudgetGrid.View.Cache.RaiseExceptionHandling<atcBudgetHis.details>(
bud, " ",
new PXSetPropertyException(
"Please specifiy reason for budget change.",
PXErrorLevel.Warning));
}
}
I've also tried BudgetGrid.Cahce.RaiseExceptionHandling
The code above doesn't raise any trace errors.
EDIT:
PXUIFieldAttribute.SetWarning<atcBudgetHis.details>(BudgetGrid.Cache, null, "Please specifiy reason for budget change.");
Works for all rows but
PXUIFieldAttribute.SetWarning<atcBudgetHis.details>(BudgetGrid.Cache, bud, "Please specifiy reason for budget change.");
Doesn't raise any warnings.
I could create another field above the grid for the notes to be inserted, but is there a way I can set the warning for the last row in the BudgetGird?
First things first, to show a warning in Acumatica one of the following events must be used:
FieldVerifying and throw PXSetPropertyException, when warning should appear only during the time user updates a record
RowUpdating with RaiseExceptionHandling method invoked on PXCache, if warning should appear on multiple fields only during the time user updates a record
RowSelected with RaiseExceptionHandling method invoked on PXCache, if warning should appear on multiple fields all the time until a user addresses the cause of warning
I guess for your particular scenario, RowSelected might work best to constantly show warnings for all empty cells within Notes column:
public void atcBudgetHis_RowSelected(PXCache sender, PXRowSelectedEventArgs e)
{
atcBudgetHis row = e.Row as atcBudgetHis;
if (row == null) return;
if (string.IsNullOrEmpty(row.Details))
{
sender.RaiseExceptionHandling<atcBudgetHis.details>(row, string.Empty,
new PXSetPropertyException("Please specify reason for budget change.", PXErrorLevel.Warning));
}
else
{
sender.RaiseExceptionHandling<atcBudgetHis.details>(row, row.Details, null);
}
}
It seems like you tried to set a warning on a DAC instance that didn't exists in the grid at the moment the event was called.
Have you tried setting the warning on the existing row returned in the event handler parameter instead?
PXUIFieldAttribute.SetWarning<atcBudgetHis.details>(BudgetGrid.Cache, row, "Please specify reason for budget change.");
The warning applies to all rows that satisfy the condition that executes this line. If you want to display it for only the last row, you would have to manually check if the row received in the parameter is the same as the last row in your data view and only then execute the warning for that row.
The solution was to use the RowInserted event for my grid and pass the row variable into SetWarning
You need to change this code:
BudgetGrid.Insert(bud);
// to attach the exception object to the field
BudgetGrid.View.Cache.RaiseExceptionHandling<atcBudgetHis.details>(bud, " ",new PXSetPropertyException("Please specifiy reason for budget change.",PXErrorLevel.Warning));
To something like this:
bud = BudgetGrid.Insert(bud); //you need to get the "bud" which is in the cache
// to attach the exception object to the field
BudgetGrid.View.Cache.RaiseExceptionHandling<atcBudgetHis.details>(bud, " ",new PXSetPropertyException("Please specifiy reason for budget change.",PXErrorLevel.Warning));
Current customization project I'm working on has the requirement of displaying / editing a grid with a "Sort Order" for records. The "SortOrder" field is read only with up/down buttons to allow the user to re-order the items in the grid.
The "SortOrder" column in the DAC is a simple Int field.
The PXSelect statement for the grid is using a OrderBy>> to display the records.
The Grid in the ASPX is a defined with "SyncPosition= true"
I've added an Up/Down button that increments/decrements the "SortOrder" value for the current selected record.
The issue that I'm running into is that the first time "Up" or "Down" is clicked, the "SortOrder" field is updated however the rows do not move. Once I click Save to persist the update, the grid then refreshes with the right order.
I've looked through the the rest of the code but all other situations where this is used is for treeviews, not grids.
I've tried adding a View.RequestRefresh() at the end of my Action but this doesn't cause the reorder.
What would be the best way without a Persist after each move to get the Grid to update and reflect the current order from the cache values? As usual I'm assuming I'm overlooking something simple.
Any advice would be appreciated.
I had a look at the generic inquiry designer source code - it has an up/down button in the grid to reorder the fields. The views don't have an OrderBy clause:
public PXSelect<GIFilter, Where<GIFilter.designID, Equal<Current<GIDesign.designID>>>> Parameters;
OrderBy is not necessary because the LineNbr field is a key field - system automatically orders the records by the key fields.
public abstract class lineNbr : IBqlField { }
[PXDBInt(IsKey = true)]
[PXDefault]
[PXLineNbr(typeof(GIDesign))]
[PXParent(typeof(Select<GIDesign,
Where<GIDesign.designID, Equal<Current<GIFilter.designID>>>>))]
public virtual int? LineNbr { get; set; }
The code for the button looks like this:
[PXButton(ImageKey = Sprite.Main.ArrowUp, Tooltip = ActionsMessages.ttipRowUp)]
[PXUIField(DisplayName = ActionsMessages.RowUp, MapEnableRights = PXCacheRights.Update)]
protected void moveUpFilter()
{
if (this.Parameters.Current == null)
return;
GIFilter prev = PXSelect<GIFilter, Where<GIFilter.designID, Equal<Current<GIDesign.designID>>, And<GIFilter.lineNbr, Less<Current<GIFilter.lineNbr>>>>, OrderBy<Desc<GIFilter.lineNbr>>>.Select(this);
if (prev != null)
this.SwapItems(this.Parameters.Cache, prev, this.Parameters.Current);
}
[PXButton(ImageKey = Sprite.Main.ArrowDown, Tooltip = ActionsMessages.ttipRowDown)]
[PXUIField(DisplayName = ActionsMessages.RowDown, MapEnableRights = PXCacheRights.Update)]
protected void moveDownFilter()
{
if (this.Parameters.Current == null)
return;
GIFilter next = PXSelect<GIFilter, Where<GIFilter.designID, Equal<Current<GIDesign.designID>>, And<GIFilter.lineNbr, Greater<Current<GIFilter.lineNbr>>>>, OrderBy<Asc<GIFilter.lineNbr>>>.Select(this);
if (next != null)
this.SwapItems(this.Parameters.Cache, next, this.Parameters.Current);
}
The SwapItems function is shared between all the move up / move down actions:
private void SwapItems(PXCache cache, object first, object second)
{
object temp = cache.CreateCopy(first);
foreach (Type field in cache.BqlFields)
if (!cache.BqlKeys.Contains(field))
cache.SetValue(first, field.Name, cache.GetValue(second, field.Name));
foreach (Type field in cache.BqlFields)
if (!cache.BqlKeys.Contains(field))
cache.SetValue(second, field.Name, cache.GetValue(temp, field.Name));
cache.Update(first);
cache.Update(second);
}
Finally, there's a bit of JavaScript code in the ASPX code - it may or may not be what you're missing to get the feature to work correctly; i'm not exactly sure what it's doing but would encourage you to open SM208000.aspx in an editor and look for commandResult. Also check out the CallbackCommands that are defined on the grids which support up/down - it may have something to do with it.
I have a task flow with two AMX pages.
First page has a selectOneChoice (Product list) which is created from data control (having allProducts(a ArrayList of a ProductPOJO class)).
I dragged and drop allProducts from my dataControl to AMX page. and got code as-
<amx:selectOneChoice value="#{bindings.allProducts.inputValue}" label="Products" id="soc1"
valueChangeListener="#{pageFlowScope.lcBean.onProductChange}">
<amx:selectItems value="#{bindings.allProducts.items}" id="si1"/>
</amx:selectOneChoice>
and set a method onProductChange(ValueChangeEvent valueChangeEvent) inside a bean class where i am getting the selected row from selectOneChoice and doing further operation.
public void onProductChange(ValueChangeEvent valueChangeEvent) {
AmxAttributeBinding productList =
(AmxAttributeBinding) AdfmfJavaUtilities.evaluateELExpression("#{bindings.allProducts}");
AmxIteratorBinding amxListIterator = productList.getIteratorBinding();
BasicIterator basicIterator = amxListIterator.getIterator();
ProductPOJO currentProduct = (ProductPOJO) basicIterator.getDataProvider();
System.out.println("InSide onProductChange"+currentProduct.getProduct());
ValueExpression ve;
ve = AdfmfJavaUtilities.getValueExpression("#{pageFlowScope.nupMinimumsFlag}", Integer.class);
ve.setValue(AdfmfJavaUtilities.getAdfELContext(), currentProduct.getNupMinimumsFlag());
ve = AdfmfJavaUtilities.getValueExpression("#{pageFlowScope.productFlag}", Integer.class);
ve.setValue(AdfmfJavaUtilities.getAdfELContext(), currentProduct.getProductFlag());
ve = AdfmfJavaUtilities.getValueExpression("#{pageFlowScope.product}", String.class);
ve.setValue(AdfmfJavaUtilities.getAdfELContext(), currentProduct.getProduct());
}
Issue is :- when we select some value in selectOneChoice and then click on submit to go to next page. and then coming back to first page. Value of selectOneChoice is getting reset to default value(first value from collection). Its not able to cache the old(selected) value.
The working behaviour of select one choice is different on Oracle MAF. It is not able to cache the Object.
In first approach I was trying to cache an object.
To cache value on selectOneChoice we will have to get the index of selected choice and save it in your datacontrol class.
I got it fixed as below.
AMX Code :-
<amx:selectOneChoice value="#{bindings.selectedProduct.inputValue}" label="Products" id="soc1"
valueChangeListener="#{bindings.onProductChange.execute}" required="true"
showRequired="true">
<amx:selectItems value="#{bindings.allPoroducts.items}" id="si1"/>
</amx:selectOneChoice>
Java Code :-
public void onProductChange() {
System.out.println("InSide onProductChange" + selectedProduct);
ProductPOJO currentProduct = allPoroducts.get(Integer.parseInt(selectedProduct));
System.out.println("InSide onProductChange" + currentProduct.getProduct());
ValueExpression ve = AdfmfJavaUtilities.getValueExpression("#{pageFlowScope.nupMinimumsFlag}", Integer.class);
ve.setValue(AdfmfJavaUtilities.getAdfELContext(), currentProduct.getNupMinimumsFlag());
ve = AdfmfJavaUtilities.getValueExpression("#{pageFlowScope.productFlag}", Integer.class);
ve.setValue(AdfmfJavaUtilities.getAdfELContext(), currentProduct.getProductFlag());
ve = AdfmfJavaUtilities.getValueExpression("#{pageFlowScope.product}", String.class);
ve.setValue(AdfmfJavaUtilities.getAdfELContext(), currentProduct.getProduct());
}
where 'private String selectedProduct;' is a string type object. so when we will select or change value on selectonechoice it will return numbers in string format like(0, 1, 2, 3, 4, 5 basically index values). which we will convert it to int and will get the value from product array with that index.