├── .gitignore ├── src ├── classes │ ├── ConvertLeadsInvocable.cls-meta.xml │ ├── ConvertLeadsInvocableTest.cls-meta.xml │ ├── ConvertLeadsInvocableTest.cls │ └── ConvertLeadsInvocable.cls └── package.xml ├── README.md └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | config/ 2 | -------------------------------------------------------------------------------- /src/classes/ConvertLeadsInvocable.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 38.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /src/classes/ConvertLeadsInvocableTest.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 38.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /src/package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ConvertLeadsInvocable 5 | ConvertLeadsInvocableTest 6 | ApexClass 7 | 8 | 38.0 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Auto Convert Leads using Process Builder or Flow 2 | 3 | Convert Leads into Accounts, Contacts, and (optional) Opportunities on-demand according to your criteria. This solution includes one apex class and its unit test. See below instructions for setting this up with Process Builder or Flow to automate your lead conversion needs. 4 | 5 | 6 | Deploy to Salesforce 8 | 9 | 10 | 11 | # Usage 12 | 13 | The invocable apex class in this repository can be called from either Process Builder or Flow to fit into your business automation requirements. 14 | 15 | 16 | Process Builder 17 | --------------- 18 | 19 | TODO 20 | 21 | https://help.salesforce.com/articleView?id=process_action_apex.htm 22 | 23 | 24 | Flow 25 | ---- 26 | 27 | TODO 28 | 29 | https://developer.salesforce.com/docs/atlas.en-us.salesforce_vpm_guide.meta/salesforce_vpm_guide/vpm_designer_elements_apex_invocable.htm 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2017, Doug Ayers 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /src/classes/ConvertLeadsInvocableTest.cls: -------------------------------------------------------------------------------- 1 | /** 2 | * Developed by Doug Ayers, douglascayers.com 3 | */ 4 | @isTest 5 | private class ConvertLeadsInvocableTest { 6 | 7 | @isTest 8 | static void test_convert_lead_yes_opportunity() { 9 | 10 | Lead ld = new Lead( 11 | firstName = 'Marc', 12 | lastName = 'Benioff', 13 | company = 'Salesforce' 14 | ); 15 | 16 | insert ld; 17 | 18 | Test.startTest(); 19 | 20 | ConvertLeadsInvocable.LeadConvertRequest request = new ConvertLeadsInvocable.LeadConvertRequest(); 21 | request.leadId = ld.id; 22 | request.convertedStatus = [ SELECT id, masterLabel FROM LeadStatus WHERE isConverted = true LIMIT 1 ].masterLabel; 23 | request.createOpportunity = true; 24 | request.opportunityName = 'Test Opportunity'; 25 | request.ownerId = UserInfo.getUserId(); 26 | request.sendEmailToOwner = true; 27 | 28 | List results = ConvertLeadsInvocable.convertLeads( new List{ request } ); 29 | 30 | Test.stopTest(); 31 | 32 | System.assert( results != null ); 33 | System.assertEquals( 1, results.size() ); 34 | 35 | ConvertLeadsInvocable.LeadConvertResult result = results[0]; 36 | 37 | System.assertEquals( 1, [ SELECT count() FROM Account WHERE id = :result.accountId ] ); 38 | System.assertEquals( 1, [ SELECT count() FROM Contact WHERE id = :result.contactId ] ); 39 | System.assertEquals( 1, [ SELECT count() FROM Opportunity WHERE id = :result.opportunityId ] ); 40 | 41 | } 42 | 43 | @isTest 44 | static void test_convert_lead_no_opportunity() { 45 | 46 | Account acct = new Account( 47 | name = 'Salesforce' 48 | ); 49 | 50 | insert acct; 51 | 52 | Contact cont = new Contact( 53 | accountId = acct.id, 54 | firstName = 'Marc', 55 | lastName = 'Benioff' 56 | ); 57 | 58 | insert cont; 59 | 60 | Lead ld = new Lead( 61 | firstName = 'Marc', 62 | lastName = 'Benioff', 63 | company = 'Salesforce' 64 | ); 65 | 66 | insert ld; 67 | 68 | Test.startTest(); 69 | 70 | ConvertLeadsInvocable.LeadConvertRequest request = new ConvertLeadsInvocable.LeadConvertRequest(); 71 | request.leadId = ld.id; 72 | request.convertedStatus = [ SELECT id, masterLabel FROM LeadStatus WHERE isConverted = true LIMIT 1 ].masterLabel; 73 | request.createOpportunity = false; 74 | request.accountId = acct.id; 75 | request.contactId = cont.id; 76 | request.overwriteLeadSource = true; 77 | 78 | List results = ConvertLeadsInvocable.convertLeads( new List{ request } ); 79 | 80 | Test.stopTest(); 81 | 82 | System.assert( results != null ); 83 | System.assertEquals( 1, results.size() ); 84 | 85 | ConvertLeadsInvocable.LeadConvertResult result = results[0]; 86 | 87 | System.assertEquals( 1, [ SELECT count() FROM Account WHERE id = :result.accountId AND id = :acct.id ] ); 88 | System.assertEquals( 1, [ SELECT count() FROM Contact WHERE id = :result.contactId AND id = :cont.id ] ); 89 | System.assertEquals( 0, [ SELECT count() FROM Opportunity WHERE id = :result.opportunityId ] ); 90 | 91 | } 92 | 93 | } -------------------------------------------------------------------------------- /src/classes/ConvertLeadsInvocable.cls: -------------------------------------------------------------------------------- 1 | /** 2 | * Allows auto-conversion of Leads based on criteria. 3 | * For example, this class can be invoked by Flow, Process Builder, or even Apex Trigger. 4 | * 5 | * https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_dml_convertLead.htm 6 | * https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_classes_annotation_InvocableVariable.htm 7 | * 8 | * Developed by Doug Ayers, douglascayers.com 9 | */ 10 | public with sharing class ConvertLeadsInvocable { 11 | 12 | /** 13 | * Represents the required and optional configuration parameters to a single lead convert request. 14 | * https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_dml_convertLead.htm 15 | */ 16 | public class LeadConvertRequest { 17 | 18 | @InvocableVariable( 19 | label = 'Lead ID' 20 | description = 'ID of the Lead to convert' 21 | required = true 22 | ) 23 | public ID leadId; 24 | 25 | @InvocableVariable( 26 | label = 'Converted Status' 27 | description = 'Lead Status picklist value that indicates this Lead is converted' 28 | required = true 29 | ) 30 | public String convertedStatus; 31 | 32 | @InvocableVariable( 33 | label = 'Account ID' 34 | description = 'The specific Account to convert Lead into. If blank then creates new Account.' 35 | ) 36 | public ID accountId; 37 | 38 | @InvocableVariable( 39 | label = 'Contact ID' 40 | description = 'The specific Contact to convert Lead into. If blank then creates new Contact.' 41 | ) 42 | public ID contactId; 43 | 44 | @InvocableVariable( 45 | label = 'Overwrite Lead Source?' 46 | description = 'Overwrite the LeadSource field on the target Contact with the LeadSource field from the Lead? Default is false. If true then must also specify "Contact ID".' 47 | ) 48 | public Boolean overwriteLeadSource = false; 49 | 50 | @InvocableVariable( 51 | label = 'Create Opportunity?' 52 | description = 'Create an Opportunity? Default is true.' 53 | ) 54 | public Boolean createOpportunity = true; 55 | 56 | @InvocableVariable( 57 | label = 'Opportunity Name' 58 | description = 'If "Create Opportunity" is true then this is the name of the new opportunity. If blank then defaults to Company field from the Lead.' 59 | ) 60 | public String opportunityName; 61 | 62 | @InvocableVariable( 63 | label = 'Owner ID' 64 | description = 'Specific user to own the new Account, Contact, and Opportunity records created. Default is the Lead owner.' 65 | ) 66 | public ID ownerId; 67 | 68 | @InvocableVariable( 69 | label = 'Send Email to Owner?' 70 | description = 'Send an email notification to owner specified in "Owner ID"? Default is false.' 71 | ) 72 | public Boolean sendEmailToOwner = false; 73 | 74 | } 75 | 76 | /** 77 | * Represents the successful results of a single lead convert. 78 | * https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_class_database_leadconvertresult.htm 79 | */ 80 | public class LeadConvertResult { 81 | 82 | @InvocableVariable( label = 'Lead ID' ) 83 | public ID leadId; 84 | 85 | @InvocableVariable( label = 'Account ID' ) 86 | public ID accountId; 87 | 88 | @InvocableVariable( label = 'Contact ID' ) 89 | public ID contactId; 90 | 91 | @InvocableVariable( label = 'Opportunity ID' ) 92 | public ID opportunityId; 93 | 94 | } 95 | 96 | // -------------------------------------------------------------------------------- 97 | 98 | @InvocableMethod( 99 | label = 'Convert Leads' 100 | ) 101 | public static List convertLeads( List requests ) { 102 | 103 | // transform the invocable request to database convert request 104 | List convertRequests = new List(); 105 | for ( LeadConvertRequest request : requests ) { 106 | convertRequests.add( transform( request ) ); 107 | } 108 | 109 | // convert leads, all or none 110 | // if any has error then exception is thrown automatically and changes rolled back 111 | List convertResults = Database.convertLead( convertRequests, true ); 112 | 113 | // transform the database convert results to invocable result 114 | List results = new List(); 115 | for ( Database.LeadConvertResult convertResult : convertResults ) { 116 | results.add( transform( convertResult ) ); 117 | } 118 | 119 | return results; 120 | } 121 | 122 | // -------------------------------------------------------------------------------- 123 | 124 | private static Database.LeadConvert transform( LeadConvertRequest request ) { 125 | 126 | Database.LeadConvert convertRequest = new Database.LeadConvert(); 127 | 128 | convertRequest.setLeadId( request.leadId ); 129 | convertRequest.setConvertedStatus( request.convertedStatus ); 130 | 131 | if ( request.accountId != null ) { 132 | convertRequest.setAccountId( request.accountId ); 133 | } 134 | 135 | if ( request.contactId != null ) { 136 | convertRequest.setContactId( request.contactId ); 137 | } 138 | 139 | if ( request.overwriteLeadSource != null && request.overwriteLeadSource ) { 140 | convertRequest.setOverwriteLeadSource( request.overwriteLeadSource ); 141 | } 142 | 143 | if ( request.createOpportunity != null && !request.createOpportunity ) { 144 | convertRequest.setDoNotCreateOpportunity( !request.createOpportunity ); 145 | } 146 | 147 | if ( request.opportunityName != null ) { 148 | convertRequest.setOpportunityName( request.opportunityName ); 149 | } 150 | 151 | if ( request.ownerId != null ) { 152 | convertRequest.setOwnerId( request.ownerId ); 153 | } 154 | 155 | if ( request.sendEmailToOwner != null && request.sendEmailToOwner ) { 156 | convertRequest.setSendNotificationEmail( request.sendEmailToOwner ); 157 | } 158 | 159 | return convertRequest; 160 | } 161 | 162 | private static LeadConvertResult transform( Database.LeadConvertResult convertResult ) { 163 | 164 | LeadConvertResult result = new LeadConvertResult(); 165 | 166 | result.leadId = convertResult.getLeadId(); 167 | result.accountId = convertResult.getAccountId(); 168 | result.contactId = convertResult.getContactId(); 169 | result.opportunityId = convertResult.getOpportunityId(); 170 | 171 | return result; 172 | } 173 | 174 | public class ConvertLeadException extends Exception {} 175 | 176 | } --------------------------------------------------------------------------------