Sequencing Async Operations in Revenue Cloud to Prevent Pricing Corruption
Revenue Cloud manages pricing through an internal mechanism called the Pricing Sales Transaction (PST). When you interact with Quotes and Quote Line Items programmatically, the PST API maintains an in-flight state that tracks which line items are being priced, their calculated values, and the overall transaction integrity. This state is fragile -- and concurrent async operations can corrupt it in ways that produce cryptic errors.
The Race Condition
The problem surfaces when two asynchronous operations touch the same Quote's pricing state simultaneously. A common anti-pattern looks like this:
- A Queueable job creates or updates Quote Line Items on a Quote.
- Within that Queueable, a separate @future method is called to sync the Quote (triggering the pricing engine).
- The Queueable has not fully committed its DML when the @future fires in a new transaction.
Because @future methods run in their own transaction, the pricing engine may attempt to recalculate while the Queueable's DML is still in flight or before the line items are fully committed. The SalesTransaction state sees partially committed data, and the pricing calculation fails or produces incorrect results.
Symptoms
"Total Price is required"errors on Quote Line Items that clearly have pricing data"FIELD_INTEGRITY_EXCEPTION"on fields the pricing engine manages internally- Intermittent failures -- the same code works sometimes and fails other times depending on transaction timing
- Quote Line Items saved with $0 values despite correct PricebookEntry data
Why Revenue Cloud Requires Strict Sequencing
The PST API expects a specific lifecycle:
- Begin -- A pricing transaction is opened for the Quote.
- Add/Modify Lines -- Line items are created or updated within the transaction context.
- Calculate -- The pricing engine runs all pricing procedures and decision tables.
- Commit -- The calculated values are persisted to the Quote and its line items.
When you split steps 2 and 3 across separate async contexts (@future and Queueable), you break this lifecycle. The pricing engine in the @future call cannot see the uncommitted state from the Queueable, or worse, it sees a partially committed state that violates internal consistency checks.
The Correct Pattern
Perform all DML and the Quote sync within the same Queueable execution context. Never delegate the sync to a separate @future method.
public class QuoteLineItemCreationJob implements Queueable {
private Id quoteId;
private List<QuoteLineItem> lineItems;
public QuoteLineItemCreationJob(Id quoteId, List<QuoteLineItem> lineItems) {
this.quoteId = quoteId;
this.lineItems = lineItems;
}
public void execute(QueueableContext ctx) {
// Step 1: Insert all line items first
insert lineItems;
// Step 2: Sync the Quote WITHIN the same execution context
// This ensures the pricing engine sees all committed line items
Quote q = [SELECT Id, IsSyncing FROM Quote WHERE Id = :quoteId];
q.IsSyncing = true;
update q;
// The pricing engine now runs with full visibility of all line items
}
}
The key principle: all DML must complete before the pricing engine is invoked, and both must happen in the same transaction.
Diagnosing the Issue
When you suspect a race condition:
- Enable debug logs for the Automated Process user (async operations often run under this user).
- Search for
SalesTransactionin the logs -- look for transaction open/close events that overlap. - Check timestamps -- if you see a pricing calculation begin before line item DML completes in a parallel log, you have confirmed the race condition.
- Look for
EXCEPTION_THROWNevents referencingTotalPrice,UnitPrice, orFIELD_INTEGRITY_EXCEPTION-- these are hallmarks of corrupted pricing state.
Key Takeaways
| Principle | Guidance |
|---|---|
| Single context | All line item DML and Quote sync must occur in the same Queueable |
| No @future for sync | Never call an @future method from a Queueable to trigger pricing |
| Order matters | Insert/update lines first, then sync the Quote as the final step |
| Debug strategy | Log the Automated Process user and search for overlapping SalesTransaction events |
Revenue Cloud's pricing engine is powerful but assumes strict transactional integrity. Respecting the PST lifecycle by keeping all operations in a single async context eliminates an entire class of intermittent pricing failures.