Reopening Won Quotes in Dynamics 365 Sales Professional / Enterprise (C#)

After you’ve spent any length of time working with the out of the box sales process within Dynamics 365 Sales Professional / Enterprise, you get used to some of the behavioural quirks that can commonly cause you challenges during an implementation. A prime candidate for consideration here concerns the various types of tables used by the application. When we consider these in detail, such as the Opportunity, Quote and Order table, many of the assumptions that we make about how things work are instantly proven wrong, as these tables tend to operate by their own set of rules. For example, the ability to easily map across attributes between the different product line table types (Opportunity Product, Quote Product etc.) becomes a challenge; we must instead revert to using custom code instead to satisfy this requirement. Little things like this can exasperate both new and long-time users of the application and can often be quite frustrating when explaining to organisations using the software. 😅 Notwithstanding these gripes, I still do genuinely believe the Dynamics 365 Sales platform provides a solid base for organisations to leverage out of the box functionality, with the ability to customise further, as needed. And, when we start to bring into the equation more advanced capabilities, via things such as custom pricing plug-ins, you begin to move to an entirely new level when it comes to what we can do with this application.
A particularly annoying behaviour quirk I dealt with recently involved Quotes marked as Won in the system. The organisation I was working with needed to, on occasions, modify details of a Won Quote after the fact. For example, if the salesperson included the wrong item or the customer changed their order after confirming it. As we can observe in the below screenshot, we have no option on the ribbon to reactivate the Quote once Won in the application:
The only “out of the box” way of dealing with this was to create and re-input all details into a new Quote; not a viable and time-effective solution. Fortunately, there is a faster way we can achieve the objective by working through the following outline steps:
- First, we need to change the Status & Status Reason of the Quote back to Draft & In Progress.
- After we have made modifications to the Quote, it needs to be set as Active again.
- Once Active, the Quote then must be closed as Won. Microsoft wraps the logic for this within the WinQuote action, which we can call via the SDK or against the Microsoft Dataverse Web API.
These steps should be achievable via either a classic workflow or a Power Automate cloud flow automation, which would typically be our first preference for a requirement like this. However, in our case, we wanted to call these steps as part of a Custom API definition, which could then be called via a JavaScript action on a Ribbon button, similar to how I’ve described previously on the blog. And, most crucially, we needed all steps to be completed synchronously. So, given these requirements, we had to look at a solution leveraging C# instead.
With all of this in mind, let’s jump into the reason why you’re probably reading this post. 😉 To open a Won Quote using C#, we would need to write and execute the following code:
//TODO: Implement code to generate your IOrganizationService reference. For plug-ins, this would look something like this:
//IOrganizationServiceFactory serviceFactory = serviceProvider.GetService<IOrganizationServiceFactory>();
//IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);
Guid quoteID = new Guid("9cc66ad5-2506-4394-854d-f60c35185d96");
Entity quote = new Entity("quote", quoteID);
quote["statecode"] = new OptionSetValue(0); // Draft
quote["statuscode"] = new OptionSetValue(1); // In Progress
service.Update(quote);
Then, once we’re ready to return the Quote to its previous state, we’d then run the following code to re-close the Quote. Note in particular, at this stage, we need to execute two separate actions and ensure they are completed successfully:
//TODO: Implement code to generate your IOrganizationService reference. For plug-ins, this would look something like this:
//IOrganizationServiceFactory serviceFactory = serviceProvider.GetService<IOrganizationServiceFactory>();
//IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);
// We execute everything in a transaction, so we can rollback cleanly on failure
ExecuteTransactionRequest transactionRequest = new ExecuteTransactionRequest()
{
Requests = new OrganizationRequestCollection(),
ReturnResponses = true,
};
ExecuteTransactionResponse transactionResponse;
Guid quoteID = new Guid("9cc66ad5-2506-4394-854d-f60c35185d96");
// First, we need to Activate the Quote ...
Entity quote = new Entity("quote", quoteID);
quote["statecode"] = new OptionSetValue(1); // Active
quote["statuscode"] = new OptionSetValue(2); // In Progress
UpdateRequest updateRequest = new UpdateRequest()
{
Target = quote,
};
transactionRequest.Requests.Add(updateRequest);
// ...with this done, now we can re-close the Quote as Won
Entity quoteClose = new Entity("quoteclose");
quoteClose["subject"] = "Quote Close" + DateTime.Now.ToString();
quoteClose["quoteid"] = quote.ToEntityReference();
WinQuoteRequest winQuoteRequest = new WinQuoteRequest()
{
QuoteClose = quoteClose,
Status = new OptionSetValue(-1),
};
transactionRequest.Requests.Add(winQuoteRequest);
transactionResponse = (ExecuteTransactionResponse)service.Execute(transactionRequest);
tracer.Trace($"Quote ID {quoteID} closed as won successfully!");
As stated earlier, if there wasn’t a need to execute these actions synchronously, then leveraging Power Automate cloud flows would be something I’d actively encourage instead, as both the ability to update and call action steps are supported via this tool. So before you act too gung-ho with the above, validate your approach against the requirements you are working against.
Regardless of the precise approach you take, it is a minor point of frustration that these types of steps are not natively supported within Dynamics 365 Sales. I understand why the system behaves like this, and there are arguable benefits to having a Quote locked after it’s been approved. However, I imagine the ability to make quick edits to a Won Quote is a common requirement across many different organisations, and expecting users to have to go through raising an entirely new Quote is both impractical and ludicrous in equal measure. It’s good to know that the underlying platform supports us in building workarounds such as this so that we are not left entirely adrift with a system that must fit around the business instead of the other way around.
Published on:
Learn moreRelated posts
Architecting Scalable Business Logic in Dynamics CRM Using Plugin Life Cycle
Dynamics CRM Plugin Life Cycle: Optimizing for Scalability means designing plugins in a way that keeps the system fast, stable, and easy to ma...
We need to talk about... Dynamics 365 Sales... Release Wave 2 for 2025
Next in my blog, I will launch a series on the changes we can expect to see as part of Release Wave 2 for 2025. Microsoft’s 2025 Release Wave ...
Fixed – “Action cannot be performed. This quote is not owned by Dynamics 365 Sales” in Dataverse / Dynamics 365
Recently, while working with Quotes in Dynamics 365 Sales integrated with Supply Chain Management (SCM) through Dual-write, we encountered an ...
Dynamics 365 Sales – Enhance customer interactions with auto-linked CRM data
We are announcing the ability to enhance customer interactions with auto-linked CRM data in Dynamics 365 Sales. How does this affect me? With ...
Avoiding Currency Mismatch Errors in Dynamics 365 CE
When working with Dynamics 365 Sales, it’s important to understand how currency behaves across related entities like Opportunity, Quote, Order...
Sales Collaboration: How Sales Teams Work in Dynamics 365 CE
A Sales Team in Microsoft Dynamics 365 Sales represents a group of users who collaborate to manage and close sales opportunities efficiently. ...
Environment Variables vs Configuration Tables vs Hardcoding in Dynamics 365 Customer Engagement (CE)
In Dynamics 365 Customer Engagement (CE), managing configuration values effectively is key to building scalable and maintainable solutions. En...
Understanding the Roles of Salesperson, Sales Team, and Sales Manager in Dynamics 365 Sales
In Dynamics 365 Sales, the concepts of Salesperson, Sales Team, and Sales Manager are essential parts of the sales hierarchy and security mode...
Dynamics 365 Sales – Connect AI agents to sales workflows using Model Context Protocol server – updated date
We are announcing the ability to connect Dynamics 365 Sales to your agents and assistants with the new Model Context Protocol server. This fea...