PAYATHON 2026

AR Invoice 必填 UDF 导致 AR Payment 报错

支付小周

结论

报错并不是因为 cash receipt 被当成了 invoice,而是因为 UDF 挂在 invoice 和 payment 共用的 DAC 上,通常是 ARRegister。ARInvoice 和 ARPayment 都基于这个 DAC,所以 [PXDefault] 的持久化必填校验同样会作用于 payment,即使 Payments 界面没有显示该字段。

建议优先把字段定义在 ARInvoice 的 DAC Extension 中。如果暂时无法调整字段结构,可以在 ARPaymentEntry 中关闭 payment 侧的持久化校验,同时保留 invoice 的必填检查。

为什么隐藏字段仍会被校验

[PXDefault] 不只是为界面提供默认值。默认情况下,它还会在记录持久化时检查字段是否为空,行为近似于:

[PXDefault(PersistingCheck = PXPersistingCheck.NullOrBlank)]

这个检查发生在 DAC Cache 的持久化过程中,与字段是否显示在当前界面没有直接关系。

假设字段定义如下:

public sealed class ARRegisterExt : PXCacheExtension<ARRegister>
{
    public abstract class usrInvoiceReference
        : PX.Data.BQL.BqlString.Field<usrInvoiceReference>
    {
    }

    [PXDBString(50, IsUnicode = true)]
    [PXUIField(DisplayName = "Invoice Reference")]
    [PXDefault]
    public string UsrInvoiceReference { get; set; }
}

该字段会同时进入 ARInvoice 和 ARPayment 的 Cache。保存未核销的 cash receipt 时,ARPayment 记录也会持久化。此时 PXDefault 检测到字段为空,就会抛出错误。

是否把 payment 应用到 invoice 并不是触发校验的关键。只要保存继承了该字段的 payment 记录,就可能触发校验。

方案一:将字段限定在 ARInvoice

如果该字段在业务上只属于 invoice,最合适的做法是把它定义在 ARInvoice 的 DAC Extension 中:

using PX.Data;
using PX.Data.BQL;
using PX.Objects.AR;

public sealed class ARInvoiceExt : PXCacheExtension<ARInvoice>
{
    public abstract class usrInvoiceReference
        : BqlString.Field<usrInvoiceReference>
    {
    }

    [PXDBString(50, IsUnicode = true)]
    [PXUIField(DisplayName = "Invoice Reference", Required = true)]
    [PXDefault]
    public string UsrInvoiceReference { get; set; }
}

调整后:

  • 保存 ARInvoice 时仍会执行必填校验;
  • ARPayment 不再包含该扩展字段;
  • Payments 界面不会再因 invoice 专用字段报错;
  • 字段的技术归属与业务含义一致。

调整现有字段前,需要确认自定义数据库列目前位于哪张表,以及生产环境中是否已经有数据。不要直接删除旧列再重新创建,否则可能丢失原有值。通常需要先增加新的 invoice 字段,迁移数据并更新界面绑定,确认无误后再处理旧字段。

方案二:仅在 Payment 中关闭持久化校验

如果字段已经定义在 ARRegisterExt 中,短期内不方便迁移,可以在 ARPaymentEntry 中把该字段的 PersistingCheck 设置为 Nothing:

using PX.Data;
using PX.Objects.AR;

public class ARPaymentEntryExt : PXGraphExtension<ARPaymentEntry>
{
    protected virtual void _(Events.RowPersisting<ARPayment> e)
    {
        if (e.Row == null)
            return;

        PXDefaultAttribute.SetPersistingCheck<ARRegisterExt.usrInvoiceReference>(
            e.Cache,
            e.Row,
            PXPersistingCheck.Nothing);
    }
}

请把这里的 ARRegisterExt.usrInvoiceReference 替换为实际的 DAC Extension 和字段名称。

这段处理只会改变 ARPaymentEntry 保存 ARPayment 时的校验方式,不会移除 invoice 侧原有的 [PXDefault]。因此:

  • 在 AR Invoices and Memos 中保存空值时仍会报错;
  • 在 AR Payments and Applications 中可以保存未应用的 cash receipt;
  • 无需在 Payments 界面增加没有业务意义的输入框。

如果 payment 还可能通过其他 Graph、Web Service、Import Scenario 或自定义处理页面创建,需要逐一检查这些入口。ARPaymentEntry 的 Graph Extension 只能保证经过该 Graph 的操作不再触发这项校验。

方案三:显式只校验 Invoice

如果字段必须继续保留在共享 DAC 上,同时又需要明确控制校验范围,可以先关闭字段自身的通用持久化检查,再在保存 invoice 时执行校验。

字段定义:

public sealed class ARRegisterExt : PXCacheExtension<ARRegister>
{
    public abstract class usrInvoiceReference
        : PX.Data.BQL.BqlString.Field<usrInvoiceReference>
    {
    }

    [PXDBString(50, IsUnicode = true)]
    [PXUIField(DisplayName = "Invoice Reference", Required = true)]
    [PXDefault(PersistingCheck = PXPersistingCheck.Nothing)]
    public string UsrInvoiceReference { get; set; }
}

在 ARInvoiceEntry 中执行持久化校验:

using PX.Data;
using PX.Objects.AR;

public class ARInvoiceEntryExt : PXGraphExtension<ARInvoiceEntry>
{
    protected virtual void _(Events.RowPersisting<ARInvoice> e)
    {
        if (e.Row == null ||
            e.Operation == PXDBOperation.Delete)
        {
            return;
        }

        ARRegisterExt extension =
            e.Row.GetExtension<ARRegisterExt>();

        if (string.IsNullOrWhiteSpace(extension?.UsrInvoiceReference))
        {
            e.Cache.RaiseExceptionHandling<ARRegisterExt.usrInvoiceReference>(
                e.Row,
                extension?.UsrInvoiceReference,
                new PXSetPropertyException(
                    "Invoice Reference is required.",
                    PXErrorLevel.Error));

            throw new PXRowPersistingException(
                typeof(ARRegisterExt.usrInvoiceReference).Name,
                extension?.UsrInvoiceReference,
                "Invoice Reference is required.");
        }
    }
}

这种方式可以更细致地控制校验范围,但也可能遗漏其他创建 invoice 的入口。除了 ARInvoiceEntry,还需要检查销售订单开票、导入、API、自定义 Graph 和批处理流程是否会持久化 ARInvoice。

注意事项

PXUIField.Required = true 主要控制界面状态和必填标识,不能单独替代持久化校验。如果必须保证字段在写入数据库前有值,仍需使用 [PXDefault] 或 RowPersisting 校验。

不要仅为消除报错而删除 [PXDefault],除非业务确实允许 invoice 的该字段为空。否则,payment 的报错虽然消失了,invoice 的数据完整性却无法得到保证。

从长期维护来看,invoice 专用字段应尽量定义在 ARInvoice 上。只有确实需要由 invoice、payment 等多种 AR 单据共用的字段,才适合定义在 ARRegister 上。

备注:内容仅供参考。