diff --git a/src/main/java/com/impactupgrade/nucleus/client/SfdcClient.java b/src/main/java/com/impactupgrade/nucleus/client/SfdcClient.java index 5eac734c0..265edd781 100644 --- a/src/main/java/com/impactupgrade/nucleus/client/SfdcClient.java +++ b/src/main/java/com/impactupgrade/nucleus/client/SfdcClient.java @@ -695,6 +695,13 @@ public Optional getUserByEmail(String email) throws ConnectionException return querySingle(query); } + protected static final String TASK_FIELDS = "Id, WhoId, OwnerId, Subject, description, status, priority, activityDate"; + + public Optional getTaskBySubject(String subject) throws ConnectionException, InterruptedException { + String query = "select " + TASK_FIELDS + " from task where subject = '" + subject + "'"; + return querySingle(query); + } + /** * Use with caution, it retrieves ALL active users. Unsuitable for orgs with many users. */ diff --git a/src/main/java/com/impactupgrade/nucleus/controller/CrmController.java b/src/main/java/com/impactupgrade/nucleus/controller/CrmController.java index fed4be7ea..7549c2e5f 100644 --- a/src/main/java/com/impactupgrade/nucleus/controller/CrmController.java +++ b/src/main/java/com/impactupgrade/nucleus/controller/CrmController.java @@ -10,6 +10,7 @@ import com.impactupgrade.nucleus.environment.EnvironmentFactory; import com.impactupgrade.nucleus.model.ContactFormData; import com.impactupgrade.nucleus.model.ContactSearch; +import com.impactupgrade.nucleus.model.CrmActivity; import com.impactupgrade.nucleus.model.CrmContact; import com.impactupgrade.nucleus.model.CrmCustomField; import com.impactupgrade.nucleus.model.CrmImportEvent; @@ -17,6 +18,7 @@ import com.impactupgrade.nucleus.security.SecurityUtil; import com.impactupgrade.nucleus.service.segment.CrmService; import com.impactupgrade.nucleus.util.GoogleSheetsUtil; +import org.apache.commons.collections.CollectionUtils; import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.CSVParser; import org.apache.commons.csv.CSVRecord; @@ -60,6 +62,57 @@ public CrmController(EnvironmentFactory envFactory) { this.envFactory = envFactory; } + //TODO: remove once done with testing + @Path("/message") + @POST + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public Response insertTask( + Message message, + @Context HttpServletRequest request + ) throws Exception { + Environment env = envFactory.init(request); + //SecurityUtil.verifyApiKey(env); + + CrmActivity crmActivity; + if (!Strings.isNullOrEmpty(message.conversationId)) { + Optional crmActivityOptional = env.primaryCrmService().getActivityByTypeAndConversationId(CrmActivity.Type.SMS_CONVERSATION, message.conversationId); + if (crmActivityOptional.isPresent()) { + // Existing conversation + crmActivity = crmActivityOptional.get(); + } else { + // New conversation + crmActivity = new CrmActivity(); + crmActivity.type = CrmActivity.Type.SMS_CONVERSATION; + crmActivity.conversationId = message.conversationId; + } + addMessage(message, crmActivity); + } else { + crmActivity = new CrmActivity(); + crmActivity.type = CrmActivity.Type.SMS_CONVERSATION; + crmActivity.messageIds = List.of(message.id); + } + env.primaryCrmService().upsertActivity(crmActivity); + + + return Response.ok(crmActivity).build(); + } + + private static final class Message { + public String id; + public String conversationId; + } + + private static void addMessage(Message message, CrmActivity crmActivity) { + List ids = new ArrayList<>(); + if (CollectionUtils.isNotEmpty(crmActivity.messageIds)) { + ids.addAll(crmActivity.messageIds); + } + + ids.add(message.id); + crmActivity.messageIds = ids; + } + /** * Retrieves a contact from the primary CRM using a variety of optional parameters. For use in external integrations, * like Twilio Studio's retrieval of the CRM's Contact ID by phone number. diff --git a/src/main/java/com/impactupgrade/nucleus/environment/EnvironmentConfig.java b/src/main/java/com/impactupgrade/nucleus/environment/EnvironmentConfig.java index adfd71a6f..4ff9dfcb3 100644 --- a/src/main/java/com/impactupgrade/nucleus/environment/EnvironmentConfig.java +++ b/src/main/java/com/impactupgrade/nucleus/environment/EnvironmentConfig.java @@ -200,6 +200,7 @@ public static class Salesforce extends Platform { public CRMFieldDefinitions fieldDefinitions = new CRMFieldDefinitions(); public SalesforceCustomFields customQueryFields = new SalesforceCustomFields(); public String defaultCampaignId = ""; + public String defaultTaskAssignee = ""; public static class SalesforceCustomFields { public Set account = new HashSet<>(); diff --git a/src/main/java/com/impactupgrade/nucleus/model/CrmActivity.java b/src/main/java/com/impactupgrade/nucleus/model/CrmActivity.java new file mode 100644 index 000000000..0d4f6a776 --- /dev/null +++ b/src/main/java/com/impactupgrade/nucleus/model/CrmActivity.java @@ -0,0 +1,21 @@ +package com.impactupgrade.nucleus.model; + +import java.util.List; + +public class CrmActivity { + + public String id; + public String targetId; + public String assignTo; // User ID + + public CrmActivity.Type type; + public String conversationId; + + public List messageIds; + + public enum Type { + SMS_CONVERSATION, + EMAIL; + } + +} diff --git a/src/main/java/com/impactupgrade/nucleus/model/CrmTask.java b/src/main/java/com/impactupgrade/nucleus/model/CrmTask.java index 5af5a1238..1230ad6bb 100644 --- a/src/main/java/com/impactupgrade/nucleus/model/CrmTask.java +++ b/src/main/java/com/impactupgrade/nucleus/model/CrmTask.java @@ -4,6 +4,7 @@ public class CrmTask { + public String id; public String targetId; public String assignTo; // User ID public String subject; diff --git a/src/main/java/com/impactupgrade/nucleus/service/segment/BasicCrmService.java b/src/main/java/com/impactupgrade/nucleus/service/segment/BasicCrmService.java index 503d7c7cf..ec1167b2f 100644 --- a/src/main/java/com/impactupgrade/nucleus/service/segment/BasicCrmService.java +++ b/src/main/java/com/impactupgrade/nucleus/service/segment/BasicCrmService.java @@ -1,6 +1,7 @@ package com.impactupgrade.nucleus.service.segment; import com.impactupgrade.nucleus.model.CrmAccount; +import com.impactupgrade.nucleus.model.CrmActivity; import com.impactupgrade.nucleus.model.CrmContact; import com.impactupgrade.nucleus.model.CrmCustomField; import com.impactupgrade.nucleus.model.CrmDonation; @@ -126,6 +127,18 @@ default String insertTask(CrmTask crmTask) throws Exception { return null; } + default String updateTask(CrmTask crmTask) throws Exception { + return null; + } + + default String upsertActivity(CrmActivity crmActivity) throws Exception { + return null; + } + + default Optional getActivityByTypeAndConversationId(CrmActivity.Type type, String externalId) throws Exception { + return Optional.empty(); + } + default Map> getActiveCampaignsByContactIds(List contactIds) throws Exception { return null; } diff --git a/src/main/java/com/impactupgrade/nucleus/service/segment/BloomerangCrmService.java b/src/main/java/com/impactupgrade/nucleus/service/segment/BloomerangCrmService.java index d20e37b9e..0d31de54f 100644 --- a/src/main/java/com/impactupgrade/nucleus/service/segment/BloomerangCrmService.java +++ b/src/main/java/com/impactupgrade/nucleus/service/segment/BloomerangCrmService.java @@ -15,6 +15,7 @@ import com.impactupgrade.nucleus.environment.EnvironmentConfig; import com.impactupgrade.nucleus.model.ContactSearch; import com.impactupgrade.nucleus.model.CrmAccount; +import com.impactupgrade.nucleus.model.CrmActivity; import com.impactupgrade.nucleus.model.CrmAddress; import com.impactupgrade.nucleus.model.CrmContact; import com.impactupgrade.nucleus.model.CrmCustomField; @@ -574,6 +575,21 @@ public String insertTask(CrmTask crmTask) throws Exception { return null; } + @Override + public String updateTask(CrmTask crmTask) throws Exception { + return null; + } + + @Override + public String upsertActivity(CrmActivity crmActivity) throws Exception { + return null; + } + + @Override + public Optional getActivityByTypeAndConversationId(CrmActivity.Type type, String externalId) throws Exception { + return Optional.empty(); + } + @Override public List insertCustomFields(String layoutName, List crmCustomFields) { return null; diff --git a/src/main/java/com/impactupgrade/nucleus/service/segment/CrmService.java b/src/main/java/com/impactupgrade/nucleus/service/segment/CrmService.java index 7cfb38ebf..1d4ed934b 100644 --- a/src/main/java/com/impactupgrade/nucleus/service/segment/CrmService.java +++ b/src/main/java/com/impactupgrade/nucleus/service/segment/CrmService.java @@ -8,6 +8,7 @@ import com.impactupgrade.nucleus.environment.EnvironmentConfig; import com.impactupgrade.nucleus.model.ContactSearch; import com.impactupgrade.nucleus.model.CrmAccount; +import com.impactupgrade.nucleus.model.CrmActivity; import com.impactupgrade.nucleus.model.CrmContact; import com.impactupgrade.nucleus.model.CrmCustomField; import com.impactupgrade.nucleus.model.CrmDonation; @@ -224,6 +225,9 @@ default void updateContact(OpportunityEvent opportunityEvent) throws Exception { Optional getUserById(String id) throws Exception; Optional getUserByEmail(String email) throws Exception; String insertTask(CrmTask crmTask) throws Exception; + String updateTask(CrmTask crmTask) throws Exception; + String upsertActivity(CrmActivity crmActivity) throws Exception; + Optional getActivityByTypeAndConversationId(CrmActivity.Type type, String externalId) throws Exception; List insertCustomFields(String layoutName, List crmCustomFields); diff --git a/src/main/java/com/impactupgrade/nucleus/service/segment/HubSpotCrmService.java b/src/main/java/com/impactupgrade/nucleus/service/segment/HubSpotCrmService.java index 7e6c9d9e8..f7f041d66 100644 --- a/src/main/java/com/impactupgrade/nucleus/service/segment/HubSpotCrmService.java +++ b/src/main/java/com/impactupgrade/nucleus/service/segment/HubSpotCrmService.java @@ -42,6 +42,7 @@ import com.impactupgrade.nucleus.environment.EnvironmentConfig; import com.impactupgrade.nucleus.model.ContactSearch; import com.impactupgrade.nucleus.model.CrmAccount; +import com.impactupgrade.nucleus.model.CrmActivity; import com.impactupgrade.nucleus.model.CrmAddress; import com.impactupgrade.nucleus.model.CrmContact; import com.impactupgrade.nucleus.model.CrmCustomField; @@ -270,6 +271,21 @@ public String insertTask(CrmTask crmTask) throws Exception { return response == null ? null : response.getId() + ""; } + @Override + public String updateTask(CrmTask crmTask) throws Exception { + return null; + } + + @Override + public String upsertActivity(CrmActivity crmActivity) throws Exception { + return null; + } + + @Override + public Optional getActivityByTypeAndConversationId(CrmActivity.Type type, String externalId) throws Exception { + return Optional.empty(); + } + @Override public List insertCustomFields(String layoutName, List crmCustomFields) { Map> propertiesByObjectTypes = crmCustomFields.stream() diff --git a/src/main/java/com/impactupgrade/nucleus/service/segment/NoOpCrmService.java b/src/main/java/com/impactupgrade/nucleus/service/segment/NoOpCrmService.java index 4fbfb3165..db393d57f 100644 --- a/src/main/java/com/impactupgrade/nucleus/service/segment/NoOpCrmService.java +++ b/src/main/java/com/impactupgrade/nucleus/service/segment/NoOpCrmService.java @@ -4,6 +4,7 @@ import com.impactupgrade.nucleus.environment.EnvironmentConfig; import com.impactupgrade.nucleus.model.ContactSearch; import com.impactupgrade.nucleus.model.CrmAccount; +import com.impactupgrade.nucleus.model.CrmActivity; import com.impactupgrade.nucleus.model.CrmContact; import com.impactupgrade.nucleus.model.CrmCustomField; import com.impactupgrade.nucleus.model.CrmDonation; @@ -211,6 +212,21 @@ public String insertTask(CrmTask crmTask) throws Exception { return null; } + @Override + public String updateTask(CrmTask crmTask) throws Exception { + return null; + } + + @Override + public String upsertActivity(CrmActivity crmActivity) throws Exception { + return null; + } + + @Override + public Optional getActivityByTypeAndConversationId(CrmActivity.Type type, String externalId) throws Exception { + return null; + } + @Override public List insertCustomFields(String layoutName, List crmCustomFields) { return Collections.emptyList(); diff --git a/src/main/java/com/impactupgrade/nucleus/service/segment/SfdcCrmService.java b/src/main/java/com/impactupgrade/nucleus/service/segment/SfdcCrmService.java index 7bd9c5062..d4e793d37 100644 --- a/src/main/java/com/impactupgrade/nucleus/service/segment/SfdcCrmService.java +++ b/src/main/java/com/impactupgrade/nucleus/service/segment/SfdcCrmService.java @@ -4,6 +4,7 @@ package com.impactupgrade.nucleus.service.segment; +import com.google.common.base.Splitter; import com.google.common.base.Strings; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; @@ -16,6 +17,7 @@ import com.impactupgrade.nucleus.environment.EnvironmentConfig; import com.impactupgrade.nucleus.model.ContactSearch; import com.impactupgrade.nucleus.model.CrmAccount; +import com.impactupgrade.nucleus.model.CrmActivity; import com.impactupgrade.nucleus.model.CrmAddress; import com.impactupgrade.nucleus.model.CrmCampaign; import com.impactupgrade.nucleus.model.CrmContact; @@ -33,6 +35,7 @@ import com.sforce.soap.metadata.FieldType; import com.sforce.soap.partner.sobject.SObject; import com.sforce.ws.ConnectionException; +import org.apache.commons.collections.CollectionUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -227,6 +230,89 @@ public String insertTask(CrmTask crmTask) throws Exception { return sfdcClient.insert(task).getId(); } + @Override + public String updateTask(CrmTask crmTask) throws Exception { + SObject task = new SObject("Task"); + setTaskFields(task, crmTask); + return sfdcClient.update(task).getId(); + } + + @Override + public String upsertActivity(CrmActivity crmActivity) throws Exception { + CrmTask crmTask = toCrmTask(crmActivity); + if (Strings.isNullOrEmpty(crmActivity.id)) { + return insertTask(crmTask); + } else { + return updateTask(crmTask); + } + } + + @Override + public Optional getActivityByTypeAndConversationId(CrmActivity.Type type, String externalId) throws Exception { + String subject = type + ":" + externalId; + Optional sObjectO = sfdcClient.getTaskBySubject(subject); + CrmTask crmTask = sObjectO.map(this::toCrmTask).orElse(null); + return Optional.of(toCrmActivity(crmTask)); + } + + protected CrmTask toCrmTask(SObject sObject) { + CrmTask crmTask = new CrmTask( + //sObject.getField("WhoId").toString(), + null, + sObject.getField("OwnerId").toString(), + sObject.getField("Subject").toString(), + sObject.getField("Description").toString(), + //CrmTask.Status.valueOf(sObject.getField("Status").toString()), + null, + //CrmTask.Priority.valueOf(sObject.getField("Priority").toString()) + null, + null); + crmTask.id = sObject.getId(); + return crmTask; + } + + private CrmTask toCrmTask(CrmActivity crmActivity) { + CrmTask crmTask = new CrmTask(); + crmTask.id = crmActivity.id; + crmTask.subject = crmActivity.type.name(); + if (!Strings.isNullOrEmpty(crmActivity.conversationId)) { + crmTask.subject = crmTask.subject + ":" + crmActivity.conversationId; + } + if (CollectionUtils.isNotEmpty(crmActivity.messageIds)) { + crmTask.description = crmActivity.messageIds.stream() + .collect(Collectors.joining(",")); + } + + crmTask.assignTo = env.getConfig().salesforce.defaultTaskAssignee; + crmTask.status = CrmTask.Status.TO_DO; + crmTask.priority = CrmTask.Priority.MEDIUM; + + return crmTask; + } + + private CrmActivity toCrmActivity(CrmTask crmTask) { + CrmActivity crmActivity = new CrmActivity(); + + crmActivity.id = crmTask.id; + crmActivity.targetId = crmTask.targetId; + crmActivity.assignTo = crmTask.assignTo; + + String subject = crmTask.subject; + String[] split = subject.split(":"); + + crmActivity.type = CrmActivity.Type.valueOf(split[0]); + if (split.length > 1) { + crmActivity.conversationId = split[1]; + } + if (!Strings.isNullOrEmpty(crmTask.description)) { + crmActivity.messageIds = Splitter.on(",") + .trimResults() + .splitToList(crmTask.description); + } + + return crmActivity; + } + @Override public List insertCustomFields(String layoutName, List crmCustomFields) { try { @@ -269,6 +355,9 @@ public EnvironmentConfig.CRMFieldDefinitions getFieldDefinitions() { } protected void setTaskFields(SObject task, CrmTask crmTask) { + if (crmTask != null) { + task.setField("Id", crmTask.id); + } task.setField("WhoId", crmTask.targetId); task.setField("OwnerId", crmTask.assignTo); task.setField("Subject", crmTask.subject); diff --git a/src/main/java/com/impactupgrade/nucleus/service/segment/SharePointCrmService.java b/src/main/java/com/impactupgrade/nucleus/service/segment/SharePointCrmService.java index 5691513f4..681e4fb10 100644 --- a/src/main/java/com/impactupgrade/nucleus/service/segment/SharePointCrmService.java +++ b/src/main/java/com/impactupgrade/nucleus/service/segment/SharePointCrmService.java @@ -9,6 +9,7 @@ import com.impactupgrade.nucleus.environment.EnvironmentConfig; import com.impactupgrade.nucleus.model.ContactSearch; import com.impactupgrade.nucleus.model.CrmAccount; +import com.impactupgrade.nucleus.model.CrmActivity; import com.impactupgrade.nucleus.model.CrmContact; import com.impactupgrade.nucleus.model.CrmCustomField; import com.impactupgrade.nucleus.model.CrmDonation; @@ -405,6 +406,21 @@ public List insertCustomFields(String layoutName, List getActivityByTypeAndConversationId(CrmActivity.Type type, String externalId) throws Exception { + return Optional.empty(); + } + @Override public EnvironmentConfig.CRMFieldDefinitions getFieldDefinitions() { return null;