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
chrissy2007chrissy2007 

Duplicate Chatter Posts - please help

Hi,

I have a trigger that posts a notification to a Chatter Group when an opportunity is Closed Won.  The trigger works, however it always posts TWO duplicate posts at the exact same time.  Below is my code....thoughts on how to fix this?  Thanks!

 

 trigger AddClosedWontoChatter on Opportunity (after update) {

    list<FeedItem> fplist = new list<FeedItem>();
    String opportunityPrefix = Opportunity.SobjectType.getDescribe().getKeyPrefix();

    for(Opportunity o : trigger.new){
        FeedItem fpost= new FeedItem();

         if(o.StageName == 'Closed Won' )
        {
            fpost.type = 'LinkPost';
            fpost.parentid = '0F930000000CbQL';
            fpost.Body = 'RTB 
Alert! Your teammate just Closed Won the $' + o.Invoice_Closed_Won__c + ' opportunity -- ' + o.Name 
            + '\nReason Won Details: ' + o.Reason_Won_Lost_Additional_Details__c;
            fpost.linkUrl = 'https://na1.salesforce.com/' + o.id;
            fpost.title = o.Name + ' - ' + o.Invoice_Closed_Won__c;
            fplist.add(fpost);
        }
    }

    if(fplist.size()!=0){
      try{
         insert fplist;
      }
       catch(system.exception e){
          system.debug('Error: '+e);
      }
    }

Best Answer chosen by Admin (Salesforce Developers) 
chrissy2007chrissy2007

So, I ended up figuring this out and wanted to post the resolution....It was actually a simple fix, but tricky to discover and I wouldn't have done so without some fabulous tips from Ashish in Premiere support = #rockstar!

Because we have workflow rules that update opportunities, that was what was triggering the double post. To work around this, I added a class:


global class test1{
public static boolean flag=true;
}

Then, reference that class in the trigger so it doesn't post twice:

if(test1.flag == true){
test1.flag=false;

 

The full code of the trigger is:

trigger AddClosedWontoChatter on Opportunity (after update) 
{
    //Setup the Feeditem array of items to post to Chatter
   list<FeedItem> fplist = new list<FeedItem>();
    
    //Setup the array to hold the ids to Iterate through
    Set<Id> pbeIds = new Set<Id>();
    
    //Iterate through the Opp
    for (Opportunity o : Trigger.new) 
    {   
       // Create individual post
       pbeIds.add(o.Id); 
    }
    
    //Setup APEX MAP Arrays to get the required data from the Pricebook and Opportunity 
     Map<Id, Opportunity> Opp = new Map<Id, Opportunity>([select Account.Name, Account.ID, Owner.Name, Owner.Id, Owner.UserRoleId from Opportunity where id in :pbeIds]);
    
    //Iterate through the line items once again to add the data to the Chatter Object
    for (Opportunity o : Trigger.new)
    {
           Opportunity oldo = Trigger.oldMap.get (o.id);
           //If the part outcomes are different, let's add to the chatter feed
              if(o.StageName != oldo.StageName && o.StageName == 'Closed Won' && o.Account.Id != '0013000000GJRrU' 
&& ((o.Invoice_Closed_Won__c > 79999 && (o.Type == 'Affiliated New' || o.Type == 'Renewal'))
|| (o.Invoice_Closed_Won__c > 19999 && (o.Type == 'SMB - Affiliated New' || o.Type == 'SMB - Renewal' || o.Type == 'SMB'))
|| (o.Invoice_Closed_Won__c > 29999 && o.Type == 'New')
|| (o.Invoice_Closed_Won__c > 5999 && o.Owner.UserRoleId == '00E30000001U9dG' ))
)      
           {
     if(test1.flag == true){
    test1.flag=false;

    FeedItem fpost= new FeedItem();  

               fpost.parentId = '0F930000000CbQL';
               fpost.createdbyid = UserInfo.getUserId();                              
               fpost.Type = 'LinkPost';
               fpost.Title = o.Name + ' - $' + o.Invoice_Closed_Won__c;
               fpost.Body = 'has Closed Won the following $' + o.Invoice_Closed_Won__c + ' opportunity on Account: ' +opp.get(o.Id).Account.Name+ '. Congrats, ' + opp.get(o.Id).Owner.Name + '!'
               + '\n*Application: ' + o.Application__c
               + '\n*Reason Won Details: ' + o.Reason_Won_Lost_Additional_Details__c;
               String id = String.valueOf(o.Id).substring(0,15);
               fpost.LinkURL = 'https://na1.salesforce.com/' + o.id;
               fplist.add(fpost); 
        }   
       } 
       }    
              
    //if the post is empty, don't try and post it to Chatter    
    if(!fplist.isEmpty())
    {
        insert fplist;
    }    
    

 

The test class is:

@isTest
public with sharing class ChatterClosedWonOppTests {
    /*
    *    Test the AddClosedWontoChatter trigger which inserts chatter feed posts whenever an opportunity is closed won at a certain value
    */
    public static testMethod void testAddClosedWontoChatter() {
    
        Profile p = [select id from profile where name='Standard User'];

        User u = new User(alias = 'test123', email='test123@noemail.com',
            emailencodingkey='UTF-8', languagelocalekey='en_US',
            localesidkey='en_US', profileid = p.Id, country='United States',
            FirstName='Test',
            LastName='User',
            timezonesidkey='America/Los_Angeles', username='test123@noemail.com');
        insert u;
          
        CollaborationGroup c = new CollaborationGroup(name='Test Group',CollaborationType='Public');
        insert c;
        
        //create the test opportunity
        Opportunity testOpp = new Opportunity( Name = 'My Test Opp',
        StageName = '2 - Discovery',
        Owner = u,
        Type = 'SMB',
        Invoice_Forecast__c = 25000,
        CloseDate = System.today());
        insert testOpp;   

        //Since it is an after update trigger, update the opp that was just inserted.
        testopp.StageName = 'Closed Won';
        testopp.Reason_Won__c = 'Price';
        testopp.Competition__c = 'None';
        testopp.Application__c = 'Compliance';
        update testopp; // this statement will fire the trigger 

// Get the feed post that was supposed to be created based on
// closing the opportunity
    
  //  List <FeedItem> posts = [SELECT id
   // FROM FeedItem
    //    WHERE ParentId = :c.Id];
  //  System.assertEquals(1,posts.size());

   //    List<CollaborationGroupFeed> posts = [SELECT Id, Type 
      // FROM CollaborationGroupFeed 
     //  WHERE ParentId = :c.id];
     //     System.assertEquals(1, posts.size());
        }

All Answers

SuperfellSuperfell

You need to check the oldValue as well, and only post if its not Closed Won, otherwise this is going to post an entry everytime the oportunity is saved once it reaches closed won.

chrissy2007chrissy2007

Thanks, I tried this, however it still appears to be posting duplicates:

 

if(o.StageName == 'Closed Won' && trigger.oldMap.get(o.id).StageName != 'Closed Won')

 

Thoughts?

chrissy2007chrissy2007

So, I ended up figuring this out and wanted to post the resolution....It was actually a simple fix, but tricky to discover and I wouldn't have done so without some fabulous tips from Ashish in Premiere support = #rockstar!

Because we have workflow rules that update opportunities, that was what was triggering the double post. To work around this, I added a class:


global class test1{
public static boolean flag=true;
}

Then, reference that class in the trigger so it doesn't post twice:

if(test1.flag == true){
test1.flag=false;

 

The full code of the trigger is:

trigger AddClosedWontoChatter on Opportunity (after update) 
{
    //Setup the Feeditem array of items to post to Chatter
   list<FeedItem> fplist = new list<FeedItem>();
    
    //Setup the array to hold the ids to Iterate through
    Set<Id> pbeIds = new Set<Id>();
    
    //Iterate through the Opp
    for (Opportunity o : Trigger.new) 
    {   
       // Create individual post
       pbeIds.add(o.Id); 
    }
    
    //Setup APEX MAP Arrays to get the required data from the Pricebook and Opportunity 
     Map<Id, Opportunity> Opp = new Map<Id, Opportunity>([select Account.Name, Account.ID, Owner.Name, Owner.Id, Owner.UserRoleId from Opportunity where id in :pbeIds]);
    
    //Iterate through the line items once again to add the data to the Chatter Object
    for (Opportunity o : Trigger.new)
    {
           Opportunity oldo = Trigger.oldMap.get (o.id);
           //If the part outcomes are different, let's add to the chatter feed
              if(o.StageName != oldo.StageName && o.StageName == 'Closed Won' && o.Account.Id != '0013000000GJRrU' 
&& ((o.Invoice_Closed_Won__c > 79999 && (o.Type == 'Affiliated New' || o.Type == 'Renewal'))
|| (o.Invoice_Closed_Won__c > 19999 && (o.Type == 'SMB - Affiliated New' || o.Type == 'SMB - Renewal' || o.Type == 'SMB'))
|| (o.Invoice_Closed_Won__c > 29999 && o.Type == 'New')
|| (o.Invoice_Closed_Won__c > 5999 && o.Owner.UserRoleId == '00E30000001U9dG' ))
)      
           {
     if(test1.flag == true){
    test1.flag=false;

    FeedItem fpost= new FeedItem();  

               fpost.parentId = '0F930000000CbQL';
               fpost.createdbyid = UserInfo.getUserId();                              
               fpost.Type = 'LinkPost';
               fpost.Title = o.Name + ' - $' + o.Invoice_Closed_Won__c;
               fpost.Body = 'has Closed Won the following $' + o.Invoice_Closed_Won__c + ' opportunity on Account: ' +opp.get(o.Id).Account.Name+ '. Congrats, ' + opp.get(o.Id).Owner.Name + '!'
               + '\n*Application: ' + o.Application__c
               + '\n*Reason Won Details: ' + o.Reason_Won_Lost_Additional_Details__c;
               String id = String.valueOf(o.Id).substring(0,15);
               fpost.LinkURL = 'https://na1.salesforce.com/' + o.id;
               fplist.add(fpost); 
        }   
       } 
       }    
              
    //if the post is empty, don't try and post it to Chatter    
    if(!fplist.isEmpty())
    {
        insert fplist;
    }    
    

 

The test class is:

@isTest
public with sharing class ChatterClosedWonOppTests {
    /*
    *    Test the AddClosedWontoChatter trigger which inserts chatter feed posts whenever an opportunity is closed won at a certain value
    */
    public static testMethod void testAddClosedWontoChatter() {
    
        Profile p = [select id from profile where name='Standard User'];

        User u = new User(alias = 'test123', email='test123@noemail.com',
            emailencodingkey='UTF-8', languagelocalekey='en_US',
            localesidkey='en_US', profileid = p.Id, country='United States',
            FirstName='Test',
            LastName='User',
            timezonesidkey='America/Los_Angeles', username='test123@noemail.com');
        insert u;
          
        CollaborationGroup c = new CollaborationGroup(name='Test Group',CollaborationType='Public');
        insert c;
        
        //create the test opportunity
        Opportunity testOpp = new Opportunity( Name = 'My Test Opp',
        StageName = '2 - Discovery',
        Owner = u,
        Type = 'SMB',
        Invoice_Forecast__c = 25000,
        CloseDate = System.today());
        insert testOpp;   

        //Since it is an after update trigger, update the opp that was just inserted.
        testopp.StageName = 'Closed Won';
        testopp.Reason_Won__c = 'Price';
        testopp.Competition__c = 'None';
        testopp.Application__c = 'Compliance';
        update testopp; // this statement will fire the trigger 

// Get the feed post that was supposed to be created based on
// closing the opportunity
    
  //  List <FeedItem> posts = [SELECT id
   // FROM FeedItem
    //    WHERE ParentId = :c.Id];
  //  System.assertEquals(1,posts.size());

   //    List<CollaborationGroupFeed> posts = [SELECT Id, Type 
      // FROM CollaborationGroupFeed 
     //  WHERE ParentId = :c.id];
     //     System.assertEquals(1, posts.size());
        }

This was selected as the best answer
VidhyaVidhya

Thanks for the workaround, Chris. I was facing the same issue and found this workaround.

 

Generally a trigger gets executed only once and hence we expect the post to happen only once.

Can someone from SF explain as to why is this happening?

And without this workaround, is there anything else we should take care so that it does not cause a double post?

elinsnerelinsner

My organization is asking for this same functionality, but I don't know Apex code at all.  What I am having trouble identifying is where the specific Chatter feed is located in this code, and also how I would adjust it to accomodate opportunities over $1M.

 

Can you help?

 

Thanks, Erik