Search code examples
c#pluginsdynamics-crmdynamics-crm-365

Quote Validation Plugin Not Preventing Closing of Quote on InvalidPluginExecutionException


I am writing a Plugin to validate a quote before it saves. We want to enforce that there must be a quote product line

Item on a quote before a quote can be activated, won or lost. Draft mode does not have this requirement.

I wrote the following code and when pressing the "Close Quote" button on the ribbon and selecting Won as the reason, a business process error box pops up with the error message.

However, upon closing the error message and refreshing the page, the quote is set to closed. Why does the quote close even though the exception was thrown?

FYI, the plugin stage is set to Pre-operation.

Here is my source code (Updated 10/02/2017):

using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ValbrunaPlugins
{
    public class QuoteValidation : IPlugin
    {
        private ITracingService tracingService;

        public void Execute(IServiceProvider serviceProvider)
        {

            // retrieve the context, factory, and service
            IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
            IOrganizationServiceFactory factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
            IOrganizationService service = factory.CreateOrganizationService(context.UserId);

            bool isCorrectEvent = context.MessageName == "SetStateDynamicEntity" || context.MessageName == "SetState" || context.MessageName == "Win" || context.MessageName == "Close";
            bool hasEnityMoniker = context.InputParameters.Contains("EntityMoniker");

            // ensure we are handling the correct event and we were passed an entity from the context
            if (!isCorrectEvent || !hasEnityMoniker) return;
            // get the reference to the quote entity
            EntityReference quoteEntityReference = (EntityReference)context.InputParameters["EntityMoniker"];

            Entity quoteEntity = null;
            try
            {
                // get the quote entity from the entity reference
                quoteEntity = ActualEntity.GetActualEntity(quoteEntityReference, service);
            } catch (Exception ex)
            {
                throw new InvalidPluginExecutionException("Quote with id " + quoteEntityReference.Id + " not found.");
            }

            // ensure that we have the correct entity
            if (quoteEntity.LogicalName != "quote") return;

            // write query to retrieve all the details for this quote
            QueryExpression retrieveQuoteDetailsQuery = new QueryExpression
            {
                EntityName = "quotedetail",
                ColumnSet = new ColumnSet(),
                Criteria = new FilterExpression
                {
                    Conditions =
                        {
                            new ConditionExpression
                            {
                            AttributeName = "quoteid",
                            Operator = ConditionOperator.Equal,
                            Values = { (Guid)quoteEntity.Id }
                            }
                        }
                }
            };

            // execute the query to retrieve the details for this quote
            EntityCollection quoteDetails = service.RetrieveMultiple(retrieveQuoteDetailsQuery);

            // retrieve the current status of the quote
            // 0 - Draft
            // 1 - Active
            // 2 - Won
            // 3 - Closed
            int quoteStatus = ((OptionSetValue)(quoteEntity.Attributes["statecode"])).Value;

            // if not in draft mode
            if (quoteStatus != 0)
            {
                // if the amount of details for the quote is less than 1
                if (quoteDetails.Entities.Count < 1)
                {
                    throw new InvalidPluginExecutionException("There must be a quote product line item on a quote before a quote can be activated, won, or lost while not in draft mode.");
                }
            }
        }

    }
}

Update 10/02/2017:

I created a separate step for SetState and updated my source code.

SetState

However, I am still having the same issue. When I close the quote I get the error but when I refresh the page, the quote has been set to closed.

Close Quote

NOTICE: Quote is active, there is no quote detail, so the quote can not be won.

Exception is successfully shown.

A business process error is displayed as it should be. However, when I refresh the page, the quote's status has been set to "Closed". Why did it do this if the exception was thrown?

Quote is closed.


Solution

  • You have to register the plugin steps for both SetState and SetStateDynamicEntity messages.

    reference

    Why do I need to register on SetState and SetStateDynamicEntity separately?
    As I mentioned earlier there are multiple messages that perform the same action in CRM. One such example is SetStateRequest and SetStateDyanmicEntityRequest . If you want to write a plug-in on SetState, then you need to register it on both messages.

    Read more