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
dandamudidandamudi 

validation rule to stop multiple quotes won on opportunity

Hi,

I have opp standard obj and quote is custom(child) object ,  validation should throw  when i change the quote status filed to won if that opportunity had already won Quote.
that means opportunity should have multiple quotes but should not multiple Won Quotes.
Abdul KhatriAbdul Khatri
Hi

You can achieve this through a trigger. Please find the code below. 

Since I don't have the exact name of custom object quote and fields. I made them up. Please change them as per your need.
 
trigger validateWonQuote on Quote__c (before update) {
    
    Map<Id, Quote__c> idOppQuoteMap = new Map<Id, Quote__c>();
    for(Quote__c quote : trigger.new) {
        
        if(quote.Status__c = 'Won') {
            idOppQuoteMap.put(quote.Opportunity__c, quote);
        }
    }
    
    if(idOppList.isEmpty()) return;
    
    List<Opportunity> oppList = [SELECT Id,  
                                 	(SELECT Id FROM Quotes__r WHERE Status__c = 'Won')
                                 FROM Opportunity WHERE Id IN :idOppQuoteMap.key()];
    
    for(Opportunity opp : oppList) {
        
        if(opp.Quotes__r == null) continue;
        
        idOppQuoteMap.get(opp.Id).addError('Opportunity already has a Won quote');
       
    }
}

 
Abdul KhatriAbdul Khatri
Was this helpful?
dandamudidandamudi
@Khatri
 Sorry for late reply , i don't  understand your logic at line no 14, how did you use master object as sub query, i ran your querry  it throws error 
in my case Quote as custom object  with opportunity field is Master detail type
Abdul KhatriAbdul Khatri
I am not sure why are you saying I am using master object as sub query, infact I am using the custom child object Quote in a subquery. You can use Master and Child Object in SOQL that way. This is called Left Outer Join. Reference https://developer.salesforce.com/page/A_Deeper_look_at_SOQL_and_Relationship_Queries_on_Force.com

You mentioned Quote is a custom child object. I am not sure why you are not using Standard Object Quote. Keeping your perspective of Quote being a custom object, I treated it as Quote__c and when it reference in the Sub Query we use the Child Relationship Name which normally ends up with Quotes__r. You can find that name by click the Quote Master/Detail field.

You haven't shared the error so I am not sure what exactly issue you are getting. Can you please share the exact error you are getting.
dandamudidandamudi
@khatri 
below error i got
                   (Select Id From Quote__r WHERE Quote_Status__c =
                              ^
ERROR at Row:2:Column:48
Didn't understand relationship 'Quote__r' in FROM part of query call. If you are attempting to use a custom relationship, be sure to append the '__r' after the custom relationship name. Please reference your WSDL or the describe call for the appropriate names.
Abdul KhatriAbdul Khatri
Would you mind shareing the schema model so that I can see what exactly you child relationship looks like. Normally Child Object have s__r in the end of the object name like in this case it should Quotes__r. 

You can also look by going on the Fields of the Quote Object and click Edit on the Opportunity field and take a look of the Child Relationship Name.

 
dandamudidandamudi
@Khatri,
iMaster detail Relationship from Quote to Opportunity
Abdul KhatriAbdul Khatri
Are you using my above code as is I mean in that code I have used the SOQL like this
 
List<Opportunity> oppList = [SELECT Id, 
                                    (SELECT Id FROM Quotes__r WHERE Quote_Status__c = 'Won')
                                 FROM Opportunity WHERE Id IN :idOppQuoteMap.key()]

I noticed you are changing the Quotes__r to Quote__r. Therefore you are getting error. Can you use the way I am using and let me know if you are still getting issues.
 
dandamudidandamudi
@Khatri
i used below same code still i got same error
List<Opportunity> oppList = [SELECT Id, 
                                    (SELECT Id FROM Quotes__r WHERE Quote_Status__c = 'Won')
                                 FROM Opportunity WHERE Id IN :idOppQuoteMap.key()]

                    (Select Id From Quotes__r WHERE Quote_Status__c
                              ^
ERROR at Row:2:Column:48
Didn't understand relationship 'Quotes__r' in FROM part of query call. If you are attempting to use a custom relationship, be sure to append the '__r' after the custom relationship name. Please reference your WSDL or the describe call for the appropriate names.
 
Abdul KhatriAbdul Khatri
Would you mind sharing your Models for the Opporutnity and Quote Objects Fields? Not all but only related ones.
dandamudidandamudi
Khatri i fixed using below code
Map<Id, Quote__c> idOppQuoteMap = new Map<Id, Quote__c>();
                 for(Quote__c quote : newQuotes){
                     if(quote.Quote_Status__c == 'Quote Won') {
                         idOppQuoteMap.put(quote.Opportunity_Name__c,quote);
                 }
             }
        
         List<Quote__c> quoteList = [select id,Quote_Status__c,Opportunity_Name__c from Quote__c WHERE Opportunity_Name__c IN : idOppQuoteMap.keyset()];
         if(quoteList.size() > 0 && idOppQuoteMap.size() > 0 ){
             system.debug('quoteList.size'+quoteList.size());
             system.debug('idOppQuoteMap.size'+idOppQuoteMap.size());
             for(Quote__c q : quoteList){
                 for(Quote__c pq : idOppQuoteMap.values()){
                     if(q.Id != pq.Id && q.Quote_Status__c == pq.Quote_Status__c){
                         system.debug('exis quote'+q.Quote_Status__c + q.Id);
             			 system.debug('current quote '+pq.Quote_Status__c + pq.Id);
                         
                         pq.addError('opp had already won quote');

                     }
                 }
                     
              }
           }

 
Abdul KhatriAbdul Khatri
This is another way to do that. It is basically the same I proposed accept changing the SOQL

You can simplify your code with this
Map<Id, Quote__c> idOppQuoteMap = new Map<Id, Quote__c>();
for(Quote__c quote : newQuotes)
{
    if(quote.Quote_Status__c == 'Quote Won') 
    {
        idOppQuoteMap.put(quote.Opportunity_Name__c,quote);
    }
}

if(idOppQuoteMap.isEmpty()) return;

List<Quote__c> quoteList = [select id,Quote_Status__c,Opportunity_Name__c from Quote__c WHERE Opportunity_Name__c IN : idOppQuoteMap.keyset() AND Quote__c = 'Quote Won'];

if(quoteList == null) return;

for(Quote__c q : quoteList){
    
    if(idOppQuoteMap.get(q.Opportunity_Name__c) == null) continue;
    
    idOppQuoteMap.get(q.Opportunity_Name__c).addError('opp had already won quote');   
}

 
Abdul KhatriAbdul Khatri
My code will work with multiple opp, your code may give unexpected error for multiple opp. You can try with the test class
Abdul KhatriAbdul Khatri
Did you give a try?
dandamudidandamudi
yes i tried, error throws even opp doesn't have existing own quotes, any idea why? 
ex: opp doesn't have won quotes, i tried to move one Quote to Won, then Error fires.
i implemented the above logic before update.
dandamudidandamudi
it works successfully, if i use my code 
Abdul KhatriAbdul Khatri
Can you tell me what error you are getting and what line number?