{"id":4451,"date":"2023-01-31T13:52:21","date_gmt":"2023-01-31T13:52:21","guid":{"rendered":"https:\/\/himbap.com\/blog\/?p=4451"},"modified":"2023-01-31T13:52:21","modified_gmt":"2023-01-31T13:52:21","slug":"syncing-product-from-quotes-to-opportunity","status":"publish","type":"post","link":"https:\/\/himbap.com\/blog\/?p=4451","title":{"rendered":"Syncing product from Quotes to Opportunity"},"content":{"rendered":"<p><strong>Requirement<\/strong><br \/>\nQuote product should be synched to opportunity when quote is activated.<\/p>\n<p><strong>Implementations<\/strong><br \/>\nIn traditional sales life cycle we create quote from opportunity which inherit all the opportunity data as well as products from opportunity.<\/p>\n<p><a href=\"https:\/\/himbap.com\/blog\/wp-content\/uploads\/2023\/01\/sales1.png\"><img decoding=\"async\" loading=\"lazy\" class=\"alignnone size-medium wp-image-4452 aligncenter\" src=\"https:\/\/himbap.com\/blog\/wp-content\/uploads\/2023\/01\/sales1-300x32.png\" alt=\"sales1\" width=\"300\" height=\"32\" srcset=\"https:\/\/himbap.com\/blog\/wp-content\/uploads\/2023\/01\/sales1-300x32.png 300w, https:\/\/himbap.com\/blog\/wp-content\/uploads\/2023\/01\/sales1.png 500w\" sizes=\"(max-width: 300px) 100vw, 300px\" \/><\/a><\/p>\n<p>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&#8217;s see how.<\/p>\n<p>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.<\/p>\n<p>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.<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\n  Entity quoteEntity = null;\r\n  Entity postupdateQuote = null;\r\n  \r\nif (pluginContext.InputParameters.Contains(&quot;Target&quot;) &amp;&amp;\r\n    pluginContext.InputParameters[&quot;Target&quot;] is Entity) {\r\n    quoteEntity = (Entity) pluginContext.InputParameters[&quot;Target&quot;];\r\n\r\n    if (pluginContext.PostEntityImages.Contains(&quot;postimage&quot;)) {\r\n      postupdateQuote = (Entity) pluginContext.PostEntityImages[&quot;postimage&quot;];\r\n    }\r\n    \/\/check if quote is active\r\n    if (quoteEntity.Contains(&quot;statecode&quot;) &amp;&amp;\r\n      quoteEntity.GetAttributeValue&lt;optionSetValue&gt;(&quot;statecode&quot;).Value == 1) {\r\n      if (postupdateQuote.Contains(&quot;opportunityid&quot;)) {\r\n        SyncQuoteProductToOpportunityProduct(postupdateQuote.GetAttributeValue&lt;EntityReference&gt; (&quot;opportunityid&quot;).Id, quoteEntity.Id);\r\n\r\n      }\r\n\r\n    }\r\n  }\r\n<\/pre>\n<p>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.<\/p>\n<p>Now next in our SyncQuoteProductToOpportunityProduct method we need to get opportunity product and quote product to compare, we can do this like below<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\nprivate void SyncQuoteProductToOpportunityProduct(Guid parentoppid, Guid parentquoteid) {\r\n  EntityCollection oppproducts = null;\r\n  EntityCollection quoteproducts = null;\r\n  bool ismatchfound = false;\r\n\r\n  oppproducts = GetOpportunityProducts(parentoppid); \/\/this method is to get all opportunity products based on opportunity\r\n  quoteproducts = GetQuoteProducts(parentquoteid); \/\/this method is to get all quote products based on quote\r\n\r\n  \/\/check all quote product available in opportunity product if yes update properties if not add new product\r\n  foreach(Entity quoteprod in quoteproducts.Entities) {\r\n    ismatchfound = false;\r\n\r\n    foreach(Entity oppprod in oppproducts.Entities) {\r\n      if (oppprod.GetAttributeValue &lt; EntityReference &gt; (&quot;productid&quot;).Id == quoteprod.GetAttributeValue &lt; EntityReference &gt; (&quot;productid&quot;).Id) {\r\n\r\n        ismatchfound = true;\r\n        \/\/sync field values\r\n        Entity oppprodUpdate = new Entity(&quot;opportunityproduct&quot;);\r\n        oppprodUpdate .Id = oppprod.Id;\r\n        oppprodUpdate [&quot;priceperunit&quot;] = quoteprod.GetAttributeValue &lt; Money &gt; (&quot;priceperunit&quot;).Value;\r\n        oppprodUpdate [&quot;quantity&quot;] = quoteprod.GetAttributeValue &lt; decimal &gt; (&quot;quantity&quot;);\r\n        oppprodUpdate [&quot;uomid&quot;] = quoteprod.GetAttributeValue &lt; EntityReference &gt; (&quot;uomid&quot;);\r\n        oppprodUpdate .Update(oppUpdate);\r\n      }\r\n\r\n    }\r\n    \/\/if no match found add new product\r\n    if (!ismatchfound) {\r\n      Entity oppprodCreate = new Entity(&quot;opportunityproduct&quot;);\r\n      oppprodCreate[&quot;productid&quot;] = quoteprod.GetAttributeValue &lt; EntityReference &gt; (&quot;productid&quot;);\r\n      oppprodCreate[&quot;priceperunit&quot;] = quoteprod.GetAttributeValue &lt; Money &gt; (&quot;priceperunit&quot;).Value;\r\n      oppprodCreate[&quot;quantity&quot;] = quoteprod.GetAttributeValue &lt; decimal &gt; (&quot;quantity&quot;);\r\n      oppprodCreate[&quot;uomid&quot;] = quoteprod.GetAttributeValue &lt; EntityReference &gt; (&quot;uomid&quot;);\r\n      oppprodCreate[&quot;opportunityid&quot;] =  quoteprod.GetAttributeValue &lt; EntityReference &gt; (&quot;opportunityid&quot;);\r\n      orgService.Create(oppprodCreate);\r\n\r\n    }\r\n  }\r\n  \/\/reset flag\r\n  ismatchfound = false;\r\n\r\n  \/\/finally delete extra opportunity products\r\n  foreach(Entity oppprod in oppproducts.Entities) {\r\n    foreach(Entity quoteprod in quoteproducts.Entities) {\r\n      if (oppprod.GetAttributeValue &lt; EntityReference &gt; (&quot;productid&quot;).Id == quoteprod.GetAttributeValue &lt; EntityReference &gt; (&quot;productid&quot;).Id) {\r\n        ismatchfound = true;\r\n      }\r\n\r\n    }\r\n    \/\/if oppprod not found on quote delte it\r\n    if (!ismatchfound) {\r\n        orgService.Delete(&quot;opportunityproduct&quot;, oppprod.Id);\r\n    }\r\n  }\r\n\r\n}\r\n\r\n<\/pre>\n<p>Once our plugin is ready we can register is on Quote update make sure to select setstatecode under filtering attributes.<\/p>\n<p><a href=\"https:\/\/himbap.com\/blog\/wp-content\/uploads\/2023\/01\/sales2.png\"><img decoding=\"async\" loading=\"lazy\" class=\"alignnone size-medium wp-image-4453 aligncenter\" src=\"https:\/\/himbap.com\/blog\/wp-content\/uploads\/2023\/01\/sales2-300x84.png\" alt=\"sales2\" width=\"300\" height=\"84\" srcset=\"https:\/\/himbap.com\/blog\/wp-content\/uploads\/2023\/01\/sales2-300x84.png 300w, https:\/\/himbap.com\/blog\/wp-content\/uploads\/2023\/01\/sales2.png 559w\" sizes=\"(max-width: 300px) 100vw, 300px\" \/><\/a><\/p>\n<p>Summary<br \/>\nThis is how we can sync products from quote to opportunity<\/p>\n<p>Hope it will help someone !!<br \/>\n<strong>Keep learning and Keep Sharing !!<\/strong><\/p>\n","protected":false},"excerpt":{"rendered":"<p>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&#8230; <a href=\"https:\/\/himbap.com\/blog\/?p=4451\">Read more &raquo;<\/a><\/p>\n","protected":false},"author":1,"featured_media":4453,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[402,522,5,6,275,20],"tags":[1020,1021,1022],"_links":{"self":[{"href":"https:\/\/himbap.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/4451"}],"collection":[{"href":"https:\/\/himbap.com\/blog\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/himbap.com\/blog\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/himbap.com\/blog\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/himbap.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=4451"}],"version-history":[{"count":2,"href":"https:\/\/himbap.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/4451\/revisions"}],"predecessor-version":[{"id":4455,"href":"https:\/\/himbap.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/4451\/revisions\/4455"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/himbap.com\/blog\/index.php?rest_route=\/wp\/v2\/media\/4453"}],"wp:attachment":[{"href":"https:\/\/himbap.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=4451"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/himbap.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=4451"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/himbap.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=4451"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}