In this post, I will explain how to customize the calculation of the total price of an opportunity using a PlugIn.
Be cautious : creating a plugin is never harmless. You should ALWAYS try to use the standard configuration functionnalities, and there are many in Dynamics 365 as I explained here.
The goal of this post is to be a follow-up of my previous post on Dynamics product prices, as well as an introduction to writing a plugin for Dynamics 365.
What I needed to create my example :
- A Dynamics 365 sandbox environment
- Visual Studio 2017
- The PluginRegistration tool : get the latest version if you have Dynamics V9. I use the PowerShell script described here : https://docs.microsoft.com/en-ca/dynamics365/customer-engagement/developer/download-tools-nuget
As a first step you should create your Visual Studio project, a simple c# class library template for .NET Framework. At the time of writing, the framework should be no greater than 4.5.2 in my Dynamics 365 V9 online.
Next, add the Nuget package used for Dynamics 365 interaction. In the solution explorer, right-click on the project and select « Manage NuGet Packages… » :
Click on the « Browse » tab, look up « Microsoft.CrmSdk.CoreAssemblies » and click the « Install » button.
In the solution explorer, remove the « Class1 » class, and create a new one named « OpportunityPricesPlugin »
Replace your code with the following
using Microsoft.Xrm.Sdk; using Microsoft.Xrm.Sdk.Query; using System; using System.ServiceModel; namespace OpportunityPrices { public class OpportuinityPricesPlugin : IPlugin { public void Execute(IServiceProvider serviceProvider) { //Extract the tracing service for use in debugging sandboxed plug-ins. ITracingService tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService)); tracingService.Trace("Will execute OpportuinityPricesPlugin"); // Obtain the execution context from the service provider. IPluginExecutionContext context = (IPluginExecutionContext) serviceProvider.GetService(typeof(IPluginExecutionContext)); if (!context.InputParameters.Contains("Target") && !(context.InputParameters["Target"] is Entity)) { throw new InvalidPluginExecutionException("OpportuinityPricesPlugin : Plugin is not correctly registered"); } // Obtain the target entity from the input parameters. Entity opportunity = (Entity)context.InputParameters["Target"]; // Verify that the target entity represents an entity type you are expecting. if (opportunity.LogicalName != "opportunity") { // The PlugIn is registered on a wrong step. return; } // Get the Dynamics 365 service that enables us to communicate with the model IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory)); IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId); try { // Ckeck the statecode, to be coherent with Dynamics rules Entity e = service.Retrieve(opportunity.LogicalName, opportunity.Id, new ColumnSet("statecode")); OptionSetValue statecode = (OptionSetValue)e["statecode"]; if (statecode.Value == 0) { // Look up the product line items : an example of a query within a plugin QueryExpression query = new QueryExpression("opportunityproduct"); query.ColumnSet.AddColumns("quantity", "priceperunit"); query.Criteria.AddCondition("opportunityid", ConditionOperator.Equal, opportunity.Id); EntityCollection ec = service.RetrieveMultiple(query); tracingService.Trace("OpportuinityPricesPlugin: I queried the product line items"); decimal total = 0; decimal discount = 0; decimal tax = 0; // Calculate the total amount for each line for (int i = 0; i < ec.Entities.Count; i++) { total = total + ((decimal)ec.Entities[i]["quantity"] * ((Money)ec.Entities[i]["priceperunit"]).Value); (ec.Entities[i])["extendedamount"] = new Money(((decimal)ec.Entities[i]["quantity"] * ((Money)ec.Entities[i]["priceperunit"]).Value)); service.Update(ec.Entities[i]); } tracingService.Trace("OpportuinityPricesPlugin: Calculated the amount of each line"); // Update the opportunity fields directely because we will register the step in PreOperation opportunity["totallineitemamount"] = new Money(total); // Calculate discount based on the total amount if (total > (decimal)800.00 && total < (decimal)1500.00) { discount = total * (decimal)0.05; } else if (total >= (decimal)1500.00) { discount = total * (decimal)0.10; } total = total - discount; opportunity["discountamount"] = new Money(discount); opportunity["totalamountlessfreight"] = new Money(total); tracingService.Trace("OpportuinityPricesPlugin: updated opp with discount"); } return; } catch (FaultException<OrganizationServiceFault> ex) { throw new InvalidPluginExecutionException("An error occurred in OpportuinityPricesPlugin.", ex); } catch (Exception ex) { tracingService.Trace("OpportuinityPricesPlugin: {0}", ex.ToString()); throw; } } } }
You should strong sign your assembly. Go in the project properties page, Signing tab, click « Sign the Assembly », select « New… » in the « Choose a strong name key file » combo.
Build the project, then launch the « Plugin Registration Tool » (PluginRegistration.exe). Start by creating a new connection by clicking the « + CREATE NEW CONNECTION » button in the toolbar :
Attention : I had a problem while connecting the first time, because my Plugin Registration Tool was at the wrong version : there is no exception message, but the tool keeps asking for login (a TLS 1.2 issue).
Once connected, register your assembly created with Visual Stiduo as in the images.
Expand you assembly plugin and add a step :
Then, choose the « Message »: « Update », « Primary Entity »: « opportunity », and « Event Pipeline Stage of Execution »: « PreOperation ».
As a final step in Dynamics 365 web application : disable standard system pricing calculation in the « Settings/Administration/System Settings », in « Sales » tab :
Now, you just have to test in dynamics, by updating an existing opportunity.
This was just an intro to plugins programming, but I hope it will help you understand the main quick steps. You have also the capability to debug plugins from within Visual Studio, which is a great feature, bu beyond the scope of this post.
Laisser un commentaire