1

Good morning,

I have an apex trigger on opportunities that creates a new opportunity for the next year when a specific "sales stage" is selected. I recently updated the trigger to fire when the Closed/Won sales stage is selected OR Membership Started is selected. The problem is that the Membership Started stage is changed at a later date to Closed/Won, which will create a duplicate new opportunity (we only want 1 new opp). Does anyone have any advice? I have been stuck for a few days. My code is shown below:

public static void createNextYearOpp(List<Opportunity> newlist, Map<Id, Opportunity> oldMap){
    Map<String, Schema.RecordTypeInfo> recordTypeInfo = Schema.SObjectType.Opportunity.getRecordTypeInfosByName();      
    list<Opportunity> newYearOppList = new list<Opportunity>();
    map<Id,Opportunity> originalOppIdToCloneOpp = new map<Id,Opportunity>();

    for(Opportunity opp : newList){
        if(oldMap != null && oldMap.get(opp.id).StageName != opp.StageName && opp.StageName == 'Closed/Won'|| opp.StageName =='Membership Started PIP'
                && opp.Buy_Type__c == 'Security Benchmarks' && opp.Prospect_Type__c == 'SB New Business'
                && opp.Membership_Start_Date__c != null) {
            Opportunity newOpp = new Opportunity();
            Id recordId = recordTypeInfo.get(opp.Prospect_Type__c).getRecordTypeId();
            newOpp.RecordTypeId = recordId;
            newOpp.name = opp.CloseDate.addYears(1).year() + ' ' + opp.name.subString(4);
            newOpp.Prospect_Type__c = 'SB Renewal';
            newOpp.CloseDate = opp.Membership_Start_Date__c.addYears(1);
            newopp.Membership_Start_Date__c = opp.Membership_Start_Date__c.addYears(1);
            newopp.Buy_Type__c = opp.Buy_Type__c;
            newOpp.AccountId = opp.AccountId;
            newOpp.NextStep = opp.NextStep;
            newOpp.Follow_up_Date__c = opp.Follow_up_Date__c;
            newOpp.CIS_CAT_Trial__c = opp.CIS_CAT_Trial__c;
            newOpp.Billing_Cycle__c = opp.Billing_Cycle__c;
            newOpp.Priority__c = opp.Priority__c;
            newOpp.Membership_Category__c = opp.Membership_Category__c;
            newOpp.OwnerId = opp.OwnerId;
            newOpp.Legacy_Membership_Fee__c = opp.Legacy_Membership_Fee__c;
            newOpp.TotalOpportunityQuantity = opp.TotalOpportunityQuantity;
            newOpp.Probability = opp.Probability;
            newOpp.Reason_Lost__c = opp.Reason_Lost__c;
            newOpp.Forecast__c = opp.Forecast__c;
            newOpp.LeadSource = opp.LeadSource;
            newOpp.Description = opp.Description;
            newOpp.Legacy_Created_By__c = opp.Legacy_Created_By__c;
            newOpp.Legacy_Created_Date__c = opp.Legacy_Created_Date__c;
            newOpp.Campaign = opp.Campaign;
            newOpp.Amount = opp.Amount;
            newOpp.StageName = 'To Be Invoiced';
            system.debug('@@ '+newOpp.name);
            //newYearOppList.add(newOpp);
            originalOppIdToCloneOpp.put(opp.Id,newOpp);
        }            
    }
1
  • 1
    You can add a custom field of type Checkbox on the Opportunity object. The first time the opportunity is duplicated, you can update the value of this checkbox on the duplicated opportunity to true. In your trigger you only have to check that this checkbox is set to false on the opportunity to duplicate to verify that you can create your new opportunity. Commented Jan 31, 2017 at 15:10

2 Answers 2

0

Sometimes, it can help to just write down what you want to happen. Based on your description, you want to generate an opportunity in the following cases:

  1. When StageName becomes 'Closed/Won'
    • Except when it was previously 'Membership Started PIP'
  2. When StageName becomes 'Membership Started PIP'

You have Trigger.oldMap and Trigger.new, so this should be as simple as adding a check that your stage hasn't gone from 'Membership Started' to 'Closed/Won'.

// I've broken up the components onto individual lines so I can clearly indicate _what_ has changed
if(oldMap != null
    && oldMap.get(opp.id).StageName != opp.StageName
    // This is the part of the condition that has changed
    // The second set of parenthesis, surrounding the two conditions being and-ed (&&) together
    //   aren't necessary (&& has a higher precedence than ||), but it helps us lowly
    //   humans in keeping track of things
    && ((opp.StageName == 'Closed/Won' && oldMap.get(opp.Id).StageName != 'Membership Started PIP') || opp.StageName =='Membership Started PIP')
    && opp.Buy_Type__c == 'Security Benchmarks' 
    && opp.Prospect_Type__c == 'SB New Business'
    && opp.Membership_Start_Date__c != null) {
        // The rest of this code, everything in the if block, isn't important to the 
        //   question you're asking
    }

+edit:

A few other things worthy of note:

Typing out trigger.oldMap.get(opp.Id) is tedious. When I find myself using something in a loop more than once, I start considering creating a temporary variable (outside of the loop) to use (inside the loop). That'd look something like

Opportunity oldOpp;
for(Opportunity opp :trigger.new){
    oldOpp = trigger.oldMap.get(opp.Id);

    // The rest of the logic in your loop goes here
    // Now, we'll be able to just use oldOpp.<some field> instead of trigger.oldMap.get(opp.Id).<some field>
}

The second thing is that it looks like the new Opportunity that you're creating is largely the same as the Opportunity that is causing you to create it. You can save yourself a lot of typing by using the clone() method, which is part of the sObject class.

if(<some conditions>){
    // clone() takes 4 arguments, but they all default to false (which is 
    //   probably what you want).
    // clone() only copies fields that are in-memory (those that you have explicitly queried,
    //   or explicitly set in code like "opp.Description = 'blah';"
    // Records in a trigger context variable have all of their own fields in-memory.
    Opportunity newOpp = opp.clone();
    // This recordtypeid is something you should consider pulling out of your loop.
    // It never changes in your loop, and it's used multiple times.
    Id recordId = recordTypeInfo.get(opp.Prospect_Type__c).getRecordTypeId();
    newOpp.RecordTypeId = recordId;
    newOpp.name = opp.CloseDate.addYears(1).year() + ' ' + opp.name.subString(4);
    newOpp.Prospect_Type__c = 'SB Renewal';
    newOpp.CloseDate = opp.Membership_Start_Date__c.addYears(1);
    newopp.Membership_Start_Date__c = opp.Membership_Start_Date__c.addYears(1);
    newOpp.StageName = 'To Be Invoiced';
}
2
  • Thank you very much, the trigger is now working as intended. I am a relative beginner to apex dev but I am slowly but surely learning.
    – Tebenhoch
    Commented Jan 31, 2017 at 20:09
  • You're welcome @Tebenhoch . Community etiquette is to help others by marking your question as solved through checking the answer that resolved your issue. If you don't, it leaves the impression to anyone who searches the topic later that your question wasn't answered to your satisfaction. Once you have sufficient reputation, you can also upvote posts and answers.
    – Derek F
    Commented Jan 31, 2017 at 20:31
0

You can create a Text field which will contain sourceOpportunityId.

If opportunities have been created by cloning process then before inserting newly created opportunities, first verify firing an SOQL query and if records exist then you can remove from your map.

Whether a record have been created by cloning, we can get SObject.getCloneSourceId(), but we cannot perform any query to fetch this.

public static void createNextYearOpp(List<Opportunity> newlist, Map<Id, Opportunity> oldMap){
    Map<String, Schema.RecordTypeInfo> recordTypeInfo = Schema.SObjectType.Opportunity.getRecordTypeInfosByName();      
    list<Opportunity> newYearOppList = new list<Opportunity>();
    map<Id,Opportunity> originalOppIdToCloneOpp = new map<Id,Opportunity>();

    for(Opportunity opp : newList)
    {
        if(oldMap != null && oldMap.get(opp.id).StageName != opp.StageName 
                && opp.StageName == 'Closed/Won'|| opp.StageName =='Membership Started PIP'
                && opp.Buy_Type__c == 'Security Benchmarks' && opp.Prospect_Type__c == 'SB New Business'
                && opp.Membership_Start_Date__c != null) 
        {

            Opportunity newOpp = new Opportunity();
            Id recordId = recordTypeInfo.get(opp.Prospect_Type__c).getRecordTypeId();
            newOpp.RecordTypeId = recordId;
            newOpp.name = opp.CloseDate.addYears(1).year() + ' ' + opp.name.subString(4);
            newOpp.Prospect_Type__c = 'SB Renewal';
            newOpp.CloseDate = opp.Membership_Start_Date__c.addYears(1);
            newopp.Membership_Start_Date__c = opp.Membership_Start_Date__c.addYears(1);

            //put source Id here
            newOpp.sourceOpportunityId__c = opp.Id;
            //newYearOppList.add(newOpp);
            originalOppIdToCloneOpp.put(opp.Id,newOpp);
        }            
    }

    //find out if opportunities already been created earlier.
    List<Opportunity> alreadyClonedList = [SELECT Id, Name FROM Opportunity WHERE sourceOpportunityId__c IN:originalOppIdToCloneOpp.keySet()];
    if(!alreadyClonedList.isEmpty())
    {
        for(Id oppId:alreadyClonedList)
        {
            originalOppIdToCloneOpp.remove(oppId);
        }
    }
    insert originalOppIdToCloneOpp.values();

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .