Apex save class when scheduled > ‘This schedulable class has jobs pending or in progress’

How do I develop in a class that is referenced in a scheduled job?You will see the following error when you try to save this class:

This schedulable class has jobs pending or in progress

There is the workaround, what I did is create a Job scheduler so basically 1 scheduled class from where I reference all my scheduled classes. We are not initializing the class by creating a object of the class by name:

global class App_Job_Scheduler implements Schedulable {

	global void execute(SchedulableContext sc) {
		System.Type closedAccountsScheduler = Type.forName('Job_Closed_Accounts');
		Schedulable closedAccountObject = (Schedulable)closedAccountsScheduler.newInstance();
		closedAccountObject.execute(sc);
       }
}

Scheduler class, now you can schedule this class and will not get the >This schedulable class has jobs pending or in progress error when you try to save any code to App_Service or Selector_Financial_Account

global class Job_Closed_Accounts implements System.Schedulable, Database.Batchable<SObject>, Database.Stateful, Database.AllowsCallouts {

	global Database.QueryLocator start(Database.BatchableContext BC) {
		return App_Selector_Financial_Account.newInstance().selectAccountAndRelatedObjectsToDelete();
	}

    global void execute(Database.BatchableContext BC, List<Financial_Account__c> financialAccountIdsToBeDeleted) {
		List<Financial_Account__c> financialAccountsToDelete = App_Selector_Financial_Account.newInstance().selectAccountAndRelatedObjectsToDelete(financialAccountIdsToBeDeleted);
		App_Service.instance.deleteRelatedAccountsRecords(financialAccountsToDelete);
	}

	global void finish(Database.BatchableContext BC) {}
}

Apex Trigger for Sales Rep Scoring

This trigger is used to track and score sales rep activities as they complete Account fields.

Account Trigger Handler to Track specific field updates by sales rep and give points for based on which field was completed.

trigger AccountTrigger on Account (after delete, after insert, after undelete, after update, before delete, before insert, before update) {
    
    if (!System.isFuture() && !System.isBatch()){
      AccountTriggerHandler handler = new AccountTriggerHandler(Trigger.isExecuting, Trigger.size);

      if(Trigger.isUpdate && Trigger.isBefore){
          handler.OnBeforeUpdate(Trigger.old, Trigger.new, Trigger.newMap);
          AccountTriggerHandler.firstRun=false;
      }
}

Account Trigger Handler calls the SalesRepActivityScoring class to loop through all the accounts see which fields have been completed and assign points accordingly.

public with sharing class AccountTriggerHandler {
	
 	public static boolean firstRun = true; 

	private Boolean m_isExecuting = false;
    private Integer batchSize = 0;

	public AccountTriggerHandler(boolean isExecuting, integer size) {
		this.m_isExecuting = isExecuting;
        this.batchSize = size;
	}

	public void onBeforeUpdate(List<Account> oldAccounts, List<Account> updatedAccounts, Map<Id, Account> accountMap){
		SalesRepActivityScoring salesRepScoring = new SalesRepActivityScoring();
		salesRepScoring.insertSalesRepActivity(oldAccounts, updatedAccounts, accountMap);
	}
}

SalesRepActivityScoring class loops through accounts and checks if field not null and assign score. If field goes from filled in to null we subtract or points.

public with sharing class SalesRepActivityScoring {
	
	private static final Integer POTENTIALTOINVEST=4;
	private static final Integer LIQUIDASSETS=1;
	private static final Integer CONTEXTOFLC=1;
	private static final Integer GOALFORFUNDS=1;
	private static final Integer OCCUPATION=1;
	private static final Integer ACCREDIDTEDINVESTOR=1;
	private static final Integer HEADABOUTUS =1;

	private static final String ExecWithDialerProfileId = [Select Id from Profile where Name='Exec Standard user with dialer'].Id;
	private static final String AssociateWithDialerProfileId = [Select Id from Profile where Name='Associate Standard User With Dialer'].Id;

	public void insertSalesRepActivity(List<Account> oldAccounts, List<Account> updatedAccounts, Map<Id, Account> accountMap){
		List<SalesRep_Scoring__c> updateSalesRepScoringRecords = new List<SalesRep_Scoring__c>();

		Integer counter = 0;
		for (Account updatedAccount : updatedAccounts){
			if (updatedAccount.Account_Actor_ID__c!=null){
				if (UserInfo.getUserId().equals(updatedAccount.Lead_Assigned_To__c) && (UserInfo.getProfileId().equals(ExecWithDialerProfileId)) || UserInfo.getProfileId().equals(AssociateWithDialerProfileId)){
			
					SalesRep_Scoring__c salesRepScoring = new SalesRep_Scoring__c();
					salesRepScoring.Account__c = updatedAccount.Id;
					salesRepScoring.Account_Number__c = updatedAccount.Account_Actor_ID__c;
					salesRepScoring.Sales_Rep__c = UserInfo.getUserId();

					Boolean flag = false;

					if (updatedAccount.Accredited_Investor__c!=null){
						salesRepScoring.Accredited_Investor__c = ACCREDIDTEDINVESTOR;
						flag=true;
					} else if (oldAccounts.get(counter).Accredited_Investor__c!=null && updatedAccount.Accredited_Investor__c==null){
						salesRepScoring.Accredited_Investor__c = 0;
						flag=true;
					}

					if (updatedAccount.Context_of_LC_in_portfolio__c!=null){
						salesRepScoring.Context_of_LC__c = CONTEXTOFLC;
						flag=true;
					}else if (oldAccounts.get(counter).Context_of_LC_in_portfolio__c!=null && updatedAccount.Context_of_LC_in_portfolio__c==null) {
						salesRepScoring.Context_of_LC__c = 0;
						flag=true;
					}

					if (updatedAccount.Goal_for_Funds_at_LC__c!=null){
						salesRepScoring.Goal_for_funds__c = GOALFORFUNDS;
						flag=true;
					} else if (oldAccounts.get(counter).Context_of_LC_in_portfolio__c!=null && updatedAccount.Goal_for_Funds_at_LC__c==null){
						salesRepScoring.Goal_for_funds__c = 0;
						flag=true;
					}

					if (updatedAccount.How_did_you_hear_about_us__c!=null){
						salesRepScoring.How_did_you_hear_about_us__c = HEADABOUTUS;
						flag=true;
					} else if (oldAccounts.get(counter).How_did_you_hear_about_us__c!=null && updatedAccount.How_did_you_hear_about_us__c==null) {
						salesRepScoring.How_did_you_hear_about_us__c = 0;
						flag=true;
					}

					if (updatedAccount.Liquid_assets_client_stated__c!=null){
						salesRepScoring.Liquid_Assets__c = LIQUIDASSETS;
						flag=true;
					} else if (oldAccounts.get(counter).Liquid_assets_client_stated__c!=null && updatedAccount.Liquid_assets_client_stated__c==null){
						salesRepScoring.Liquid_Assets__c = 0;
						flag=true;
					}

					if (updatedAccount.Head_of_household_Occupation__c!=null){
						salesRepScoring.Occupation__c=OCCUPATION;
						flag=true;
					} else if (oldAccounts.get(counter).Head_of_household_Occupation__c!=null && updatedAccount.Head_of_household_Occupation__c==null){
						salesRepScoring.Occupation__c=0;
						flag=true;
					}

					if (updatedAccount.Potential_to_invest__c!=null){
						salesRepScoring.Potential_to_Invest__c=POTENTIALTOINVEST;
						flag=true;
					} else if (oldAccounts.get(counter).Potential_to_invest__c!=null && updatedAccount.Potential_to_invest__c==null) {
						salesRepScoring.Potential_to_Invest__c=0;
						flag=true;
					}

					if (flag){
						updateSalesRepScoringRecords.add(salesRepScoring);
					}
				}	
			}
			counter++;
		}
		upsert updateSalesRepScoringRecords Account_Number__c;
	}
}

SalesRepActivityScoringTest Class (100% code coverage). The first test method will test that the scoring only works for specific profiles. Second and third test method test scoring when fields completed for selected profiles. The last method will test if the scores is subtracted when fields goes from filled in to blank.

@isTest
private class SalesRepActivityScoringTest {
	

	public Id refProfileIdByName(String profileName){
        Profile getprofileId = [SELECT id FROM Profile WHERE name= :profileName];
        return getProfileId.id;
    }
    public User createAssociateSalesRepByProfile(String profileName){   
        User toUser = new User(alias = 'lcrep', 
        email='salesrep@lendingclub.com', 
        emailencodingkey='UTF-8',
        firstname='Associate', 
        lastname='Rep', 
        languagelocalekey='en_US', 
        localesidkey='en_US', 
        profileid = refProfileIdByName(profileName), 
        timezonesidkey='America/Los_Angeles', 
        username='assosciatesalesrep@lendingclub.com',
        Record_Count__c = 0,
        Assigned_Record_Count__c=10,
        Record_Tier__c='1;2;3;4;5;6;7;8;9;10',
        isActive=true);
        
        return toUser;
    }

    private Account createDummyAccount(User newUser, Decimal actorId){
    	Account newAccount = new Account();
		newAccount.Name='[TEST]AccountRepActivity';
		newAccount.Lead_Assigned_To__c=newUser.Id;
		newAccount.OwnerId= newUser.Id;
		newAccount.Account_Actor_ID__c=actorId;
		insert newAccount;

	
		return newAccount;
    }

	@isTest(SeeAllData=true) static void testSalesRepActivityScoringNotAddingForStandardRep() {
		SalesRepActivityScoringTest sra = new SalesRepActivityScoringTest();
		User standardUser = sra.createAssociateSalesRepByProfile('Standard User');
		insert standardUser;
		Account newAccount = sra.createDummyAccount(standardUser, 1111.0);

		Test.startTest();
			update newAccount;
		Test.stopTest();

		System.assertEquals([Select id from SalesRep_Scoring__c where Account__c=:newAccount.Id].size(), 0);
	}
	
	@isTest static void testSalesRepActivityScoringAddingForExecRep() {
		SalesRepActivityScoringTest sra = new SalesRepActivityScoringTest();
		User execUser = sra.createAssociateSalesRepByProfile('Exec Standard user with dialer');
		insert execUser;
		System.runAs(execUser){
			Account newAccount = sra.createDummyAccount(execUser, 1234.0);

			newAccount.Account_Actor_ID__c=1234.0;
			newAccount.Accredited_Investor__c='Not Accredited';
			newAccount.Context_of_LC_in_portfolio__c='a';
			newAccount.Goal_for_Funds_at_LC__c='a';
			newAccount.How_did_you_hear_about_us__c='a';
			newAccount.Liquid_assets_client_stated__c='a';
			newAccount.Head_of_household_Occupation__c='a';
			newAccount.Potential_to_invest__c='a';

			Test.startTest();
				update newAccount;
			Test.stopTest();

			SalesRep_Scoring__c salesRepScoring = [select Goal_for_funds__c,Account__c, Account_Number__c, Accredited_Investor__c, Context_of_LC__c, CreatedById, CreatedDate, How_did_you_hear_about_us__c, Id, IsDeleted, LastActivityDate, LastModifiedById, LastModifiedDate, Liquid_Assets__c, Name, Occupation__c, OwnerId, Potential_to_Invest__c, Sales_Rep__c, SystemModstamp from SalesRep_Scoring__c where Account__c=:newAccount.Id][0];

			System.assertEquals(salesRepScoring.Account__c, newAccount.Id);
			System.assertEquals(salesRepScoring.Sales_Rep__c, execUser.Id);
			System.assertEquals(salesRepScoring.Accredited_Investor__c, 1);
			System.assertEquals(salesRepScoring.Context_of_LC__c, 1);
			System.assertEquals(salesRepScoring.Goal_for_funds__c, 1);
			System.assertEquals(salesRepScoring.How_did_you_hear_about_us__c, 1);
			System.assertEquals(salesRepScoring.Liquid_Assets__c, 1);
			System.assertEquals(salesRepScoring.Occupation__c, 1);
			System.assertEquals(salesRepScoring.Potential_to_Invest__c, 4);
		}
	}

	@isTest(SeeAllData=true) static void testSalesRepActivityScoringAddingForAssociateRep() {
		SalesRepActivityScoringTest sra = new SalesRepActivityScoringTest();
		User associate = sra.createAssociateSalesRepByProfile('Associate Standard User With Dialer');
		insert associate;
		System.runAs(associate){
			
			Account newAccount = sra.createDummyAccount(associate, 1235.0);

			newAccount.Account_Actor_ID__c=1235.0;
			newAccount.Accredited_Investor__c='Not Accredited';
			newAccount.Context_of_LC_in_portfolio__c='a';
			newAccount.Goal_for_Funds_at_LC__c='a';
			newAccount.How_did_you_hear_about_us__c='a';
			newAccount.Liquid_assets_client_stated__c='a';
			newAccount.Head_of_household_Occupation__c='a';
			newAccount.Potential_to_invest__c='a';

			Test.startTest();
				update newAccount;
			Test.stopTest();

			SalesRep_Scoring__c salesRepScoring = [select Goal_for_funds__c,Account__c, Account_Number__c, Accredited_Investor__c, Context_of_LC__c, CreatedById, CreatedDate, How_did_you_hear_about_us__c, Id, IsDeleted, LastActivityDate, LastModifiedById, LastModifiedDate, Liquid_Assets__c, Name, Occupation__c, OwnerId, Potential_to_Invest__c, Sales_Rep__c, SystemModstamp from SalesRep_Scoring__c where Account__c=:newAccount.Id][0];

			System.assertEquals(salesRepScoring.Account__c, newAccount.Id);
			System.assertEquals(salesRepScoring.Sales_Rep__c, associate.Id);
			System.assertEquals(salesRepScoring.Accredited_Investor__c, 1);
			System.assertEquals(salesRepScoring.Context_of_LC__c, 1);
			System.assertEquals(salesRepScoring.Goal_for_funds__c, 1);
			System.assertEquals(salesRepScoring.How_did_you_hear_about_us__c, 1);
			System.assertEquals(salesRepScoring.Liquid_Assets__c, 1);
			System.assertEquals(salesRepScoring.Occupation__c, 1);
			System.assertEquals(salesRepScoring.Potential_to_Invest__c, 4);

		}
	}

	@isTest(SeeAllData=true)  static void testSalesRepActivityResetNullResetScore(){
		SalesRepActivityScoringTest sra = new SalesRepActivityScoringTest();
		User execUser = sra.createAssociateSalesRepByProfile('Exec Standard user with dialer');
		insert execUser;
		System.runAs(execUser){
			Account newAccount = sra.createDummyAccount(execUser, 1234.0);

			newAccount.Account_Actor_ID__c=1234.0;
			newAccount.Accredited_Investor__c='Not Accredited';
			newAccount.Context_of_LC_in_portfolio__c='a';
			newAccount.Goal_for_Funds_at_LC__c='a';
			newAccount.How_did_you_hear_about_us__c='a';
			newAccount.Liquid_assets_client_stated__c='a';
			newAccount.Head_of_household_Occupation__c='a';
			newAccount.Potential_to_invest__c='a';

			update newAccount;

			SalesRep_Scoring__c salesRepScoring = [select Goal_for_funds__c,Account__c, Account_Number__c, Accredited_Investor__c, Context_of_LC__c, CreatedById, CreatedDate, How_did_you_hear_about_us__c, Id, IsDeleted, LastActivityDate, LastModifiedById, LastModifiedDate, Liquid_Assets__c, Name, Occupation__c, OwnerId, Potential_to_Invest__c, Sales_Rep__c, SystemModstamp from SalesRep_Scoring__c where Account__c=:newAccount.Id][0];

			System.assertEquals(salesRepScoring.Account__c, newAccount.Id);
			System.assertEquals(salesRepScoring.Sales_Rep__c, execUser.Id);
			System.assertEquals(salesRepScoring.Accredited_Investor__c, 1);
			System.assertEquals(salesRepScoring.Context_of_LC__c, 1);
			System.assertEquals(salesRepScoring.Goal_for_funds__c, 1);
			System.assertEquals(salesRepScoring.How_did_you_hear_about_us__c, 1);
			System.assertEquals(salesRepScoring.Liquid_Assets__c, 1);
			System.assertEquals(salesRepScoring.Occupation__c, 1);
			System.assertEquals(salesRepScoring.Potential_to_Invest__c, 4);

			newAccount.Accredited_Investor__c=null;
			newAccount.Context_of_LC_in_portfolio__c=null;
			newAccount.Goal_for_Funds_at_LC__c=null;
			newAccount.How_did_you_hear_about_us__c=null;
			newAccount.Liquid_assets_client_stated__c=null;
			newAccount.Head_of_household_Occupation__c=null;
			newAccount.Potential_to_invest__c=null;

			Test.startTest();
				update newAccount;
			Test.stopTest();

			SalesRep_Scoring__c salesRepScoringReset = [select Goal_for_funds__c,Account__c, Account_Number__c, Accredited_Investor__c, Context_of_LC__c, CreatedById, CreatedDate, How_did_you_hear_about_us__c, Id, IsDeleted, LastActivityDate, LastModifiedById, LastModifiedDate, Liquid_Assets__c, Name, Occupation__c, OwnerId, Potential_to_Invest__c, Sales_Rep__c, SystemModstamp from SalesRep_Scoring__c where Account__c=:newAccount.Id][0];

			System.assertEquals(salesRepScoringReset.Account__c, newAccount.Id);
			System.assertEquals(salesRepScoringReset.Sales_Rep__c, execUser.Id);
			System.assertEquals(salesRepScoringReset.Accredited_Investor__c, 0);
			System.assertEquals(salesRepScoringReset.Context_of_LC__c, 0);
			System.assertEquals(salesRepScoringReset.Goal_for_funds__c, 0);
			System.assertEquals(salesRepScoringReset.How_did_you_hear_about_us__c, 0);
			System.assertEquals(salesRepScoringReset.Liquid_Assets__c, 0);
			System.assertEquals(salesRepScoringReset.Occupation__c, 0);
			System.assertEquals(salesRepScoringReset.Potential_to_Invest__c, 0);
		}
	}
}

Salesforce Update Product Schedule from Opportunity Product using Apex Trigger

Update the Product ScheduleDate when the Opportunity Product ServiceDate changes. First we create an OpportunityProduct Trigger to see when ServiceDate changes and parse the amount of days difference between the old and new date.

trigger ProductDateUpdate on OpportunityLineItem (after update) {
   
    for (OpportunityLineItem oli: Trigger.new) {
        OpportunityLineItem oldCloseDate = Trigger.oldMap.get(oli.ID);
        if (oli.ServiceDate != oldCloseDate.ServiceDate) {
            Integer dateval = oldCloseDate.ServiceDate.daysBetween(oli.ServiceDate);
            ProductScheduleUpdate.ScheduleProductUpdate(oli.id, dateval);  
        }
    }
}

The Trigger class then updates all the Product Schedule Dates by the amount of days.

public class ProductScheduleUpdate
{
    public static void ScheduleProductUpdate(String oid, Integer dateval)
    {
           List datelist;
           datelist = [SELECT ScheduleDate FROM OpportunityLineItemSchedule WHERE OpportunityLineItemId =:oid];
           for (Integer k = 0; k < datelist.size(); k++)
               {
               System.debug('DateList = ' + datelist[k]);
               Date mydate=Date.newInstance(datelist[k].ScheduleDate.Year(),datelist[k].ScheduleDate.Month(),datelist[k].ScheduleDate.Day());
               mydate=mydate.addDays(dateval);
               datelist[k].ScheduleDate = mydate;
               update datelist;
               //date datediff = date.parse(datelist[k]);
               }
        
    }
}

Salesforce Update Product Schedule from Opportunity using Apex Trigger

The tutorial below shows how you can use Salesforce Triggers to update the CloseDate of an opportunity and the associated Product Schedule.

First thing is we must create a product Trigger, below is the code for the product trigger:

trigger OpportunityScheduleUpdate on Opportunity (after update) {
  
  for (Opportunity o: Trigger.new) {
        Opportunity oldCloseDate = Trigger.oldMap.get(o.ID);
        if (o.CloseDate != oldCloseDate.CloseDate) {
            Integer dateval = oldCloseDate.CloseDate.daysBetween(o.CloseDate);
            OppScheduleUpdate.ScheduleDateUpdate(o.id, dateval);  
        }
    }
}

The trigger class updates all the Product ScheduleDate by the amount of the days the Opportunity Close Date has changed:

public class OppScheduleUpdate
{
    public static void ScheduleDateUpdate(String oppid, Integer dateval) 
    {
       List idval = [SELECT id FROM OpportunityLineItem WHERE OpportunityId=:oppid];
       List datelist;
       for (Integer i = 0; i < idval.size(); i++)
       {
           datelist = [SELECT ScheduleDate FROM OpportunityLineItemSchedule WHERE OpportunityLineItemId =:idval[i].id];
           for (Integer k = 0; k < datelist.size(); k++)
               {
               System.debug('DateList = ' + datelist[k]);
               Date mydate=Date.newInstance(datelist[k].ScheduleDate.Year(),datelist[k].ScheduleDate.Month(),datelist[k].ScheduleDate.Day());
               mydate=mydate.addDays(dateval);
               datelist[k].ScheduleDate = mydate;
               update datelist;
               //date datediff = date.parse(datelist[k]);
               }
       }      
    }
}