Hi, I'm working on a customization project in Acumatica 2024 R2 to allow for split payments for Cash Sales. I have a custom DAC that will store the multiple payments for a cash sale if it's allowed for that invoice. However after trying to add a line counter to the SOInvoiceExt to keep track of the lines added for the split payment I'm unable to load the SO303000 screen. Please see below code for the SOInvoice DAC, Custom DAC and the extended graph. Any help is greatly appreciated.
**SOInvoiceExt**
public class SOInvoiceExt : PXCacheExtension<PX.Objects.SO.SOInvoice>
{
#region UsrSplitPayment
[PXDBBool]
[PXDefault(false)]
[PXUIField(DisplayName="Split Payment")]
public bool? UsrSplitPayment { get; set; }
public abstract class usrSplitPayment : PX.Data.BQL.BqlBool.Field<usrSplitPayment> { }
#endregion
#region UsrSplitLineCntr
[PXDBInt()]
[PXDefault(0)]
//[PXUIField(DisplayName="Split Line Counter", Enabled= false)]
public int? UsrSplitPaymentLineCntr {get; set;}
public abstract class usrSplitPaymentLineCntr: PX.Data.BQL.BqlInt.Field<usrSplitPaymentLineCntr> {}
#endregion
//rest of fields
}
}
**Custom DAC (SplitPaymentLine)**
using System;
using PX.Data;
using PX.Objects.SO;
using PX.Objects.AR;
using PX.Data.BQL;
using PX.Data.BQL.Fluent;
using PX.Objects.CS;
using PX.Objects.CA;
namespace SplitPayment
{
public static class SplitLineTypes
{
public const string Payment = "P";
public class payment : BqlString.Constant<payment>
{
public payment() : base(Payment) { }
}
}
[Serializable]
[PXCacheName("SplitPaymentLine")]
public class SplitPaymentLine : PXBqlTable, IBqlTable
{
#region LineNbr
[PXDBInt(IsKey = true)]
[PXLineNbr(typeof(SOInvoiceExt.usrSplitPaymentLineCntr))]
public virtual int LineNbr { get; set; }
public abstract class lineNbr : PX.Data.BQL.BqlInt.Field<lineNbr> { }
#endregion
#region RefNbr
[PXDBString(15,IsKey = true, IsUnicode = true, InputMask = "")]
[PXUIField(DisplayName = "Ref Nbr")]
[PXDBDefault(typeof(SOInvoice.refNbr), PersistingCheck = PXPersistingCheck.Nothing)]
//[PXParent(typeof(SelectFrom<SOInvoice>
// .Where<SOInvoice.docType.IsEqual<SplitPaymentLine.docType.FromCurrent>
// .And<SOInvoice.refNbr.IsEqual<SplitPaymentLine.refNbr.FromCurrent>>>))]
public virtual string RefNbr { get; set; }
public abstract class refNbr : PX.Data.BQL.BqlString.Field<refNbr> { }
#endregion
#region DocType
[PXDBString(3, IsKey = true, IsUnicode = true, InputMask = "")]
[PXUIField(DisplayName = "Doc Type")]
[PXDBDefault(typeof(SOInvoice.docType), PersistingCheck = PXPersistingCheck.Nothing)]
[PXParent(typeof(Select<SOInvoice,
Where<SOInvoice.docType, Equal<Current<SplitPaymentLine.docType>>,
And<SOInvoice.refNbr, Equal<Current<SplitPaymentLine.refNbr>>>>>))]
public virtual string DocType { get; set; }
public abstract class docType : PX.Data.BQL.BqlString.Field<docType> { }
#endregion
//rest of fields
}
}
**Graph**
public class SOInvoiceEntry_Extension : PXGraphExtension<PX.Objects.SO.SOInvoiceEntry>
{
public SelectFrom<SplitPaymentLine>
//.Where<SplitPaymentLine.companyID.IsEqual<SOInvoice.companyID.FromCurrent>
.Where<SplitPaymentLine.docType.IsEqual<SOInvoice.docType.FromCurrent>
.And<SplitPaymentLine.refNbr.IsEqual<SOInvoice.refNbr.FromCurrent>>>
.View SplitPayments;
//[PXLineNbr(typeof(SOInvoiceExt.usrSplitPaymentLineCntr))]
protected void SplitPaymentLine_LineNbr_CacheAttached(PXCache sender) { }
public PXSetup<SOSetup> soSetup;
#region Event Handlers
//SOInvoice
protected void _(Events.FieldUpdated<SOInvoice, SOInvoice.docType> e)
{
var row = (SOInvoice)e.Row;
if(row == null) return;
PXTrace.WriteInformation("Doc Type: {0}",row.DocType);
//clear usrSplitPayment
row.GetExtension<SOInvoiceExt>().UsrSplitPayment = false;
//Trigger RowSelected event
e.Cache.RaiseRowSelected(row);
}
#region SOInvoice.RowSelected
protected void _(Events.RowSelected<SOInvoice> e)
{
PXTrace.WriteInformation("Event RowSelected: SOInvoice");
var row = e.Row;
if(row == null) return;
bool allowSplitPayment = false;
if(!string.IsNullOrEmpty(row.DocType))
{
allowSplitPayment = row.DocType == ARDocType.CashSale;
//Enable/Disable Split Payment Checkbox
PXUIFieldAttribute.SetEnabled<SOInvoiceExt.usrSplitPayment>(e.Cache, row, allowSplitPayment);
//Fetch Split Payment value
bool splitPayment = row.GetExtension<SOInvoiceExt>()?.UsrSplitPayment == true;
PXTrace.WriteInformation("Split Payment: {0}", splitPayment);
//Enable/Disable Grid actions
SplitPayments.Cache.AllowSelect = splitPayment;
SplitPayments.Cache.AllowInsert = splitPayment;
SplitPayments.Cache.AllowDelete = splitPayment;
SplitPayments.Cache.AllowUpdate = splitPayment;
//SplitPayments.Cache.AllowSave = false;
//rest of business logic
}
}
#endregion
//SOInvoice Row Persisting
protected void _(Events.RowPersisting<SOInvoice> e)
{
var row = e.Row;
if(row == null) return;
if(row.DocType == ARDocType.CashSale)
{
bool splitPayment = row.GetExtension<SOInvoiceExt>()?.UsrSplitPayment == true;
if(splitPayment)
{
var splitLineCount = SplitPayments.Cache.Cached
.OfType<SplitPaymentLine>()
.Count(l => SplitPayments.Cache.GetStatus(l) != PXEntryStatus.Deleted
&& !string.IsNullOrEmpty(l.LineDocType) // Use DocType, not SplitDocType
&& l.PayAmount > 0);
//SplitPayments.Cache.Cached
// .OfType<SplitPaymentLine>()
// .Count(l => SplitPayments.Cache.GetStatus(l) != PXEntryStatus.Deleted
// && l.DocType != null);
//SplitPayments.Cache.Cached
//.OfType<SplitPaymentLine>()
//.Count(l => SplitPayments.Cache.GetStatus(l) != PXEntryStatus.Deleted);
//SplitPayments.Cache.Cached.Count(x => x != null && ((SplitPaymentLine)x).DocType != null);//SplitPayments.Select().Count();
PXTrace.WriteInformation("Lines: {0}", splitLineCount);
if(splitLineCount < 2)
{
throw new PXException("At least two split payment lines are required when 'Split Payment' is enabled.");
}
//Loop thru each SplitPayment Line
}
else
{
//Delete lines
foreach (SplitPaymentLine line in SplitPayments.Select())
{
SplitPayments.Delete(line);
}
}
}
}
//SplitPaymentLine
protected void _(Events.RowSelected<SplitPaymentLine> e)
{
var row = e.Row;
if(row == null) return;
var docType = row.LineDocType ;
PXTrace.WriteInformation("Line Doc Type: {0}", docType);
//rest of logic
}
//rest of event handlers
#endregion
#region Business Logic
private int GetNextLineNumber()
{
var invoice = Base.Document.Current;
if (invoice == null) return 1;
var maxLine = SplitPayments.Select().RowCast<SplitPaymentLine>()
// .Where(x => x.DocType == invoice.docType &&
// x.RefNbr == invoice.RefNbr)
.Max(x => x.LineNbr);
return maxLine + 1;
}
#endregion
}
}I added a new tab to the SO303000 screen for entering the multiple payment info. This tab is shown based on a checkbox and the records are linked to the invoice by the refnbr and doctype. The line counter is setup similar to the grid in the Details tab