function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
RaoSRaoS 

Update Contact status related to an Opportunity

If a Contact is attached to an opportunity that then gets closed, Change status to Recycled only if there are no other open opportunities related to that same contact.  I have written the following trigger on Opportunity but it is giving error.  Kindly let me know what I am missing here.

trigger updateContactStatusToRecycle on Opportunity (After update) {

    Id optyId,contactId;
    string vstatusField,optyStatus;

    List<Opportunity> lstOpportunitiesToUpdate=new List<Opportunity>();        
    List<Contact> contactObj= new List<Contact>();
    for(Opportunity optyObj : Trigger.new)
    {
        optyId         = optyObj.Id; 
        optyStatus    = optyObj.Stagename;
        
        // get Contact Id for the opportunity
        for (OpportunityContactRole ocrObj : [select Id, ContactId,Contact.Status__c from OpportunityContactRole where OpportunityId = :optyId ])
        {
           // optyStatus    = ocrObj.Opportunity.Stagename ;
            contactId = ocrObj.ContactId;
        } 
        //Get the contact status and the other opportunities tied to this contact
        Contact conObj =  [select Id, Status__c from contact where Id = :contactId];

            vstatusField = conObj.Status__c;
            
            //Get all the opportunities related to this contact.
           contactObj = [SELECT Id,(SELECT Id,OpportunityId,Opportunity.StageName,  ContactId FROM OpportunityContactRoles) FROM Contact];

            for(Contact contactObj1 : contactObj)
            {   
                If(contactObj1.Opportunity.StageName == 'Open')
                {
                    lstOpportunitiesToUpdate.add(contactObj1.OpportunityId);
                }
            }  
            //If there are no open opportunities then update contact status
            If (lstOpportunitiesToUpdate.size()==0)
            {
                if (optyStatus=='Closed Won' || optyStatus=='Closed Lost')
                {
                    if (conObj.Status__c <>'Recycle')
                    {
                        conObj.Status__c = 'Recycle';
                         update(conObj);
                    }
                    }
            }    
    }

    
}
v varaprasadv varaprasad
Hi Rao,

Try like this : 
 
for(Contact contactObj1 : contactObj)
            {
        for(OpportunityContactRole opr : contactObj1.OpportunityContactRoles){			
                If(opr.Opportunity.StageName == 'Open')
                 {
                    lstOpportunitiesToUpdate.add(opr.OpportunityId);
                 }
				}
            }

Your code also not following best practices.

1) One Trigger Per Object
A single Apex Trigger is all you need for one particular object. If you develop multiple Triggers for a single object, you have no way of controlling the order of execution if those Triggers can run in the same contexts

2) Logic-less Triggers
If you write methods in your Triggers, those can’t be exposed for test purposes. You also can’t expose logic to be re-used anywhere else in your org.

3) Context-Specific Handler Methods
Create context-specific handler methods in Trigger handlers

4) Bulkify your Code
Bulkifying Apex code refers to the concept of making sure the code properly handles more than one record at a time.

5) Avoid SOQL Queries or DML statements inside FOR Loops
An individual Apex request gets a maximum of 100 SOQL queries before exceeding that governor limit. So if this trigger is invoked by a batch of more than 100 Account records, the governor limit will throw a runtime exception

6) Using Collections, Streamlining Queries, and Efficient For Loops
It is important to use Apex Collections to efficiently query data and store the data in memory. A combination of using collections and streamlining SOQL queries can substantially help writing efficient Apex code and avoid governor limits

7) Querying Large Data Sets
The total number of records that can be returned by SOQL queries in a request is 50,000. If returning a large set of queries causes you to exceed your heap limit, then a SOQL query for loop must be used instead. It can process multiple batches of records through the use of internal calls to query and queryMore

8) Use @future Appropriately
It is critical to write your Apex code to efficiently handle bulk or many records at a time. This is also true for asynchronous Apex methods (those annotated with the @future keyword). The differences between synchronous and asynchronous Apex can be found

9) Avoid Hardcoding IDs
When deploying Apex code between sandbox and production environments, or installing Force.com AppExchange packages, it is essential to avoid hardcoding IDs in the Apex code. By doing so, if the record IDs change between environments, the logic can dynamically identify the proper data to operate against and not fail.

Few more Best Practices for Triggers
There should only be one trigger for each object.
Avoid complex logic in triggers. To simplify testing and resuse, triggers should delegate to Apex classes which contain the actual execution logic. See Mike Leach's excellent trigger template for more info.
Bulkify any "helper" classes and/or methods.
Trigers should be "bulkified" and be able to process up to 200 records for each call.
Execute DML statements using collections instead of individual records per DML statement.
Use Collections in SOQL "WHERE" clauses to retrieve all records back in single query
Use a consistent naming convention including the object name (e.g., AccountTrigger)

Hope this helps you!
If my answer helps resolve your query, please mark it as the 'Best Answer' & upvote it to benefit others.

Thanks
Varaprasad
@For Salesforce Project Support: varaprasad4sfdc@gmail.com

Salesforce latest interview questions  :
https://www.youtube.com/channel/UCOcam_Hb4KjeBdYJlJWV_ZA?sub_confirmation=1
Bhargavi TunuguntlaBhargavi Tunuguntla
Hi
Try the below code to update contacts based on opportunity:
 
trigger oppTrigger on Opportunity(after insert,before insert,after update,before update)
{
if(trigger.isAfter)
{
if(trigger.isInsert || trigger.isUpdate)
{
List<Id> contactIds=new List<Id>();
for(Opportunity opp:trigger.new)
{
contactIds.add(opp.contactId);
}
List<Opportunity> oppList=new List<Opportunity>([select id,contactId from opportunity where contactId in: contactIds]);
Map<Id,List<Opportunity>> conOpp=new Map<Id,List<Opportunity>>();
for(Opportunity opp:oppList)
{
if(conOpp.containsKey(opp.contactId))
{
List<Opportunity> newopp=new List<Opportunity>();
newopp.addAll(conOpp.get(opp.contactId));
newOpp.add(opp);
conOpp.put(opp.contactId,newOpp);
}
else
conOpp.put(opp.contactId,new List<Opportunity>(opp));
}
for(Opportunity opp:conOpp.values())
{
	if(opp.Status.contains(closed'))
	{
		conOpp.remove(opp.contactId);
	}
}
List<Contact> conList=new List<Contact>([select id,status from COntact where id in: conOpp.keySet()]);
for(Contact con:conList)
{
	con.Status='Recycled';
}
update conList;
}
}
}

Thanks
Hope this will be helpful.
RaoSRaoS
Thank you Varaprasad for your detailed review on the code and the feedback.  

Thank you Bhargavi for the code.  I will test your code and let you know.  I had one question regarding line 28.  I have to change the contact status to 'Recycle' only when the opportunity is closed.  If the opportunity is open then I should not change the status.  I think the line 28 should be changed like this ( not closed) 

if(!opp.Status.contains(closed'))

Let me know.

Thanks
Bhargavi TunuguntlaBhargavi Tunuguntla
Yes your right Rao .Please let me know if it works fine.
RaoSRaoS
Hi Bhargavi,
Forgot to mention that there is no ContactId on opportunity.
RaoSRaoS
Hi Varaprasad,
Here is my updated code.  I am getting following error "Method does not exist or incorrect signature: void add(Id) from the type List<Opportunity>"

trigger updateContactStatusToRecycle on Opportunity (After update) {
    Id optyId,contactId;
    string vstatusField,optyStatus;

    List<Opportunity> lstOpportunitiesToUpdate=new List<Opportunity>();        
    List<Contact> contactObj= new List<Contact>();
    List<OpportunityContactRole> lstOprConRole=new List<OpportunityContactRole>();
    List<OpportunityContactRole> lstOprConRoleToUpdate=new List<OpportunityContactRole>();
    for(Opportunity optyObj : Trigger.new)
    {
        optyId         = optyObj.Id; 
        optyStatus    = optyObj.Stagename;
    }
        // get Contact Id for the opportunity
        for (OpportunityContactRole ocrObj : [select Id, ContactId,Contact.Status__c from OpportunityContactRole where OpportunityId = :optyId ])
        {
           // optyStatus    = ocrObj.Opportunity.Stagename ;
            contactId = ocrObj.ContactId;
        } 
        //Get the contact status and the other opportunities tied to this contact
        Contact conObj =  [select Id, Status__c from contact where Id = :contactId];

            vstatusField = conObj.Status__c;
            
            //Get all the opportunities related to this contact.
           //contactObj = [SELECT Id,(SELECT Id,OpportunityId,Opportunity.Id,Opportunity.StageName,  ContactId FROM OpportunityContactRoles) FROM Contact];
           lstOprConRole = [SELECT Id,OpportunityId,Opportunity.Id,Opportunity.StageName,  ContactId FROM OpportunityContactRole where ContactId = :contactId];
            for(OpportunityContactRole contactObj1 : lstOprConRole)
            {   
                If(contactObj1.Opportunity.StageName == 'Open')
                {
                    lstOpportunitiesToUpdate.add(contactObj1.OpportunityId);
                }
            } 

            //If there are no open opportunities then update contact status
            If (lstOpportunitiesToUpdate.size()==0)
            {
                if (optyStatus=='Closed Won' || optyStatus=='Closed Lost')
                {
                    if (conObj.Status__c <>'Recycle')
                    {
                        conObj.Status__c = 'Recycle';
                         update(conObj);
                    }
                    }
            }    

}