Files
smartclone/force-app/main/default/classes/OpportunityCloneService.cls
T

231 lines
11 KiB
OpenEdge ABL

/**
* @description This service exposes methods that contain logic to make cloning an opportunity
* much easier and more functional.
* @author Cloud Performer Inc. <cgary@cloudperformer.com>
* @version 1.0
* @group Opportunity Cloning
* @history 2021-04-02 - CJG - Created
* 2021-04-12 - CJG - Added setting to restrict child records shown to a particular List
*/
public with sharing class OpportunityCloneService {
/**
* @description exception class that is thrown when something happens in the opportunity
* clonde process.
*/
public class CloneException extends System.Exception {}
/**
* @description Map of Child relationship objects to the opportunity.
*/
public static Map<String,Schema.DescribeSObjectResult> opportunityChildrenMap;
/**
* @description static constructor that will populate the opportunityChildrenMap
*/
static {
List<Schema.ChildRelationship> childRelationshipList =
Schema.Opportunity.getSObjectType().getDescribe().getChildRelationships();
opportunityChildrenMap = new Map<String, Schema.DescribeSObjectResult>();
List<String> restrictedRelationshipList = new List<String>();
String restrictedChildList = Label.SmartCloneChildList;
if(String.isNotBlank(restrictedChildList)){
restrictedRelationshipList = restrictedChildList.split(';');
}
for(Schema.ChildRelationship childRel:childRelationshipList) {
if(String.isNotBlank(childRel.getRelationshipName())){
Schema.DescribeSObjectResult childObjRes = childRel.getChildSObject().getDescribe();
if(!restrictedRelationshipList.isEmpty() && restrictedRelationshipList[0] != 'N/A'){
if(restrictedRelationshipList.contains(childObjRes.getName())){
opportunityChildrenMap.put(childRel.getRelationshipName(),childObjRes);
}
} else {
opportunityChildrenMap.put(childRel.getRelationshipName(),childObjRes);
}
}
}
}
/**
* @description Lightning Exposed method that will provide a map of Child relationships
* to an Opportunity that can be cloned.
* @return Map<String,String> Map where the key represents the relationship name and the value is the plural label
* of the child object.
*/
@AuraEnabled(cacheable=true)
public static Map<String,String> ObtainOpporunityChildRelationships(){
//return new List<String>(opportunityChildrenMap.keySet());
Map<String,String> relationshipMap = new Map<String,String>();
for(String key:opportunityChildrenMap.keySet()){
Schema.DescribeSObjectResult res = opportunityChildrenMap.get(key);
if(res.isAccessible() && res.isCreateable()){
String pluralLabel = res.getLabelPlural();
relationshipMap.put(key,pluralLabel);
}
}
return relationshipMap;
}
/**
* @description Lightning Exposed method that will provide a Map of fields in a fieldset on the
* opportunity.
* @param fieldsetName The name of the fieldset to use
* @return Map<String,Boolean> Map where the key is the apiFieldName and the value is whether the field is marked as
* 'required' in the fieldset.
*/
@AuraEnabled(cacheable=true)
public static Map<String, Boolean> ObtainCloneOpportunityFields(String fieldsetName){
Map<String,Boolean> fieldsToReturn = new Map<String, Boolean>();
Map<String,Schema.FieldSet> fieldSetMap = Schema.Opportunity.getSObjectType().getDescribe().fieldsets.getMap();
Schema.FieldSet fieldSetItem = fieldSetMap.get(fieldsetName);
if(fieldSetItem == null){
throw new OpportunityCloneService.CloneException('No field set named ' + fieldsetName + 'found on the Opportunity.');
}
for(Schema.FieldSetMember fieldMember:fieldSetMap.get(fieldsetName).getFields()) {
fieldsToReturn.put(fieldMember.getFieldPath(),fieldMember.getRequired());
}
return fieldsToReturn;
}
@AuraEnabled
public static Id CloneOpportunityWithChildren(Id oppId, Map<String,String> newValues,
List<String> childRelationshipsToClone) {
//dynamic query in order to find the original opportunity.
String originalOppQueryString = ObtainQueryStringForOppAndChildren(oppId, childRelationshipsToClone);
System.debug(LoggingLevel.INFO,'\n\n*****\nQuery: ' + originalOppQueryString + '\n*****\n\n');
List<Opportunity> oppList = Database.query(originalOppQueryString);
//if the original opp cannot be found, throw and exception
if(oppList.isEmpty()){
throw new AuraHandledException('There was a problem obtaining original opportunity.');
}
Opportunity originalOpp = oppList[0];
//clone the original opportunity
Opportunity clonedOpp = originalOpp.clone(false,true,false,false);
//replace the values on the Opportunity with the new ones
for(String fieldName:newValues.keySet()){
clonedOpp.put(fieldName, CreateObjectValueFromString(fieldName,newValues.get(fieldName)));
}
//insert the new Opportunity
try{
insert clonedOpp;
} catch(System.Exception ex){
System.debug(LoggingLevel.INFO,'\n\n*****\nProblem creating new Opportunity: ' + ex.getMessage() + '\n*****\n\n');
throw new AuraHandledException('Unable to create new opportunity.');
}
//now we try to loop through the children and clone the records
List<Schema.ChildRelationship> childRelationships =
Schema.Opportunity.getSObjectType().getDescribe().getChildRelationships();
List<SObject> childRecsToInsert = new List<SObject>();
for(Schema.ChildRelationship childRel:childRelationships){
//if the child relationship name was passed in, get the records
if(childRelationshipsToClone.contains(childRel.getRelationshipName())){
List<SObject> childRecs = originalOpp.getSObjects(childRel.getRelationshipName());
childRecsToInsert.addAll(CloneAndReassociateRecords(childRecs,
childRel.getField().getDescribe().getName(),clonedOpp.Id));
}
}
if(!childRecsToInsert.isEmpty()){
try{
insert childRecsToInsert;
} catch(System.Exception ex) {
System.debug(LoggingLevel.INFO,'\n\n*****\nError cloning children: ' + ex.getMessage() + '\n*****\n\n');
throw new AuraHandledException('Unable to clone children.');
}
}
return clonedOpp.Id;
}
public static String ObtainQueryStringForOppAndChildren(Id oppRecordId, List<String> relationships) {
String queryString = 'SELECT ';
queryString += String.join(ObtainFieldsForObject('Opportunity'),',');
List<String> subqueryList = new List<String>();
for(String relationship:relationships){
Schema.DescribeSObjectResult childObject = opportunityChildrenMap.get(relationship);
String childQueryString = '(SELECT ' + String.join(ObtainFieldsForObject(childObject.getName()),',') + ' FROM ';
childQueryString += relationship + ')';
subqueryList.add(childQueryString);
}
if(!subqueryList.isEmpty()){
queryString += ', ' + String.join(subqueryList,',') + 'FROM Opportunity WHERE Id =\'' + oppRecordId + '\'';
} else {
queryString += ' FROM Opportunity WHERE Id =\'' + oppRecordId + '\'';
}
return queryString;
}
/**
* @description this method is responsible for getting the list of fields for a particular object
*/
private static List<String> ObtainFieldsForObject(String objectName) {
List<String> fieldNameList;
List<String> objectListToDescribe = new List<String>{objectName};
List<Schema.DescribeSObjectResult> describeItem = System.Schema.describeSObjects(objectListToDescribe);
if(describeItem.isEmpty()) {
throw new OpportunityCloneService.CloneException('Could not find info for ' + objectName + '.');
}
Map<String, Schema.SObjectField> fieldSObjectMapping = describeItem[0].fields.getMap();
fieldNameList = new List<String>();
for(Schema.SObjectField fieldToken:fieldSObjectMapping.values()) {
Schema.DescribeFieldResult fieldTokenDescribe = fieldToken.getDescribe();
if(fieldTokenDescribe.isCreateable() && fieldTokenDescribe.isUpdateable()) {
fieldNameList.add(fieldTokenDescribe.getName());
}
}
//need to account for weird objects where not all of the fields come back
if(objectName.toUpperCase() == 'OPPORTUNITYLINEITEM'){
fieldNameList.add('PricebookEntryId');
}
return fieldNameList;
}
private static List<SObject> CloneAndReassociateRecords(List<SObject> recList, String parentFieldName, Id newParentId) {
List<SObject> newList = new List<sObject>();
for(SObject rec:recList){
SObject newRec = rec.clone(false,true,false,false);
newRec.put(parentFieldName, newParentId);
if(newRec.getSObjectType().getDescribe().getName().toUpperCase() == 'OPPORTUNITYLINEITEM') {
newRec.put('UnitPrice',null);
}
newList.add(newRec);
}
return newList;
}
private static Object CreateObjectValueFromString(String fieldName, String fieldValue) {
Map<String,Schema.SObjectField> fieldSObjectMapping = Schema.Opportunity.getSObjectType().getDescribe().fields.getMap();
Schema.DisplayType fieldType = fieldSObjectMapping.get(fieldName).getDescribe().getType();
if(String.isBlank(fieldValue)){
return null;
}
Object returnObj;
switch on fieldType {
when ADDRESS, COMBOBOX, EMAIL, ENCRYPTEDSTRING, MULTIPICKLIST, PHONE, PICKLIST, STRING, TEXTAREA,URL {
returnObj = fieldValue;
}
when BOOLEAN {
returnObj = Boolean.valueOf(fieldValue);
}
when CURRENCY, DOUBLE, PERCENT {
returnObj = Double.valueOf(fieldValue);
}
when DATE {
returnObj = Date.valueOf(fieldValue);
}
when DATETIME {
returnObj = Datetime.valueOf(fieldValue);
}
when ID {
returnObj = Id.valueOf(fieldValue);
}
when INTEGER {
returnObj = Integer.valueOf(fieldValue);
}
when LONG {
returnObj = Long.valueOf(fieldValue);
}
when TIME {
returnObj = fieldValue;
}
when else {
returnObj = fieldValue;
}
}
return returnObj;
}
}