Manager for Salesforce Simple Tasks – Part III – Implementation

Manager for Salesforce Simple Tasks – Part III – Implementation

In the first blog post of this series, we talked about the different models and the interface that we will implement to have a full functional Salesforce manager, in the second blog post we talked about the authentication process and the different settings fields that were need to properly connect to Salesforce. In the final part of this series, we will work on the implementation of the Salesforce manager. As mentioned before, we are using the Force.com library in a dot.net core solution. So without further due lets begin

The first method we will implement is the create object, which will allow you to create a Lead, Task, Opportunity and even assign an Opportunity to a Contact

public async Task<string> CreateObjectAsync(string objectName, object data)
    {
        try
        {
            var wasCreated = await _client.CreateAsync(objectName, data);
            if (wasCreated.Success)
            {
                return wasCreated.Id;
            }

            _currentSalesForceError = wasCreated.Errors.ToString();
            return string.Empty;
        }
        catch (Exception ex)
        {
            _currentSalesForceError = ex.InnerException + "-" + ex.Message;
            return string.Empty;
        }
    }

The next method, create lead will allow you to create a lead in Salesforce with a stripped version of the Lead model. We use the create object method defined above to accomplish the task.

public async Task<string> CreateLeadAsync(NewAccountContactData accountData, int engagementScore)
    {
        if (!_isAuthenticated)
        {
            return null;
        }

        var leadStatusNew = await GetLeadStatusAsync(1);
        var defaultOwner = _defaultOwner;

        return await CreateObjectAsync("Lead",
            new Lead
            {
                FirstName = accountData.FirstName,
                LastName = accountData.LastName,
                Email = accountData.EmailAddress,
                Status = leadStatusNew.MasterLabel,
                EngagementScoreC = engagementScore,
                OwnerId = defaultOwner,
                LeadSource = "Website",
                Company = accountData.Company ?? $"{accountData.FirstName} {accountData.LastName}"
            });
    }

The method update object is the next one, it will allow you to update a previous object in Salesforce with new data.

public async Task<bool> UpdateObjectAsync(string objectName, string objectId, object data)
    {
        try
        {
            var wasUpdated = await _client.UpdateAsync(objectName, objectId, data);
            if (wasUpdated.Success)
            {
                return true;
            }

            _currentSalesForceError = wasUpdated.Errors.ToString();
            return false;
        }
        catch (Exception ex)
        {
            _currentSalesForceError = ex.InnerException + "-" + ex.Message;
            return false;
        }
    }

The following method will allow you the identify if an specific person with an email is a lead or contact.

public async Task<LeadOrContact> IsLeadOrContactAsync(string email)
    {
        if (!_isAuthenticated)
        {
            return new LeadOrContact();
        }

        var sfContact = await GetContactByEmailAsync(email);
        if (sfContact != null)
        {
            return new LeadOrContact { Contact = sfContact };
        }

        var sfLead = await GetLeadByEmailAsync(email);

        return sfLead != null ? new LeadOrContact { Lead = sfLead } : new LeadOrContact();
    }

For the method above to work, we use 3 functions. The first one Get contact by email will get a contact if it founds one from Salesforce using the provided email.

  public async Task<Contact> GetContactByEmailAsync(string email)
    {
        var query =
            $@"SELECT Id, AccountId, FirstName, LastName, Email, Phone, Title, Engagement_Score__c FROM Contact where Email = '{email}'";
        var contacts = await _client.QueryAsync<Contact>(query);
        return contacts.Records.FirstOrDefault();
    }

If it fails, it will try to get a lead instead of a contact by the email provided.

public async Task<Lead> GetLeadByEmailAsync(string email)
    {
        var leads = await _client.QueryAsync<Lead>(
            $"SELECT id, FirstName, Email, Phone, Engagement_Score__c, Status, LastName, Company, Industry, LeadSource, Title, OwnerId FROM Lead where Email = '{email}'");
        return leads.Records.FirstOrDefault();
    }

We then have the update Salesforce data method which will find one contact by email and then update the object with new data.

 public async Task<bool> UpdateSalesforceDataAsync(string email, int? engagementScore = null,
        int? statusIndex = null, NewAccountContactData accountData = null)
    {
        if (!_isAuthenticated)
        {
            return false;
        }

        var sfContact = await GetContactByEmailAsync(email);
        if (sfContact != null)
        {
            await UpdateSalesforceContactAsync(sfContact, engagementScore);
        }
        else
        {
            return await CreateSalesforceContactAsync(email, engagementScore, statusIndex, accountData);
        }

        return true;
    }

If a contact was found it will use the update salesforce contact method and update any property that is needed, in our case the engagement score. This method will use the previously defined Update object method.

   public async Task UpdateSalesforceContactAsync(Contact sfContact, int? engagementScore = null)
    {
        var contactId = sfContact.Id;
        sfContact.Id = null;
        sfContact.EngagementScore = engagementScore ?? sfContact.EngagementScore;

        var resultUpdate = await UpdateObjectAsync("Contact", contactId, sfContact);
        if (!resultUpdate)
        {
            _logService.Warning($"Cannot update contact with id {contactId}: ${_currentSalesForceError}");
        }
    }

If a contact was not found, a new contact will be created using the create Salesforce contact method

 private async Task<bool> CreateSalesforceContactAsync(string email, int? engagementScore = null,
        int? statusIndex = null, NewAccountContactData accountData = null)
    {
        var lead = await GetLeadByEmailAsync(email);

        // If you create account without subscribing, you create a new lead at that moment
        if (lead == null && statusIndex == 4 && accountData != null && engagementScore.HasValue)
        {
            await CreateLeadAsync(accountData, engagementScore.Value);
            lead = await GetLeadByEmailAsync(email);
        }

        if (lead == null)
        {
            return false;
        }

        var leadId = lead.Id;
        lead.Id = null;

        // Update lead status
        if (statusIndex.HasValue)
        {
            var currentStatus = lead.Status;
            var leadStatus = await GetAllLeadStatusAsync();
            var allStatus = leadStatus.Select(x => x.MasterLabel).ToList();
            var currentStatusIndex = allStatus.IndexOf(currentStatus);

            if (currentStatusIndex < statusIndex.Value)
            {
                var previousLeadStatus = await GetLeadStatusAsync(statusIndex.Value);
                lead.Status = previousLeadStatus?.MasterLabel;
            }
        }

        //update engagement score from contact

        if (engagementScore.HasValue)
        {
            lead.EngagementScoreC = engagementScore.Value;
        }

        var updateLead = await UpdateObjectAsync("Lead", leadId, lead);
        if (!updateLead)
        {
            _logService.Warning($"Cannot update Lead with id {leadId}");
            return false;
        }

        return await CreateTaskForLeadAsync(leadId, lead, email, statusIndex, engagementScore);
    }

This particular method, uses the get all lead status which will return all the different status a lead can be

    public async Task<IList<LeadStatus>> GetAllLeadStatusAsync()
    {
        var status = await _client.QueryAsync<LeadStatus>("SELECT SortOrder,MasterLabel FROM LeadStatus");
        var records = status.Records.OrderBy(s => s.SortOrder).ToList();

        return records;
    }

It also uses the get lead status method which returns the current lead status by position

    public async Task<LeadStatus> GetLeadStatusAsync(int position)
    {
        var status = await _client.QueryAsync<LeadStatus>("SELECT SortOrder,MasterLabel FROM LeadStatus");
        var records = status.Records.OrderBy(s => s.SortOrder).ToList();

        if (position >= 0 && position < records.Count)
        {
            return records[position];
        }

        return null;
    }

and as last method, it also uses the create task for lead method, which will create and assign a task for an specific lead provided by its email

private async Task<bool> CreateTaskForLeadAsync(string leadId, Lead lead, string email, int? statusIndex = null,
        int? engagementScore = null)
    {
        // Create task for lead
        if (statusIndex == 2)
        {
            await CreateTaskAsync(lead.Email);
        }

        // Transform lead to contact
        else if (statusIndex == 4)
        {
            var updateLead = await UpdateObjectAsync("Lead", leadId, new { Rating = "Hot" });

            if (!updateLead)
            {
                _logService.Warning($"Cannot convert Lead with id {leadId}: ${_currentSalesForceError}");
            }
            else
            {
                var contactFound = await GetContactByEmailAsync(email);
                if (contactFound == null)
                {
                    return false;
                }

                await UpdateSalesforceContactAsync(contactFound, engagementScore);
            }
        }

        return true;
    }

The create task for lead method uses the create task method if the lead is in status index 2, but can be configured to whatever is needed.

public async Task<bool> CreateTaskAsync(string email)
    {
        try
        {
            var leadOrContact = await IsLeadOrContactAsync(email);
            if (!leadOrContact.Exist)
            {
                return false;
            }

            var defaultOwner = _defaultOwner;
            var whoId = leadOrContact.IsContact ? leadOrContact.Contact.Id : leadOrContact.Lead.Id;
            var subject = leadOrContact.IsContact
                ? $"Contact ({leadOrContact.Contact.FirstName} {leadOrContact.Contact.LastName})"
                : $"Contact lead ({leadOrContact.Lead.FirstName} {leadOrContact.Lead.LastName})";
            var taskCreated = await CreateObjectAsync("Task",
                new Models.Task
                {
                    WhoId = whoId,
                    Status = "Open",
                    Priority = "Normal",
                    Subject = subject,
                    ReminderDateTime = DateTime.Now.AddDays(3).ToString("s"),
                    OwnerId = defaultOwner
                });

            return !string.IsNullOrEmpty(taskCreated);
        }
        catch (Exception e)
        {
            _logService.Warning($"Cannot create task for user with email: {email}; Errors: ${e}");
            return false;
        }
    }

In order to update user data attributes without caring if is a lead or contact, the method update user data is implemented.

    public async Task<bool> UpdateUserData(string email, int engagementScore, string name = null, string lastName = null, bool transform = false, string phoneNumber = null)
    {
        try
        {
            var leadOrContact = await IsLeadOrContactAsync(email);
            if (!leadOrContact.Exist) return false;
            if (leadOrContact.IsContact)
            {
                var contactId = leadOrContact.Contact.Id;
                leadOrContact.Contact.Id = null;
                leadOrContact.Contact.EngagementScore += engagementScore;
                leadOrContact.Contact.FirstName = string.IsNullOrEmpty(name) ? leadOrContact.Contact.FirstName : name;
                leadOrContact.Contact.LastName = string.IsNullOrEmpty(lastName) ? leadOrContact.Contact.LastName : lastName;
                leadOrContact.Contact.Phone = string.IsNullOrEmpty(phoneNumber) ? leadOrContact.Contact.Phone : phoneNumber;
                return await UpdateObjectAsync("Contact", contactId, leadOrContact.Contact);
            }
            if (leadOrContact.IsLead)
            {
                var leadId = leadOrContact.Lead.Id;
                leadOrContact.Lead.Id = null;
                leadOrContact.Lead.EngagementScoreC += engagementScore;
                leadOrContact.Lead.FirstName = string.IsNullOrEmpty(name) ? leadOrContact.Lead.FirstName : name;
                leadOrContact.Lead.LastName = string.IsNullOrEmpty(lastName) ? leadOrContact.Lead.LastName : lastName;
                leadOrContact.Lead.Phone = string.IsNullOrEmpty(phoneNumber) ? leadOrContact.Lead.Phone : phoneNumber;
                var updatedLead = await UpdateObjectAsync("Lead", leadId, leadOrContact.Lead);
                if (!transform) return updatedLead;
                return updatedLead && await TransformLeadToContact(leadId, leadOrContact.Lead);
            }
        }
        catch (Exception e)
        {
            _logService.Warning($"Cannot update user with email: {email}; Errors: ${e}");
            return false;
        }

        return false;
    }

The method verify user and create it will check if a user with email, first name and last name exists and if it does not will create a new lead

    public async Task<bool> VerifyUserOrCreateIt(string email, string name, string lastName)
    {
        try
        {
            var leadOrContact = await IsLeadOrContactAsync(email);
            if (leadOrContact.Exist) return true;

            var leadId = await CreateLeadAsync(
                new NewAccountContactData
                {
                    FirstName = name,
                    LastName = lastName,
                    EmailAddress = email
                }, 0);
            return !string.IsNullOrEmpty(leadId);
        }
        catch (Exception e)
        {
            _logService.Warning($"Cannot create user with email: {email}; Errors: ${e}");
            return false;
        }
    }

Now, if you want create an opportunity in Salesforce you can use the create opportunity method which requires an email and a description as parameters

    public async Task<string> CreateOpportunityAsync(string email, string description)
    {
        try
        {
            var leadOrContact = await IsLeadOrContactAsync(email);
            if (!leadOrContact.IsContact) return string.Empty;

            var firstName = leadOrContact.Contact.FirstName;
            var lastName = leadOrContact.Contact.LastName;
            var account = await GetAccountByEmailAndNameAsync(email, firstName, lastName);
            var opportunityCreated = await CreateObjectAsync("Opportunity",
                new Opportunity
                {
                    AccountId = account.Id,
                    Name = $"{firstName} {lastName}",
                    StageName = "Qualification",
                    CloseDate = DateTime.UtcNow.AddDays(10).ToString("yyyy-MM-dd"),
                    Description = description
                });
            return opportunityCreated;
        }
        catch (Exception e)
        {
            _logService.Warning($"Cannot create opportunity for user with email: {email}; Errors: ${e}");
            return string.Empty;
        }
    }

This method uses the get account by email and name method which as specifies tries to find one account using those parameters.

    public async Task<Account> GetAccountByEmailAndNameAsync(string email, string firstName, string lastName)
    {
        var accounts =
            await _client.QueryAsync<Account>($"SELECT id,Name,Description FROM Account where Name = '{firstName} {lastName}'");
        Account selectedAccount = null;
        foreach (var account in accounts.Records)
        {
            var contact = await GetContactByEmailAndAccountIdAsync(email, account.Id);
            if (contact != null) selectedAccount = account;
        }

        return selectedAccount;
    }

At the same time, this method uses the get contact by email and account id method to return a contact

    public async Task<Contact> GetContactByEmailAndAccountIdAsync(string email, string accountId)
    {
        var query =
            $@"SELECT Id, AccountId, Email FROM Contact where Email = '{email}' and AccountId='{accountId}'";
        var contacts = await _client.QueryAsync<Contact>(query);
        return contacts.Records.FirstOrDefault();
    }

The two methods described above have one more versions. The first one allows you to get an account only by name

    public async Task<Account> GetAccountByNameAsync(string name)
    {
        var accounts =
            await _client.QueryAsync<Account>($"SELECT id,Name,Description FROM Account where Name = '{name}'");
        return accounts.Records.FirstOrDefault();
    }

and the other one, lets you get a contact by lead and account

    public async Task<Contact> GetContactByLeadAndAccountAsync(Lead lead, Account account)
    {
        var query =
            $@"SELECT Id, AccountId, FirstName, LastName FROM Contact where FirstName = '{lead.FirstName}' and LastName='{lead.LastName}' and AccountId='{account.Id}'";
        var contacts = await _client.QueryAsync<Contact>(query);
        return contacts.Records.FirstOrDefault();
    }

We also have a method to assign an opportunity to a contact

  public async Task<bool> AssignOpportunityToContact(string opportunityId, string contactId)
    {
        var opportunityAssigned = await CreateObjectAsync("OpportunityContactRole", new OpportunityContactRole
        {
            OpportunityId = opportunityId,
            ContactId = contactId
        });
        return !string.IsNullOrEmpty(opportunityAssigned);
    }

And one last method which will transform a lead to a contact

    public async Task<bool> TransformLeadToContact(string leadId, Lead lead)
    {
        try
        {
            var updateLead = await UpdateObjectAsync("Lead", leadId, new { Rating = "Hot" });
            if (updateLead) return await UpdateUserData(lead.Email, (int)lead.EngagementScoreC,
                lead.FirstName, lead.LastName, false, lead.Phone);
        }
        catch (Exception e)
        {
            _logService.Warning($"Cannot convert Lead with email {lead.Email}: ${e}");
            return false;
        }

        return false;
    }

And that is it. You now have a basic Salesforce manager that can authenticate to your Salesforce solution and execute several tasks such as create and update leads, contacts, opportunities and tasks, transform a lead to contact and more. If you have any questions or suggestions please let me know in the comments. I hope this can help someone and as always keep learning !!!

Written by:

Jorge Cardenas

Developer with several years of experience who is passionate about technology and how to solve problems through it.

View All Posts

Leave a Reply