Additional feature to pre-determine children list to clone
This commit is contained in:
1 parent
21df2d0bd2
commit
a33604b498
10 files changed
+258
-28
No files matched your search
@@ -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) {
|
||||
if(String.isNotBlank(childRel.getRelationshipName())){
|
||||
Schema.DescribeSObjectResult childObjRes = childRel.getChildSObject().getDescribe();
|
||||
opportunityChildrenMap.put(childObjRes.getName(),childObjRes);
|
||||
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);
|
||||
}
|
||||
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,9 +6,6 @@
|
||||
<template for:each={formFields} for:item="formField">
|
||||
<lightning-input-field key={formField.apiName} field-name={formField.apiName} value={formField.value}></lightning-input-field>
|
||||
</template>
|
||||
<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>
|
||||
@@ -19,6 +16,10 @@
|
||||
<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>
|
||||
</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
@@ -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>
|
||||
Reference in new issue
Block a user