function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
kingkong94538kingkong94538 

Urgent: Lead Assignment Rule Trigger recursively updates itself and fails

I need a way to apply lead assignment rules whenever the lead score goes above 99 and the lead review status is complete.

 

Here is my trigger code:

 

 

trigger ApplyLeadAssignmentRules on Lead (before update, before insert) {


    for (Lead l:trigger.new) {


        Decimal score =   l.Score__c;
        String review_status =  l.Review_Status__c;

        if(score == null) score = 0.0;
        if(review_status == null) review_status = '';
            
        if( (score >= 100) && (review_status.equalsIgnoreCase('Complete'))  ){
            Database.DMLOptions dmo = new Database.DMLOptions();
            dmo.assignmentRuleHeader.useDefaultRule = true;
            l.setOptions(dmo);
            Database.update(l);   
        }     

    }


}

 

 

This throws the following exception:

 

Apex script unhandled trigger exception by user/organization:

Source organization:

ApplyLeadAssignmentRules: execution of BeforeUpdate

caused by: System.DmlException: Update failed. First exception on row 0 with id ; first error: SELF_REFERENCE_FROM_TRIGGER, Object (id = 00QQ0000001Q2av) is currently in trigger ApplyLeadAssignmentRules, therefore it cannot recursively update itself: []

 

 

How do I fix this? Any solution?

Best Answer chosen by Admin (Salesforce Developers) 
jkucerajkucera

This post has what you need:

http://community.salesforce.com/sforce/board/message?board.id=apex&message.id=26391#M26391

 

 

Code from that post:

trigger reassignLeads on Lead (after update){
List<Id> lIds=new List<id>();
For (lead l:trigger.new){
if (l.IsConverted==False){
lIds.add(l.Id);
}
}
if (AssignLeads.assignAlreadyCalled()==FALSE){
system.debug('Assign already called? '+AssignLeads.assignAlreadyCalled());
AssignLeads.Assign(lIds);
}
}

 

 

public global class AssignLeads{

public static Boolean assignAlreadyCalled=FALSE;

public static boolean assignAlreadyCalled(){
return assignAlreadyCalled;
}

@future
public static void assign(List<Id> lIds){
assignAlreadyCalled=TRUE;
List<Lead> leads=[SELECT Id FROM Lead WHERE Id IN: lIds];
For (lead l:leads){
Database.DMLOptions dmo = new Database.DMLOptions();
dmo.assignmentRuleHeader.useDefaultRule= true;
l.setOptions(dmo);

}
update(leads);
}

}

 

All Answers

aerethaereth

Heya,

 

I'm not so familiar with assignment rules, but in this case it seems you should try to use after update trigger?

 

If you can't, use trigger.new and trigger.old (can't be used in insert trigger) to compare wheter the value ie. sorce or review status has been changed.

 

Also in before insert, you should never try to call update on object that isn't yet inserted to database.


Check this how trigger has been built: http://wiki.developerforce.com/index.php/Apex_Day_Lab_Exercises

 

jkucerajkucera

This post has what you need:

http://community.salesforce.com/sforce/board/message?board.id=apex&message.id=26391#M26391

 

 

Code from that post:

trigger reassignLeads on Lead (after update){
List<Id> lIds=new List<id>();
For (lead l:trigger.new){
if (l.IsConverted==False){
lIds.add(l.Id);
}
}
if (AssignLeads.assignAlreadyCalled()==FALSE){
system.debug('Assign already called? '+AssignLeads.assignAlreadyCalled());
AssignLeads.Assign(lIds);
}
}

 

 

public global class AssignLeads{

public static Boolean assignAlreadyCalled=FALSE;

public static boolean assignAlreadyCalled(){
return assignAlreadyCalled;
}

@future
public static void assign(List<Id> lIds){
assignAlreadyCalled=TRUE;
List<Lead> leads=[SELECT Id FROM Lead WHERE Id IN: lIds];
For (lead l:leads){
Database.DMLOptions dmo = new Database.DMLOptions();
dmo.assignmentRuleHeader.useDefaultRule= true;
l.setOptions(dmo);

}
update(leads);
}

}

 

This was selected as the best answer
kingkong94538kingkong94538

Thanks for the code and sending me the link to the post!

 

The code works in Sandbox.

 

We have Marketo configured in Salesforce Prod and Marketo sets the lead score based on various parameters.

Do you know if that may have any impact on the code in Prod? As per the post, the solution works for clean orgs...

 

jkucerajkucera

The only potential conflicts are around other methods that execute asynchronously (@future).

 

Marketo uses the API to update records, not asynch apex, so you should be good to go.

Message Edited by jkucera on 03-17-2010 02:34 PM
kingkong94538kingkong94538

Awesome!

 

Thanks John!

JLockardJLockard

Hey Guys,

We have nearly the same challenge.  That is, to have existing Leads go through the Assignment Rules when they reach a certain status in Marketo.  From this post and replies, it seems that a simple Update Trigger is not sufficient to force the Leads through the Assignment Rules.

 

Do I really need a trigger that calls the code to run the Assignment Rules or are there simpler alternatives?

 

Thanks....

jkucerajkucera

You need code.  You could push Marketo to add it to their integration as they can build it once for all customers.

 

The non code methods aren't a good fit - exporting the leads & then importing in SFDC using the wizards.

kingkong94538kingkong94538

This is the only automated way of applying lead assignment rules with in salesforce. The trigger is simple and works with Marketo - you may want to go through the link posted above. If you need help building your trigger, let me know.

 

The only issue with @future is: when you click update on the lead (no need to select apply assignment rules checkbox) - the update is done but the page refresh does not happen. So, for example if your lead assignment rules assign John Smith as the lead owner, you won't see him as the lead owner unless you refresh the page.

 

Other option is to use APEX data loader but again it's a manual process. You can build the same process outside salesforce using APEX Data Loader or using WS calls... If you are already touching the APEX governance limit then you may want to use Salesforce WS.

 

If you are thinking about using workflow to activate assignment rules, nope that can't be done.

 

JLockardJLockard

Thanks John. 

 

Yes, I plan to suggest or promote an enhancement to Marketo to allow us to specify that Assignment Rules be run, even for exiisting SFDC Leads.  Currently, it will fire Assignment Rules when a Lead is uploaded from Marketo to SFDC, but only for net new Leads.

 

I was just looking for clarification on the post where a trigger calls code defined in an Apex class, versus simply creating a trigger that fires the Assignment Rules for me.

 

Does adding the method call via the Class object add deployment difficulties above and beyond a simple trigger?

 

Thanks!

Jeff

kingkong94538kingkong94538

You need to write a test class for the trigger before you can deploy. No difficulties!

You can also try out the outboud/inbound change set feature.

JLockardJLockard

Thanks for the confirmation K-squared!

 

I'm playing around with the trigger logic now and it appears to be working.  I'd like to narrow down the instances where the Assignment Rules actually fire so I don't take the chance of assigning Leads accidentally to the Fallout Queue.  I'll let you know if I run into some challenges for which your experience would be helpful.

 

Right now, I'm just working in our Sandbox and I've not actually deployed any code like this to our production environment before.  Any quick advice on that process would be appreciated too.

 

Thanks!

Jeff

JLockardJLockard

King Kong,

 

Using the IDE, I now have the Lead Trigger and the Class coded, as described in this post.  Also, I put a test method in the Class file and am now attempting to deply just the Class to our Production org.  Basically, the test method creates a new Dummy Lead record, then calls the same AssignLead method that is called from the Trigger code.

 

Unfortunately, the deployment to production is being blocked because it says I have only 72% code coverage with my test.  Not sure how to write the test method in any other way that might increase this percentage.  Also, how do I deploy trigger code if I cannot write a test method for it?

 

Here is the code for the AssignLead class, including/starting with the test method (should look familiar:...smileywink)  Any idea why the test method only gets credit for executing 72% of the lines of code?

 

TIA, Jeff

 

public global class AssignLeads{  

	static testMethod void AssignLeadTest() {
	    List<Id> TestlIds=new List<id>();
        System.debug('Beginning AssignLeadTest()....');
	
	    // For Unconverted Leads, with Marketing Status = "SQL" and the current
	    // Owner is Marketing MQL Lead Queue, run them through the current Assignment Rules.
	    
	    // Insert a new Lead record
        Lead newLead = new Lead(company='Test, Inc', LastName='Tester', FirstName='Testy', State='CA', Marketing_Status__c='MQL');
        newLead.Marketing_Status__c = 'SQL';
        TestlIds.add(newLead.Id);
	    //update(TestlIds);
	    
	    
	    if (AssignLeads.assignAlreadyCalled()==FALSE){
	        system.debug('Assign already called? '+AssignLeads.assignAlreadyCalled());
	        AssignLeads.Assign(TestlIds);
	    }
    }
    
    public static Boolean assignAlreadyCalled=FALSE;

    public static boolean assignAlreadyCalled(){        
        return assignAlreadyCalled;    
    }
    
    @future  
    public static void assign(List<Id> lIds){    
        assignAlreadyCalled=TRUE;    
        List<Lead> leads=[SELECT Id FROM Lead WHERE Id IN: lIds];    
        For (lead l:leads){
            Database.DMLOptions dmo = new Database.DMLOptions();      
            dmo.assignmentRuleHeader.useDefaultRule= true;       
            l.setOptions(dmo);
        }    
        
        update(leads);
    }
}

 

jkucerajkucera

Start by commenting out your system.debug lines as they aren't called from tests.

JLockardJLockard

Thanks John.  Yes, I should have known to do that....I recall reading it.  I've now commented/removed the debug lines, but I'm still at 72% coverage and not allowed to complete the deployment to Production.

Thanks...Jeff

kingkong94538kingkong94538

just 2 steps:

1. Insert a new lead (you are already doing it).

2. Update an existing lead

 

That should take you to 78%. To deploy it from IDE, select the trigger and class (both) and then deploy it.

 

Let me know if you get into any issues.

 

 

kingkong94538kingkong94538

Jeff,

 

My bad - didn't fully go through your test class when I responded earlier.

 

I think you are missing the insert piece. (You just add lead object to the list collection).

If adding insert does not take you to 75+ the add another line to update some lead. That will surely take you over 75+.

 

 

Thanks!

JLockardJLockard

King Kong and John,

 

Thanks for your advice.  I was able to deploy the Class and the Trigger via the IDE to our production environment.  I appreciate your help!

 

Jeff

jlcoverityjlcoverity

I'm trying to implement something similar to address reassigning leads that already exist in the system. Specifically in our case, we have old leads that are assigned to Sales reps that are no longer active. Instead of reassiging all of the old leads to the new reps (and have them wade through the junk) we want the leads to be reassigned after the lead becomes "re-engaged" with us (i.e. engages in some Marketing activity synched from Marketo).

 

I've pretty much used your code exactly, except I perform one additional query to get the list of Owner Ids from the User object WHERE IsActive = false.

 

One a one-by-one basis, it works great, but if I try to update more than 10 leads at once (e.g. change lead status from a list view), I get a "too many future calls" exception. I do not have any other future calls being made in other triggers/classes.

 

Here's my code for the trigger:

 

trigger cvAfterLeadUpdate on Lead (after update) {
  
  List<Id> updatedIds=new List<Id>();
  List<Id> ownerIds = new List<Id>();
  for (Lead l:trigger.new){
        if (l.IsConverted==false){
            updatedIds.add(l.Id);
            ownerIds.add(l.OwnerId);
        }
    }
  if (cvLeadAssignmentUtils.isRescuedFromZombiesDone() == false){
        System.debug('Zombie rescue called? '+cvLeadAssignmentUtils.isRescuedFromZombiesDone());
        cvLeadAssignmentUtils.rescueFromZombies(updatedIds, ownerIds);
    }
}

 

 

Here's the code for the class:

 

public global class cvLeadAssignmentUtils {

 public static boolean blZombieRescueExecuted = false;
 
 public static boolean isRescuedFromZombiesDone() {
 	return blZombieRescueExecuted;
 }

@future
 public static void rescueFromZombies(List<Id> updatedIds, List<Id> ownerIds) {
 	//reset status
 	blZombieRescueExecuted = true;
 	
    //Now find out which OwnerIds belong to zombies
    List<User> zombies = [SELECT Id,IsActive FROM User WHERE IsActive = false AND Id IN :ownerIds];
    //Now just grab the leads that need to be reassiged
    List<Lead> rescueLeads =  [SELECT Id FROM Lead WHERE IsConverted = false AND Id IN :updatedIds AND OwnerId IN :zombies];
    if(!rescueLeads.isEmpty()) {
     for (Lead r:rescueLeads) {
      	Database.DMLOptions dmo = new Database.DMLOptions();
        dmo.assignmentRuleHeader.useDefaultRule= true;
        r.setOptions(dmo);
      }
   	  	update(rescueLeads);
    }
 }
 
}

 

 

Thoughts?

 

Thanks,

-jl

kingkong94538kingkong94538

I think there is a limit for @future. You can have like 10 calls at the most. I think you need to modify the code. But, wait for someone to confirm this or check the documentation.

kingkong94538kingkong94538

If you can't find solution or can't wait try using Batch APEX. Here is the link: http://www.salesforce.com/us/developer/docs/apexcode/index_Left.htm#StartTopic=Content/apex_batch.htm

 

 

jkucerajkucera

The limit for @future calls is 200 * # users per 24 hours so if you have 20 users in your org, you get 4,000 per day. 

 

If the @future method is called via lead trigger, that means 1 call is "used" every time 1 or more leads are created or updated.

 

You may be able to file a ticket with support to request an increase.  I can't guarantee that will work though.

jlcoverityjlcoverity

John -

 

That's the organization wide limit, but per trigger you are only allowed to make 10 per invocation, correct?

 

What I thought originally was that the other triggers I had on leads (one for before update and another after update) may not have been properly "bulkified" and therefore causing the exception to be thrown. However, in my Sandbox I was testing this using a custom VF page I wrote to change lead statuses (we have a validation rule that requires reps to enter in a Reason Rejected value if they choose "Rejected" as a value). It appears that the problem is here (even though I'm using the standard controller for Leads). I retested by using the default "Change Status" button and everything worked (even with the other triggers being active) - meaning that the @future method processed all the records in a single call since I had properly "bulkified" the trigger. I guess the default "Change Status" button has some additional mojo to handle records in bulk better??

 

I *did* decide to go ahead and write a Batch Apex class and got it to work beautifully. Here's the code for that:

 

 

global class cvBatchZombieRescue implements Database.Batchable<sObject>{
public String query;
public String email;

 global database.querylocator start(Database.BatchableContext BC){
            return Database.getQueryLocator(query);}

 global void execute(Database.BatchableContext BC, List<sObject> scope){
    List<Lead> updatedLeads = new List<Lead>();
    List<Id> ownerIds = new List<Id>();

    for(sObject s : scope){Lead l = (Lead)s;
        if(l.IsConverted == false){
            updatedLeads.add(l);
            ownerIds.add(l.OwnerId);
        }
    }
    List<User> zombies = [SELECT Id,IsActive FROM User WHERE IsActive = false AND Id IN :ownerIds];
    //Now just grab the leads that need to be reassiged
    List<Lead> rescueLeads =  [SELECT Id,Last_Zombie_Rescue__c FROM Lead WHERE IsConverted = false AND Id IN :updatedLeads AND OwnerId IN :zombies];
    
    //Now process reassignment
    if(!rescueLeads.isEmpty()) {
     for (Lead r:rescueLeads) {
     	r.Last_Zombie_Rescue__c = Datetime.now();
      	Database.DMLOptions dmo = new Database.DMLOptions();
        dmo.assignmentRuleHeader.useDefaultRule= true;
        r.setOptions(dmo);
      }
   	  	update(rescueLeads);
    }
 }
 global void finish(Database.BatchableContext BC){
  Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();

  mail.setToAddresses(new String[] {email});
  mail.setReplyTo('XXXXX@XXXXX.com');  //names changed to protect the innoncent
  mail.setSenderDisplayName('Batch Processing');
  mail.setSubject('Batch Process Completed');
  mail.setPlainTextBody('Batch Process has completed');

  Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
 }
}

 

And here's the Scheduler class:

 

global class ScheduledZombieRescue implements Schedulable{
 global void execute(SchedulableContext sc) {
    cvBatchZombieRescue bzr = new cvBatchZombieRescue();
	bzr.query='SELECT Id, OwnerId, IsConverted FROM Lead WHERE LastModifiedDate = TODAY AND Last_Zombie_Rescue__c <> TODAY LIMIT 200';
	bzr.email='XXXX@XXX.com'; //names changed to protect the innocent
	Id batchprocessid = Database.executeBatch(bzr);
   }
}

 

 

I just put the LIMIT 200 in for testing, I'll probably increase that...

 

 

The only unfortunate thing using the Batch is that I can run it once per day (or, I suppose I could create a number of scheduled jobs to run each day), which means that we lose the near-real-time response to the prospect by the rep.

 

I guess one possible solution is to create a custom list controller for my custom Change Lead Status VF page and override the Save button to "bulkify" the processing...but not sure I've got the time to tackle that right now.

 

Anyhow, interesting exercise and glad I got a chance to play with Batch Apex. Thanks for all of the feedback!

 

SteveBowerSteveBower

Just a drive-by comment....

 

Instead of using @Future to do this, couldn't you just do it in the Trigger itself and use a Static variable to control the recursion?   I think there's an example in the Cookbook somewhere, search for "recursion".  Then I think you could just do the update right there and not worry about @future limits.

 

Best, Steve

jlcoverityjlcoverity

I tried not making this an asynchronous (@future) call, but when I tried it would not fire the assignment rules. Maybe I did something wrong though...?

kingkong94538kingkong94538

For assignment rules, @future is the only way in a trigger... You can google lead assignment rule @future salesforce to see all discussions on this topic.

SteveBowerSteveBower

I haven't tried this myself as I haven't had the need yet, and perhaps I'm missing something, but how does this correlate with this thread:  

 

http://community.salesforce.com/t5/Apex-Code-Development/Trigger-to-Fire-Lead-Assignment-Rules-Does-not-Fire-them/m-p/150720

 

It seems to me that as long as you make sure that recursion doesn't happen for the Lead Updates, the DMO options should work.   I don't see anything in the documentation that leads me to believe that there is a restriction on Assignment Rules being kicked off with a trigger?   (Perhaps I've missed it?)    Obviously the proof is in the pudding, and I assume you guys have tried coding it up, it just seems weird to me.  

 

Best, Steve.

kingkong94538kingkong94538

It seems to me that as long as you make sure that recursion doesn't happen for the Lead Updates...

This is the only concern and @future is the only solution that prevents this from happening. The other solution (avoding recusrion not using @future) did not work for me - probably there were other triggers tied to the Lead object and I was not able to modify the Lead object to add a flag because there were like over 600K leads in the system and if I had added a new field the Marketo sync process would have killed the system for at least 11 days. If you have been  using Marketo for some time you'd know that.

jkucerajkucera

The recursion problem is that you can't update a lead in an update trigger, even with DML options.  I'm pretty sure I've used DMLOptions update in a before insert trigger, but update in an update trigger definitely doesn't work (which is what most people want to do).

 

That means you have to use @future for re-assignment to avoid the problem.

 

I've updated the linked to post where I incorrectly stated you could use Database.Update in an update trigger.  Sorry for the confusion.

SteveBowerSteveBower

I hate to disagree with you all, and perhaps I'm really missing something, but this seems to work for me:

 

Note: On Lead, I've created a field called "RunReassignmentRule__c as a boolean which I set when I want the assignment rules applied.

If adding a field is a problem using the third party app, you could instead overload a field.  For example, if "Company" is set to "XXXX_CompanyName", then apply the assignment rule and reset the Company name back to it's proper form.

 

I'm sure you get the idea.  Best, Steve.

 

Trigger:

 

trigger AssignmentRuleOnRatingChange on Lead (after update) {
	if (Utilities_RecursionControl.weAreAlreadyProcessing()) return;
	Database.DMLOptions dmo = new Database.DMLOptions();
	dmo.assignmentRuleHeader.useDefaultRule= true;	
	List<Id> leadIdsToAssign = new List<Id>();
	for (Integer i=0; i < trigger.new.size(); i++){
		if (trigger.new[i].RunReassignmentRule__c){
			leadIdsToAssign.add(trigger.new[i].id);
		}
	}
	if (leadIdsToAssign.isEmpty()) return;
			
	// Reload Leads, yes, in mid-update, as we can't use the instances in trigger.new
    List<Lead> leadsToAssign = new List<Lead>([select id, RunReassignmentRule__c from lead where id in :leadIdsToAssign]);
    for(Lead l: leadsToAssign) {
    	l.setOptions(dmo);
    	l.RunReassignmentRule__c = false;
    }
    Utilities_RecursionControl.disallowFurtherRecursion();
    try {
    	Database.Update(leadsToAssign);
    } catch (Exception e) {
    	system.debug('Exception in AssignmentRoleOnRatingChange: ');
    	system.debug(e);
    }
    Utilities_RecursionControl.resetRecursion();
}

 

 

Utility class for Recursion:

 

public class Utilities_RecursionControl {
	// Steve Bower  5/6/2009    sforce@stevebower.com
	// See page 150 of the Workbook on how to control Trigger recursion.
	private static Boolean alreadyProcessing = False;
	public static Boolean weAreAlreadyProcessing() { return alreadyProcessing; }
	public static void disallowFurtherRecursion() { alreadyProcessing = True; return; }
	public static void resetRecursion() { alreadyProcessing = False ; return; }
}

 Test Code:

 

 

 

@isTest
private class Test_AssignmentRoleOnRatingChange {
	// SBower 10/10/2010
    
    static void doCheck (Lead lead, String rating, String queueName){
    	Lead checkLead = [select LastName, Company, Rating, RunReassignmentRule__c, ownerId, owner.Name from Lead where id = :lead.id limit 1];
    	system.assertEquals(rating, checkLead.Rating);
    	system.assertEquals(false, checkLead.RunReassignmentRule__c);
    	system.assertEquals(queueName, checkLead.owner.Name);
    }
    
    static testMethod void TestReassignments() {
    	// Since I can't set up Assignment Rules in my test code, I have to run using the Groups and Rules that are already extant.
   	
        // Create a few groups.
        List<Group> groups = new List<Group>([select id, name from Group where name like 'Assignment %' and type='Queue' order by name asc]);
        // This should give me my four groups, 'Assignment A', 'Assignment B', 'Assignment C', and 'Assignment D'
        system.assertEquals(4,groups.size());

		
		// Let's create some new Leads
        // Assignment means the OwnerId of the Lead is set to the Group's Id.  So, default them all to Group A.
        // Note: I'm not setting the Rating field value.
        // Note: I'm not setting the RunReassignmentRule__c value;
        
        // My Assignment rules are:
        // If Rating = 'Hot' -> Assignment B
        // If Rating = 'Warm' -> Assignment C
        // If Rating = 'Cold' -> Assignment D
       
		List<Lead> leads = new List<Lead>();
		leads.add(new Lead(LastName = 'AAA', Company = 'Test_AssignmentRoleOnRatingChange AAA', Status = 'Open - Not Contacted', ownerId = groups[0].id));        
		leads.add(new Lead(LastName = 'BBB', Company = 'Test_AssignmentRoleOnRatingChange BBB', Status = 'Open - Not Contacted', ownerId = groups[0].id));       
		leads.add(new Lead(LastName = 'CCC', Company = 'Test_AssignmentRoleOnRatingChange CCC', Status = 'Open - Not Contacted', ownerId = groups[0].id)); 
		insert leads;      
		system.assertEquals(3,[select count() from lead where Company like 'Test_AssignmentRoleOnRatingChange %']);

        // Ok, so let's do some testing.
        // Start by setting the first three to each of the three ratings, and trigger the assignment.
        leads[0].Rating = 'Hot';
        leads[0].RunReassignmentRule__c = true;
        update leads[0];
        doCheck(leads[0], 'Hot', 'Assignment B');
        
        leads[1].Rating = 'Warm';
        leads[1].RunReassignmentRule__c = true;
        update leads[1];
        doCheck(leads[1], 'Warm', 'Assignment C');
        
        leads[2].Rating = 'Cold';
        leads[2].RunReassignmentRule__c = true;
        update leads[2];
        doCheck(leads[2], 'Cold', 'Assignment D');
      
        leads.clear();
        leads.add(new Lead(LastName = 'DDD_0', Company = 'Test_AssignmentRoleOnRatingChange DDD_0', Status = 'Open - Not Contacted', ownerId = groups[0].id));
		leads.add(new Lead(LastName = 'DDD_1', Company = 'Test_AssignmentRoleOnRatingChange DDD_1', Status = 'Open - Not Contacted', ownerId = groups[0].id));
		leads.add(new Lead(LastName = 'DDD_2', Company = 'Test_AssignmentRoleOnRatingChange DDD_2', Status = 'Open - Not Contacted', ownerId = groups[0].id));
		leads.add(new Lead(LastName = 'DDD_3', Company = 'Test_AssignmentRoleOnRatingChange DDD_3', Status = 'Open - Not Contacted', ownerId = groups[0].id));
		leads.add(new Lead(LastName = 'DDD_4', Company = 'Test_AssignmentRoleOnRatingChange DDD_4', Status = 'Open - Not Contacted', ownerId = groups[0].id));
		leads.add(new Lead(LastName = 'DDD_5', Company = 'Test_AssignmentRoleOnRatingChange DDD_5', Status = 'Open - Not Contacted', ownerId = groups[0].id));
		leads.add(new Lead(LastName = 'DDD_6', Company = 'Test_AssignmentRoleOnRatingChange DDD_6', Status = 'Open - Not Contacted', ownerId = groups[0].id));
		leads.add(new Lead(LastName = 'DDD_7', Company = 'Test_AssignmentRoleOnRatingChange DDD_7', Status = 'Open - Not Contacted', ownerId = groups[0].id));
		leads.add(new Lead(LastName = 'DDD_8', Company = 'Test_AssignmentRoleOnRatingChange DDD_8', Status = 'Open - Not Contacted', ownerId = groups[0].id));
		leads.add(new Lead(LastName = 'DDD_9', Company = 'Test_AssignmentRoleOnRatingChange DDD_9', Status = 'Open - Not Contacted', ownerId = groups[0].id));
		insert leads;
		
		// Check small bulk
		for (Lead l: leads) {
			l.Rating = 'Hot';
        	l.RunReassignmentRule__c = true;
		}

		// verify that there are no Leads assigned to the 'Hot' queue from this batch, and that they are all in Queue A
		system.assertEquals(10,[select count() from lead where Company like 'Test_AssignmentRoleOnRatingChange DDD%' and owner.Name = 'Assignment A']);
		system.assertEquals(0,[select count() from lead where Company like 'Test_AssignmentRoleOnRatingChange DDD%' and owner.Name = 'Assignment B']);
        update leads;
        // verify the Assignments are done.
		system.assertEquals(0,[select count() from lead where Company like 'Test_AssignmentRoleOnRatingChange DDD%' and owner.Name = 'Assignment A']);
		system.assertEquals(10,[select count() from lead where Company like 'Test_AssignmentRoleOnRatingChange DDD%' and owner.Name = 'Assignment B']);    
   
    }
    
}

Of course this isn't perfect, especially as far as the recursion control goes. Critical sections would be nice; you might  want to create a use-specific version of the Utility class, and perhaps even a user-specific check within the class to further restrict the possibilities for conflict.

 

 

SrinuSrinu
Hi John,
I am getting 72% of code coverage for the below class. I could not cover line 15,16 and 17. Can post ur test Method for this?
 public class AssignLeads{
 2	  
 3	   public static Boolean assignAlreadyCalled=FALSE;
 4	  
 5	   public static boolean assignAlreadyCalled(){
 6	   return assignAlreadyCalled;
 7	   }
 8	  
 9	   @future
 10	   public static void assign(List<Id> lIds){
 11	   assignAlreadyCalled=TRUE;
 12	   List<Lead> leads=[SELECT Id,LastModifiedBy.Alias FROM Lead WHERE Id IN: lIds];
 13	   for(lead l:leads){
 14	   //if(l.LastModifiedBy.Alias == 'svcadmin') {
 15	   Database.DMLOptions dmo = new Database.DMLOptions();
 16	   dmo.assignmentRuleHeader.useDefaultRule= true;
 17	   l.setOptions(dmo);
 18	   //}
 19	   }
 20	   update(leads);
 21	   }
 22	  
 23	  }

 

jkucerajkucera

Please post your test code.  

 

Something like this should cover it:

1) Create lead

2) Change owner to something else (create a user in the test if you don't have a 2nd one)

3) test.start(); then call this method, then test.stop()

4) Assert that owner has reverted to the original intended user

SrinuSrinu

Hi Jon,

    Thank for the reply.. Still am struggling on that.... No Luck..

 

Here is my code still i developed:

1	  public class AssignLeads{
 2	  
 3	   public static Boolean assignAlreadyCalled=FALSE;
 4	  
 5	   public static boolean assignAlreadyCalled(){
 6	   return assignAlreadyCalled;
 7	   }
 8	  
 9	   @future
 10	   public static void assign(List<Id> lIds){
 11	   assignAlreadyCalled=TRUE;
 12	   //System.debug('+++++++++++++++LastModifiedById:'+LastModifiedById);
 13	   //System.debug('+++++++++++++++LastModifiedBy.Alias:'+LastModifiedBy.Alias);
 14	   List<Lead> leads=[SELECT Id,LastModifiedById,LastModifiedBy.Alias FROM Lead WHERE Id IN: lIds];
 15	  
 16	   for(lead l:leads){
 17	   System.debug('+++++++++++++++l:'+l);
 18	   System.debug('+++++++++++++++l.LastModifiedBy.Alias:'+l.LastModifiedBy.Alias);
 19	   if(l.LastModifiedBy.Alias == 'svcadmin') {
 20	   Database.DMLOptions dmo = new Database.DMLOptions();
 21	   dmo.assignmentRuleHeader.useDefaultRule= true;
 22	   l.setOptions(dmo);
 23	   }
 24	   }
 25	   update(leads);
 26	   }
 27	  
 28	   public static testmethod void testCoverage()
 29	   {
 30	   User testUser1 = [SELECT Id, Name, Alias FROM User WHERE IsActive=true AND Alias='svcadmin' LIMIT 1];
 31	  
 32	   list<id> lds=new list<id>();
 33	   lead l=new lead(lastname='test',company='abc',LastModifiedById =testUser1.Id);
 34	   //insert l;
 35	   lds.add(l.id);
 36	   //AssignLeads a=new AssignLeads();
 37	   AssignLeads.assignAlreadyCalled();
 38	   AssignLeads.assign(lds);
 39	  
 40	   }
 41	  }

 with this I got 66% coverage. Lines 19 to 23 are could not cover.

 Here is the trigger for the above class.

  

trigger reassignLeads on Lead (after update){
    List<Id> lIds=new List<id>();
     for(lead l:trigger.new){
        if (l.IsConverted==False){
            lIds.add(l.Id);
        }
     }
    if (AssignLeads.assignAlreadyCalled()==FALSE){
        system.debug('Assign already called? '+AssignLeads.assignAlreadyCalled());
        AssignLeads.Assign(lIds);
    }
}

   Here is code I developed for above trigger.

   

@isTest
private class TestreassignLeads {
  @isTest
    private Static Void testMarketingAutomationUser(){
        
                
        Lead ld1 = new Lead();
        ld1.LastName = 'TestLName';
        ld1.Company = 'Test Company';
        ld1.Status = 'Open';
        //ld1.RecordTypeId = rtLi.Id;
        ld1.Country = 'United States';
        ld1.State = 'AK';
        ld1.Title = 'test title';
        ld1.Stratus_Product_S__c = 'Avance';
        ld1.IsConverted = False;
        
        insert ld1;
        
        
    }
}

   In this also getting errors.

 

  Can you show me the way, how to cover test coverage for the trigger and class?

 

Thanks in advance...!!

jkucerajkucera

Two things that might help:

1) After your lead insert, change the owner & call an update

2) Do that update between test.startTest(); and test.stopTest()

 

#2 is key to make sure the @future call finishes during the test method:

 

insert ld1;
ld1.owner=<some other userId>;
test.startTest();
update ld1;
test.stopTest();

 Also, you should have asserts in there to make sure it actually works like:

system.assertEquals(<expected userId>, ld1.OwnerId);

 Note that system.debug lines never get used in a test, so comment them out to up your %.

samuel singh 1samuel singh 1
Hi John,
   Actually you must focus on the base condition. Follow these steps and your issue will be fixed:
1) Create lead
2) Change owner to something else (create a user in the test if you don't have a 2nd one)
3) test.start(); then call this method, then test.stop()
4) Assert that owner has reverted to the original intended user

Thanks! For posting question, for more technical info, you can refer this Website (https://trendinghacker.com/)