Requirement
Quote product should be synched to opportunity when quote is activated.
Implementations
In 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 existing product is not require anymore by the customer and this can be easy done in the quote but what about if we want to synchronize quote products with opportunity product once quote is final maybe when 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 in plugin development please refer Dynamics 365 CE SDK for how to write plugin.
Back to topic so we want to synchronize products when quote is activated which means we need to make sure our plugin is triggered on Quote Activation, earlier we used do that in different events like setstate etc. but not 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 above code we are getting our entity object and getting postupdatequote from the post entity image as we need opportunity and we will not get it under quote entity input parameter so we need to get it using entity image. Once we have quote entity we are checking if statecode value is 1 which means quote is activated.
Now next in our SyncQuoteProductToOpportunityProduct method we need to get 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 is 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 !!