Acumatica , modify Generated Gl batch from so invoice to handle multi payments - acumatica

i added Mix Payments Tab in So Invoice Screen to handle multi payments issue in cash sale
So Invoice with Multi Payments
Then i customized Released Button in SoInvoice Screen to Modify in GL batch generated
But i have the next problems.
1- Issue doesn’t release Automatic after release invoice .
2- I added only some records in GL batch but i couldn’t delete any record .
(I need to delete one record from generated GL batch)
3-In database table (GlTran) after I added some recorded , I notice four columns with different values
Sql pic
namespace PX.Objects.SO
{
public class SOInvoiceEntry_Extension : PXGraphExtension<SOInvoiceEntry>
{
public delegate IEnumerable ReleaseDelegate(PXAdapter adapter);
[PXOverride]
public IEnumerable Release(PXAdapter adapter, ReleaseDelegate baseMethod)
{
PXGraph.InstanceCreated.AddHandler<JournalEntry>((JournalEntry graph) =>
{
graph.GetExtension<JournalEntry_Extension>().RefNbrmix = this.Base.Document.Current.RefNbr;
graph.GetExtension<JournalEntry_Extension>().DocTypemix = this.Base.Document.Current.DocType;
graph.GetExtension<JournalEntry_Extension>().ModifyBatchFromAP = true;
});
return baseMethod(adapter);
}
}
}
namespace PX.Objects.GL
{
public class JournalEntry_Extension : PXGraphExtension<JournalEntry>
{
[PXOverride]
public void Persist(Action del)
{
if (ModifyBatchFromAP)
{
string RefNbr = RefNbrmix;
string DocType = DocTypemix;
decimal? Total = 0;
ArrayList tran = new ArrayList();
PXSelectBase<MixPayments> nonStk = new PXSelectReadonly<MixPayments, Where<MixPayments.refNbr, Equal<Required<MixPayments.refNbr>>, And<MixPayments.docType, Equal<Required<MixPayments.docType>>>>>(this.Base);
nonStk.Cache.ClearQueryCache();
PXResultset<MixPayments> resultNonStk = nonStk.Select(RefNbr, DocType);
foreach (MixPayments nonStkItem in resultNonStk)
{
var glTran = Base.GLTranModuleBatNbr.Insert();
Base.GLTranModuleBatNbr.SetValueExt<GLTran.accountID>(glTran, nonStkItem.AccountID);
glTran = Base.GLTranModuleBatNbr.Update(glTran);
Base.GLTranModuleBatNbr.SetValueExt<GLTran.subID>(glTran, nonStkItem.SubId);
Base.GLTranModuleBatNbr.SetValueExt<GLTran.curyDebitAmt>(glTran, nonStkItem.LineAmount);
Base.GLTranModuleBatNbr.SetValueExt<GLTran.debitAmt>(glTran, nonStkItem.LineAmount);
Base.GLTranModuleBatNbr.SetValueExt<GLTran.tranType>(glTran, DocType);
Base.GLTranModuleBatNbr.SetValueExt<GLTran.refNbr>(glTran, RefNbr);
Base.GLTranModuleBatNbr.Update(glTran);
Total = Total + nonStkItem.LineAmount;
}
var glTran2 = Base.GLTranModuleBatNbr.Insert();
Base.GLTranModuleBatNbr.SetValueExt<GLTran.accountID>(glTran2, "1301056");
glTran2 = Base.GLTranModuleBatNbr.Update(glTran2);
Base.GLTranModuleBatNbr.SetValueExt<GLTran.subID>(glTran2, "HDM0000");
glTran2.CuryCreditAmt = Total;
glTran2.CreditAmt = Total;
glTran2.TranType = DocType;
glTran2.RefNbr = RefNbr;
Base.GLTranModuleBatNbr.Update(glTran2);
}
del();
}
}

Related

Unable to Update Custom Field in Sales Quote Screen From Sales Order on an Action Event

I have created an Action in Sales Quotes screen to create a sales order. The sales order is getting created. I want the created Order Nbr to be populated in a custom field of Sales Quote Screen and the Action button to be disabled if the Sales order is created. Though I am not getting any error in the system.
When I refresh the Sales Quote Screen I get the exact result, but not during the Click of Create SO Action.
I am not sure where I am going wrong. Please refer the code and Image below: Thanks.
Following is my Code :
public class QuoteMaint_Extension : PXGraphExtension<QuoteMaint>
{
#region Event Handlers
protected void CRQuote_RowSelected(PXCache cache, PXRowSelectedEventArgs e)
{
var row = (CRQuote)e.Row;
CRQuoteExt quoteExt = PXCache<CRQuote>.GetExtension<CRQuoteExt>(row);
if (quoteExt.UsrOrderNbr != null)
{
createSalesOrder.SetEnabled(false);
}
else
{
createSalesOrder.SetEnabled(true);
}
}
#endregion
#region Create Sales Order
public PXAction<CRQuote> createSalesOrder;
[PXUIField(DisplayName = "Create SO", MapEnableRights = PXCacheRights.Update, MapViewRights = PXCacheRights.Update)]
[PXProcessButton(CommitChanges = true)]
public IEnumerable CreateSalesOrder(PXAdapter adapter)
{
QuoteMaint graphObject = PXGraph.CreateInstance<QuoteMaint>();
foreach (CRQuote quote in adapter.Get())
{
//Create resultset for Quote Details
PXResultset<CROpportunityProducts> PXSetLine = PXSelect<CROpportunityProducts,
Where<CROpportunityProducts.quoteID,
Equal<Required<CROpportunityProducts.quoteID>>>>.Select(this.Base, quote.QuoteID);
List<CROpportunityProducts> QuoteList = new List<CROpportunityProducts>();
foreach (CROpportunityProducts line in PXSetLine)
{
QuoteList.Add(line);
}
PXLongOperation.StartOperation(this, delegate ()
{
CreateSalesOrderMethod(quote, QuoteList);
});
yield return quote;
}
}
//Private Method for Create Sales Order
public virtual void CreateSalesOrderMethod(CRQuote quote, List<CROpportunityProducts> QuoteList)
{
//Base.Save.Press();
bool var_orderCreated = false;
bool erroroccured = false;
string ErrMsg = "";
SOOrderEntry orderGraphObjet = PXGraph.CreateInstance<SOOrderEntry>();
SOOrder orderHeaderObject = new SOOrder();
QuoteMaint currGRPH = PXGraph.CreateInstance<QuoteMaint>();
var Extension = this.Base.GetExtension<QuoteMaint_Extension>();
try
{
BAccount customer = PXSelect<BAccount, Where<BAccount.bAccountID, Equal<Current<CRQuote.bAccountID>>>>.Select(this.Base, quote.BAccountID);
if (customer.Type == "CU")
{
orderHeaderObject.CustomerID = quote.BAccountID;
}
else
{
throw new PXException("Business Account not converted to Customer yet");
}
orderHeaderObject.CuryOrderTotal = quote.CuryProductsAmount;
orderHeaderObject.CuryTaxTotal = quote.CuryTaxTotal;
orderHeaderObject.OrderDesc = quote.Subject;
orderHeaderObject = orderGraphObjet.CurrentDocument.Insert(orderHeaderObject);
orderGraphObjet.CurrentDocument.Current = orderHeaderObject;
orderGraphObjet.Actions.PressSave();
orderHeaderObject = orderGraphObjet.CurrentDocument.Current;
foreach (CROpportunityProducts tran in QuoteList)
{
CROpportunityProductsExt xOppProductExt = PXCache<CROpportunityProducts>.GetExtension<CROpportunityProductsExt>(tran);
SOLine transline = new SOLine();
SOLineExt _soLext = PXCache<SOLine>.GetExtension<SOLineExt>(transline);
transline.OrderNbr = orderHeaderObject.OrderNbr;
transline.BranchID = orderHeaderObject.BranchID;
transline.InventoryID = tran.InventoryID;
transline.TranDesc = tran.Descr;
transline.UOM = tran.UOM;
transline.OrderQty = tran.Quantity;
transline.SiteID = tran.SiteID;
transline.CuryUnitPrice = tran.CuryUnitPrice;
transline.CuryExtPrice = tran.CuryExtPrice;
_soLext.UsrXSeqID = xOppProductExt.UsrXSequenceID;
_soLext.UsrXGroupID = xOppProductExt.UsrXGroupID;
_soLext.UsrInternalRemk = xOppProductExt.UsrInternalRemk;
orderGraphObjet.Transactions.Insert(transline);
}
orderGraphObjet.Actions.PressSave();
var_orderCreated = true;
}
catch (Exception e)
{
erroroccured = true;
ErrMsg = e.Message;
}
if (erroroccured)
{
throw new PXException("Cannot create Order: " + ErrMsg);
}
else
{
if(var_orderCreated)
{
CRQuote QUOT = Base.Quote.Current;
CRQuoteExt QUOT_EXT = PXCache<CRQuote>.GetExtension<CRQuoteExt>(QUOT);
QUOT_EXT.UsrOrderNbr = orderHeaderObject.OrderNbr;
Base.Quote.Update(QUOT);
Base.Save.Press();
}
}
}
#endregion
}
}
DAC FIELD :
#region UsrOrderNbr
[PXDBString(50)]
[PXUIField(DisplayName = "Sales Order Nbr", IsReadOnly = true)]
[PXSelector(
typeof(Search<SOOrder.orderNbr>),
typeof(SOOrder.orderType),
typeof(SOOrder.orderNbr),
typeof(SOOrder.curyOrderTotal),
typeof(SOOrder.status),
Filterable = true)]
public virtual string UsrOrderNbr { get; set; }
public abstract class usrOrderNbr : PX.Data.BQL.BqlString.Field<usrOrderNbr> { }
#endregion
The DAC extension is fetched from transLine object before the SOLine keys are defined. Try moving the GetExtension call after transLine is inserted in the dataview.
SOLine transline = new SOLine();
SOLineExt _soLext = PXCache<SOLine>.GetExtension<SOLineExt>(transline);
The pattern should look like this:
Create empty DAC object
Insert DAC object in dataview
Get DAC extension and assign custom field
Update DAC object in dataview

Issue with Process Delegate after implementing a non-persistent DAC

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

Acumatica - adding REPORTS dropdown to the Opportunity screen

Hopefully this will be an easy question!
I've created a new report for the Acumatica Opportunity screen, and I'd like to add it to the screen itself. I've previously added a new report to the existing REPORTS dropdown, but I have not played with Automation Steps enough to create the REPORTS dropdown if it does not already exist.
Can someone provide instructions or point me in the direction of the appropriate documentation?
EDIT: So, what I'm running into is that the Opportunity doesn't have a Reports section already there to add to; I have to create it.
What I have so far gets a REPORTS button on the screen but it's not a dropdown and it doesn't appear to do anything.
public class OpportunityMaint_Extension : PXGraphExtension<OpportunityMaint>
{
#region Event Handlers
#endregion
#region Actions
public PXAction<CROpportunity> report;
[PXUIField(DisplayName = "Reports", MapEnableRights = PXCacheRights.Select)]
[PXButton(SpecialType = PXSpecialButtonType.Report)]
protected virtual IEnumerable Report(PXAdapter adapter,
[PXString(8, InputMask = "CC.CC.CC.CC")]
[PXStringList(new string[] { "IOCR6410" }, new string[] { "Quote" })]
string reportID)
{
List<CROpportunity> list = adapter.Get<CROpportunity>().ToList();
if (!String.IsNullOrEmpty(reportID))
{
Base.Save.Press();
int i = 0;
Dictionary<string, string> parameters = new Dictionary<string, string>();
foreach (CROpportunity opp in list)
{
if (reportID == "IOCR6410")
{
parameters["CROpportunity.OpportunityID" + i.ToString()] = opp.OpportunityID;
}
i++;
}
if (i > 0)
{
throw new PXReportRequiredException(parameters, reportID, string.Format("Report {0}", reportID));
}
}
return list;
}
#endregion
}
For this type of changes, it's better to not make changes in automation step(s), but follow the approach suggested in How to add report to Inventory Transfers dropdown menu for reports
Below is a slightly updated version of your code snippet, which should lead to the following result:
public class OpportunityMaint_Extension : PXGraphExtension<OpportunityMaint>
{
public override void Initialize()
{
Report.AddMenuAction(QuoteReport);
Report.MenuAutoOpen = true;
}
public PXAction<CROpportunity> Report;
[PXButton]
[PXUIField(DisplayName = "Reports", MapEnableRights = PXCacheRights.Select)]
protected void report()
{ }
public PXAction<CROpportunity> QuoteReport;
[PXButton]
[PXUIField(DisplayName = "Quote", MapEnableRights = PXCacheRights.Select)]
protected IEnumerable quoteReport(PXAdapter adapter)
{
var reportID = "IOCR6410";
List<CROpportunity> list = adapter.Get<CROpportunity>().ToList();
Base.Save.Press();
int i = 0;
Dictionary<string, string> parameters = new Dictionary<string, string>();
foreach (CROpportunity opp in list)
{
parameters["CROpportunity.OpportunityID" + i.ToString()] = opp.OpportunityID;
i++;
}
if (i > 0)
{
throw new PXReportRequiredException(parameters, reportID, string.Format("Report {0}", reportID));
}
return list;
}
}

Updating custom field is ending into infinite loop

I have a custom field in AR Invoice and Memos (Screen ID AR301000) for the corresponding AP Ref. Nbr. And in the similar manager another custom field in AP Bills and Adjustment (Screen ID AP301000) for the corresponding AR Ref. Nbr.
I am trying to update AP Ref. Nbr. on AR screen when user updates the AR Ref. Nbr. in AP screen.
For example-
I am on AR Screen Invoice 0001, I am updating AP Ref. Nbr. to abc01. System will automatically update the AP Bill abc01 with the corresponding AR Ref. Nbr. with 0001.
I have below code written to achieve this but it runs into infinite loop as both it is trying to update the corresponding fields in other screen. Let me know if I missing anything or there is a better way.
On AR Graph Extension
public class ARInvoiceEntryExtension : PXGraphExtension<ARInvoiceEntry>
{
protected virtual void ARInvoice_RowUpdated(PXCache sender, PXRowUpdatedEventArgs e)
{
var row = (ARInvoice)e.Row;
if (row != null && sender.IsDirty)
{
ARRegisterExtension ext = PXCache<ARRegister>.GetExtension<ARRegisterExtension>(row);
if (ext != null && !string.IsNullOrEmpty(ext.UsrAPRefNbr))
{
APInvoiceEntry graph = PXGraph.CreateInstance<APInvoiceEntry>();
APInvoice apRow = PXSelect<APInvoice,
Where<APInvoice.refNbr, Equal<Required<APInvoice.refNbr>>>>.Select(graph, ext.UsrAPRefNbr);
if (apRow != null)
{
APRegisterExtension ext1 = PXCache<APRegister>.GetExtension<APRegisterExtension>(apRow);
if (ext1 != null && string.IsNullOrEmpty(ext1.UsrARRefNbr)) //Update only if it is empty
{
ext1.UsrARRefNbr = row.RefNbr;
graph.Document.Current = apRow;
graph.Caches[typeof(APRegister)].SetValue<APRegisterExtension.usrARRefNbr>(apRow, row.RefNbr);
graph.Caches[typeof(APRegister)].Update(apRow);
graph.Actions.PressSave();
}
}
}
}
}
}
On AP Graph Extension
public class APInvoiceEntryExtension : PXGraphExtension<APInvoiceEntry>
{
protected virtual void APInvoice_RowUpdated(PXCache sender, PXRowUpdatedEventArgs e)
{
var row = (APInvoice)e.Row;
if (row != null && sender.IsDirty)
{
APRegisterExtension ext = PXCache<APRegister>.GetExtension<APRegisterExtension>(row);
if (ext != null && !string.IsNullOrEmpty(ext.UsrARRefNbr))
{
ARInvoiceEntry graph = PXGraph.CreateInstance<ARInvoiceEntry>();
ARInvoice arRow = PXSelect<ARInvoice,
Where<ARInvoice.refNbr, Equal<Required<ARInvoice.refNbr>>>>.Select(graph, ext.UsrARRefNbr);
if (arRow != null)
{
ARRegisterExtension ext1 = PXCache<ARRegister>.GetExtension<ARRegisterExtension>(arRow);
if (ext1 != null && string.IsNullOrEmpty(ext1.UsrAPRefNbr)) //Update only if it is empty
{
ext1.UsrAPRefNbr = row.RefNbr;
graph.Document.Current = arRow;
graph.Caches[typeof(ARRegister)].SetValue<ARRegisterExtension.usrAPRefNbr>(arRow, row.RefNbr);
graph.Caches[typeof(ARRegister)].Update(arRow);
graph.Actions.PressSave();
}
}
}
}
}
}
AR Cache Extension
public class ARRegisterExtension : PXCacheExtension<ARRegister>
{
public abstract class usrAPRefNbr : PX.Data.IBqlField
{
}
protected string _usrAPRefNbr;
[PXDBString(15)]
[PXUIField(DisplayName = "AP Ref Nbr.", Visibility = PXUIVisibility.SelectorVisible)]
[APInvoiceType.RefNbr(typeof(Search3<PX.Objects.AP.Standalone.APRegisterAlias.refNbr,
InnerJoinSingleTable<APInvoice, On<APInvoice.docType, Equal<PX.Objects.AP.Standalone.APRegisterAlias.docType>,
And<APInvoice.refNbr, Equal<PX.Objects.AP.Standalone.APRegisterAlias.refNbr>>>,
InnerJoinSingleTable<Vendor, On<PX.Objects.AP.Standalone.APRegisterAlias.vendorID, Equal<Vendor.bAccountID>>>>,
OrderBy<Desc<APRegister.refNbr>>>))]
public virtual string UsrAPRefNbr
{
get; set;
}
}
AP Cache Extension
public class APRegisterExtension : PXCacheExtension<APRegister>
{
public abstract class usrARRefNbr : PX.Data.IBqlField
{
}
protected string _usrARRefNbr;
[PXDBString(15)]
[PXUIField(DisplayName = "AR Ref Nbr.", Visibility = PXUIVisibility.SelectorVisible)]
[ARInvoiceType.RefNbr(typeof(Search3<PX.Objects.AR.Standalone.ARRegisterAlias.refNbr,
InnerJoinSingleTable<ARInvoice, On<ARInvoice.docType, Equal<PX.Objects.AR.Standalone.ARRegisterAlias.docType>,
And<ARInvoice.refNbr, Equal<PX.Objects.AR.Standalone.ARRegisterAlias.refNbr>>>,
InnerJoinSingleTable<Customer, On<PX.Objects.AR.Standalone.ARRegisterAlias.customerID, Equal<Customer.bAccountID>>>>,
OrderBy<Desc<ARRegister.refNbr>>>))]
public virtual string UsrARRefNbr
{
get; set;
}
}
When saving, APInvoice_RowUpdated modifies ARInvoice which in turn modifies APInvoice which triggers APInvoice_RowUpdated producing an infinite loop of event calls. The inverse in ARInvoice_RowUpdated which updates APInvoice will result in a similar infinite loop.
To get out of this, you can remove the graph event handler at runtime after instantiating the graph. First make your event handler access modifier public so you can reference them:
public virtual void APInvoice_RowUpdated(PXCache sender, PXRowUpdatedEventArgs e)
After creating a graph, get the extension and remove the handler that cause the infinite loop:
APInvoiceEntry graph = PXGraph.CreateInstance<APInvoiceEntry>();
APInvoiceEntryExtension graphExt = graph.GetExtension<APInvoiceEntryExtension>();
graphExt.RowUpdated.RemoveHandler<APInvoice>(graphExt.APInvoice_RowUpdated);
The same modification has to be done for ARInvoice because the infinite loop goes both way, from AP to AR and from AR to AP.

ExpandableListView extended using BaseExpandableListAdapter but reading from Sqlite DB example

Senior Geeks.
I'd like to request a simple but fully working example of how to implement an ExpandableListView while extending from BaseExpandableListAdapter Yet Reading data from an Sqlite Database.
I have researched and experimented on the question (see here), but with minimal success where i was able to display some data in the header, albeit it was same values repeating for all group headers. Also child items don't show.
The reason for extending with BaseExpandableListAdapter is to have a custom layout for the group header. The reason for SQLite access is naturally because thats where my data is stored.
All examples trawled on the net so far use either SimpleCursorTreeAdapter or CursorTreeAdapter as the extender in DB based applications or simply BaseExpandableListAdapter when data used is in ArrayLists.
Below is the Experimentation thus far. (with this code,only the group header is populated with the same figures over and over. Childitems dont appear)
public class ExpandableListViewAdapterCustom extends BaseExpandableListAdapter {
protected Activity currentActivity;
public ExpandableListViewAdapterCustom(Activity callingActivity){
this.currentActivity = callingActivity;
}
private Cursor mGroupsCursorLocal ;
private Cursor mChildCursor;
private Context ctx;
private int groupItem;
private int childItem;
private String[] fieldsToUseFromGroupCursor;
private int[] screenTextsToMapGroupDataTo;
private String[] fieldsToUseFromChildCursor;
private int[] screenTextsToMapChildDataTo;
public ArrayList<String> tempChild;
public LayoutInflater minflater;
public Activity activity;
public int intGroupTotal;
public void setCurrentActivity(Activity activity) {
this.activity = activity;
}
public void setCtx(Context ctx) {
this.ctx = ctx;
}
public void setGroupItem(int groupItem) {
this.groupItem = groupItem;
}
public void setChildItem(int childItem) {
this.childItem = childItem;
}
public Activity getCurrentActivity() {
return currentActivity;
}
public Cursor getmGroupsCursorLocal() {
return mGroupsCursorLocal;
}
public Context getCtx() {
return currentActivity.getBaseContext();
}
public void setmGroupsCursorLocal(Cursor mGroupsCursor) {
this.mGroupsCursorLocal = mGroupsCursor;
}
public ExpandableListViewAdapterCustom(Cursor mGroupsCursor,
Activity activity,
int groupItem,
int childItem,
String[] fieldsToUseFromGroupCursor,
int[] screenTextsToMapGroupDataTo,
String[] fieldsToUseFromChildCursor,
int[] screenTextsToMapChildDataTo) {
DatabaseRoutines db = new DatabaseRoutines(activity);
setmGroupsCursorLocal(mGroupsCursor);
mGroupsCursorLocal = db.fetchGroup();
activity.startManagingCursor (mGroupsCursor);
mGroupsCursorLocal.moveToFirst();
mChildCursor=db.fetchChildren(mGroupsCursorLocal.getColumnIndex("Year"));
mChildCursor.moveToFirst();
activity.startManagingCursor(mChildCursor);
setCtx(activity);
setCurrentActivity(activity);
}
public void setInflater(LayoutInflater mInflater, Activity act) {
this.minflater = mInflater;
activity = act;
}
#Override
public Object getChild(int groupPosition, int childPosition) {
return null;
}
#Override
public long getChildId(int groupPosition, int childPosition) {
return 0;
}
#Override
public View getChildView(int groupPosition,
int childPosition,boolean
isLastChild,
View convertView,
ViewGroup parent) {
View v = convertView;
if (v == null)
{
LayoutInflater inflater =
(LayoutInflater) ctx.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = inflater.inflate(R.layout.exp_listview_childrow, parent, false);
}
TextView txtMonth = (TextView) v.findViewById(R.id.txtMonth);
TextView txtMonthAmountSent = (TextView)
v.findViewById(R.id.txtMonthAmountSentValue);
TextView txtMonthReceived = (TextView)
v.findViewById(R.id.txtMonthAmountReceivedValue);
txtMonth.setText(mChildCursor.getString(mChildCursor.getColumnIndex("Month")));
txtMonthAmountSent.setText
(mChildCursor.getString(mChildCursor.getColumnIndex("TotalSent")));
txtMonthReceived.setText
(mChildCursor.getString(mChildCursor.getColumnIndex("TotalReceived")));
return v;
}
#Override
public int getChildrenCount(int groupPosition) {
return (mChildCursor.getCount());
}
#Override
public Object getGroup(int groupPosition) {
return null;
}
#Override
public int getGroupCount() {
return mGroupsCursorLocal.getCount();
}
#Override
public void onGroupCollapsed(int groupPosition) {
super.onGroupCollapsed(groupPosition);
}
#Override
public void onGroupExpanded(int groupPosition) {
super.onGroupExpanded(groupPosition);
}
#Override
public long getGroupId(int groupPosition) {
return 0;
}
#Override
public View getGroupView(
int groupPosition,
boolean isExpanded,
View convertView,
ViewGroup parent)
{
View v = convertView;
if (v == null) {
LayoutInflater inflater =
(LayoutInflater) ctx.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = inflater.inflate(R.layout.exp_listview_groupheader, parent, false);
}
TextView txtYear = (TextView) v.findViewById(R.id.txtYearValue);
TextView txtAmountSent = (TextView) v.findViewById(R.id.txtAmountSentValue);
TextView txtAmountRecieved = (TextView)
v.findViewById(R.id.txtAmountReceivedValue);
txtYear.setText(mGroupsCursorLocal.getString(
mGroupsCursorLocal.getColumnIndex("Year")));
txtAmountSent.setText(
mGroupsCursorLocal.getString(mGroupsCursorLocal.getColumnIndex("TotalSent")));
txtAmountRecieved.setText(
GroupsCursorLocal.getString(mGroupsCursorLocal.getColumnIndex("TotalReceived")));
return v;
}
#Override
public boolean hasStableIds() {
return true;
}
#Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
return false;
}
}
The Database code is like this
public Cursor fetchGroup() {
SQLiteDatabase db = this.getReadableDatabase(); //if memory leaks check here
String query = "SELECT DISTINCT MIN(ID) AS id,
Year, SUM(SentAmount) AS TotalSent, SUM(ReceivedAmount) AS TotalReceived
FROM MyTbl GROUP BY Year ORDER BY Year DESC ";
return db.rawQuery(query, null);}
public Cursor fetchChildren(int Yr) {
SQLiteDatabase db = this.getReadableDatabase(); //if memory leaks check here
String query = "SELECT MIN(ID) AS id,
Year, Month, SUM(SentAmount) AS TotalSent,
SUM(ReceivedAmount) AS TotalReceived
FROM MyTbl Where Year= "+ Yr +" GROUP BY Year,
Month ORDER BY Year DESC, Month DESC";
return db.rawQuery(query, null);
}
The Code is called from main activity using the following
ExpandableListView elv = (ExpandableListView)
findViewById(R.id.expandableListView);
ExpandableListAdapter mAdapter = new
ExpandableListViewAdapterCustom(mGroupsCursor,
MyActivity.this,
R.layout.exp_listview_groupheader,// Your row layout for a group
R.layout.exp_listview_childrow, // Your row layout for a child
new String[] { "Year",
"TotalSent",
"TotalReceived" },// Field(s) to use from group cursor
new int[] {R.id.txtYearValue,
R.id.txtAmountSentValue,
R.id.txtAmountReceivedValue },// Widget ids to put group data
into new String[] { "Year","Month",
"TotalSent",
"TotalReceived" }, // Field(s) to use from child cursors new
int[] {R.id.txtMonthValue,
R.id.txtMonthAmountSentValue,
R.id.txtMonthAmountReceivedValue});// Widget ids to put child d
data into
elv.setClickable(true);
elv.setAdapter(mAdapter); // set the
After almost two weeks and no answer, i decided to simply use an ExpandableListView example using ArrayLists and modified it such that the ArrayLists were populated by data from the DB. Its not what i wanted but it works. I was actually suprised that nowhwere on the web is there an example of using ExpandableListview extended form BaseAdapter but reading from SQlite using say cursorTreeAdapter or SimpleCursorAdapter.
Below is how i did it in case it helps someone in future. the code shown is the bit that populates the ArrayList from DB
public ArrayList<ExpandListGroup> SetStandardGroups() {
ArrayList<ExpandListGroup> list = new ArrayList<ExpandListGroup>();
ArrayList<ExpandListChild> list2 = new ArrayList<ExpandListChild>();
int intMonthNum;
ExpandListGroup grp;
ExpandListChild chld;
//initialize db code here
DatabaseRoutines db = new DatabaseRoutines(this);
//create the Groups retreival cursor;
Cursor mGroupsCursor = db.fetchGroup();
//---the database call is done using this code which is in my
//---custom db class which implements the sqlhelper methods etc
//------start of db code snippet-------------------------------
//---public Cursor fetchGroup() {
//---SQLiteDatabase db = this.getReadableDatabase();
//--- String query = "SELECT DISTINCT MIN(ID) AS id, Year,
//--- SUM(SentAmount) AS TotalSent,
//--- SUM(ReceivedAmount) AS TotalReceived
//--- FROM Tbl GROUP BY Year ORDER BY Year DESC ";
//--- return db.rawQuery(query, null);}
//------end of db code snippet-------------------------------
mGroupsCursor.moveToFirst();
//method is depreciated from api14 but i'm targeting Gingerbread (api10) so i need to use it.
startManagingCursor(mGroupsCursor);
int intYear;
int intHeaderCounter = 0;
int intChildCounter = 0;
int intChildTotalCount = 0;
int intHeaderTotalGroupCount = mGroupsCursor.getCount();
//set the starting Year for the loop, if there is data;
if (intHeaderTotalGroupCount > 0) {
//get the first year
//intYear = mGroupsCursor.getInt(mGroupsCursor.getColumnIndex("Year"));
for (intHeaderCounter = 0; intHeaderCounter < intHeaderTotalGroupCount; intHeaderCounter++) {
grp = new ExpandListGroup();
intYear = mGroupsCursor.getInt(mGroupsCursor.getColumnIndex("Year"));
grp.setYear(intYear);
grp.setYearAmountReceived(mGroupsCursor.getDouble(mGroupsCursor.getColumnIndex("TotalReceived")));
grp.setYearAmountSent(mGroupsCursor.getDouble(mGroupsCursor.getColumnIndex("TotalSent")));
grp.setTag(mGroupsCursor.getString(mGroupsCursor.getColumnIndex("id")));
//Prepare counters for inner loop for child items of each
Cursor mChildCursor = db.fetchChildren(intYear);
mChildCursor.moveToFirst();
intChildTotalCount = mChildCursor.getCount();
//populate child items
for (intChildCounter = 0; intChildCounter < intChildTotalCount; intChildCounter++) {
chld = new ExpandListChild();
intMonthNum = mChildCursor.getInt(mChildCursor.getColumnIndex("Month"));
chld.setMonthNumber(intMonthNum);
chld.setTotalReceivedMonth(mChildCursor.getInt(mChildCursor.getColumnIndex("TotalReceived")));
chld.setTotalSentMonth(mChildCursor.getInt(mChildCursor.getColumnIndex("TotalSent")));
chld.setTag(mGroupsCursor.getString(mGroupsCursor.getColumnIndex("id")).toString());
list2.add(chld);
//grp.setItems(list2);
//move to next child record;
mChildCursor.moveToNext();
}
grp.setItems(list2);
list.add(grp);
list2 = new ArrayList<ExpandListChild>();
//move to next parent record;
mGroupsCursor.moveToNext();
}
} else {
log.d( "yourdebugtag_here", "Sorry, No Transactions Found.");
}
//db.close();
return list;
}

Resources