Github Link : https://github.com/AourLegacy/AourFactory/tree/TransfertContract
Introduction
In the realm of Salesforce CRM, the transfer of contracts between accounts is a common yet complex task. This process often involves more than just transferring the contract itself; it necessitates the cloning and linking of associated opportunities, quotes, and orders. Let's explore how Salesforce Apex can be harnessed to streamline this process, ensuring a smooth and error-free transfer of contracts from one account to another.
The Challenge
The principal challenge in transferring a contract between accounts lies in maintaining the relational integrity of the associated records. This involves replicating the opportunity, quote, and order related to the original contract and linking these clones to the new account. It's a task that demands careful coding, especially in handling scenarios where some records might not be present.
Step-by-Step Solution
1. Cloning the Opportunity
The transfer process starts with the opportunity linked to the contract. We clone this opportunity to maintain its relationship with the original contract while associating it with the new account.
2. Cloning the Quote
Next, we focus on the quote linked to the original contract. This is cloned and associated with both the cloned opportunity and the new account, ensuring consistency in the sales process.
3. Cloning or Creating the Order
One of the trickier aspects is handling the order. If an existing order is linked to the contract, it's cloned; if not, a new order is created. This ensures continuity in the order management process.
4. Cloning the Contract
Finally, the contract itself is cloned, linking it to the new account as well as the freshly cloned opportunity, quote, and order. This step is crucial as it ensures that the new contract maintains its association with the relevant sales records.
5. Error Handling and Validation
Throughout the process, robust error handling is vital. This includes managing null references and ensuring that cloned records are valid and complete. Utilizing `try-catch` blocks helps in managing unexpected issues during the cloning process.
Best Practices
- Bulkification: Adapt the code to handle multiple records simultaneously, keeping in mind Salesforce's governor limits.
- Data Integrity: Ensure that the cloned records adhere to your org’s business logic and data validation rules.
- Comprehensive Testing: Implement extensive test coverage to validate various scenarios and maintain compliance with Salesforce's testing requirements.
- Governor Limits: Be mindful of Salesforce's limits in terms of SOQL queries, DML operations, and script execution time.
The Solution :
Flow :
Apex Code :
public class TransfertContract {
@InvocableMethod(label='Transfert Contract Apex')
public static List<String> transfertContract(List<FlowInputs> param) {
Id contractId = param[0].recordId;
Id AccountId = param[0].IdAccount;
List<String> ids = new List<String> ();
Contract contract = [SELECT id,AccountId, ActivatedById, ActivatedDate, BillingAddress, CompanySignedDate, CompanySignedId, ContractTerm, CreatedById, CreatedDate, CustomerSignedDate, CustomerSignedId, CustomerSignedTitle, Description, DummyObject__c, EndDate, IsDeleted,SBQQ__AmendmentOwner__c, SBQQ__AmendmentPricebookId__c, SBQQ__AmendmentRenewalBehavior__c, SBQQ__AmendmentStartDate__c, SBQQ__DefaultRenewalContactRoles__c, SBQQ__DefaultRenewalPartners__c, SBQQ__DisableAmendmentCoTerm__c, SBQQ__Evergreen__c, SBQQ__ExpirationDate__c, SBQQ__MasterContract__c, SBQQ__MDQRenewalBehavior__c, SBQQ__Opportunity__c, SBQQ__OpportunityPricebookId__c, SBQQ__Order__c, SBQQ__PreserveBundleStructureUponRenewals__c, SBQQ__Quote__c, SBQQ__RenewalForecast__c, SBQQ__RenewalOpportunity__c, SBQQ__RenewalOpportunityRecordTypeId__c, SBQQ__RenewalOpportunityStage__c, SBQQ__RenewalOwner__c, SBQQ__RenewalPricebookId__c, SBQQ__RenewalQuoted__c, SBQQ__RenewalTerm__c, SBQQ__RenewalUpliftRate__c, SBQQ__SubscriptionQuantitiesCombined__c, ShippingAddress, StartDate, Status, StatusCode, Terminated_Date__c FROM Contract
WHERE Id =:contractId LIMIT 1];
SBQQ.TriggerControl.disable();
try {
List<Opportunity> oppList = [SELECT id,AccountId,CloseDate, Achievement__c, Amount, AwareCPQGoal__c, AwareCPQProd__c, AwareCPQQTCPP__c, AwareDreamOutcome__c, AwareLinkCPQTasks__c, ContactRole__c, ContractId, CreatedById, CreatedDate, Description, Fiscal, FiscalQuarter, FiscalYear, IsClosed, HasOpenActivity, ForecastCategoryName, ForecastCategory, LastCloseDateChangedHistoryId, LastModifiedById, LastModifiedDate, LastReferencedDate, LastStageChangeDate, LastViewedDate, LeadSource, Length__c, Loss_Reason__c, Market__c, LostReasonComment__c, Name, NextStep, SBQQ__AmendedContract__c, RecordTypeId, RecordType__c, Pricebook2Id, Probability, Period__c, OwnerId, Objective__c, SBQQ__QuotePricebookId__c, SBQQ__Renewal__c, SBQQ__RenewedContract__c, SBQQ__Contracted__c, SBQQ__CreateContractedPrices__c, SBQQ__Ordered__c, SBQQ__OrderGroupID__c, SolutionInterest__c, SolutionShared__c, StageName, Type__c, Type FROM Opportunity WHERE Id =:contract.SBQQ__Opportunity__c LIMIT 1 ];
Opportunity orgOpp = oppList[0];
SBQQ__Quote__c orgQuote = [SELECT Id, BillingAdresse__c, BundleDiscountAmount__c, BundleDiscountPercentage__c, DiscountAmount__c, Discount1__c, DifficultytoMaintainCustomer__c, DeltaDiscount__c, CreatedDate, CreatedById, IsDeleted, NBbundle__c, NetAfterTva__c, NombreDevisiteur__c, NombreUtilisateur__c, Objectif__c, Optional__c, OwnerId, PartnerLevel__c, Pricehold__c, PricingMethods__c, PrixNegocie__c, Process__c, SBQQ__AdditionalDiscountAmount__c, SBQQ__Account__c, Promotion__c, SBQQ__AverageCustomerDiscount__c, SBQQ__AveragePartnerDiscount__c, SBQQ__Distributor__c, SBQQ__DeliveryMethod__c, SBQQ__DefaultTemplate__c, SBQQ__DaysQuoteOpen__c, SBQQ__CustomerDiscount__c, SBQQ__CustomerAmount__c, SBQQ__ContractingMethod__c, SBQQ__ConsumptionRateOverride__c, SBQQ__DistributorDiscount__c, SBQQ__EndDate__c, SBQQ__ExpirationDate__c, SBQQ__FirstSegmentTermEndDate__c, SBQQ__MarkupRate__c, SBQQ__ListAmount__c, SBQQ__LineItemsPrinted__c, SBQQ__LineItemsGrouped__c, SBQQ__LineItemCount__c, SBQQ__MasterEvergreenContract__c, SBQQ__NetAmount__c, SBQQ__Notes__c, SBQQ__Opportunity2__c, SBQQ__OrderBy__c, SBQQ__OrderByQuoteLineGroup__c, SBQQ__Ordered__c, SBQQ__Primary__c, SBQQ__PricebookId__c, SBQQ__PriceBook__c, SBQQ__PaymentTerms__c, SBQQ__PartnerDiscount__c, SBQQ__Partner__c, SBQQ__OriginalQuote__c, SBQQ__PrimaryContact__c, SBQQ__RegularAmount__c, SBQQ__RenewalTerm__c, SBQQ__StartDate__c, SBQQ__Source__c , SBQQ__SalesRep__c, SBQQ__Status__c, SBQQ__SubscriptionTerm__c, SBQQ__Type__c, SBQQ__Uncalculated__c FROM SBQQ__Quote__c WHERE id=:contract.SBQQ__Quote__c];
Order orgOrder = [SELECT Id,AccountId, ActivatedById, ActivatedDate, BillingAddress, CompanyAuthorizedById, ContractId, CreatedById, CreatedDate, CustomerAuthorizedById, Description, EffectiveDate, EndDate, IsDeleted, IsReductionOrder, LastModifiedById, LastModifiedDate, OriginalOrderId, OwnerId, Pricebook2Id, SBQQ__Contracted__c, SBQQ__ContractingMethod__c, SBQQ__OrderBookings__c, SBQQ__PaymentTerm__c, SBQQ__PriceCalcStatus__c, SBQQ__PriceCalcStatusMessage__c, SBQQ__Quote__c, Status, ShippingAddress, SBQQ__TaxAmount__c, SBQQ__RenewalUpliftRate__c, SBQQ__RenewalTerm__c, StatusCode FROM Order
WHERE Id =:contract.SBQQ__Order__c ];
// Clone and link Opportunity
Opportunity clonedOpportunity = orgOpp.clone(false, true);
clonedOpportunity.AccountId = accountId;
// Set any other fields required for the cloned opportunity
insert clonedOpportunity;
// Clone and link Quote
SBQQ__Quote__c clonedQuote = orgQuote.clone(false, true);
clonedQuote.SBQQ__Account__c = accountId;
clonedQuote.SBQQ__Opportunity2__c = clonedOpportunity.Id;
// Set any other fields required for the cloned quote
insert clonedQuote;
System.debug('@@@ Quote good');
Id newOrderid;
if(orgOrder != null){
Order clonedOrder = orgOrder.clone(false, true);
clonedOrder.Status = 'Draft';
clonedOrder.SBQQ__Quote__c = clonedQuote.Id;
// Set any other fields required for the cloned order
insert clonedOrder;
newOrderid =clonedOrder.Id;
}else {
// Create a new Order
Order newOrder = new Order();
newOrder.Status = 'Draft';
newOrder.SBQQ__Quote__c = clonedQuote.Id;
// Initialize other fields as necessary for the new order
insert newOrder;
newOrderid = newOrder.Id;
}
// Clone and link Contract
Contract clonedContract = contract.clone(false, true);
clonedContract.AccountId = accountId;
clonedContract.SBQQ__Opportunity__c = clonedOpportunity.Id;
clonedContract.SBQQ__Quote__c = clonedQuote.Id;
clonedContract.SBQQ__Order__c = newOrderid;
// Set any other fields required for the cloned contract
insert clonedContract;
ids.add(clonedContract.Id);
return ids;
}catch (System.QueryException e) {
System.debug('Error : '+e);
}finally {
SBQQ.TriggerControl.enable();
}
return ids;
}
public class FlowInputs{
@InvocableVariable
public Id recordId;
@InvocableVariable
public Id IdAccount;
}
}
Conclusion
Transferring contracts between accounts in Salesforce, while maintaining the integrity and association of related sales records, can be streamlined using Apex. The key lies in effectively cloning the associated opportunity, quote, order, and finally, the contract itself. By following the outlined steps and adhering to Salesforce's best practices, developers can ensure a seamless and error-free contract transfer process, enhancing the efficiency and reliability of their Salesforce CRM system.
Comments