Tuesday 4 November 2014

Close Related Won Quotes when Opportunity was Lost CRM 2011/2013 C#

When we close Opportunity is Lost, if you notice, it will not auto-close as lost all related Quotes.
It still remains the Won Quote there…

*Before Close the Opportunity

image

*After Close the Opportunity as Lost

image

*As you can see the Quotes are still having Won Status..Which our customer does want.

Sometimes we have requirement to Close All Quotes as Lost as well if the Opportunity was Lost in order to keep in sync each other.

*Remember you cannot close Opportunity if you still have Draft or Active Quotes

image

So here is the code to Close All Quotes as Lost or Canceled when an Opportunity was Lost

#region variables

Guid guidOpportunityId;
List<Quote> quoteToBeCanceled = new List<Quote>();

#endregion

if (context.InputParameters.Contains("OpportunityClose") && context.InputParameters["OpportunityClose"] is Entity)
{
       //create entity context
       Entity entity = (Entity)context.InputParameters["OpportunityClose"];

       if (entity.LogicalName != OpportunityClose.EntityLogicalName)
       {
           return;
       }
       try 
       {
           //create target entity as early bound
           OpportunityClose TargetEntity = entity.ToEntity<OpportunityClose>();

           #region Close all Quotes if Opportunity was Lost

           //get the Opportunity Id
           guidOpportunityId = TargetEntity.OpportunityId.Id;

           //get all quote related to this Opportunity
           quoteToBeCanceled = GetQuoteToBeCanceled(baseContext, guidOpportunityId);
           if (quoteToBeCanceled != null)
           {
               foreach (Quote quote in quoteToBeCanceled)
               {
                   if (quote.StateCode.Value == QuoteState.Won || quote.StateCode.Value == QuoteState.Closed)
                   {
                      //have to be deactivated first as Draft
                      ConvertToDraftQuote(service, quote);

                      //have to be activated first before it closes the Quote
                      ActivateDraftQuote(service, quote);
                   }

                   if (quote.StateCode.Value == QuoteState.Draft)
                   {
                      //have to be activated first
                      ActivateDraftQuote(service, quote);
                   }

                   //finally Close the Quote (Active Quote will be directly closed
                   //You can Choose Canceled or Lost
                       
                   //as Canceled
                   ExecuteCloseQuoteAsCanceled(service, quote, quote.QuoteNumber);                 
                   //as Lost
                   ExecuteCloseQuoteAsLost(service, quote, quote.QuoteNumber);
               }
          }

          #endregion
       }

       catch (Exception ex)
       {
             throw new InvalidPluginExecutionException(ex.Message);
       }
}


Query to Get All related Quotes from Opportunity
//return all of the Quote from related Opportunity to be Canceled/Lost
//I am using LINQ here..

public List<Quote> GetQuoteToBeCanceled(KonicaBaseContext baseContext, Guid guidOpportunityId)
{
       //get the Quote to be canceled in same Opportunity, exclude the wonQuote
       var quoteToBeCanceled = from x in baseContext.QuoteSet
                               orderby x.CreatedOn descending
                               where x.OpportunityId.Id == guidOpportunityId
                               select x;

       if (quoteToBeCanceled.ToList().Count > 0)
       {
            return quoteToBeCanceled.ToList<Quote>();
       }
       else
       {
            return null;
       }

}

And the Helper Code…

Which you can use this code (C# code) for other purposes, such as How to Activate a Draft Quote, and so on..

The helper code consists of:

1. Convert Quote back to Draft
2. Activate a Draft Quote
3. Close the Quote as Closed - Canceled
4. Close the Quote as Closed – Lost

Convert Quote back to Draft
//make the Quote as Draft first
public void ConvertToDraftQuote(IOrganizationService service, Quote erQuote)
{

       // Activate the quote
       SetStateRequest activateQuote = new SetStateRequest()
       {
             EntityMoniker = erQuote.ToEntityReference(),
             State = new OptionSetValue((int)QuoteState.Draft),
             Status = new OptionSetValue((int)1) //in progress
        };
       service.Execute(activateQuote);
 }

Activate a Draft Quote
//activate the Draft Quote
public void ActivateDraftQuote(IOrganizationService service, Quote erQuote)
 {

       // Activate the quote
       SetStateRequest activateQuote = new SetStateRequest()
       {
             EntityMoniker = erQuote.ToEntityReference(),
             State = new OptionSetValue((int)QuoteState.Active),
             Status = new OptionSetValue((int)2) //in progress
       };
       service.Execute(activateQuote);
}

Close the Quote as Closed - Canceled
//And finally Close the Quotes as Canceled
public void ExecuteCloseQuoteAsCanceled(IOrganizationService service, Quote erQuote, string strQuoteNumber)
{
       CloseQuoteRequest closeQuoteRequest = new CloseQuoteRequest()
       {
            QuoteClose = new QuoteClose()
            {
                Subject = String.Format("Quote Closed (Canceled) - {0} - {1}", strQuoteNumber, DateTime.Now.ToString()),
                QuoteId = erQuote.ToEntityReference()
            },
                Status = new OptionSetValue(6)
       };
       service.Execute(closeQuoteRequest);
}

Close the Quote as Closed - Lost
public void ExecuteCloseQuoteAsLost(IOrganizationService service, Quote erQuote, string strQuoteNumber)
 {
        CloseQuoteRequest closeQuoteRequest = new CloseQuoteRequest()
        {
             QuoteClose = new QuoteClose()
             {
                Subject = String.Format("Quote Closed (Opportunity Lost) - {0} - {1}", strQuoteNumber, DateTime.Now.ToString()),
                QuoteId = erQuote.ToEntityReference()
             },
             Status = new OptionSetValue(5)
       };
       service.Execute(closeQuoteRequest);
}

Register the Plugin:

image

Message: Lose
Primary Entity: opportunity
Event: Post-operation
Execution Mode: Synchronous

Result:

image

Hope this helps you!

Thanks.

8 comments:

  1. This doesn't quite work.. Even if you register this plugin, the 'close as lost' still shows you the warning. When, exactly, will this code execute?

    ReplyDelete
    Replies
    1. Did you ever receive a reply to your post or get this to work?

      Delete
  2. Hi,

    I have created script to close all the quotes of opportunity. please refer the below link.

    https://venkykolagani.wordpress.com/2015/06/10/close-all-related-quotes-of-opportunity-as-lost-crm-201120132015-javascript/?preview_id=14

    Hope it will help you.

    Thanks,
    Venky.

    ReplyDelete
  3. Great post!

    Will this work in CRM 2016/365?

    Also, we are trying to get an addition to this behaviour.

    If an opportunity is Closed as Won, all Won Quotes stay untouched. Open Quotes are closed as Lost. Would that be possible?

    ReplyDelete
  4. This plugin is not firing in D365 . Is there any addition we need to do ?

    ReplyDelete
  5. Instead at the first place getting the error message provided in the post.

    ReplyDelete

My Name is..