I get this error when trying to run a batch job:
FATAL_ERROR System.LimitException: SBQQ:Too many queueable jobs added to the queue: 2
This is my code in the batch:
global class UpliftBatch implements Database.Batchable<sObject>, Database.AllowsCallouts{
global Database.QueryLocator start(Database.BatchableContext bc){
system.debug('Start');
return Database.getQueryLocator([SELECT Id FROM Contract WHERE Uplift_Batch_Check__c = true]);
}
global void execute(Database.BatchableContext bc, List<Contract> lList){
try{
system.debug('Execute' + lList);
RAM_Uplift test = new RAM_Uplift();
system.debug('New Uplift class called');
for(Contract objCon : lList){
objCon.Uplift_Batch_Check__c = false;
string contractID = objCon.Id;
system.debug('Execute: ' + objCon);
string objQuoteID = test.createAmendment(contractID);
system.debug('Create amendment called');
Contract objContract = [select Id, SBQQ__Quote__r.SBQQ__Opportunity2__r.NAV_Legal_Entity__c from Contract where id = :contractID];
system.debug('Contract object created');
test.updateQuantity(objQuoteID, objContract.SBQQ__Quote__r.SBQQ__Opportunity2__r.NAV_Legal_Entity__c);
system.debug('quote update done');
Order objOrder = [SElECT Id, Status, SBQQ__Contracted__c FROM Order WHERE SBQQ__Quote__r.id = :objQuoteID];
test.activateOrder(objOrder.Id);
system.debug('order done');
}
update lList;
}catch(DmlException e){
System.debug('The following exception has occurred: ' + e.getMessage());
}
}
global void finish(Database.BatchableContext bc){
AsyncApexJob a = [SELECT Id, Status, NumberOfErrors, JobItemsProcessed, TotalJobItems, CreatedBy.Email FROM AsyncApexJob WHERE Id = :BC.getJobId()];
Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
String[] toAddresses = new String[] {a.CreatedBy.Email};
mail.setToAddresses(toAddresses);
mail.setSubject('Contract Amendement Uplift ' + a.Status);
mail.setPlainTextBody('The batch Apex job processed ' + a.TotalJobItems +
' batches with '+ a.NumberOfErrors + ' failures.');
Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
}
}
The RAM_Uplift as follows:
public with sharing class RAM_Uplift {
public class QuoteModel {
public SBQQ__Quote__c record;
public QuoteLineModel[] lineItems;
public QuoteLineGroupModel[] lineItemGroups;
public Integer nextKey;
public Boolean applyAdditionalDiscountLast;
public Boolean applyPartnerDiscountFirst;
public Boolean channelDiscountsOffList;
public Decimal customerTotal;
public Decimal netTotal;
public Decimal netNonSegmentTotal;
}
public class QuoteLineModel {
public SBQQ__QuoteLine__c record;
public Boolean amountDiscountProrated;
public Integer parentGroupKey;
public Integer parentItemKey;
public Integer key;
public Boolean upliftable;
public String configurationType;
public String configurationEvent;
public Boolean reconfigurationDisabled;
public Boolean descriptionLocked;
public Boolean productQuantityEditable;
public Decimal productQuantityScale;
public String dimensionType;
public Boolean productHasDimensions;
public Decimal targetCustomerAmount;
public Decimal targetCustomerTotal;
}
public class QuoteLineGroupModel {
public SBQQ__QuoteLineGroup__c record;
public Decimal netNonSegmentTotal;
public Integer key;
}
public with sharing class AmendContext {
public Boolean returnOnlyQuoteId;
}
public with sharing class AmendedContract{
public QuoteModel load(String contractId, String context) {
String quoteJSON = SBQQ.ServiceRouter.load('SBQQ.ContractManipulationAPI.ContractAmender', contractId, context);
return (QuoteModel) JSON.deserialize(quoteJSON, QuoteModel.class);
}
}
public String createAmendment(String cID)
{
String quoteId;
try{
AmendContext con1 = new AmendContext();
con1.returnOnlyQuoteId = false;
String contextJson = JSON.serialize(con1);
AmendedContract amender = new AmendedContract();
QuoteModel quote = amender.load(cID, contextJson);
SBQQ__Quote__c quoteRec = quote.record;
quoteId = (String) quoteRec.Id;
}catch(DmlException e){
System.debug('The following exception has occurred: ' + e.getMessage());
}
return quoteId;
}
public void updateQuantity(string quoteID, id legalEntity){
try {
id idQuote = quoteID;
SBQQ__Quote__c objQuote = new SBQQ__Quote__c();
Opportunity objOpportunity = new Opportunity();
objQuote.id = idQuote;
SBQQ__Quote__c objQuoteQuery = [SELECT NAV_Annual_Uplift_Rate__c, SBQQ__Opportunity2__c FROM SBQQ__Quote__c WHERE Id = :quoteID];
objOpportunity.id = objQuoteQuery.SBQQ__Opportunity2__c;
objOpportunity.NAV_Legal_Entity__c = legalEntity;
objOpportunity.clone(false,false,false,false);
update objOpportunity;
list<SBQQ__QuoteLine__c> toUpdateQline = new list<SBQQ__QuoteLine__c>();
list<SBQQ__QuoteLine__c> toInsertQline = new list<SBQQ__QuoteLine__c>();
for(SBQQ__QuoteLine__c ln :
[SELECT
SBQQ__AdditionalDiscount__c,
SBQQ__Discount__c,
SBQQ__AdditionalDiscountAmount__c,
SBQQ__AdditionalQuantity__c,
SBQQ__AllowAssetRefund__c,
SBQQ__BatchQuantity__c,
SBQQ__BillingFrequency__c,
SBQQ__BillingType__c,
SBQQ__BlockPrice__c,
SBQQ__Bundled__c,
SBQQ__carryoverLine__c,
SBQQ__chargeType__c,
SBQQ__componentCost__c,
SBQQ__componentDiscountedByPackage__c,
SBQQ__componentListTotal__c,
SBQQ__componentTotal__c,
SBQQ__componentSubscriptionScope__c,
SBQQ__componentUpliftedByPackage__c,
SBQQ__componentVisibility__c,
SBQQ__compoundDiscountRate__c,
SBQQ__configurationRequired__c,
SBQQ__cost__c,
SBQQ__costEditable__c,
SBQQ__DefaultSubscriptionTerm__c,
SBQQ__Description__c,
SBQQ__DiscountSchedule__c,
SBQQ__DiscountScheduleType__c,
SBQQ__DiscountTier__c,
SBQQ__DistributorDiscount__c,
SBQQ__DynamicOptionId__c,
SBQQ__EarliestValidAmendmentStartDate__c,
SBQQ__EffectiveEndDate__c,
SBQQ__EffectiveQuantity__c,
SBQQ__EffectiveStartDate__c,
SBQQ__EffectiveSubscriptionTerm__c,
SBQQ__EndDate__c,
SBQQ__Existing__c,
SBQQ__Favorite__c,
SBQQ__GenerateContractedPrice__c,
SBQQ__GrossProfit__c,
SBQQ__Group__c,
SBQQ__Guidance__c,
SBQQ__HasConsumptionSchedule__c,
SBQQ__Hidden__c,
SBQQ__Incomplete__c,
SBQQ__Markup__c,
SBQQ__MarkupRate__c,
SBQQ__MarkupAmount__c,
SBQQ__MaximumPrice__c,
SBQQ__MinimumPrice__c,
SBQQ__NonDiscountable__c,
SBQQ__NonPartnerDiscountable__c,
SBQQ__Number__c,
SBQQ__OptionDiscount__c,
SBQQ__OptionDiscountAmount__c,
SBQQ__OptionLevel__c,
SBQQ__OptionType__c,
SBQQ__Optional__c,
SBQQ__OriginalPrice__c,
SBQQ__OriginalQuoteLineId__c,
SBQQ__Bundle__c,
SBQQ__PackageCost__c,
SBQQ__PackageListTotal__c,
SBQQ__PackageProductCode__c,
SBQQ__PackageProductDescription__c,
SBQQ__PackageTotal__c,
SBQQ__PartnerDiscount__c,
SBQQ__PartnerTotal__c,
SBQQ__PartnerPrice__c,
SBQQ__SubscriptionPercent__c,
SBQQ__SubscriptionBase__c,
SBQQ__SubscriptionCategory__c,
SBQQ__SubscriptionScope__c,
SBQQ__SubscriptionTargetPrice__c,
SBQQ__PreviousSegmentPrice__c,
SBQQ__PreviousSegmentUplift__c,
SBQQ__Dimension__c,
SBQQ__PriceEditable__c,
SBQQ__PricebookEntryId__c,
SBQQ__PricingMethod__c,
SBQQ__PricingMethodEditable__c,
SBQQ__PriorQuantity__c,
SBQQ__Product__c,
SBQQ__ProductCode__c,
SBQQ__ProductFamily__c,
SBQQ__ProductName__c,
SBQQ__ProductOption__c,
SBQQ__ProductSubscriptionType__c,
SBQQ__ProrateMultiplier__c,
SBQQ__ProratedListPrice__c,
SBQQ__ProratedPrice__c,
SBQQ__Quantity__c,
SBQQ__Quote__c,
SBQQ__RegularTotal__c,
SBQQ__RegularPrice__c,
SBQQ__Renewal__c,
SBQQ__RenewedAsset__c,
SBQQ__RenewedSubscription__c,
SBQQ__RequiredBy__c,
SBQQ__SegmentIndex__c,
SBQQ__SegmentKey__c,
SBQQ__SegmentLabel__c,
SBQQ__Source__c,
SBQQ__SpecialPrice__c,
SBQQ__SpecialPriceDescription__c,
SBQQ__SpecialPriceType__c,
SBQQ__StartDate__c,
SBQQ__SubscribedAssetIds__c,
SBQQ__SubscriptionPricing__c,
SBQQ__SubscriptionTerm__c,
SBQQ__SubscriptionType__c,
NAV_Ship_To__c,
NAV_Bill_To__c,
SBQQ__TaxCode__c,
SBQQ__Taxable__c,
SBQQ__TermDiscount__c,
SBQQ__TermDiscountSchedule__c,
SBQQ__TermDiscountTier__c,
SBQQ__TotalDiscountRate__c,
SBQQ__TotalDiscountAmount__c,
SBQQ__UpgradedAsset__c,
SBQQ__UpgradedQuantity__c,
SBQQ__UpgradedSubscription__c,
SBQQ__Uplift__c,
SBQQ__UpliftAmount__c,
Uplift_Percentage__c,
SBQQ__VolumeDiscount__c
FROM SBQQ__QuoteLine__c WHERE SBQQ__Quote__c = :quoteID])
{
SBQQ__QuoteLine__c newLN = ln.clone();
newLN.SBQQ__Quantity__c = ln.SBQQ__Quantity__c;
newLN.Uplift_Percentage__c = objQuoteQuery.NAV_Annual_Uplift_Rate__c;
toInsertQline.add(newLN);
}
for(SBQQ__QuoteLine__c line: [SELECT id, SBQQ__Quantity__c, SBQQ__Quote__r.Name, Uplift_Percentage__c, NAV_Auto_Uplifted__c FROM SBQQ__QuoteLine__c WHERE SBQQ__Quote__c = :quoteID])
{
line.SBQQ__Quantity__c = 0;
line.NAV_Auto_Uplifted__c = true;
toUpdateQline.add(line);
}
insert toInsertQline;
update toUpdateQline;
objQuote.SBQQ__Ordered__c = true;
objQuote.SBQQ__Status__c = 'Approved';
update objQuote;
} catch (DmlException e) {
System.debug('The following exception has occurred: ' + e.getMessage());
}
}
public void activateOrder(id objQuoteID){
try{
Order objOrder = new Order();
objOrder.id = objQuoteID;
objOrder.Status = 'Activated';
objOrder.SBQQ__Contracted__c = true;
update objOrder;
}catch(DmlException e){
System.debug('The following exception has occurred: ' + e.getMessage());
}
}
}
I'm trying to understand why its triggering this issue above when i'm only taking contracts id in a for loop and passing them one at a time to complete this task.
Asynchronous apex (Schedulable, Batchable, Queueable, @future methods) can only create one queueable job per transaction.
Go through the list at https://help.salesforce.com/s/articleView?id=000383160&type=1, do you set Renewal Forecast to true maybe? With a flow, trigger etc?
A general rule of thumb is that any callouts to the Heroku service will en-queue a job (both the calculation and document services). It's important to note that generating an order and renewing contracts are both processes which run calculations.
There are some ideas in https://salesforce.stackexchange.com/questions/346950/too-many-queueable-jobs-added-to-the-queue-2-testing-help-needed but best thing would be to capture a debug log to determine it, chances are it's your trigger that calls System.enqueue, not CPQ being broken.
Options...
That SF stackexchange post has decent idea to skip System.enqueue (Alternative would be to call System.isBatch() for example). And you could then manually enqueue what's needed (or even just the actual Queueable's execute() method) in batch's finish()).
Chances are you have queueable there because you have poor performing code - but in batch the limit is 200 SOQLs, not 100 so calling it "raw" has a chance to work.
Another trick would be to stop using queueables. Raise a platform event and then in event trigger handler you have a fresh context, it isn't a batch anymore, do your original queueable's logic there.