We're on 2026 R1 and building field-change history for time-in-stage reporting, starting with CRLead.Status and CROpportunity.StageID. Each change should add a row to a custom table, BXFieldHistory (entity NoteID, field name, old value, new value, changed by, changed on).
Updates come from several places: the stock screens, Lead → Opportunity conversion, mass-update screens, and an external integration using the contract-based REST API. So we plan to attach a custom attribute (a PXEventSubscriberAttribute subclass) to the fields through a DAC extension with PXMergeAttributes, rather than one graph extension per screen.
public class BXTrackHistoryAttribute : PXEventSubscriberAttribute,
IPXRowUpdatedSubscriber, IPXRowPersistedSubscriber
{
// RowUpdated: compare e.Row vs e.OldRow, remember the change
// RowPersisted: write the BXFieldHistory row
}What's the supported way to write the BXFieldHistory row from inside the attribute, so it commits or rolls back with the parent record in every graph that uses the DAC?
- Insert into
sender.Graph.Caches[typeof(BXFieldHistory)]duringRowUpdated/RowPersistingso the graph persists it. Will a graph reliably persist a cache for a DAC it has no view for? PXDatabase.Insert<BXFieldHistory>(...)inRowPersistedwhilee.TranStatus == PXTranStatus.Open. Is a direct insert there acceptable for a plain log table?- Some other pattern, or a stock attribute that already does this that we could model on.
Also, are there any stock processes that update these fields without raising cache events (for example PXDatabase.Update), which would bypass the attribute?
Thanks!