Disclaimer
This is a “better write it down for the future me” post.
For the TL;DR pals, this is the Github repository.
It was years I wans’t working with Campaigns, so it took me a while to remember a simple thing: you cannot automate
Campaign Member Status configuration with point & click.
It seems awkward, mostly because you have a Status picklist field on the Campaign Member.
And when you cannot find the New button on the picklist related list of that field you start feeling depressed.
After a while (you don’t really need much time, just google it a bit) you find out there is a wonderful Campaign Member Status object, that holds the details of a specific Campaign’s members statuses.
Then you learn (again) that you need to use the Advanced Setup button (in Classic) or the Campaign Member Status related list (in LEX):
When you add a new value the list of values in the Campaign Member Status is automatically updated:
So the next question is:
How to automate this?
Well, unfortunately you can’t, you have to manually create the Campaign Member Status values on every Campaign you create.
If your users create campaigns often, believe they won’t accept it as a solution!
As a simple solution we can:
- correlate Campaign Member Status values to Campaign Record Types
- store Campaign Record Type / Campaign Member Status Value couples inside a Custom Metadata object
- implement a trigger that executes on campaign create or if its record type changes
CampaignMemberStatusConfiguration__mdt
Name and Label standard fields will not be used to store actual values for our feature implementation, becausr they can only store 40 chars that is definitely less than 80 required for Record Type Developer name and 765 required for Campaign Member Status value (we’ll be using max 255 chars in this implementation).
Campaign Record Types
Custom Metadata values
Apex Code algorithm
The following code, invoked from the Campaign trigger, calculates the new Campaign Member Status objects, removing the old one and creating the new ones.
/** * @author Enrico Murru (https://enree.co, @enreeco) * @description Creates CampaignMemberStatus records based on custom metadata object configuration */ public class CampaignTriggerHandler { public static void execute(){ //new statuses to be created / updated (with default or responded) List<CampaignMemberStatus> defaultOrRespondedStatuses = new List<CampaignMemberStatus>(); //new statuses to be created / updated (without default or responded) List<CampaignMemberStatus> otherStatuses = new List<CampaignMemberStatus>(); //statuses to be deleted List<CampaignMemberStatus> deleteStatusesList = new List<CampaignMemberStatus>(); //selected campaigns List<Campaign> cmpList = new List<Campaign>(); //record types List<ID> rTypesList = new List<ID>(); //select only campaigns that are inserted or that changed their record types for(Integer i = 0; i < Trigger.new.size(); i++){ Campaign nCmp = (Campaign)Trigger.new[i]; if(!Trigger.isInsert && nCmp.RecordTypeId == ((Campaign)Trigger.old[i]).RecordTypeId) continue; cmpList.add(nCmp); rTypesList.add(nCmp.RecordTypeId); } if(cmpList.isEmpty()) return; //delete standard statuses deleteStatusesList = [SELECT Id, Label, CampaignId, IsDefault, HasResponded From CampaignMemberStatus Where CampaignId IN :cmpList Order By Label]; //query record types Map<ID, Recordtype> rTypeMap = new Map<ID,RecordType>([Select Id, DeveloperName From RecordType Where SObjectType = 'Campaign' and Id IN :rTypesList]); for(Campaign cmp : cmpList){ //we can do as many query as we want with custom metadata for(CampaignMemberStatusConfiguration__mdt cmsc : [SELECT Id, RecordTypeDeveloperName__c, StatusValue__c, SortOrder__c,IsDefault__c, Responded__c FROM CampaignMemberStatusConfiguration__mdt WHERE RecordTypeDeveloperName__c = :rTypeMap.get(cmp.RecordTypeId).DeveloperName ORDER BY StatusValue__c, IsDefault__c DESC, Responded__c DESC]){ //gets CMS with same label (avoid duplicates on upsert) CampaignMemberStatus oldCMS = null; for(Integer ci = deleteStatusesList.size()-1; ci >= 0; ci--){ CampaignMemberStatus cms = deleteStatusesList[ci]; if(cms.CampaignId != cmp.Id) continue; if(cms.Label == cmsc.StatusValue__c){ oldCMS = cms; deleteStatusesList.remove(ci); break; } } CampaignMemberStatus newCMS = new CampaignMemberStatus(Label = cmsc.StatusValue__c, SortOrder = cmsc.SortOrder__c.intValue(), IsDefault = cmsc.IsDefault__c, HasResponded = cmsc.Responded__c); if(oldCMS != null){ newCMS.Id = oldCMS.Id; }else{ newCMS.CampaignId = cmp.Id; } if(!newCMS.IsDefault && !newCMS.HasResponded){ otherStatuses.add(newCMS); }else{ defaultOrRespondedStatuses.add(newCMS); } } } //this DML sequence guarantees no conflicts upsert defaultOrRespondedStatuses; delete deleteStatusesList; upsert otherStatuses; } }
Find all the details in this Github repository.