+ Start a Discussion
TreskoTresko 

Apex Trigger on Task from CTI Phone Calls - Update Lead Status from CallDisposition

So I'm trying to have the CallDisposition on the CTI Softphone to update the Lead status.

 

I think I'm pretty close, but I'm getting an 'Exec Anon Error' when Executing the Trigger is the Console: line 1, column 0: required (...)+ loop did not match anything at input 'trigger'

 

I'm very new to Apex - or any code for that matter, although I have tweaked different kinds of code, which is essentially what I'm trying to do here. I copied the code from here: http://boards.developerforce.com/t5/Apex-Code-Development/Task-Trigger-to-Update-Date-Time-field-on-Lead/td-p/279615

 

trigger ActivityFirstcontact on Task (after insert) {

List<Lead> updatedLeads= new List<Lead>();
 
    for(Task newTask:Trigger.new){
            if(string.valueOf(NewTask.WhoId).startsWith('00Q') && newTask.Subject != 'Lead Source Details')
            {
            Lead changedLead = [select id, First_Contact_Date_Time__c from lead where id=:NewTask.WhoId];
               if(changedLead.First_Contact_Date_Time__c == NULL){
                   changedLead.First_Contact_Date_Time__c=newTask.CreatedDate;
                   updatedLeads.add(changedLead);
           }}
   
              update updatedLeads;
              
   }
}

 

And came up with this:

 

trigger ActivityCall on Task (after insert) {

    List<Lead> updatedLeads= new List<Lead>();
 
    for(Task newTask:Trigger.new){
            if(string.valueOf(NewTask.WhoId).startsWith('00Q') && newTask.CallType != 'Incoming')
            {
            Lead changedLead = [select id, Status from lead where id=:NewTask.WhoId];
                   changedLead.Status=newTask.CallDisposition;
                   updatedLeads.add(changedLead);
           }}
   
              update updatedLeads;
}

 

It's definitely missing a few other things, because I want it to trigger from all incoming and outgoing calls. I may also just do one trigger for each disposition, as the dispositions don't always match up, but certain CallDispositions will move the lead to a different status/stage. - As long as I can get it to work for one scenario, I can build from there.

 

Thank you in advance for your help and potentially making me look like a hero!!!

 

 

 

sfdcfoxsfdcfox

Triggers are placed into "trigger" code, not Execute Anonymous code. Where exactly are you trying to input this code (I've never seen that error!)? Technically, that code isn't a best example anyways, as it has a SOQL inside a loop (bad stuff). We'd love to help you out, but we just need to know where you're starting. Also, do you have a Sandbox or a Developer account? You're going to need one to do any serious code work.

TreskoTresko

Ok, that actually makes more sense...

 

I created it in the Developer Console from my Sandbox (EE). I tweaked a few things to get rid of any errors/problems that displayed on the left and bottom. Then, I thought you had to paste the code into the top Execute bar... See attached pic. I clicked Execute from that box to get the error...

 

 

 

 

For some reason I thought that was a validation.

 

I have 8 sandboxes available, only using one currently...

 

Not sure if this matters, but I have the InGenius Salesforce Adapter for Polycom running.... works pretty well, but the major weakness is not being able to run any workflow actions on the lead from call results.

 

Thanks again!

sfdcfoxsfdcfox

Login to your Sandbox, click on Setup > Customize > Activities > Task Triggers > New, and put your code in there. Try it out and see if it's working. If so, you can upload a "change set" to move it into production ( you will need a test method to cover the code, as well ). Sandboxes are great for trying out changes before you upload them to the server and commit those changes to the users. The dialog you found, the "Execute Anonymous", is used to immediately execute code. It's useful for scheduling batch jobs, quickly cleaning up a batch of data, and so on. The Repository tab can be used for inputting code and triggers, but I'd advise against it until (probably) the next release, as there's some bugs around its usage right now.

TreskoTresko

Oh boy... maybe I'm getting way ahead of myself... I have no idea how to create a test class... Unless it's something I can just copy paste and use globally, maybe I should take a step back and go through some of the premier training courses on Apex... it says it's 350 minutes long, so... it's gonna be a late night i guess...

sfdcfoxsfdcfox

A proper test class proves that you understand the system well enough to write the code you wrote, as it would check the output from your trigger to assert that the code works with given inputs.

 

IMHO, don't worry about getting it 1))% right the first time. You only need 1% coverage so long as other code covers over 75%.

 

You could get away with just this:

 

@isTest
private class testTriggers {
  static testMethod void test() {
    lead lead = new lead(lastname='test',company='test');
    insert lead;
    task task = new task(whoid=lead.id, subject='Outbound',calltype='Outgoing',activityduedate=date.today());
    insert task;
  }
}

Your trigger needs some work before you try and use it in production, though. Here's a hint: you can't query inside loops without risking errors. See my various posts:

 

https://encrypted.google.com/search?hl=en&q=aggregate%20query%20update%20sfdcfox

 

 

TreskoTresko

It seems to be working... I had tested with a call and it's updating the fields...

 

I also had created a new field on the task, with a workflow formula to summarize the Comments, so it can be visible in Views and lists with LEFT(Description, 255). I added an update to this trigger, after a real test call, that is working as well...

 

Here's what I ended up with:

trigger ActivityCall on Task (after insert, after update) {

    List<Lead> updatedLeads= new List<Lead>();
 
    for(Task newTask:Trigger.new){
            if(string.valueOf(NewTask.WhoId).startsWith('00Q') && newTask.Type != 'Call')
            {
            Lead changedLead = [select id, Status, Notes__c  from lead where id=:NewTask.WhoId];
                   changedLead.Status=newTask.CallDisposition;
                   changedLead.Notes__c=newTask.Note_Summary__c;
                   updatedLeads.add(changedLead);
           }}
   
              update updatedLeads;
}

 It says I have 100% code coverage...

 

Hide Section - Trigger Code CoverageTrigger Code Coverage
Trigger NameCoverage %
ActivityCall100
 

 

 So, isn't the query "List", which is outside the 'for' loop?

 

 

I couldn't get your test to work... It's throwing this error:

 

Time Started9/30/2012 7:31 PM
ClasstestTriggers
Method Nametest
Pass/FailFail
Error MessageSystem.DmlException: Insert failed. First exception on row 0; first error: CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY, ActivityCall: execution of AfterInsert

caused by: System.DmlException: Update failed. First exception on row 0 with id 00QL0000001pPgJMAU; first error: REQUIRED_FIELD_MISSING, Required fields are missing: [Status]: [Status]

Trigger.ActivityCall: line 14, column 1: []
Stack TraceClass.testTriggers.test: line 7, column 1

 

 

do you think I'd be better off using Map instead of list to identify which Lead to update?

 

I did read some of your posts about aggegrate queries and it seems like this is used to do updates in bulk? I only need this trigger to run when a new call is dispo'd.

 

If fact, I'll most likely refine this a litle further to map certain call dispositions to certain lead status. i.e. IF(CallDisposition = No answer, Left Message, Not available) THEN Lead Status=Attempting contact.

 

Can one trigger include multiple if statements, and a predefined value for lead status when each of those evalutae to true? Or would a trigger for each if statement be better?

 

It's strange that Salesforce didn't build the CTI to be integrated with business logic/work flow.

sfdcfoxsfdcfox

The problem is line 8 in your trigger. You're performing a query inside the loop (which starts on line 5). If you wrote a test that inserted 200 leads, and then a task for each, you'd get an error and the test would fail. The list of updated leads is good; you're preventing a too-many-DML-statements error, but you've still got a too-many-SOQL-calls error.

 

You're right on my previous test method; I forgot the lead status. Throwing it all together, we can come up with this:

 

trigger activitycall on task( after insert, after update ) {
  lead[] leads = new lead[ 0 ];
  for( task record : trigger.new ) {
    if( newtask.type != 'Call' && record.whoid != null && record.whoid.getsobjecttype() == lead.sobjecttype ) {
      leads.add( new lead( id = record.whoid, status = record.calldisposition, notes__c = record.note_summary__c ) );
    }
  }
  update leads;
}

The new "GetSObjectType()" call works on Winter 13, so you can use the string.valueof form you have right now.

 

Your test method can then be:

 

static testmethod void test() {
  lead leadrecord = new lead(company='test',lastname='test',status='open');
  insert leadrecord;
  task taskrecord = new task(whoid=lead.id, subject='Outbound',calltype='Outgoing',activityduedate=date.today(),notes__c='test notes');
  insert task;
}

If I'm missing any other fields, feel free to add them in, but this is the general form you would probably want.

sfdcfoxsfdcfox

do you think I'd be better off using Map instead of list to identify which Lead to update?

 

Actually, yes, you should probably change the list to a map (even in my example code), because you can't update the same record more than about 5 times without a dml error (not that it's likely to happen, but you don't know what strange things might come up, like offline syncs).

 

I did read some of your posts about aggegrate queries and it seems like this is used to do updates in bulk? I only need this trigger to run when a new call is dispo'd.

 

It doesn't matter, per se, because even though you might only wish it to fire when a single task record is inserted/updated, there's plenty of ways that other people later might use the system, and then they'd be left cleaning up the mess you left them because you didn't take the extra time to use forward thinking. Messy code now will likely cause a loss of time later for someone, if not you, than your successor.

 

If fact, I'll most likely refine this a litle further to map certain call dispositions to certain lead status. i.e. IF(CallDisposition = No answer, Left Message, Not available) THEN Lead Status=Attempting contact.

 

Can one trigger include multiple if statements, and a predefined value for lead status when each of those evalutae to true? Or would a trigger for each if statement be better?

 

Salesforce.com best practices advise coalescing multiple triggers whenever possible, as separate triggers costs more time and usually results in redunant logic; in your case, you'd be looping over the same records more than once, as an example. Also, if you're using queries, you'll end up using more queries than necessary.

 

It's strange that Salesforce didn't build the CTI to be integrated with business logic/work flow.

 

It's really not much more than an API pre-built to load tasks into the system, more of a toolkit designed to save time rather than break any new ground. Without CTI, each developer would have to write the same redundant lines of code instead of having a nice system library to call upon. CTI isn't any more integrated than, say, creating tasks.

Iain ClementsIain Clements
Really useful post - thanks everyone