-1

Opportunity records fields compareing with custom metadata record and based on values match opportunity stagename will update but it is not working and gettin error in apex job also that duplicate id in list : 006M00011bEasQQ

here is the screenshot of record

enter image description here

Below is the custom metadata record

enter image description here

Above record have country usa , type new customer , lead source phone inquiry and that same value is matching with custom metadata record in that case opportunity stagename should be update with custom metadata record which is qualification but it is not working. opportunity stage is still prospecting.

below is the code

global class updatestageBatch implements Database.batchable<sobject>,database.stateful{

string query;
List<Opportunity> oppUdateList = new List<Opportunity>();
String country;
String type;
String leadSource;
String stage;
global database.queryLocator start(database.BatchableContext bc){
    query = 'select id, stagename,country__c,LeadSource,type from opportunity';
    return database.getQueryLocator(query);
}

global void execute(database.BatchableContext bc, List<Opportunity> scopeOfOpp){
    
    Map<String,updateStageMetadata__mdt> meta = updateStageMetadata__mdt.getAll();
    for(String str : meta.keyset()){
        country = meta.get(str).Country__c;
        type = meta.get(str).Type__c;
        leadSource = meta.get(str).Lead_Source__c;
        stage = meta.get(str).Stage__c;
    
    
        for(Opportunity opp : scopeOfOpp){
            Opportunity op = new Opportunity();
            op.id = opp.id;
            if(opp.Country__c  == country && opp.Type == type && opp.leadSource == leadSource){
                op.StageName = stage;
            } else{
                op.StageName = 'Closed Lost';
            }
            oppUdateList.add(op);
            
        }
    }
    if(oppUdateList != null){
        database.update(oppUdateList,false);
    }
    
}

global void finish(database.BatchableContext bc){}

}

2
  • Has Salesforce released getAll for Custom Metadata without telling us? Last I heard that method was unavailable.
    – Adrian Larson
    Commented Jan 25, 2021 at 14:36
  • @AdrianLarson It's in Spring '21, which means only in certain Sandboxes, Scratch Orgs, and Preview Orgs... for now. About two weeks away for most of us in prod.
    – sfdcfox
    Commented Jan 25, 2021 at 14:45

3 Answers 3

3

Don't use Database.Stateful here; you'll end up running against governor limits eventually, because every opportunity from every prior batch will end up getting updated every execute method.

Don't use class-level variables that should be defined in the execute method, especially with Database.Stateful.

Don't copy values to a DML update list, just call update scopeOfOpp; instead, since you're updating all the records.

Do use an inline query for Database.getQueryLocator. This provides compiler-time safety for field and object names. Only use the string version if you need dynamic field access or filters.

Do use a Map to locate values in the Custom Metadata so you're not needlessly looping and finding the wrong values for your opportunity records.


Revised Code

global class updatestageBatch implements Database.batchable<sobject> {
    class MDTWrapper {
        String country, oppType, leadSource;
        MDTWrapper(String country, String oppType, String leadSource, String stageName) {
            this.country  = country?.toLowerCase();
            this.oppType = oppType?.toLowerCase();
            this.leadSource = leadSource?.toLowerCase();
        }
        public Boolean equals(Object o) {
            MDTWrapper other = (MDTWrapper)o;
            return other.country == country && other.oppType == oppType && other.leadSource == leadSource;
        }
        public Integer hashCode() {
            return (country+'/'+oppType+'/'+leadSource).hashCode();
        }
    }

    Map<MDTWrapper, String> stageMapping;

    global database.queryLocator start(database.BatchableContext bc){
        stageMapping = new Map<MDTWrapper, String>();
        for(updateStageMetadata__mdt meta: updateStageMetadata__mdt.getAll().values()) {
            stageMapping.put(new MDTWrapper(meta.Country__c, meta.Type__c, meta.Lead_Source__c), meta.Stage__c);
        }
        return database.getQueryLocator([
            select stagename,country__c,LeadSource,type from opportunity
        ]);
    }

    global void execute(database.BatchableContext bc, List<Opportunity> scopeOfOpp){
        for(Opportunity opp : scopeOfOpp){
            String stageName = stageMapping.get(new MDTWrapper(opp.Country__c, opp.Type, opp.LeadSource));
            if(stageName != null) {
                opp.StageName = stageName;
            } else {
                op.StageName = 'Closed Lost';
            }
        }
        Database.update(scopeOfOpp, false);        
    }

    global void finish(database.BatchableContext bc){}
}
3
  • Here i need to update one another field so how can i put that values also alongwith stagename Commented Jan 27, 2021 at 7:18
  • Do i need to create another map and wrapper class to update CurrencyIsoCode field which is also a picklist along with stagename. CurrencyIsoCode is also created in metadata. so if country,type,lead source will match then update the stagename and CurrencyIsoCode field with custom metadata matching record. Commented Jan 27, 2021 at 11:03
  • @cloudZigZag I wrote the original code assuming you needed just the one value. If you want, you can put the Custom Metadata record directly into the stageMapping map if you'd like, or you can use a second map, as you propose.
    – sfdcfox
    Commented Jan 27, 2021 at 12:40
1

Ok, I see your problem now. You shouldn't create a new opportunity, but reuse the one that arrives, for the update:

for(Opportunity opp : scopeOfOpp){
    if(opp.Country__c  == country && opp.Type == type && opp.leadSource == leadSource){
        opp.StageName = stage;
    } else{
        opp.StageName = 'Closed Lost';
    }
    oppUdateList.add(opp);
}
4
  • Even better, just call update scopeOfOpp; instead of copying the values needlessly.
    – sfdcfox
    Commented Jan 25, 2021 at 14:48
  • definitely, good catch
    – Alba Rivas
    Commented Jan 25, 2021 at 14:52
  • @AlbaRivas thanks that works, but loops have loops (nested loop) that is causing performance issue and getting cpu time out limit exception Commented Jan 25, 2021 at 15:11
  • Yes, you're right - I just proposed the solution for one of the problems your code has. Please follow sfdcfox advice, as his response is complete.
    – Alba Rivas
    Commented Jan 25, 2021 at 15:14
0

Your algorithm as written loops over the mappings one at a time, then evaluates all opportunities against that. You need to invert the ordering of those two loops. As is currently, the last looping over a mapping is the only one applied, because you just set everything which doesn't match to a fallback value. What you should actually be doing is loop over every opportunity, then find if any mappings match against it, and if not then set the fallback value.

You must log in to answer this question.

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