Requirement
The quote product should be synched to the opportunity when a quote is activated.
Implementations
In the traditional sales life cycle, we create quote from opportunity which inherit all the opportunity data as well as products from opportunity.
But sometimes while working on the quote negotiations with the customer some information can be updated on the quote products or may new product is added or maybe the existing product is not required anymore by the customer and this can be easily done in the quote. But what about if we want to synchronize quote products with opportunity product once the quote is final maybe when the quote is activated? We can do this using a plugin, let’s see how.
I am not going to provide steps to create VS solution but if you are new to plugin development please refer to Dynamics 365 CE SDK for how to write plugins.
Back to the topic. So we want to synchronize products when the quote is activated which means we need to make sure our plugin is triggered on Quote Activation, earlier we used to do that in different events like setstate, etc. but now we can simply register it on Update of quote entity.
Entity quoteEntity = null;
Entity postupdateQuote = null;
if (pluginContext.InputParameters.Contains("Target") && pluginContext.InputParameters["Target"] is Entity) {
quoteEntity = (Entity) pluginContext.InputParameters["Target"];
if (pluginContext.PostEntityImages.Contains("postimage")) {
postupdateQuote = (Entity) pluginContext.PostEntityImages["postimage"];
}
//check if quote is active
if (quoteEntity.Contains("statecode") && quoteEntity.GetAttributeValue < optionSetValue > ("statecode").Value == 1) {
if (postupdateQuote.Contains("opportunityid")) {
SyncQuoteProductToOpportunityProduct(postupdateQuote.GetAttributeValue < EntityReference > ("opportunityid").Id, quoteEntity.Id);
}
}
}
In the above code, we are getting our entity object and getting postupdatequote from the post entity image as we need the opportunity and we will not get it under the quote entity input parameter. So we need to get it using entity image. Once we have the quote entity we are checking if statecode value is 1 which means the quote is activated.
Now next in our SyncQuoteProductToOpportunityProduct method, we need to get the opportunity product and quote product to compare, we can do this like below
private void SyncQuoteProductToOpportunityProduct(Guid parentoppid, Guid parentquoteid) {
EntityCollection oppproducts = null;
EntityCollection quoteproducts = null;
bool ismatchfound = false;
oppproducts = GetOpportunityProducts(parentoppid); //this method is to get all opportunity products based on opportunity
quoteproducts = GetQuoteProducts(parentquoteid); //this method is to get all quote products based on quote
//check all quote product available in opportunity product if yes update properties if not add new product
foreach(Entity quoteprod in quoteproducts.Entities) {
ismatchfound = false;
foreach(Entity oppprod in oppproducts.Entities) {
if (oppprod.GetAttributeValue < EntityReference > ("productid").Id == quoteprod.GetAttributeValue < EntityReference > ("productid").Id) {
ismatchfound = true;
//sync field values
Entity oppprodUpdate = new Entity("opportunityproduct");
oppprodUpdate.Id = oppprod.Id;
oppprodUpdate["priceperunit"] = quoteprod.GetAttributeValue < Money > ("priceperunit").Value;
oppprodUpdate["quantity"] = quoteprod.GetAttributeValue < decimal > ("quantity");
oppprodUpdate["uomid"] = quoteprod.GetAttributeValue < EntityReference > ("uomid");
oppprodUpdate.Update(oppUpdate);
}
}
//if no match found add new product
if (!ismatchfound) {
Entity oppprodCreate = new Entity("opportunityproduct");
oppprodCreate["productid"] = quoteprod.GetAttributeValue < EntityReference > ("productid");
oppprodCreate["priceperunit"] = quoteprod.GetAttributeValue < Money > ("priceperunit").Value;
oppprodCreate["quantity"] = quoteprod.GetAttributeValue < decimal > ("quantity");
oppprodCreate["uomid"] = quoteprod.GetAttributeValue < EntityReference > ("uomid");
oppprodCreate["opportunityid"] = quoteprod.GetAttributeValue < EntityReference > ("opportunityid");
orgService.Create(oppprodCreate);
}
}
//reset flag
ismatchfound = false;
//finally delete extra opportunity products
foreach(Entity oppprod in oppproducts.Entities) {
foreach(Entity quoteprod in quoteproducts.Entities) {
if (oppprod.GetAttributeValue < EntityReference > ("productid").Id == quoteprod.GetAttributeValue < EntityReference > ("productid").Id) {
ismatchfound = true;
}
}
//if oppprod not found on quote delte it
if (!ismatchfound) {
orgService.Delete("opportunityproduct", oppprod.Id);
}
}
}
Once our plugin is ready we can register it on Quote update make sure to select setstatecode under filtering attributes.
Summary
This is how we can sync products from quote to opportunity
Hope it will help someone !!
Keep learning and Keep Sharing !!