In today's fast-paced business environment, managing and replicating orders efficiently is a key driver of success. This is particularly true when there's a need to recreate similar orders for different accounts. Manually tackling this task is not only time-consuming but also prone to errors. In this blog, we delve into an automated solution for cloning orders and their related products in Salesforce, attaching them to a new account using a custom Apex class and a screen flow.
Business Need
Efficiency in Order Management
Businesses often need to duplicate existing orders for different accounts. Doing this manually is not only labor-intensive but also increases the likelihood of errors.
Customization and Flexibility
Each order might require minor adjustments, like changing the associated account or tweaking product details. Automation should provide this level of customization.
Data Integrity
It's crucial that the cloning process maintains the integrity of the original data while allowing necessary modifications.
Solution Overview
Apex Class Implementation
The `OrderTreeCloneInvocable` Apex class is engineered to simplify the cloning process. It duplicates an existing order along with its related products, linking the clone to the selected account.
public with sharing class OrderTreeCloneInvocable {
private static Map<String, Schema.SObjectType> globalDescribeMap{
get{
if(globalDescribeMap == null){
globalDescribeMap = Schema.getGlobalDescribe();
}
return globalDescribeMap;
}
set;
}
@InvocableMethod(label='Clone Order Tree' description='Clones the order and all related order products and child usage summaries' category='Billing')
public static List<Results> cloneOrderTree(List<Requests> requestList) {
Results res = new Results();
Savepoint savepoint = Database.setSavepoint();
try{
//Only one order at a time
Requests req = requestList[0];
//Get new Account with Billing Account info
Account newAcc = [SELECT Id, BillingCity, BillingCountry, BillingGeocodeAccuracy, BillingLatitude,
BillingLongitude, BillingPostalCode, BillingState, ShippingCity, ShippingCountry, ShippingGeocodeAccuracy,
ShippingLatitude, ShippingLongitude, ShippingPostalCode, ShippingState, ShippingStreet
FROM Account
WHERE Id = :req.accountId];
//clone the order
String orderId = req.orderId;
String queryString = 'SELECT '+getAllFields('Order')+' FROM Order WHERE Id = :orderId';
Order originalOrder = Database.query(queryString);
if(!String.isBlank(originalOrder.Name) || !originalOrder.Name.containsIgnoreCase('_Mother Order')){
originalOrder.OrderName__c += '_Mother Order';
update originalOrder;
}
Order clonedOrder = originalOrder.clone(false, true, req.preserveReadonlyTimestamps, false);
clonedOrder.AccountId = newAcc.Id;
clonedOrder.ContractId = originalOrder.ContractId;
clonedOrder.OrderName__c = originalOrder.OrderName__c.remove('_Mother Order');
clonedOrder.SBQQ__Contracted__c = false;
clonedOrder.Status = 'Draft';
clonedOrder.BillingCity = newAcc.BillingCity;
clonedOrder.BillingCountry = newAcc.BillingCountry;
clonedOrder.BillingGeocodeAccuracy = newAcc.BillingGeocodeAccuracy;
clonedOrder.BillingLatitude = newAcc.BillingLatitude;
clonedOrder.BillingLongitude = newAcc.BillingLongitude;
clonedOrder.BillingPostalCode = newAcc.BillingPostalCode;
clonedOrder.BillingState = newAcc.BillingState;
clonedOrder.ShippingCity = newAcc.ShippingCity;
clonedOrder.ShippingCountry = newAcc.ShippingCountry;
clonedOrder.ShippingGeocodeAccuracy = newAcc.ShippingGeocodeAccuracy;
clonedOrder.ShippingLatitude = newAcc.ShippingLatitude;
clonedOrder.ShippingLongitude = newAcc.ShippingLongitude;
clonedOrder.ShippingPostalCode = newAcc.ShippingPostalCode;
clonedOrder.ShippingState = newAcc.ShippingState;
clonedOrder.ShippingStreet = newAcc.ShippingStreet;
SBQQ.TriggerControl.disable();
insert clonedOrder;
res.clonedOrderId = clonedOrder.Id;
String originalOrderId = originalOrder.Id;
//clone order products
Map<Id,Id> oldToNewOrderProductIdMap = new Map<Id,Id>();
List<OrderItem> clonedOrderProducts = new List<OrderItem>();
queryString = 'SELECT '+getAllFields('OrderItem')+' FROM OrderItem WHERE OrderId = :originalOrderId';
List<OrderItem> originalOrderProducts = Database.query(queryString);
if(!originalOrderProducts.isEmpty()){
for(OrderItem op : originalOrderProducts){
OrderItem clonedOP = op.clone(false, true, req.preserveReadonlyTimestamps, false);
clonedOP.OrderId = clonedOrder.Id;
clonedOrderProducts.add(clonedOP);
}
insert clonedOrderProducts;
res.nClonedOrderProducts = clonedOrderProducts.size();
}
clonedOrder.Status = 'Activated';
update clonedOrder;
res.isSuccess = true;
}catch(Exception e){
res.errorMessage = 'Order cloning process failed: '+'\n\n'+e.getMessage()+'\n\n'+e.getStackTraceString();
Database.rollback(savepoint);
}finally{
SBQQ.TriggerControl.enable();
}
return new List<Results>{res};
}
private static String getAllFields(String objName){
String fieldsNames = '';
for (Schema.SObjectField sObjectField : globalDescribeMap.get(objName).getDescribe().fields.getMap().values()) {
fieldsNames += sObjectField.getDescribe().getName() + ',';
}
return fieldsNames.removeEnd(',');
}
public class Requests {
@InvocableVariable(label='Order Id' description='Id of the order to clone' required=true)
public Id orderId;
@InvocableVariable(label='New Account Id' description='Id of the new account for the cloned order' required=true)
public Id accountId;
@InvocableVariable(label='preserve time stamps?' description='flag to control if clone should preserve timestamps fields or not' required=true)
public Boolean preserveReadonlyTimestamps;
}
public class Results {
@InvocableVariable(label='clonedOrderID' description='Id of the cloned order' required=false)
public Id clonedOrderId;
@InvocableVariable(label='clonedOrderProductsCount' description='Number of cloned child order products' required=false)
public Integer nClonedOrderProducts;
@InvocableVariable(label='clonedUsageSummaries' description='Number of cloned child usage summaries' required=false)
public Integer nUsageSummaries;
@InvocableVariable(label='errorMessage' description='Error message if process fails' required=false)
public String errorMessage;
@InvocableVariable(label='isSuccess' description='Successful operation true/false' required=true)
public Boolean isSuccess;
public Results(){
this.nUsageSummaries = 0;
this.nClonedOrderProducts = 0;
this.isSuccess = false;
}
}
}
Screen Flow Integration
A user-friendly screen flow in Salesforce will enable users to select a new account before initiating the cloning process.
Conclusion
Efficiently cloning orders and reassigning them to different accounts not only saves valuable time but also enhances data accuracy and operational efficiency. By leveraging the capabilities of Apex and Salesforce flows, this solution streamlines order management processes, making them more agile and adaptable to the ever-changing business needs.
Comments