Skip to main content
Solved

SO303000 - Error: The entry form (ID: SO303000, title: Invoices) cannot be automated. Object reference not set to an instance of an object.

  • September 29, 2025
  • 7 replies
  • 114 views

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

Best answer by mecheann

Hey guys, thanks for all your help. I did find a solution. One of the objects I was referencing could be potentially null but I was still trying to access a property on that object hence the issue.

 

 We found a workaround for accepting multiple cash sale payments by using a clearing account and doing a transfer based on the split payment entries. We also had to look at the Bank Deposit aspect of it as well.

 

@Dmitrii Naumov You were right. I did have to go bit into the GL account processes but it worked out.

7 replies

mohammadnawaz51
Jr Varsity I
Forum|alt.badge.img+6

@mecheann Can you review the below article?

https://asiablog.acumatica.com/index.php/2019/04/this-form-cannot-be-automated-investigation/#:~:text=In%20case%20there%20is%20any,message%20is%20even%20more%20confusing.


Dmitrii Naumov
Acumatica Moderator
Forum|alt.badge.img+7
  • Acumatica Moderator
  • September 29, 2025

@mecheann It usually means you have UI elements not aligning with your DACs. But I think the bigger question is why you even need the code there? SO303000 allows multiple payments OOTB. You probably just need to use a different document type instead. That would be a much better route. Otherwise you are going too deep in the release process, adjusting GL posting, etc.


  • Author
  • Freshman II
  • September 29, 2025

@Dmitrii Naumov the requirement for the customization is for Cash Sales so the doc type cannot be changed unfortunately.

@mohammadnawaz51 I’m using a VM to do the development. The screen only loads after I restart the VM despise no change in the code.


Dmitrii Naumov
Acumatica Moderator
Forum|alt.badge.img+7
  • Acumatica Moderator
  • September 29, 2025

@mecheann  I’d recommend to rethink the requirement. I don’t think users really insist that it’s a Cash Sale specifically. You can have SO document type like “cash sale with multiple payments” that behaves in a similar way but generates SO of type invoice instead.

 

Otherwise you’ll face a lot of challenges when it comes to GL posting, including ones that leave GL accounts unbalanced and things like that. 


  • Author
  • Freshman II
  • September 29, 2025

@Dmitrii Naumov Unfortunately, having the doc type be different is non-negotiable. The compromise that was made is for a transfer account setup instead


Chris Hackett
Community Manager
Forum|alt.badge.img
  • Acumatica Community Manager
  • November 5, 2025

Hi ​@mecheann were you able to find a solution? Thank you!


  • Author
  • Freshman II
  • Answer
  • January 16, 2026

Hey guys, thanks for all your help. I did find a solution. One of the objects I was referencing could be potentially null but I was still trying to access a property on that object hence the issue.

 

 We found a workaround for accepting multiple cash sale payments by using a clearing account and doing a transfer based on the split payment entries. We also had to look at the Bank Deposit aspect of it as well.

 

@Dmitrii Naumov You were right. I did have to go bit into the GL account processes but it worked out.