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
*.flexipage
# Exclude Listviews
*.listView
# Exclude Settings
*.setting
# Exclude FieldSets
*.fieldSet
@@ -5,6 +5,7 @@
* @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 {
/**
@@ -23,9 +24,22 @@ public with sharing class OpportunityCloneService {
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) {
Schema.DescribeSObjectResult childObjRes = childRel.getChildSObject().getDescribe();
opportunityChildrenMap.put(childObjRes.getName(),childObjRes);
if(String.isNotBlank(childRel.getRelationshipName())){
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) {
//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()){
@@ -125,7 +140,11 @@ public with sharing class OpportunityCloneService {
childQueryString += relationship + ')';
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;
}
@@ -147,6 +166,10 @@ public with sharing class OpportunityCloneService {
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;
}
@@ -155,6 +178,9 @@ public with sharing class OpportunityCloneService {
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;
@@ -163,6 +189,9 @@ public with sharing class OpportunityCloneService {
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 {
@@ -175,7 +204,7 @@ public with sharing class OpportunityCloneService {
returnObj = Double.valueOf(fieldValue);
}
when DATE {
returnObj = Date.parse(fieldValue);
returnObj = Date.valueOf(fieldValue);
}
when DATETIME {
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">
<lightning-input-field key={formField.apiName} field-name={formField.apiName} value={formField.value}></lightning-input-field>
</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-record-edit-form>
</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>
</lightning-card>
</template>
@@ -1,12 +1,13 @@
import { LightningElement, api, wire, track } from 'lwc';
import { getObjectInfo } from 'lightning/uiObjectInfoApi';
import { getRecord } from 'lightning/uiRecordApi';
import { NavigationMixin} from 'lightning/navigation';
import OPPORTUNITY_OBJECT from '@salesforce/schema/Opportunity';
import ObtainCloneOpportunityFields from '@salesforce/apex/OpportunityCloneService.ObtainCloneOpportunityFields';
import ObtainChildRelationships from '@salesforce/apex/OpportunityCloneService.ObtainOpporunityChildRelationships';
import CloneOpportunityWithChildren from '@salesforce/apex/OpportunityCloneService.CloneOpportunityWithChildren';
export default class OpportunityCustomClone extends LightningElement {
export default class OpportunityCustomClone extends NavigationMixin(LightningElement) {
@api recordId;
formObjectApiName;
@@ -100,7 +101,30 @@ export default class OpportunityCustomClone extends LightningElement {
handleSubmit(evt){
evt.preventDefault();
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(){
+11 -21
View File
@@ -1,36 +1,26 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
<types>
<members>*</members>
<members>OpportunityCloneService</members>
<members>OpportunityCloneServiceTest</members>
<members>TestCloneUtility</members>
<name>ApexClass</name>
</types>
<types>
<members>*</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>
<members>OppCustomCloneAura</members>
<name>AuraDefinitionBundle</name>
</types>
<types>
<members>*</members>
<members>SmartCloneChildList</members>
<name>CustomLabel</name>
</types>
<types>
<members>opportunityCustomClone</members>
<name>LightningComponentBundle</name>
</types>
<types>
<members>*</members>
<name>StaticResource</name>
<members>Opportunity.SmartClone</members>
<name>QuickAction</name>
</types>
<version>51.0</version>
</Package>