Additional feature to pre-determine children list to clone

This commit is contained in:
Christopher Gary committed 2021-04-14 07:34:56 -04:00
1 parent 21df2d0bd2
commit a33604b498
10 files changed
+267 -37

No files matched your search

+6
View File
@@ -20,5 +20,11 @@ package.xml
# Exclude FlexiPage # Exclude FlexiPage
*.flexipage *.flexipage
# Exclude Listviews
*.listView
# Exclude Settings
*.setting
# Exclude FieldSets # Exclude FieldSets
*.fieldSet *.fieldSet
@@ -5,6 +5,7 @@
* @version 1.0 * @version 1.0
* @group Opportunity Cloning * @group Opportunity Cloning
* @history 2021-04-02 - CJG - Created * @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 { public with sharing class OpportunityCloneService {
/** /**
@@ -23,9 +24,22 @@ public with sharing class OpportunityCloneService {
List<Schema.ChildRelationship> childRelationshipList = List<Schema.ChildRelationship> childRelationshipList =
Schema.Opportunity.getSObjectType().getDescribe().getChildRelationships(); Schema.Opportunity.getSObjectType().getDescribe().getChildRelationships();
opportunityChildrenMap = new Map<String, Schema.DescribeSObjectResult>(); 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) { for(Schema.ChildRelationship childRel:childRelationshipList) {
Schema.DescribeSObjectResult childObjRes = childRel.getChildSObject().getDescribe(); if(String.isNotBlank(childRel.getRelationshipName())){
opportunityChildrenMap.put(childObjRes.getName(),childObjRes); Schema.DescribeSObjectResult childObjRes = childRel.getChildSObject().getDescribe();
if(!restrictedRelationshipList.isEmpty()){
if(restrictedRelationshipList.contains(childObjRes.getName())){
opportunityChildrenMap.put(childRel.getRelationshipName(),childObjRes);
}
} else {
opportunityChildrenMap.put(childRel.getRelationshipName(),childObjRes);
}
}
} }
} }
/** /**
@@ -73,6 +87,7 @@ public with sharing class OpportunityCloneService {
List<String> childRelationshipsToClone) { List<String> childRelationshipsToClone) {
//dynamic query in order to find the original opportunity. //dynamic query in order to find the original opportunity.
String originalOppQueryString = ObtainQueryStringForOppAndChildren(oppId, childRelationshipsToClone); String originalOppQueryString = ObtainQueryStringForOppAndChildren(oppId, childRelationshipsToClone);
System.debug(LoggingLevel.INFO,'\n\n*****\nQuery: ' + originalOppQueryString + '\n*****\n\n');
List<Opportunity> oppList = Database.query(originalOppQueryString); List<Opportunity> oppList = Database.query(originalOppQueryString);
//if the original opp cannot be found, throw and exception //if the original opp cannot be found, throw and exception
if(oppList.isEmpty()){ if(oppList.isEmpty()){
@@ -125,7 +140,11 @@ public with sharing class OpportunityCloneService {
childQueryString += relationship + ')'; childQueryString += relationship + ')';
subqueryList.add(childQueryString); subqueryList.add(childQueryString);
} }
queryString += ', ' + String.join(subqueryList,',') + 'FROM Opportunity WHERE Id =\'' + oppRecordId + '\''; if(!subqueryList.isEmpty()){
queryString += ', ' + String.join(subqueryList,',') + 'FROM Opportunity WHERE Id =\'' + oppRecordId + '\'';
} else {
queryString += ' FROM Opportunity WHERE Id =\'' + oppRecordId + '\'';
}
return queryString; return queryString;
} }
@@ -147,6 +166,10 @@ public with sharing class OpportunityCloneService {
fieldNameList.add(fieldTokenDescribe.getName()); 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; return fieldNameList;
} }
@@ -155,6 +178,9 @@ public with sharing class OpportunityCloneService {
for(SObject rec:recList){ for(SObject rec:recList){
SObject newRec = rec.clone(false,true,false,false); SObject newRec = rec.clone(false,true,false,false);
newRec.put(parentFieldName, newParentId); newRec.put(parentFieldName, newParentId);
if(newRec.getSObjectType().getDescribe().getName().toUpperCase() == 'OPPORTUNITYLINEITEM') {
newRec.put('UnitPrice',null);
}
newList.add(newRec); newList.add(newRec);
} }
return newList; return newList;
@@ -163,6 +189,9 @@ public with sharing class OpportunityCloneService {
private static Object CreateObjectValueFromString(String fieldName, String fieldValue) { private static Object CreateObjectValueFromString(String fieldName, String fieldValue) {
Map<String,Schema.SObjectField> fieldSObjectMapping = Schema.Opportunity.getSObjectType().getDescribe().fields.getMap(); Map<String,Schema.SObjectField> fieldSObjectMapping = Schema.Opportunity.getSObjectType().getDescribe().fields.getMap();
Schema.DisplayType fieldType = fieldSObjectMapping.get(fieldName).getDescribe().getType(); Schema.DisplayType fieldType = fieldSObjectMapping.get(fieldName).getDescribe().getType();
if(String.isBlank(fieldValue)){
return null;
}
Object returnObj; Object returnObj;
switch on fieldType { switch on fieldType {
when ADDRESS, COMBOBOX, EMAIL, ENCRYPTEDSTRING, MULTIPICKLIST, PHONE, PICKLIST, STRING, TEXTAREA,URL { when ADDRESS, COMBOBOX, EMAIL, ENCRYPTEDSTRING, MULTIPICKLIST, PHONE, PICKLIST, STRING, TEXTAREA,URL {
@@ -175,7 +204,7 @@ public with sharing class OpportunityCloneService {
returnObj = Double.valueOf(fieldValue); returnObj = Double.valueOf(fieldValue);
} }
when DATE { when DATE {
returnObj = Date.parse(fieldValue); returnObj = Date.valueOf(fieldValue);
} }
when DATETIME { when DATETIME {
returnObj = Datetime.parse(fieldValue); returnObj = Datetime.parse(fieldValue);
@@ -0,0 +1,120 @@
/**
* @description The class will provide code coverage for the OpportunityCloneService
* @author Cloud Performer <cgary@cloupderformer.com>
* @version 1.0
* @group Opportunity Cloning
* @history 2021-04-12 - CJG - Created
*/
@isTest
private class OpportunityCloneServiceTest {
@TestSetup
static void makeData(){
List<Account> acctTests = new List<Account>();
List<Product2> productList = new List<Product2>();
List<PricebookEntry> pricebookItems = new List<PricebookEntry>();
List<Opportunity> oppTests = new List<Opportunity>();
List<OpportunityLineItem> oliTestLines = new List<OpportunityLineItem>();
//create accounts
acctTests.add(new Account(
Name = TestCloneUtility.GenerateRandomString(10),
AccountNumber = TestCloneUtility.GenerateRandomString(8).toUpperCase(),
BillingStreet = '1 ' + TestCloneUtility.GenerateRandomString(10) + ' Street',
BillingCity = 'Atlanta',
BillingState = 'Georgia',
BillingPostalCode = '30303',
BillingCountry = 'USA' ,
ShippingStreet = '2 ' + TestCloneUtility.GenerateRandomString(8) + ' Street',
ShippingCity = 'Atlanta',
ShippingState = 'Georgia',
ShippingPostalCode = '30303',
ShippingCountry = 'USA'
));
insert acctTests;
//create Products
for(Integer i=0; i < 5;i++){
productList.add(new Product2(
Name=TestCloneUtility.GenerateRandomString(5),
isActive = true,
ProductCode = TestCloneUtility.GenerateRandomString(6).toUpperCase()
));
}
insert productList;
//obtain test Pricbook
Id testPricebookId = test.getStandardPricebookId();
//create PricebookEntry records
for(Product2 prod:productList){
pricebookItems.add(new PricebookEntry(
IsActive = true,
Pricebook2Id = testPricebookId,
UnitPrice = 1.0,
Product2Id = prod.Id
));
}
insert pricebookItems;
//create test Opportunities
Schema.DescribeFieldResult fr = Schema.SObjectType.Opportunity.fields.StageName;
List<Schema.PicklistEntry> stageValues = fr.getPicklistValues();
for(Account a:acctTests){
oppTests.add(new Opportunity(
Name = TestCloneUtility.GenerateRandomString(8),
CloseDate = System.today().addDays(30),
StageName = stageValues[0].getValue()
));
}
insert oppTests;
//create OppLineItems
for(Opportunity opp:oppTests){
for(PricebookEntry pbe:pricebookItems){
oliTestLines.add(new OpportunityLineItem(PricebookEntryId=pbe.Id,Quantity=1.0,UnitPrice=pbe.UnitPrice,
OpportunityId=opp.Id));
}
}
insert oliTestLines;
}
@isTest
static void ObtainOpporunityChildRelationships(){
Test.startTest();
Map<String,String> resultMap = OpportunityCloneService.ObtainOpporunityChildRelationships();
System.assert(!resultMap.isEmpty());
Test.stopTest();
}
@isTest
static void ObtainCloneOpportunityFields(){
Test.startTest();
Map<String,Boolean> resultMap = OpportunityCloneService.ObtainCloneOpportunityFields('ClonedFields');
System.assert(!resultMap.isEmpty());
Test.stopTest();
}
@isTest
static void ObtainQueryStringForOppAndChildren(){
//get an opportunity
Opportunity testOpp = [SELECT Id FROM Opportunity LIMIT 1];
List<String> relStringList = new List<String>{'OpportunityLineItems'};
Test.startTest();
String queryStringTest = OpportunityCloneService.ObtainQueryStringForOppAndChildren(testOpp.Id, relStringList);
System.assert(String.isNotBlank(queryStringTest));
Test.stopTest();
}
@isTest
static void CloneOpportunityWithChildren(){
Opportunity testOpp = [SELECT Id, Name FROM Opportunity LIMIT 1];
Map<String,String> testReplacements = new Map<String,String>();
testReplacements.put('Name',TestCloneUtility.GenerateRandomString(10));
Test.startTest();
Id testResult = OpportunityCloneService.CloneOpportunityWithChildren(testOpp.Id, testReplacements,
new List<String>{'OpportunityLineItems'});
System.assertNotEquals(null, testResult);
Test.stopTest();
}
}
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>51.0</apiVersion>
<status>Active</status>
</ApexClass>
@@ -0,0 +1,39 @@
/**
* @description This is a class that provides utility methods for test classes
*/
public class TestCloneUtility {
/**
* @description This method will generate a string that is a certain length. Optionally, you can determine if the string will include numbers, or if the string will consist of numeric characters only. The option to consist of numeric characters only overrides the option of including numeric characters in the string characters.
* @param stringLength (Integer) Length of String
* @param includeNumbers (Boolean) Option to include numbers
* @param numbersOnly (Boolean) Option to only post numbers
* @return (String) Randomly Generated String
*/
static public String GenerateRandomString(Integer stringLength, Boolean includeNumbers, Boolean numbersOnly) {
String returnString = '';
String characterBucket = '';
String stringBucket = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQURSTUVWXYZ';
String numericBucket = '123456789';
characterBucket += stringBucket;
if(includeNumbers){
characterBucket += numericBucket;
}
if(numbersOnly){
characterBucket = numericBucket;
}
//now lets start to generate the String
while (returnString.length() < stringLength) {
Integer idx = Math.mod(Math.abs(Crypto.getRandomInteger()), characterBucket.length());
returnString += characterBucket.substring(idx, idx+1);
}
return returnString;
}
/**
* @description Generate a random string with wordly characters
* @param (Integer) stringLength;
* @return (String) random string
*/
static public String GenerateRandomString(Integer stringLength){
return GenerateRandomString(stringLength, false, false);
}
}
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>51.0</apiVersion>
<status>Active</status>
</ApexClass>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<CustomLabels xmlns="http://soap.sforce.com/2006/04/metadata">
<labels>
<fullName>SmartCloneChildList</fullName>
<categories>SmartClone Config</categories>
<language>en_US</language>
<protected>true</protected>
<shortDescription>SmartClone Child List</shortDescription>
<value>OpportunityLineItem</value>
</labels>
</CustomLabels>
@@ -6,19 +6,20 @@
<template for:each={formFields} for:item="formField"> <template for:each={formFields} for:item="formField">
<lightning-input-field key={formField.apiName} field-name={formField.apiName} value={formField.value}></lightning-input-field> <lightning-input-field key={formField.apiName} field-name={formField.apiName} value={formField.value}></lightning-input-field>
</template> </template>
<div class="slds-col slds-size_1-of-1">
<h2 class="slds-text-heading_medium">Child Records to Clone</h2>
</div>
<div class="slds-col slds-size_1-of-1">
<lightning-combobox name="childRelationships" label="Child Relationships" placeholder="Select To Clone"
options={childRelationshipDisplayList} onchange={handleChildRelationshipSelection}></lightning-combobox>
</div>
<div class="slds-col slds-size_1-of-1">
<lightning-pill-container items={childRelationshipSelectionList} onitemremove={handleChildSelectionRemoval}></lightning-pill-container>
</div>
<lightning-button type="submit" label="Submit"></lightning-button> <lightning-button type="submit" label="Submit"></lightning-button>
</lightning-record-edit-form> </lightning-record-edit-form>
</div> </div>
<div class="slds-col slds-size_1-of-1">
<h2 class="slds-text-heading_medium">Child Records to Clone</h2>
</div>
<div class="slds-col slds-size_1-of-1">
<lightning-combobox name="childRelationships" label="Child Relationships" placeholder="Select To Clone"
options={childRelationshipDisplayList} onchange={handleChildRelationshipSelection}></lightning-combobox>
</div>
<div class="slds-col slds-size_1-of-1">
<lightning-pill-container items={childRelationshipSelectionList} onitemremove={handleChildSelectionRemoval}></lightning-pill-container>
</div>
</div> </div>
</lightning-card> </lightning-card>
</template> </template>
@@ -1,12 +1,13 @@
import { LightningElement, api, wire, track } from 'lwc'; import { LightningElement, api, wire, track } from 'lwc';
import { getObjectInfo } from 'lightning/uiObjectInfoApi'; import { getObjectInfo } from 'lightning/uiObjectInfoApi';
import { getRecord } from 'lightning/uiRecordApi'; import { getRecord } from 'lightning/uiRecordApi';
import { NavigationMixin} from 'lightning/navigation';
import OPPORTUNITY_OBJECT from '@salesforce/schema/Opportunity'; import OPPORTUNITY_OBJECT from '@salesforce/schema/Opportunity';
import ObtainCloneOpportunityFields from '@salesforce/apex/OpportunityCloneService.ObtainCloneOpportunityFields'; import ObtainCloneOpportunityFields from '@salesforce/apex/OpportunityCloneService.ObtainCloneOpportunityFields';
import ObtainChildRelationships from '@salesforce/apex/OpportunityCloneService.ObtainOpporunityChildRelationships'; import ObtainChildRelationships from '@salesforce/apex/OpportunityCloneService.ObtainOpporunityChildRelationships';
import CloneOpportunityWithChildren from '@salesforce/apex/OpportunityCloneService.CloneOpportunityWithChildren'; import CloneOpportunityWithChildren from '@salesforce/apex/OpportunityCloneService.CloneOpportunityWithChildren';
export default class OpportunityCustomClone extends LightningElement { export default class OpportunityCustomClone extends NavigationMixin(LightningElement) {
@api recordId; @api recordId;
formObjectApiName; formObjectApiName;
@@ -100,7 +101,30 @@ export default class OpportunityCustomClone extends LightningElement {
handleSubmit(evt){ handleSubmit(evt){
evt.preventDefault(); evt.preventDefault();
var fields = evt.detail.fields; var fields = evt.detail.fields;
window.console.log('Fields Submitted: %s',JSON.stringify(fields,null,"\t")); var childRelList = [];
this.childRelationshipSelectionList.forEach( selection => {
childRelList.push(selection.value);
});
window.console.log('Child Relationships Selected: %s',JSON.stringify(childRelList,null,"\t"));
CloneOpportunityWithChildren({'oppId':this.recordId,'newValues':fields,'childRelationshipsToClone':childRelList})
.then((results)=>{
window.console.log('Successful callout. %s',JSON.stringify(results,null,"\t"));
this.sendToOpp(results);
})
.catch((err)=>{
window.console.log('There was an error attempting to clone records.');
});
}
sendToOpp(oppId){
window.console.log('Send to Opp executed.');
this[NavigationMixin.Navigate]({
type:'standard__recordPage',
attributes:{
recordId:oppId,
objectApiName:OPPORTUNITY_OBJECT.objectApiName,
actionName:'view'
}
});
} }
get formFields(){ get formFields(){
+11 -21
View File
@@ -1,36 +1,26 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Package xmlns="http://soap.sforce.com/2006/04/metadata"> <Package xmlns="http://soap.sforce.com/2006/04/metadata">
<types> <types>
<members>*</members> <members>OpportunityCloneService</members>
<members>OpportunityCloneServiceTest</members>
<members>TestCloneUtility</members>
<name>ApexClass</name> <name>ApexClass</name>
</types> </types>
<types> <types>
<members>*</members> <members>OppCustomCloneAura</members>
<name>ApexComponent</name>
</types>
<types>
<members>*</members>
<name>ApexPage</name>
</types>
<types>
<members>*</members>
<name>ApexTestSuite</name>
</types>
<types>
<members>*</members>
<name>ApexTrigger</name>
</types>
<types>
<members>*</members>
<name>AuraDefinitionBundle</name> <name>AuraDefinitionBundle</name>
</types> </types>
<types> <types>
<members>*</members> <members>SmartCloneChildList</members>
<name>CustomLabel</name>
</types>
<types>
<members>opportunityCustomClone</members>
<name>LightningComponentBundle</name> <name>LightningComponentBundle</name>
</types> </types>
<types> <types>
<members>*</members> <members>Opportunity.SmartClone</members>
<name>StaticResource</name> <name>QuickAction</name>
</types> </types>
<version>51.0</version> <version>51.0</version>
</Package> </Package>