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
greenstorkgreenstork 

New exception with Winter '10 when trying to update CampaignMember records

I have a trigger on Opportunity that either creates a campaign membership for the contact that is the primary contact role on an opportunity, or updates the campaign membership to a campaign member status of 'Donated' when the opportunity IsWon. 

 

Before the Summer '09 release, I could just create a new campaign member for a contact and it would overwrite the status.  With Summer '09 however, Salesforce disallowed the creation of a new campaign member record (if one already existed) and I was getting exceptions that a duplicate campaign member record exists already. 

 

So I refactored, adding a SOQL to find out if the CM existed and then just update the status. 

 

My trigger just starting failing with the Winter '10 release.  Specifically, I have a custom lead converter, and a test that is testing whether an opp gets created on lead conversion.  But the opp isn't getting created because when the lead converter test runs, my Opportunity trigger fires, tries to update the Campaign Member, and throws an exception telling me that I can't update a converted lead.  I don't have any DML's related to lead on my Opportunity trigger, all I'm trying to do is update CampaignMember records.

 

Please advise, I suspect a bug here related to some of the new CampaignMember functionality added with Winter' 10.  I'm happy to provide code if that's helpful.

jkucerajkucera

It sounds like upon lead convert you aren't grabbing the resulting Contact ID instead of of Lead ID, causing exception due to the fact that converted leads can't be changed (or referenced for creating new campaign members).

 

I'd throw a check for whether the lead is converted in your CM create code, and if so, grab the contact ID from that converted lead record, to be used for creating the CM.  

greenstorkgreenstork

Hi John,

 

Just to clarify, we're not creating new CM's on lead conversion -- these are leads with existing campaign memberships. The custom lead converter uses the standard leadConvert method, but then adds a few additional functions (like creating Opportunities). 

 

All we're doing on lead conversion is updating the CM record to change the member status (handled by a separate trigger actually, not the controller and class of the custom lead converter), my code does not touch the contact or lead IDs of the campaign member record.

 

I would think that updating the status of the campaign member record should have absolutely nothing to do with whether or not the lead is converted or not.  Again, the trigger that updates the CMs does not touch lead or contact at all, the only DML is on CampaignMember.

jkucerajkucera

Note if you're using API v15.0 and below, campaign member insert & update still work as upsert, as they used to.  The new Upsert behavior is a bit different than the old. 

 

If that doesn't solve your problem, log a case w/ support to dive into the details.

greenstorkgreenstork

Hi John,

 

This problem is not resolved yet, although my case has been escalated.

 

I'm getting down to the root of the matter though.  I've inserted a bunch ofdebug statements and here is the important one:

 

20091105183035.347:Trigger.ONEN_Opportunity_AddToCampaign: line 141, column 3: CMDebug: (CampaignMember:{CampaignId=70180000000BSviAAG, Status=Donated, HasResponded=false, LeadId=00Q8000000XEmUREA1, ContactId=0038000000nRnu6AAC, Id=00v8000000OywKjAAJ})
20091105183035.347:Trigger.ONEN_Opportunity_AddToCampaign: line 143, column 4: Update: LIST:SOBJECT:CampaignMember
20091105183035.347:Trigger.ONEN_Opportunity_AddToCampaign: line 143, column 4:     DML Operation executed in 52 ms
System.DmlException: Update failed. First exception on row 0 with id 00v8000000OywKjAAJ; first error: CANNOT_UPDATE_CONVERTED_LEAD, cannot reference converted lead

 

CMDebug is my debug statement and it's displaying a list of Campaign members to update, in this case, there is only one.  Note that this Campaign Member was created and intially associated with a lead, the lead was then converted using the Database.LeadConvert method, and the CM now has a ContactId.  When I attempt the update DML on the CampaignMember list, I get an exception that I cannot update a converted lead.This error makes no sense to me, as this CampaignMember clearly has a ContactId already. Please advise.

jkucerajkucera
It might help if you post your trigger code here.  It does sound wierd that the update fails even w/ a contactID on the campaign member.
greenstorkgreenstork

Here's my trigger:

 

 

trigger ONEN_Opportunity_AddToCampaign on Opportunity (after insert, after update) {

// when we first end up w/ a closedwon Opp that has a campaign source,

// ensure that the primary contact has a campaign mbrship for that campaign set to Responded

// don't worry about opp deletions - don't ever remove camp mbrship once created

// note: this doesn't prevent user from later removing camp mbr or changing it to not responded - that's OK I think

// also note: won't trigger if user later changes primary contact on opp - that's probably OK too

 

//system.debug ( 'TRIGGER FIRED');

 

string DefaultDonatedStatus = ONEN_Constants.CAMPAIGN_DEFAULT_DONATED_STATUS;

Set<id> allOppIds = new Set<id> ();

Map<id,id> OppToContactIds = new Map<id,id> ();

// Map<id,Map<id,CampaignMember>> ContactToCMs = new Map<id,Map<id,CampaignMember>> ();

Map<id,Opportunity> oppsToProcess = new Map<id,Opportunity> ();

List<CampaignMember> CMsToInsert = new List<CampaignMember> ();

List<CampaignMember> CMsToUpdate = new List<CampaignMember> ();

// this map's keys will be a concatenation of contactID & campaignID

Map<string,CampaignMember> comboIDMap = new Map<string,CampaignMember> ();

// use a set for this because it will automatically dedup

Set<id> CampaignsToCheckCMS = new Set<id> ();

Set<id> CampToCheckResponded = new Set<id> ();

Set<id> CMIDsToUpdateSet = new set<id> ();

Set<id> CampaignIds = new set<id> ();

set<id> nongiftRTids = ONEN_RecTypes.GetRecordTypeIdSet('Opportunity',ONEN_Constants.OPP_RECTYPES_NOT_GIFT);

 

// first, make sure we have checked for contact role creation if necessary

// (have to do this here to insure triggers happen in the right order)

if ( Trigger.isInsert && ONEN_OpportunityContactRoles.haveCheckedContactRoles == false ) {

ONEN_OpportunityContactRoles.CheckContactRoles ( trigger.newmap );

}

 

// first figure out which opps need processing - only want contact opps whose

for ( Opportunity opp : Trigger.new ) {

If ( opp.CampaignID != Null && opp.AccountID==null &&

(Trigger.IsInsert ||

Trigger.Old[0].CampaignID != opp.CampaignID ||

Trigger.Old[0].StageName != opp.StageName )) {

oppsToProcess.put (opp.id, opp);

CampaignIds.add(opp.CampaignId);

}

}

 

If ( oppsToProcess.size() > 0 ) {

// * get set of oppID's in trigger.new

// and pass set to OppContactRoles.getPrimaryContactID, returns map PrimaryContacts (OppID -> ContactID) allOppIds = oppsToProcess.keySet();

OppToContactIds = ONEN_OpportunityContactRoles.GetPrimaryContactIdsBulk (allOppIds);

 

//system.debug ('oppToContactIds map: ' + OppToContactIds);

// map combos of contact & camp ID's to the CM's

for (CampaignMember thisCM : [Select Id, ContactId, LeadId, Lead.IsConverted, CampaignId, Status, HasResponded From CampaignMember WHERE ContactId IN :OppToContactIds.values() AND CampaignId IN :CampaignIds]) {

string comboID = thisCM.ContactID + '|' + thisCM.CampaignID;

comboIDMap.put (comboID,thisCM);

}

 

system.debug('This CampaignMember: ' + comboIDMap);

 

// now loop through all of our opps

for (Opportunity thisOpp : oppsToProcess.values() ) {

id ConId = OppToContactIds.get(thisOpp.Id);

id CampId = thisOpp.CampaignId;

 

// but only process those that actually have a primary contact

if ( ConId != null) {

CampaignMember thisCM;

string comboID = ConId + '|' + CampId;

thisCM = comboIDMap.get(comboID);

// if this contact doesn't already have a CM for this campaign, create one

if ( thisCM == null ) {

CampaignMember newCM = new CampaignMember(ContactId = ConId, CampaignId = CampId);

//use the default status unless the opp is won, then use the default donated status if it's a gift

if (thisOpp.IsWon == True) {

If ( nongiftRTids.contains(thisOpp.RecordTypeId) == true) {

newCM.Status = 'Responded';

CampToCheckResponded.add (CampId);

} else {

newCM.Status = DefaultDonatedStatus;

CampaignsToCheckCMS.add (CampId);

}

}

CMsToInsert.add (newCM);

comboIDMap.put (comboID,newCM);

} else {

// if they do already have a CM, chg the status where appropriate

if ( thisCM.HasResponded == true || thisOpp.IsWon == false) {

// if already responded, or not won, do nothing

} else {

If ( nongiftRTids.contains(thisOpp.RecordTypeId) == true) {

thisCM.Status = 'Responded';

CampToCheckResponded.add (CampId);

} else {

thisCM.Status = DefaultDonatedStatus;

CampaignsToCheckCMS.add (CampId);

}

 

if (CMIDsToUpdateSet.add(thisCM.Id)) {

CMsToUpdate.add(thisCM);

}

}

}

}

 

// check list of Campaigns that they have the Donated status, add if not

// last param means we require that the status be HasResponded, and change it to that if it's not

// already

boolean statusOK = ONEN_CampaignMemberStatus.CheckCMStatusExistsBulk (CampaignsToCheckCMS,DefaultDonatedStatus,true);

boolean statusOK2 = ONEN_CampaignMemberStatus.CheckCMStatusExistsBulk (CampToCheckResponded,'Responded',true);

//system.debug ('to update: ' + CMsToUpdate);

//system.debug ('to insert: ' + CMsToInsert);

// need to insert the new ones first, in case any will be subsequent dups that need updating.

 

if ( CMsToInsert.size() > 0 ) {

insert CMsToInsert;

}

 

system.debug('CMDebug: ' + CMsToUpdate);

 

if ( CMsToUpdate.size() > 0 ) {

update CMsToUpdate;

}

}

}

 

Message Edited by greenstork on 11-05-2009 11:41 AM
Message Edited by greenstork on 11-05-2009 11:45 AM
greenstorkgreenstork
Any ideas John? Just checking in...
jkucerajkucera

Sounds like these 2 lines are causing the problem:

 

CampaignMember newCM = new CampaignMember(ContactId = ConId, CampaignId = CampId);

 ...

Insert CMsToInsert;


I haven't done so, but can you create a simple trigger, hard code a campaign ID and a contact ID that was from a converted lead, and try to insert a campaign member using these 2 lines?  If that error comes up, might be a bug for my team to look at.

 

I'd check myself but I'm smashed w/ Dreamforce right now.

 

greenstorkgreenstork

Hi John,

 

I'm back from Dreamforce and just following up on this thread.  Sorry I didn't get a chance to meet you.

 

On your suggestion, I wrote an very simple trigger and test to confirm the bug.  The trigger is on contact, and it fires off an update to a campaign member record of a converted lead.  Here is the trigger code:

 

trigger Test_Trigger on Contact (after insert, after update) { CampaignMember cm = new CampaignMember();

if (trigger.new[0].MailingCity == 'TOTALLY_BOGUS') { cm = [select id, status from CampaignMember where ContactId = :trigger.new[0].id LIMIT 1]; cm.status = 'Donated'; update cm;

}

}

 And here is the test:

 

 

@isTest

private class TEST_TestTrigger {

 

static testMethod void myUnitTest() { // status label is needed for conversion

LeadStatus convertStatus = [Select MasterLabel from LeadStatus where IsConverted=true order by MasterLabel limit 1];

 

Account individual = [select id from Account where Name = 'Individual' LIMIT 1];

 

Database.LeadConvert lc = new Database.LeadConvert();

Lead l = new Lead(

LastName = 'Bogus',

Company = '[not provided]'

);

insert l;

 

Campaign camp = new Campaign(

Name = 'New BOGUS Campaign'

);

insert camp;

 

CampaignMember member = new CampaignMember(

LeadId = l.id,

CampaignId = camp.id,

Status = 'Sent'

);

insert member;

 

  lc.setLeadId(l.id);

lc.setAccountId(individual.id);

lc.setConvertedStatus(convertStatus.MasterLabel);

 

Database.leadConvertResult lcr = Database.convertLead(lc, false);

 

system.assert(lcr.isSuccess());

 

Contact con = [select id, MailingCity from Contact WHERE id = :lcr.getContactId() LIMIT 1];

  con.MailingCity = 'TOTALLY_BOGUS';

update con;

}

}

 

 Here is the error message that I pull from the debug log: System.DmlException: Update failed. First exception on row 0 with id 00v8000000PWoK6AAL; first error: CANNOT_UPDATE_CONVERTED_LEAD, cannot reference converted lead: []

 

 

 

jkucerajkucera
File a case w/ support - it should get escalated up to my team.  The trigger works fine for updates on a converted lead, but the test fails - not sure why.
greenstorkgreenstork

I created a case in October and it's been escalated but it hasn't seen any traction in a couple of weeks.  

 

Case Number: 03040305

 

Any status update that you could provide would be much appreciated.  Thanks!

jkucerajkucera
It looks like they were trying to get login access to look into it further and received it around 9 days ago.  I'll send a note to the rep.