Sunday, 29 September 2024

How to define generic null check of fields ?

 Use Case : Lets say you have a requirement to copy modified field values from latest opportunity to Account , then how can you do field mapping effectively ? 

Assumptions: We are not using any flows here. 

Step - 1 Get all Account Ids from Opportunity Trigger/trigger helper

Step -2 Query Account and Its related opportunity records, assume you want to define latest opportunity by LastStageChangeDate, you can use createdDate too. This will give account details and latest opportunity without the new values that has been modified. so this is basically old opportunity. 

private static List<Account> getAccounts(set<id> accountIdset){
    return [SELECT id,Field_1__c,Field_2__c,Field_3__c,Field_4__c,PersonEmail,Phone,
                               
            (SELECT id,Field_1__c,Field_2__c,Field_3__c,Field_4__c, Email__c,Phone__c
            FROM opportunities
            WHERE stagename = 'Admit' OR stagename = 'Return From Hospital'
            ORDER BY laststagechangeDate DESC LIMIT 1)
            FROM Account where Id IN: accountIdset WITH User_Mode];
}

Step - 3 Create a Map key as AccountId and value as Opportunity from trigger helper. This opportunity will be considered as new opportunity.

Step - 4 Iterate over Account records, under account list there will be another loop to iterate opportunity but that will be one record. when old opportunity Id and new opportunity id same that means latest opportunity is modified. Will leave up to you to find latest records. Now comes field mapping

Step - 5 Field Mapping 

 if(oldOppy.Id == newOppy.Id){
    if(newOppy.Field_1__c !=null){
        account.Field_1__c = newOppy.Field_1__c ;
    }
    if(newOppy.Field_2__c !=null){
        account.Field_2__c = newOppy.Field_2__c ;
    }
    if(newOppy.Field_3__c !=null){
        account.Field_3__c = newOppy.Field_3__c ;
    }
    if(newOppy.Field_4__c !=null){
        account.Field_4__c = newOppy.Field_4__c ;
    }

 } 

If you have few fields, then it is ok, if you need to map 100 fields then 100 ifs need to written, this is code redundancy, What if you need to get values from other object say Case then ?

So Instead of writing multiple Ifs , we can have one generic utility method that will work across object and field. 

Here is generic method 

 public Static void updateFieldIfNotNull(SObject record, String fieldName, Object fieldValue){
    if (fieldValue != null) {
            record.put(fieldName, fieldValue);
     }
       
}

Calling generic method.
 if(oldOppy.Id == newOppy.Id){
    updateFieldIfNotNull(acc,'Field_1__c',newOppy.Field_1__c);
    updateFieldIfNotNull(acc,'Field_2__c',newOppy.Field_2__c);
    updateFieldIfNotNull(acc,'Field_3__c',newOppy.Field_3__c);
    updateFieldIfNotNull(acc,'Field_4__c',newOppy.Field_4__c);
   
 }
Now code is short and readable.
Please try this approach and let me know in comments if this is useful for you.
Keep Learning !!


Thursday, 26 September 2024

How to update only one latest child record's from list of Parent records ?

 This post is all about how to use aggregate query effectively or write less complex apex code.

Use Case: Lets say you need to update latest case record of each opportunity and  you have been given list of opportunity Ids. One opportunity can have multiple cases. How can you do it?

Approach - 1 If you are strong in SQL, you might be linking to use Group By Opportunity__c Order By CreatedDate Desc and Limit 1. SOQL does not support Order By limit along with Group By.

Approach - 2 Using Map to hold Opportunity Id with latest Case

Here is sample code 

// List of Opportunity IDs
List<Id> opportunityIds = new List<Id>{'oppId1', 'oppId2', 'oppId3'};

// Map to hold the latest Case for each Opportunity
Map<Id, Case> latestCaseMap = new Map<Id, Case>();
// Query all cases related to the Opportunity IDs,
sorted by CreatedDate descending to get the latest case first
List<Case> cases = [
    SELECT Id, Opportunity__c, Subject, Status, CreatedDate
    FROM Case
    WHERE Opportunity__c IN :opportunityIds
    ORDER BY CreatedDate DESC
];
// Loop through the cases and store the latest case for each Opportunity ID
for (Case c : cases) {
    Id oppId = c.Opportunity__c; // Assuming 'Opportunity__c'
is the lookup field on the Case object
   
    // If this Opportunity ID is not already in the map, add the current case
as the latest case
    if (!latestCaseMap.containsKey(oppId)) {
        latestCaseMap.put(oppId, c);
    }
    // Since the cases are already sorted by CreatedDate DESC, the first case
encountered is the latest one,
    // so no need to process further cases for the same Opportunity.
}

// Now latestCaseMap contains each Opportunity ID and its corresponding latest Case.
// You can iterate over it and perform the necessary update for each latest Case

List<Case> casesToUpdate = new List<Case>();
for (Id oppId : latestCaseMap.keySet()) {
    Case latestCase = latestCaseMap.get(oppId);    
    // Perform any necessary updates on the latest case
    latestCase.Status = 'Closed'; // Example: updating the status to 'Closed'
   
    // Add the case to the list for DML update
    casesToUpdate.add(latestCase);
}
// Update the latest cases for each Opportunity
if (!casesToUpdate.isEmpty()) {
    update casesToUpdate;
}
System.debug('Updated the latest case for each Opportunity.');

We can accomplish requirement in approach -2, but this is not efficient one.

Approach - 3 Using Aggregate Query.

Set<Id> caseIds = new Set<Id>();
List<Case> caseUpdateList = new List<Case>();
Case caseTemp;
// Query to get one Case ID per Approved_Lead__c (Opportunity)
List<AggregateResult> caseList = [
    SELECT Approved_Lead__c, Max(Id) caseId
    FROM Case
    WHERE Approved_Lead__c IN :oppyIds
    GROUP BY Approved_Lead__c
];

// Iterate through the results
for (AggregateResult ar : caseList) {
    Id opportunityId = (Id) ar.get('Approved_Lead__c'); // Opportunity ID (Approved_Lead__c)
    Id caseId = (Id) ar.get('caseId'); // The Case ID (MIN or MAX)
    caseIds.add(caseId);
    // Now you have the Opportunity ID and the Case ID
    System.debug('Opportunity ID: ' + opportunityId + ' Latest Case ID: ' + caseId);
}
for(Id cseId: caseIds){
caseTemp = new Case(Id=cseId, Status='Closed');
caseUpdateList.add(caseTemp);
}
If(!caseUpdateList.IsEmpty()){
    Update caseUpdateList;
}