I'm trying to store results from a BQL statement in a list by looping through and adding the objects to the list during a PXAction, so I can run some checks against the list later in a FieldUpdated event. However When I get to the FieldUpdated event, the contents of the list are back to zero.
public class SOShipmentEntry_Extension : PXGraphExtension<SOShipmentEntry>
{
public PXSelectJoin<INItemLotSerial, InnerJoin<SOShipLine, On<INItemLotSerial.inventoryID, Equal<Current<SOShipLine.inventoryID>>>>, Where<SOShipLine.shipmentNbr, Equal<Current<SOShipment.shipmentNbr>>>> AvailSerialList;
List<INItemLotSerial> AvailToPick = new List<INItemLotSerial>();
Here's where I add results into the list (added a watch on debugger to make sure I have a count of the content added, and verify that the list has content)
public PXAction<SOShipment> uploadlotserial;
[PXUIField(DisplayName = "Upload LotSerial", MapEnableRights = PXCacheRights.Select, MapViewRights = PXCacheRights.Select)]
[PXButton]
public virtual IEnumerable uploadLotserial(PXAdapter adapter)
{
if (AvailToPick.Count == 0)
{
foreach (INItemLotSerial pick in AvailSerialList.Select(this))
{
AvailToPick.Add(pick);
}
}
if (SerialFilter.AskExt() == WebDialogResult.OK)
{
GenerateSoShipmentSplitLine();
DeleteAllTempRecords();
}
DeleteAllTempRecords();
SerialList.Cache.Clear();
SerialList.Cache.ClearQueryCache();
return adapter.Get();
}
However when I hit this event, the count shows zero.
protected void InfoLotSerialFilter_LotSerialNbr_FieldUpdated(PXCache cache, PXFieldUpdatedEventArgs e, PXFieldUpdated InvokeBaseHandler)
{
if (InvokeBaseHandler != null)
InvokeBaseHandler(cache, e);
var row = (InfoLotSerialFilter)e.Row;
if (row.LotSerialNbr != null)
{
if (AvailToPick.Count > 0)
{
Any insight as to why the list contents are cleared would be appreciated. Thank you!
I would suggest that instead of the list you create a simple View for INItemLotSerial that will create a cache when the page is run. You can Insert records into the cache during your action and then push them into a List<> and count them during your event.
Related
I am trying to add a new record to a grid during the persist logic. However, even though the record does get added to the grid in the UI, when the page gets refreshed, the new line disappears. It is not getting persisted in the DB.
I am using the Bills page as reference.
Code sample
protected virtual void APTran_RowPersisting(PXCache sender, PXRowPersistingEventArgs e)
{
if (e.Row == null)
{
return;
}
APInvoice invoiceRow = this.Base.Document.Current;
if (invoiceRow != null)
{
APTran tranRow = new APTran();
tranRow = this.Base.Transactions.Insert(tranRow);
tranRow.InventoryID = 10043;
this.Base.Transactions.Update(tranRow);
tranRow.Qty = 3;
this.Base.Transactions.Update(tranRow);
}
}
Result after saving - Record is shown in the grid:
Result after cancelling - Record disappears from the grid:
Something like this I tend to override the Persist method and insert or update related records before calling base persist. Here is a possible example which goes inside your graph extension:
[PXOverride]
public virtual void Persist(Action del)
{
foreach(APInvoice invoiceRow in Base.Document.Cache.Inserted)
{
APTran tranRow = this.Base.Transactions.Insert();
tranRow.InventoryID = 10043;
tranRow = this.Base.Transactions.Update(tranRow);
tranRow.Qty = 3;
this.Base.Transactions.Update(tranRow);
}
del?.Invoke();
}
I have a user field on an SOOrder DAC extension, which is a sum of some of the lines on the document (based on a field in the SOLine extension). When I add a new line, the total is updating properly. However, when I load the document for the first time, the screen is showing 0.00. I created an SOOrderEntry extension and I put code into the SOLine_RowSelecting event handler. When I load the document, it steps into the code and it looks like it is setting the fields properly, but they don't show on the screen. The same method is called from the SOLine_CuryLineAmt_FieldUpdated, and that works just fine. Here is the code I'm using:
public class SOOrderEntryExt : PXGraphExtension<SOOrderEntry>
{
//Used to prevent recursive calls in RowSelecting
bool _isCalculating = false;
protected virtual void SOLine_RowSelecting(PXCache cache, PXRowSelectingEventArgs e)
{
var row = e.Row as SOLine;
if (row == null) return;
using (new PXConnectionScope())
{
if (!_isCalculating)
CalcTotals();
}
}
protected virtual void SOLine_CuryLineAmt_FieldUpdated(PXCache cache, PXFieldUpdatedEventArgs e)
{
if (!_isCalculating)
CalcTotals();
}
public void CalcTotals()
{
SOOrder order = Base.CurrentDocument.Select();
if (order == null) return;
_isCalculating = true;
var orderExt = order.GetExtension<SOOrderExt>();
orderExt.UsrMyCustomField = 0m;
//Get totals
foreach (SOLine lineSum in Base.Transactions.Select())
{
var lineSumExt = lineSum.GetExtension<SOLineExt>();
if (lineSumExt.UsrMyCondition)
orderExt.UsrMyCustomField += lineSum.CuryLineAmt;
}
_isCalculating = false;
}
}
RowSelected is called on each callback to select the data. There's no need to re-calculate on FieldUpdated event too because RowSelected will be called when updating records. Therefore consider removing SOLine_CuryLineAmt_FieldUpdated
You have the RowSelected event declared for SOLine DAC. The event then selects all SOLine to compute the totals. This amount to when selecting one of the Detail compute the total of all Detail, that smells lack a recursive pattern. Therefore consider declaring RowSelected on the Master document which is SOOrder in this case and remove all the workarounds you have to break recursion.
There's no null check in computations. Acumatica DAC fields are nullable. With your code you can end up in situation where you add null to a number which would results in type violation at runtime. Therefore consider checking if CuryLineAmt is null before using it's value to compute the total.
You are accumulating the total in the UsrMyCustomField DAC field
using the += addition assignment operator. It works but I would
advise against that. The DAC fields aren't meant as register for
computations or temporary value place-holder. Therefore consider
accumulating the total in a local variable and assign only the final
computed value to the DAC field.
Code to compute a total with all these points considered:
public class SOOrderEntryExt : PXGraphExtension<SOOrderEntry>
{
public void SOOrder_RowSelected(PXCache cache, PXRowSelectedEventArgs e)
{
SOOrder order = e.Row as SOOrder;
if (order != null)
{
SOOrderExt orderExt = order.GetExtension<SOOrderExt>();
if (orderExt != null)
{
decimal total = 0M;
foreach (SOLine line in Base.Transactions.Select())
{
total += line.CuryLineAmt.HasValue ? line.CuryLineAmt.Value : 0M;
}
orderExt.UsrMyCustomField = total;
}
}
}
}
I want to show a popup with some message to user whenever screen gets loaded for the first time for each record.
For example when Sales Order screen is loaded for particular sales order, it should show the popup only once. Then user navigates to a next sales order, it should again show the popup for that particular sales order only once.
I have written the code in constructor and RowSelected event, but it does not has the Current record. That is CRCurrentCaseNotes is always null in both this events. However, with the button (ViewNotes in below code sample), it works.
[PXViewName("CRCurrentCaseNotes")]
[PXCopyPasteHiddenView]
public PXSelect<Note,
Where<CRCase.caseID, Equal<Current<CRCase.caseID>>>> CRCurrentCaseNotes;
public CRCaseMaintExtension()
: base()
{
if (CRCurrentCaseNotes.Current != null)
{
CRCurrentCaseNotes.AskExt();
}
}
protected virtual void CRCase_RowSelecting(PXCache cache, PXRowSelectingEventArgs e)
{
var caseRow = (CRCase)e.Row;
if (caseRow == null) return;
if (CRCurrentCaseNotes.Current != null)
{
CRCurrentCaseNotes.AskExt();
}
}
// SAME CODE WORKS WITH THE BUTTON CLICK
public PXAction<CRCase> viewNotes;
[PXUIField(DisplayName = "View Notes")]
[PXButton]
protected virtual IEnumerable ViewNotes(PXAdapter adapter)
{
if (CRCurrentCaseNotes.Current != null)
{
CRCurrentCaseNotes.AskExt();
}
return adapter.Get();
}
Try to show the dialog when the primary DAC key field is modified:
protected void CRCase_CaseID_FieldUpdated(PXCache sender, PXFieldUpdatedEventArgs e)
{
CRCase case = e.Row as CRCase;
if (case != null && case.CaseID != null && !e.ExternalCall)
{
// Show dialog
}
}
If there's no other way you could use JavaScript to show/hide the SmartPanel:
document.getElementById("<%=SmartPanelID.ClientID %>").style.display = 'block';
EDIT:
Dialog can only be shown from an action event handler (FieldUpdated won't work) or from JavaScript. To open dialog when the page open, you can try hooking DocumentReady event in JavaScript and call the Acumatica action from JavaScript too: px_alls['ds'].executeCallback('ActionName');
I have a customization to the Sales Orders screen, where I use a RowPersisting event to update the Requested Date to the current date upon saving. The problem is, if the save is not executed, but the Actions.PrepareInvoice is initiated, the new Requested Date (hopefully set by the RowPersisting event) is not used. I've tried to override the base method as follows (to save the Sales Order record with the new Requested Date before the Prepare Invoice process is run):
public delegate IEnumerable PrepareInvoiceDelegate(PXAdapter adapter);
[PXOverride]
public IEnumerable PrepareInvoice(PXAdapter adapter, PrepareInvoiceDelegate baseMethod)
{
Base.Actions.PressSave();
return baseMethod(adapter);
}
But I receive the following error - "Error: The previous operation has not been complete yet."
How can I ensure that a modified Requested Date is used for the Prepare Invoice process if the Sales Order record is not yet saved?
The PrepareInvoice method in case if the call is not from Processing Page(adapter.MassProcess==false) is calling this.Save.Press() anyway.
Below is the code from PrepareInvoice action. Before creation of the Invoice there is being called this.Save.Press() so any your update will be saved and used for creation of the invoice.
[PXUIField(DisplayName = "Prepare Invoice", MapEnableRights = PXCacheRights.Select, MapViewRights = PXCacheRights.Select, Visible = false), PXButton]
public virtual IEnumerable PrepareInvoice(PXAdapter adapter)
{
List<SOOrder> list = adapter.Get<SOOrder>().ToList<SOOrder>();
foreach (SOOrder current in list)
{
if (this.Document.Cache.GetStatus(current) != PXEntryStatus.Inserted)
{
this.Document.Cache.SetStatus(current, PXEntryStatus.Updated);
}
}
if (!adapter.MassProcess)
{
try
{
this.RecalculateAvalaraTaxesSync = true;
this.Save.Press();
}
finally
{
this.RecalculateAvalaraTaxesSync = false;
}
}
PXLongOperation.StartOperation(this, delegate
{
DocumentList<ARInvoice, SOInvoice> documentList = new DocumentList<ARInvoice, SOInvoice>(PXGraph.CreateInstance<SOShipmentEntry>());
SOOrderEntry.InvoiceOrder(adapter.Arguments, list, documentList, adapter.MassProcess);
if (!adapter.MassProcess && documentList.Count > 0)
{
using (new PXTimeStampScope(null))
{
SOInvoiceEntry sOInvoiceEntry = PXGraph.CreateInstance<SOInvoiceEntry>();
sOInvoiceEntry.Document.Current = sOInvoiceEntry.Document.Search<ARInvoice.docType, ARInvoice.refNbr>(documentList[0].DocType, documentList[0].RefNbr, new object[]
{
documentList[0].DocType
});
throw new PXRedirectRequiredException(sOInvoiceEntry, "Invoice");
}
}
});
return list;
}
The solution seems to be to recreate the Action method in a graph extension and add the following code:
if (!adapter.MassProcess)
{
//****Code added to update the Requested Date to today's date...
var soorder = (SOOrder)Base.Caches[typeof(SOOrder)].Current;
soorder.RequestDate = DateTime.Now;
Base.Caches[typeof(SOOrder)].Update(soorder);
//****End of code added...
try
{
Base.RecalculateAvalaraTaxesSync = true;
Base.Save.Press();
}
finally
{
Base.RecalculateAvalaraTaxesSync = false;
}
}
I need to make Salesperson ID on SOLine as a required field. But as Transfer orders do not have Salesperson, hence it should only validate when I create orders other than Transfer orders.
I tried with below code but it seems it is not working. Might be it is overrided with some existing code. Let me know if anyone has any suggestions.
public PXSetup<SOOrderTypeOperation,
Where<SOOrderTypeOperation.orderType, Equal<Optional<SOOrderType.orderType>>,
And<SOOrderTypeOperation.operation, Equal<Optional<SOOrderType.defaultOperation>>>>> sooperation;
protected bool IsTransferOrder
{
get
{
return (sooperation.Current.INDocType == INTranType.Transfer);
}
}
protected virtual void SOLine_RowPersisting(PXCache sender, PXRowPersistingEventArgs e)
{
var row = (SOLine)e.Row;
if (row == null) return;
PXDefaultAttribute.SetPersistingCheck<SOLine.salesPersonID>(sender, row, IsTransferOrder ? PXPersistingCheck.Nothing : PXPersistingCheck.Null);
}
I usually thrown an appropriate exception in Row Persisting when the condition exists.
Here is an example from SOShipmentEntry checking for transfer and checking the null value of a field:
protected virtual void SOShipment_RowPersisting(PXCache sender, PXRowPersistingEventArgs e)
{
SOShipment doc = (SOShipment)e.Row;
if (doc.ShipmentType == SOShipmentType.Transfer && doc.DestinationSiteID == null)
{
throw new PXRowPersistingException(typeof(SOOrder.destinationSiteID).Name, null, ErrorMessages.FieldIsEmpty, typeof(SOOrder.destinationSiteID).Name);
}
}
I have also called RaiseExceptionHandling similar to this example within RowPersisting
// sender = PXCache
if (row.OrderQty == Decimal.Zero)
sender.RaiseExceptionHandling<POLine.orderQty>(row, row.OrderQty, new PXSetPropertyException(Messages.POLineQuantityMustBeGreaterThanZero, PXErrorLevel.Error));
Both examples should stop the page from the save. calling the Raise Exception handling should point out the field with the Red X which is the better approach and easier for the user to find the field in question.
For your example:
protected virtual void SOLine_RowPersisting(PXCache sender, PXRowPersistingEventArgs e)
{
SOLine row = (SOLine)e.Row;
if (row == null)
{
return;
}
if (!IsTransferOrder && row.SalesPersonID == null)
{
sender.RaiseExceptionHandling<SOLine.salesPersonID>(row, row.SalesPersonID, new PXSetPropertyException(ErrorMessages.FieldIsEmpty, PXErrorLevel.Error));
}
}