Home

Saturday, 18 January 2025

sfdc-learn #17: Salesforce Trigger : Prevent contact records delete (automatically as cascade delete) when account deleted as Cascade delete.

Requirement: Prevent to delete contact record when account deleted as Cascade delete.

Implementation: 

 Using apex trigger, In before delete, set deleted account's  associated contact records AccountId field to null..

Refer below apex trigger code :


trigger accountTrigger on Account (before delete){
    List<Contact> conToUpdateList =  new List<Contact>();
    Set<Id> accountIdSet =  new Set<Id>();
   
    for(Account acc : trigger.old){
        accountIdSet.add(acc.Id);
    }

    if(!accountIdSet.isEmpty()){
        List<Contact> contactList = [SELECT id
                                    FROM Contact
                                    WHERE AccountId In: accountIdSet];

        for(Contact con : contactList){
            con.AccountId = null;
            conToUpdateList.add(con);
        }

        if(!conToUpdateList.isEmpty()){
            update conToUpdateList;
        }
    }
}

Wednesday, 8 January 2025

sfdc-learn #16: Rollup Summary using trigger (Child - Parent -Grandparent)

 If we have requirement to rollup summary on two level using Apex trigger

We have Account  as Grandparent

  Contact as parent 

 Location__c as child 

When any record inserted / updated or deleted in Location object we need to calculate number of contacts count and calculate number of location count associated with each contact associated with account and  update below two custom fields on Account.

No Of Contacts (All contact associated with account )

No Of Contact Locations   -->(all location count from each contact associated with Account)

Apex Trigger:

trigger locationTrigger on Location__c (after insert, after update, after delete) {
    Set<Id> contactIdSet =  new Set<Id>();
    List<Account> accUpdateList =  new List<Account>();

    if(trigger.isInsert){
            for(Location__c locRec: trigger.new){
                if(locRec.Contact__c != null){
                    contactIdSet.add(locRec.Contact__c);
                }
            }
    }

    if( trigger.isUpdate){
        for(Location__c locRec: trigger.new){
            if(locRec.Contact__c != null &&
            locRec.Contact__c != trigger.oldMap.get(locRec.Id).Contact__c){
                contactIdSet.add(locRec.Contact__c);
                contactIdSet.add(trigger.oldMap.get(locRec.Id).Contact__c);
            }
        }
    }

    if(trigger.isDelete){
        for(Location__c locRec: trigger.old){
            contactIdSet.add(locRec.Contact__c);
        }  
    }

    Set<Id> accountIdSet = new Set<Id>();
    set<Id> allContactIdSet = new Set<Id>();
    for (Contact con : [SELECT Id,AccountId
from Contact where Id In:contactIdSet ]) {
        if(con.AccountId != null){
            accountIdSet.add(con.AccountId);
        }
    }
    for (Contact con : [SELECT Id from Contact where AccountId In:accountIdSet ]) {
            allContactIdSet.add(con.Id);
    }

    Map<Id, Integer> accountWithLocCountMap = new Map<Id, Integer>();
   
    List<AggregateResult> locAggrList = [SELECT Count(Id)locCount,Contact__r.AccountId AccId
                                from Location__c
                                where contact__c In:allContactIdSet
                                group by Contact__r.AccountId];
   
                                           
    for(AggregateResult aggr: locAggrList){
        if((string)aggr.get('AccId') != null){
          accountWithLocCountMap.put((string)aggr.get('AccId'), (Integer)aggr.get('locCount') );
        }
    }

    if(locAggrList.isEmpty() ||
(!locAggrList.isEmpty() && accountWithLocCountMap.isEmpty())){
        for(Id accId: accountIdSet){
            accountWithLocCountMap.put(accId, 0 );
        }
    }
   
    Map<Id, Integer> accountWithContactMap =  new Map<Id, Integer>();
    List<AggregateResult> conAggrList = [SELECT count(Id)conCount, AccountId
                                FROM Contact
                                where  AccountId In:accountWithLocCountMap.keySet()
                                group by AccountId];

    for(AggregateResult aggr: conAggrList){
    accountWithContactMap.put((string)aggr.get('AccountId'), (Integer)aggr.get('conCount') );
    }

    if(conAggrList.isEmpty()){
        for(Id accId: accountIdSet){
            accountWithContactMap.put(accId, 0 );
        }
    }

    for(Id accId : accountWithContactMap.keySet()){
        Account  acc =  new Account();
        acc.Id = accId;
        acc.No_Of_Contacts__c = accountWithContactMap.get(accId);
        acc.No_Of_Contact_Location__c = accountWithLocCountMap.get(accId);
        accUpdateList.add(acc);
    }

    if(!accUpdateList.isEmpty()){
        update accUpdateList;
    }
}

Test Results:

Account Which having 2 contacts

Create New location on one of Contact

Check Contact and location count on account

Add another location on another contact

Check Count on Account

Then Try to delete one of location

Check Location count on account


                  Update Location and associate to another contact which not associated with this old contact account (update contact which account is different)
Check Count is reduced..



Monday, 6 January 2025

sfdc-learn #15 : Pass parameter to apex batch Class

If we have requirements like pass Case ids  and  status to apex batch class and update all case record with that status in batch class. Also send email and In that email send how many record are updated count.

Apex Batch Class:
global class UpdateCaseBatch  implements Database.Batchable<sObject>, Database.Stateful {
    global set<Id> caseIdSet;
    global string caseStatus;
    global integer recordProcessed = 0;

    public UpdateCaseBatch( set<Id> caseId, string strStatus) {
      this.caseIdSet = caseId;
      this.caseStatus = strStatus;
    }

    global Database.querylocator start(Database.BatchableContext bc){
        string squery= 'SELECT Id,status from case where Id In:caseIdSet';
        return Database.getQueryLocator(squery);
    }

    global void execute(Database.BatchableContext bc , List<Case> scope){
     
        for(Case cse: scope){
            cse.status = caseStatus;
            recordProcessed = recordProcessed +1;
        }
        update scope;
    }

    global void finish(Database.BatchableContext bc){
        Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
            mail.setToAddresses(new String[] {'dineshsamudre94@gmail.com'});
            mail.setSenderDisplayName('Batch Process');
            mail.setSubject('Case Statuses updated successfully');
            mail.setPlainTextBody(recordProcessed + ': case record updated successfully');
            Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
     }


}


Execute above batch class:
//Anonymous apex

Map<Id,Case> caseMap = new Map<Id,Case>([SELECT ID,status from case ]);
 
if(!caseMap.isEmpty()){
     UpdateCaseBatch obj =  new UpdateCaseBatch(caseMap.keySet(),'Closed');
     string jobId = Database.executeBatch(obj,20);
     System.debug('jobId:'+jobId);
}


Case status updated successfully


Email Sent


Friday, 3 January 2025

sfdc-learn #14 : Salesforce Trigger Scenario

 /*Requirement:
 We have two object Task__c and Project__C.
write trigger to update project status as 'Completed' when all associated tasks are completed below scenario:
  • updating task status or 
  • creating new task or 
  • by deleting task . 
 Otherwise update Project status as 'In Progress'.
 Note: On Task object have Project Lookup (Project -->Parent and task is child) */

Trigger Code:


    trigger taskTrigger on Task__c (after update, after insert , after delete) {
        set<Id> projIdSet = new set<Id>();
        List<Project__c> projList =  new List<Project__c>();
        Boolean isInsert = trigger.oldmap == null ? true: false;

        if(trigger.isInsert || trigger.isUpdate){
            for(Task__c taskrec: trigger.new){
                if( taskrec.Project__c != null && (isInsert || (!isInsert
&& taskrec.Status__c != trigger.oldmap.get(taskrec.Id).Status__c))){
                    projIdSet.add(taskrec.Project__c);
                }
            }
        }else if(trigger.isDelete){
            for(Task__c taskrec: trigger.old){
                if( taskrec.Project__c != null){
                    projIdSet.add(taskrec.Project__c);
                }
            }  
        }

       
        //Create map of project with associated all task count
        Map<string,Integer> projectWithTaskTotalCount = new Map<string,Integer>();
   
        for(AggregateResult aggr:[SELECT Count(Id)taskCount, Project__c
                                FROM Task__c
                                GROUP BY Project__c
                                HAVING Project__c IN:projIdSet]){
                                   
      projectWithTaskTotalCount.put(string.valueOf(aggr.get('Project__c')), Integer.valueOf(aggr.get('taskCount')));
        }
       
        map<Id,string> projWithstatusMap = new map<Id,string>();
       
        //Compare each task status count with total task count for associated project.
        //And Create map with project id and status
        for(AggregateResult aggr:[SELECT count(Id)countStatus, Status__c,Project__c
                                            from Task__c
                                            group by Status__c,Project__c
                                            Having Project__c IN:projIdSet ]){
           
              string strpro = string.valueOf(aggr.get('Project__C'));
            if(aggr.get('Status__c') == 'Completed' && Integer.ValueOf(projectWithTaskTotalCount.get(strpro)) == Integer.valueOf(aggr.get('countStatus'))){
            projWithstatusMap.put(string.valueOf(aggr.get('Project__C')), 'Completed');
            }else{
                string strid = string.valueOf(aggr.get('Project__C'));
                projWithstatusMap.put(string.valueOf(aggr.get('Project__C')), 'In Progress');
            }
        }
       
        // Create project list to update
        if(!projWithstatusMap.isEmpty()){
            for(Id proId: projWithstatusMap.keySet()){
                Project__c proj = new Project__c(Id=proId , Status__c = projWithstatusMap.get(proId));
                projList.add(proj);    
            }
        }
        //Update projects.
        if(!projList.isEmpty()){
            update projList;
        }
    }
Test Results:

Update Scenario:





Create Scenario:


Delete Scenario:




Wednesday, 1 January 2025

sfdc-learn #13 : Record Hierarchy Using Lightning tree grid in Lwc

Requirement to show Account with contacts in tree like structure as below:





We can implement this above hierarchy tree like structure in lwc using <Lightning-Grid-Tree>

Lwc Code:

lwcGridTree.html

<template>
    <lightning-button label="Collapse All" onclick={handleCollapseAll}></lightning-button>
    <lightning-button label="Expand All" onclick={handleExpandAll}></lightning-button>
  <lightning-tree-grid
        columns={gridColumn}
        data={gridData}
        key-field="Id">
    </lightning-tree-grid>
</template>

 lwcGridTree.js

import { LightningElement,wire } from 'lwc';
import getAccountWithContact from '@salesforce/apex/SfDataController.getAccountWithAssociatedContacts';
export default class LwcGreedTree extends LightningElement {
  gridData;
  gridColumn = [
    {
      type:'text',
      fieldName:'Name',
      label:'Acc Name'
    },
    {
      type:'text',
      fieldName:'Id',
      label:'Record Id'
    },
    {
        type:'text',
        fieldName:'FirstName',
        label:'Contact FirstName'
    },
    {
        type:'text',
        fieldName:'LastName',
        label:'Contact LastName'
    }
  ]
 
  @wire(getAccountWithContact)
  accDetails({ data, error }) {
    if (data) {
      let accdata = JSON.parse(JSON.stringify(data));
      // create grid data map
      for(let i = 0; i < accdata.length;i++ ){
        accdata[i]._children = accdata[i]['Contacts'];
        delete accdata[i]['Contacts'];
      }
      this.gridData = accdata;
    }
      if (error){
        console.log(error);
      }
  }

  handleCollapseAll(){
    const grid = this.template.querySelector('lightning-tree-grid');
    grid.collapseAll();
  }

  handleExpandAll(){
    const grid = this.template.querySelector('lightning-tree-grid');
    grid.expandAll();
  }
}

lwcGridTree.js-meta.xml

 <?xml version="1.0" encoding="UTF-8"?>

<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>61.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
    <target>lightning__AppPage</target>
    <target>lightning__HomePage</target>
    <target>lightning__RecordPage</target>
    </targets>
</LightningComponentBundle>


Apex Controller:
sfDataController Apex class


public without sharing class SfDataController {

    @AuraEnabled (Cacheable=true)
    public static List<Account> getAccountWithAssociatedContacts(){
    return [SELECT id , Name,
           (SELECT id , FirstName,LastName from Contacts)
           From Account ];
    }

}

Test Results: