Build 18.203.0006
Page: AP301000
Good day, I have extended the APRegister Class by adding 2 new checkbox fields. I want to iterate through al the APTran transactions and look for suIDs that start with FBL and GAS. If I find them the new tick boxes should tick.
Currently, the fields(tick boxes) do not save to the database. I am not sure how to tell Acumatica the APRegisterExt has updated.
APInvoiceEntry_Extension:
namespace PX.Objects.AP
{
public class APRegisterExt : PXCacheExtension<PX.Objects.AP.APRegister>
{
#region UsrGroupAEmail
[PXDBBool]
[PXUIField(DisplayName="GroupA Email")]
public virtual bool? UsrGroupAEmail { get; set; }
public abstract class usrGroupAEmail : IBqlField { }
#endregion
#region UsrGroupBEmail
[PXDBBool]
[PXUIField(DisplayName="GroupB Email")]
public virtual bool? UsrGroupBEmail { get; set; }
public abstract class usrGroupBEmail : IBqlField { }
#endregion
}
}
APInvoiceEntry_Extension:
namespace PX.Objects.AP
{
public class APInvoiceEntry_Extension : PXGraphExtension<APInvoiceEntry>
{
#region Event Handlers
protected void APInvoice_RowSelected(PXCache cache, PXRowSelectedEventArgs e)
{
APInvoice invoice = e.Row as APInvoice;
if (invoice == null) return;
var apRX = invoice.GetExtension<APRegisterExt>();
PXResultset<APTran> Tlist = PXSelectJoin<APTran,
LeftJoin<POReceiptLine,
On<POReceiptLine.receiptNbr, Equal<APTran.receiptNbr>,
And<POReceiptLine.lineNbr, Equal<APTran.receiptLineNbr>>>>,
Where<
APTran.tranType, Equal<Current<APInvoice.docType>>,
And<APTran.refNbr, Equal<Current<APInvoice.refNbr>>>>,
OrderBy<
Asc<APTran.tranType,
Asc<APTran.refNbr,
Asc<APTran.lineNbr>>>>>.Select(Base);
apRX.UsrGroupBEmail = false;
apRX.UsrGroupAEmail = false;
foreach (APTran item in Tlist)
{
if (item.SubID.Value.ToString().StartsWith("FBL") || item.SubID.Value.ToString().StartsWith("GAS"))
{
apRX.UsrGroupBEmail = true;
cache.SetValue<APRegisterExt.usrGroupBEmail>(e.Row, true);
}
else
{
apRX.UsrGroupAEmail = true;
}
}
}
#endregion
}
}
Can someone please show me the correct way of saving the data to the new tick boxes so that cache updates.
Consider moving the logic to APRegister_RowPersisting.
Importantly, APTran.subID is an Int dataType.
Read instead the Sub table/DAC during a For Loop of
Base.Transactions.Select() to detect SubCD startsWith FBL or GAS.
foreach(APTran item in Base.Transaction.Select())
{
Sub sub = PXSelect<Sub,
Where<Sub.subID, Equal<Required<Sub.subID>>>>
.Select(graph, item.SubID);
if (item.SubCD.StartsWith("FBL") ||
item.SubCD.StartsWith("GAS"))
{
apRX.UsrGroupBEmail = true;
}
else
{
apRX.UsrGroupAEmail = true;
}
}
To be complete, you may also need to reset both flags during APTran_SubID_FieldUpdated regardless of the changed value:
protected void APTran_SubID_FieldUpdated(PXCache sender,
PXFieldUpdatedEventArgs e, PXFieldUpdated del)
{
del?.Invoke(sender,e);
var apRX = Base.Document.Current.GetExtension<APRegisterExt>();
apRX.UsrGroupBEmail = false;
apRX.UsrGroupAEmail = false;
}
Related
Good afternoon everyone,
I need your help, I want to record one or several records on the PM301000 Projects screen in the detail tab, Cost Budget.
When recording one or more records, two Mark for PO and Vendor ID fields must be disabled. When the condition of the Mark for PO field is equal to true.
I have used the RowPersisting event and it disables it but when I modify or leave the registry the fields are enabled again.
Please help me or tell me how I should do it, my code is as follows.
Thanks in advance.
namespace PX.Objects.PM
{
public class PMBudgetExt : PXCacheExtension<PX.Objects.PM.PMBudget>
{
#region UsrVendorID
[PXDBInt]
[PXUIField(DisplayName = "Vendor ID", Visibility = PXUIVisibility.Visible)]
[PXDimensionSelectorAttribute("VENDOR", typeof(Search<VendorR.bAccountID, Where<VendorR.type, Equal<BAccountType.vendorType>,
And<VendorR.status, Equal<BAccount.status.active>>>>),
typeof(VendorR.acctCD), new Type[] { typeof(VendorR.acctCD), typeof(VendorR.acctName) })]
public virtual int? UsrVendorID { get; set; }
public abstract class usrVendorID : PX.Data.BQL.BqlInt.Field<usrVendorID> { }
#endregion
#region UsrMarkforPO
[PXDBBool()]
[PXDefault(false)]
[PXUIField(DisplayName = "Mark for PO")]
public virtual bool? UsrMarkforPO { get; set; }
public abstract class usrMarkforPO : PX.Data.BQL.BqlBool.Field<usrMarkforPO> { }
#endregion
}
}
namespace PX.Objects.PM
{
public class ProjectEntry_Extension : PXGraphExtension<ProjectEntry>
{
#region Event Handlers
protected void PMCostBudget_RowPersisting(PXCache cache, PXRowPersistingEventArgs e)
{
PMCostBudget newRow = (PMCostBudget)e.Row;
if (newRow == null) return;
PMBudgetExt newRowE = PXCache<PMBudget>.GetExtension<PMBudgetExt>(newRow);
if (Base.CostBudget.Cache.AllowUpdate == true)
{
if (newRowE.UsrMarkforPO == true)
{
PXUIFieldAttribute.SetEnabled<PMBudgetExt.usrMarkforPO>(cache, newRow, false);
PXUIFieldAttribute.SetEnabled<PMBudgetExt.usrVendorID>(cache, newRow, false);
}
}
}
#endregion
}
}
RowPersisting event executes only on save event. Therefore it's not suited for setting the field states. You will get better results with RowSelected event which is executed everytime a record is selected to be displayed on screen. You should set the state on every callback whether it is enabled or disabled. Also, the event should be declared on the same DAC type you are using to set the field state so the cache object match.
public void PMBudget_RowSelected(PXCache sender, PXRowSelectedEventArgs e, PXRowSelected del)
{
if (del != null)
{
del(sender, e);
}
bool isFieldEnabled = [your_condition];
PXUIFieldAttribute.SetEnabled<PMBudget.field>(sender, e.Row, isFieldEnabled);
}
I am struggling with getting my Process Delegate to wire up correctly after implementing the advice pointed out in this ticket Implementing a DAC with no persisted fields. The processing page for the most part now behaves as we need. The records autoload based off of data pulled in via a ReST web service and we are not persisting any data to the ERP until the processing buttons are used. The issue I am having now is the SetProcessDeligate method is now not doing anything when I hit the process buttons. When I wire the older code into place that has one persistent field and requires a user to hit a load button the Process and Process All buttons work as expected.
I have created this screencast to walk through and give visual context to the issue. https://www.dropbox.com/s/j8vnp8p3556nj1e/issue%20with%20the%20PXProcessing%20page%20not%20wiring%20into%20the%20event%20handler%202019-01-03_12-57-50.mp4?dl=0
As always I am very grateful for any help. Thank you
Robert
//This is how my Graph is defined now.
public class CtpPaymentProcess : PXGraph<CtpPaymentProcess>
{
//public PXAction<CtpPayment> checkForC2PPayments;
public PXSetup<CtpSetup> setup;
public PXProcessing<CtpPayment> Payments;
public QueryPaymentsResponseViewModel paymentsFromCtpServer { get; internal set; }
public IEnumerable payments()
{
paymentsFromCtpServer = CtpAcumatica.CheckForAllNewPayments(100);
PXTrace.WriteInformation("Processing " + (paymentsFromCtpServer.Payments.Count) + " paymentsFromCtpServer");
if (paymentsFromCtpServer.Payments != null)
{
// Loop processing each payment returned from the gateway, storing the
// information into non persisted cache.
foreach (var payment in paymentsFromCtpServer.Payments)
{
if (!payment.IsMarkedRetrieved)
{
yield return BuildCtpPayment(payment);
}
}
}
}
private CtpPayment BuildCtpPayment(PaymentViewModel payment)
{
var customer = (Customer)PXSelect<Customer,
Where<Customer.bAccountID, Equal<Required<Customer.bAccountID>>>>
.Select(this, payment.CustomerId).FirstOrDefault();
//Todo: add assertion that will assert payment is made to only matching company payment.CompanyId
//Todo: find out if we need to handel Bank Account Types differently payment.BankAccountType
DateTime.TryParse(payment.Date, out var payDate);
return new CtpPayment
{
CustomerID = int.Parse(payment.CustomerId),
Customer = $"{customer.AcctCD}:{customer.AcctName}",
Amount = payment.Amount,
Description = $"Payment:{payment.Id}",
Id = payment.Id,
ApsTransactionID = payment.ApsTransactionId,
Currency = payment.Currency,
PaymentDate = payDate,
Invoices = InvoicesAsString(payment)
};
}
private static string InvoicesAsString(PaymentViewModel payment)
{
var Invoices = payment.Invoices.Select(x => x.InvoiceId);
StringBuilder stringBuilder = new StringBuilder();
foreach (string inv in Invoices)
{
stringBuilder.AppendFormat("{0} ", inv);
}
string result = stringBuilder.ToString();
if (result.Length > 100) result = result.Substring(0, 96) + "...";
return result;
}
private CtpAcumatica _ctpAcumatica;
public CtpAcumatica CtpAcumatica
{
get
{
if (_ctpAcumatica == null)
{
var graph = PXGraph.CreateInstance<PXGraph>();
_ctpAcumatica = new CtpAcumatica(setup.Current.CtpUrl,
setup.Current.CtpApiKey,
"NoLongerNeeded", //todo: refactor this out.
"NoLongerNeeded", //todo: refactor this out.
graph);
}
return _ctpAcumatica;
}
}
public CtpPaymentProcess()
{
Payments.SetProcessCaption("Process Payments");
Payments.SetProcessAllCaption("Process All Payments");
Payments.SetProcessDelegate<CtpPaymentProcess>(
delegate (CtpPaymentProcess graph, CtpPayment payment)
{
graph.Clear();
graph.ProcessPayment(payment, true);
}
);
//Alternate attempt proved un-successful
//Payments.SetProcessDelegate(PaymentGenerationDelegate);
}
/* implemented as a test. will remove from production code
private void PaymentGenerationDelegate(List<CtpPayment> list)
{
foreach (var payment in list)
{
ProcessPayment(payment, true);
}
}
*/
private void ProcessPayment(CtpPayment payment, bool massProcess)
{
PXTrace.WriteInformation($"Processing {payment}");
//for now we will only write to the trace window.
//Stopwatch stopWatch = new Stopwatch();
//stopWatch.Start();
//createPayment(payment);
//stopWatch.Stop();
//PXTrace.WriteInformation($"Payment {payment.ApsTransactionID} finished in {stopWatch.Elapsed.TotalSeconds} Seconds");
}
//todo: unfinished
private void createPayment(CtpPayment paymentData)
{
var paymentFromCtp = CtpAcumatica.GetPaymentRecord(long.Parse(paymentData.Id));
ARPaymentEntry arPaymentEntry = PXGraph.CreateInstance<ARPaymentEntry>();
ARPayment payment = new ARPayment
{
CustomerID = int.Parse(paymentFromCtp.CustomerId),
CuryOrigDocAmt = paymentData.Amount
};
arPaymentEntry.CurrentDocument.Insert(payment);
foreach (var invoice in paymentFromCtp.Invoices)
{
ARAdjust adj = new ARAdjust
{
AdjdRefNbr = invoice.InvoiceId,
CuryAdjgAmt = invoice.Amount
};
arPaymentEntry.Adjustments.Insert(adj);
}
arPaymentEntry.Persist();
PXTrace.WriteInformation(arPaymentEntry.ToString());
}
}
//This is the DAC definition.
[Serializable]
[PXPrimaryGraph(typeof(CtpPaymentProcess))]
//[PXNonInstantiatedExtension] this looked close
//to what we are looking for but experimenting
//with it did not yield desired results.
public class CtpPayment : IBqlTable
{
#region Selected
public abstract class selected : IBqlField{ }
[PXBool]
[PXUIField(DisplayName = "Selected")]
public virtual bool? Selected { get; set; }
#endregion
public abstract class id : IBqlField { }
//todo: find out what size we need 50 is just a guess.
//[PXString(50, IsKey = true)] //We are able to get this to work only if
//we have at least one persisting field.
//we can live with this but would prefer to
//have the whole class as non-persistent
[PXString(50,IsKey = true)] //having only non persisting attributes will result in a
//Incorrect syntax near the keyword 'FROM'. error.
[PXUIField(DisplayName = "Click To Pay Id")]
public virtual string Id { get; set; }
public abstract class customer : IBqlField { }
[PXString(100)]
[PXUIField(DisplayName = "Customer")]
public virtual string Customer { get; set; }
public abstract class description : IBqlField {}
[PXString(200)]
[PXUIField(DisplayName = "Payment Description")]
public virtual string Description { get; set; }
public abstract class amount : IBqlField { }
[PXDecimal(2)]
[PXUIField(DisplayName = "Payment Amount")]
public virtual decimal? Amount { get; set; }
public abstract class customerId : IBqlField { }
[PXInt]
[PXUIField(DisplayName = "Customer ID")]
//todo: decorate this with the needed attributes to display friendly key instead of int.
public virtual int? CustomerID { get; set; }
public abstract class apsTransactionID : IBqlField { }
[PXString]
[PXUIField(DisplayName = "Transaction ID")]
public virtual string ApsTransactionID { get; set; }
public abstract class currency : IBqlField { }
[PXString(10)]//todo: determine best size. 10 is a guess.
[PXUIField(DisplayName = "Currency")]
public virtual string Currency { get; set; }
public abstract class paymentDate : IBqlField { }
[PXDate]
[PXUIField(DisplayName = "Payment Date")]
public virtual DateTime? PaymentDate { get; set; }
public abstract class invoices : IBqlField { }
[PXString(100)]
[PXUIField(DisplayName = "Invoices")]
public virtual string Invoices { get; set; }
}
Thanks, HB_Acumatica for the direction on this.
Changing my code to use the following got to the end result I needed. I Hope this helps someone in the future.
//old implementation that would not render any result when the process buttons where clicked.
//public IEnumerable payments()
//{
// paymentsFromCtpServer = CtpAcumatica.CheckForAllNewPayments(100);
// PXTrace.WriteInformation("Processing " + (paymentsFromCtpServer.Payments.Count) + " paymentsFromCtpServer");
// if (paymentsFromCtpServer.Payments != null)
// {
// // Loop processing each payment returned from the gateway, storing the
// // information into non persisted cache.
// foreach (var payment in paymentsFromCtpServer.Payments)
// {
// if (!payment.IsMarkedRetrieved)
// {
// yield return BuildCtpPayment(payment);
// }
// }
// }
//}
public IEnumerable payments()
{
paymentsFromCtpServer = CtpAcumatica.CheckForAllNewPayments(100);
PXCache cache = Caches[typeof(CtpPayment)];
cache.AllowInsert = false;
cache.AllowUpdate = false;
if (cache.Current == null)
{
foreach (var payment in paymentsFromCtpServer.Payments)
{
if (!payment.IsMarkedRetrieved)
{
cache.SetStatus(BuildCtpPayment(payment), PXEntryStatus.Held);
}
}
}
return Payments.Cache.Cached;
}
I have a button in which, when pressed, I want to show or hide rows in a grid based on certain criteria. Is it possible to change the PXSelect a view uses on the fly so that it re-queries the database and retrieves different results? I will, of course, be querying the same table and not changing up the structure of the View or grid.
The code below adds a non-visible field to the header record that is set by the button press this value is then used by the child records view delegate to determine based on the child records criteria (in this case a Boolean on each child) if they are shown.
public sealed class APInvoiceExtension : PXCacheExtension<APInvoice>
{
#region UsrShowAll
public abstract class usrShowAll : IBqlField
{
}
[PXBool]
public bool? UsrShowAll { get; set; }
#endregion
}
public sealed class APTranExtension : PXCacheExtension<APTran>
{
#region UsrHidden
public abstract class usrHidden : IBqlField
{
}
[PXDBBool]
[PXUIField(DisplayName = "Hidden", Enabled = false)]
public bool? UsrHidden { get; set; }
#endregion
}
public class APInvoiceEntryExtension : PXGraphExtension<APInvoiceEntry>
{
public PXAction<APInvoice> SHW;
[PXUIField(DisplayName = "Show All Records", MapEnableRights = PXCacheRights.Update, MapViewRights = PXCacheRights.Update)]
[PXButton]
protected void sHW()
{
if (Base.Document.Current != null)
{
APInvoiceExtension docExt = Base.Document.Current.GetExtension<APInvoiceExtension>();
docExt.UsrShowAll = !(docExt.UsrShowAll ?? false);
}
}
protected virtual IEnumerable transactions()
{
bool showAll = Base.Document.Current != null ? (Base.Document.Current.GetExtension<APInvoiceExtension>().UsrShowAll ?? false) : false;
APTran tran;
foreach (PXResult<APTran, POReceiptLine> res in Base.Transactions.Select())
{
tran = res[0] as APTran;
if (!showAll)
{
if (!(tran.GetExtension<APTranExtension>().UsrHidden ?? false))
{
yield return res;
}
}
else
{
yield return res;
}
}
}
}
I have a requirement to have a field on SalesOrder screen and the same field should appear on Shipment screen also for respective SalesOrder. And the user should be able to update these field on both the screen.
I created a bound field on Sales Order screen which user can save it. Then I created an unbound field on Shipment screen to show the text from Sales Order. For that I have written a SOShipment_RowSelected event and later for user to update it to the database, I have written SOShipment_RowUpdated. However, when I try to edit the field, it fires RowSelected event and it overwrites the editing and bring back in the same original value.
I have tried with SOShipment_ShipmentNbr_FieldUpdated & SOShipment_ShipmentNbr_FieldUpdating event but its not firing everytime.
Here is the code for Cache extension-
public class SOOrderExtension : PXCacheExtension<SOOrder>
{
#region UsrNotesText
[PXDBString(255)]
[PXUIField(DisplayName = "Pick List Notes")]
public virtual string UsrNotesText { get; set; }
public abstract class usrNotesText : IBqlField { }
#endregion
}
public class SOShipmentExtension : PXCacheExtension<SOShipment>
{
#region UsrNotesText
[PXString(255)]
[PXUIField(DisplayName = "Pick List Notes")]
public virtual string UsrNotesText { get; set; }
public abstract class usrNotesText : IBqlField { }
#endregion
}
SOShipmentExtension code-
public class SOShipmentEntryExtension : PXGraphExtension<SOShipmentEntry>
{
PXSelect<SOOrder> soOrder;
protected virtual void SOShipment_RowSelected(PXCache sender, PXRowSelectedEventArgs e)
{
if (e.Row != null)
{
SOOrder order = PXSelectJoin<SOOrder,
LeftJoin<SOOrderShipment, On<SOOrder.orderNbr, Equal<SOOrderShipment.orderNbr>,
And<SOOrder.orderType, Equal<SOOrderShipment.orderType>>>,
LeftJoin<SOShipment, On<SOOrderShipment.shipmentNbr, Equal<SOShipment.shipmentNbr>>>>,
Where<SOShipment.shipmentNbr, Equal<Current<SOShipment.shipmentNbr>>>>.Select(Base);
if (order != null)
{
SOOrderExtension orderExt = PXCache<SOOrder>.GetExtension<SOOrderExtension>(order);
SOShipment soShipment = Base.Document.Current;
SOShipmentExtension ext = PXCache<SOShipment>.GetExtension<SOShipmentExtension>(soShipment);
ext.UsrNotesText = orderExt.UsrNotesText;
}
}
}
protected virtual void SOShipment_RowUpdated(PXCache sender, PXRowUpdatedEventArgs e)
{
SOShipment oldRow = (SOShipment)e.OldRow;
SOShipment newRow = (SOShipment)e.Row;
if (oldRow != null || newRow != null)
{
SOShipmentExtension oldExt = PXCache<SOShipment>.GetExtension<SOShipmentExtension>(oldRow);
SOShipmentExtension newExt = PXCache<SOShipment>.GetExtension<SOShipmentExtension>(newRow);
if (oldExt.UsrNotesText != newExt.UsrNotesText)
{
{
SOOrder order = PXSelectJoin<SOOrder,
LeftJoin<SOOrderShipment, On<SOOrder.orderNbr, Equal<SOOrderShipment.orderNbr>,
And<SOOrder.orderType, Equal<SOOrderShipment.orderType>>>,
LeftJoin<SOShipment, On<SOOrderShipment.shipmentNbr, Equal<SOShipment.shipmentNbr>>>>,
Where<SOShipment.shipmentNbr, Equal<Current<SOShipment.shipmentNbr>>>>.Select(Base);
soOrder.Current = order;
if (order != null)
{
SOOrderExtension orderExt = PXCache<SOOrder>.GetExtension<SOOrderExtension>(order);
orderExt.UsrNotesText = newExt.UsrNotesText;
soOrder.Update(order);
}
}
}
}
}
}
Any suggestions?
The trick is to initialize UsrNotesText elsewhere.
You can use PXDefault attribute:
[PXDefault(typeof(Search<SOOrderExtension.usrNotesText, Where< [...] >>))]
Or FieldDefaulting event handler:
public void SOShipment_UsrNotesText_FieldDefaulting(PXCache sender, PXFieldDefaultingEventArgs e)
{
e.NewValue = [...]
}
Sometimes you also want to re-initialize when user changes key fields that are not re-triggering the default.:
public void SOShipment_ShipmentNbr_FieldUpdated(PXCache sender, PXFieldDefaultingEventArgs e)
{
SOShipment shipment = e.Row as SOShipment;
if (shipment != null)
{
SOShipmentExtension shipmentExt = PXCache<SOShipment>.GetExtension<SOShipmentExtension>(shipment);
if (shipmentExt != null)
{
shipmentExt.UsrNotesText = [...];
}
}
}
In such case manually re-triggering FieldDefaulting event with RaiseFieldDefaulting is often a better option.
However method you choose to initialize avoid setting the field value in RowSelected because that event is called at times when you don't want to initialize the custom field.
May I ask for your guidance here, my problem is I added a Branch Filter on the Create Requisition(RQ504000) screen, I tried to override the RQRequestProcessing Class but it just don't work. I also tried adding a WhereAnd on the Records view upon field updated but it also does not work. Any suggestion is greatly appreciated thanks.
I propose you following solution:
public class RQRequestSelectionExt : PXCacheExtension<RQRequestSelection>
{
#region AllocatedAmount2
public abstract class usrBranchID : IBqlField
{
}
[PXDBInt]
[PXUIField(DisplayName = "Branch ID")]
[PXDefault(2)]
[Branch]
public int? UsrBranchID { get; set; }
#endregion
}
public class RQRequestProcessExt : PXGraphExtension<RQRequestProcess>
{
[PXFilterable(new System.Type[] { })]
public RQRequestProcess.RQRequestProcessing Records;
public IEnumerable records()
{
var currentFilter = Base.Filter.Current;
var filterExt = currentFilter.GetExtension<RQRequestSelectionExt>();
var newList = Base.Records.Select();
ArrayList result = new ArrayList();
foreach (PXResult<RQRequestLineOwned> listItme in newList)
{
var row = listItme.GetItem<RQRequestLineOwned>();
if (filterExt.UsrBranchID != null)
{
if (row.BranchID == filterExt.UsrBranchID)
{
result.Add(row);
}
}
else
{
result.Add(row);
}
}
return result;
}
}
Another one, you can try to play with WhereAnd, but I for myself wasn't very successful with it.