Thursday, August 4, 2011

Convert a Quote to a Sales Order Using Jscript or .NET in Microsoft Dynamics CRM 2011

This illustration shows how to set a quote's status to won and also how to convert a quote to a sales order record in Microsoft Dynamics CRM 2011 in code with the WinQuoteRequest and ConvertQuoteToSalesOrderRequest.   This example will be given in Jscript (SOAP) and in C# (.NET).

One thing to note is that you must first set the status of your quote to won and then convert it to a sales order. This all happens as part of one contiguous step in the UI but requires two separate calls in code.

Ok, here is what the code looks like!
First in C#:

WinQuoteRequest req = new WinQuoteRequest();
Entity quoteClose = new Entity("quoteclose");
quoteClose.Attributes.Add("quoteid", new EntityReference("quote", new Guid("6EDE549D-A5BE-E011-8D99-1CC1DE7955DB")));
quoteClose.Attributes.Add("subject", "Won the Quote");
req.QuoteClose = quoteClose;
req.RequestName = "WinQuote";
OptionSetValue o = new OptionSetValue();
o.Value = 4;
req.Status = o;
WinQuoteResponse resp = (WinQuoteResponse)service.Execute(req);

// Define columns to be retrieved after creating the order
ColumnSet salesOrderColumns =
    new ColumnSet("salesorderid", "totalamount");

// Convert the quote to a sales order
ConvertQuoteToSalesOrderRequest convertQuoteRequest =
    new ConvertQuoteToSalesOrderRequest()
    {
        QuoteId = new Guid("6EDE549D-A5BE-E011-8D99-1CC1DE7955DB"),
        ColumnSet = salesOrderColumns
    };
ConvertQuoteToSalesOrderResponse convertQuoteResponse =
    (ConvertQuoteToSalesOrderResponse)service.Execute(convertQuoteRequest);
SalesOrder salesOrder = (SalesOrder)convertQuoteResponse.Entity;

If you need help instantiating a service object in .NET within a plugin check out this post:
http://mileyja.blogspot.com/2011/04/instantiating-service-object-within.html

Now here is the Jscript nicely formatted by the CRM 2011 SOAP formatter. Available at: http://crm2011soap.codeplex.com/

Now in Jscript


This example is asynchronous, if you want to learn how to make JScript SOAP calls synchronously please visit this posthttp://mileyja.blogspot.com/2011/07/using-jscript-to-access-soap-web.html

First the WinQuoteRequest

if (typeof (SDK) == "undefined")
   { SDK = { __namespace: true }; }
       //This will establish a more unique namespace for functions in this library. This will reduce the 
       // potential for functions to be overwritten due to a duplicate name when the library is loaded.
       SDK.SAMPLES = {
           _getServerUrl: function () {
               ///<summary>
               /// Returns the URL for the SOAP endpoint using the context information available in the form
               /// or HTML Web resource.
               ///</summary>
               var OrgServicePath = "/XRMServices/2011/Organization.svc/web";
               var serverUrl = "";
               if (typeof GetGlobalContext == "function") {
                   var context = GetGlobalContext();
                   serverUrl = context.getServerUrl();
               }
               else {
                   if (typeof Xrm.Page.context == "object") {
                         serverUrl = Xrm.Page.context.getServerUrl();
                   }
                   else
                   { throw new Error("Unable to access the server URL"); }
                   }
                  if (serverUrl.match(/\/$/)) {
                       serverUrl = serverUrl.substring(0, serverUrl.length - 1);
                   } 
                   return serverUrl + OrgServicePath;
               }, 
           WinQuoteRequest: function () {
               var requestMain = ""
               requestMain += "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\">";
               requestMain += "  <s:Body>";
               requestMain += "    <Execute xmlns=\"http://schemas.microsoft.com/xrm/2011/Contracts/Services\" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">";
               requestMain += "      <request i:type=\"b:WinQuoteRequest\" xmlns:a=\"http://schemas.microsoft.com/xrm/2011/Contracts\" xmlns:b=\"http://schemas.microsoft.com/crm/2011/Contracts\">";
               requestMain += "        <a:Parameters xmlns:c=\"http://schemas.datacontract.org/2004/07/System.Collections.Generic\">";
               requestMain += "          <a:KeyValuePairOfstringanyType>";
               requestMain += "            <c:key>QuoteClose</c:key>";
               requestMain += "            <c:value i:type=\"a:Entity\">";
               requestMain += "              <a:Attributes>";
               requestMain += "                <a:KeyValuePairOfstringanyType>";
               requestMain += "                  <c:key>quoteid</c:key>";
               requestMain += "                  <c:value i:type=\"a:EntityReference\">";
               requestMain += "                    <a:Id>6ede549d-a5be-e011-8d99-1cc1de7955db</a:Id>";
               requestMain += "                    <a:LogicalName>quote</a:LogicalName>";
               requestMain += "                    <a:Name i:nil=\"true\" />";
               requestMain += "                  </c:value>";
               requestMain += "                </a:KeyValuePairOfstringanyType>";
               requestMain += "                <a:KeyValuePairOfstringanyType>";
               requestMain += "                  <c:key>subject</c:key>";
               requestMain += "                  <c:value i:type=\"d:string\" xmlns:d=\"http://www.w3.org/2001/XMLSchema\">Won the Quote</c:value>";
               requestMain += "                </a:KeyValuePairOfstringanyType>";
               requestMain += "              </a:Attributes>";
               requestMain += "              <a:EntityState i:nil=\"true\" />";
               requestMain += "              <a:FormattedValues />";
               requestMain += "              <a:Id>00000000-0000-0000-0000-000000000000</a:Id>";
               requestMain += "              <a:LogicalName>quoteclose</a:LogicalName>";
               requestMain += "              <a:RelatedEntities />";
               requestMain += "            </c:value>";
               requestMain += "          </a:KeyValuePairOfstringanyType>";
               requestMain += "          <a:KeyValuePairOfstringanyType>";
               requestMain += "            <c:key>Status</c:key>";
               requestMain += "            <c:value i:type=\"a:OptionSetValue\">";
               requestMain += "              <a:Value>4</a:Value>";
               requestMain += "            </c:value>";
               requestMain += "          </a:KeyValuePairOfstringanyType>";
               requestMain += "        </a:Parameters>";
               requestMain += "        <a:RequestId i:nil=\"true\" />";
               requestMain += "        <a:RequestName>WinQuote</a:RequestName>";
               requestMain += "      </request>";
               requestMain += "    </Execute>";
               requestMain += "  </s:Body>";
               requestMain += "</s:Envelope>";
               var req = new XMLHttpRequest();
               req.open("POST", SDK.SAMPLES._getServerUrl(), true)
               // Responses will return XML. It isn't possible to return JSON.
               req.setRequestHeader("Accept", "application/xml, text/xml, */*");
               req.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
               req.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/xrm/2011/Contracts/Services/IOrganizationService/Execute");
               var successCallback = null;
               var errorCallback = null;
               req.onreadystatechange = function () { SDK.SAMPLES.WinQuoteResponse(req, successCallback, errorCallback); };
               req.send(requestMain);
           },
       WinQuoteResponse: function (req, successCallback, errorCallback) {
               ///<summary>
               /// Recieves the assign response
               ///</summary>
               ///<param name="req" Type="XMLHttpRequest">
               /// The XMLHttpRequest response
               ///</param>
               ///<param name="successCallback" Type="Function">
               /// The function to perform when an successfult response is returned.
               /// For this message no data is returned so a success callback is not really necessary.
               ///</param>
               ///<param name="errorCallback" Type="Function">
               /// The function to perform when an error is returned.
               /// This function accepts a JScript error returned by the _getError function
               ///</param>
               if (req.readyState == 4) {
               if (req.status == 200) {
               if (successCallback != null)
               { successCallback(); }
               }
               else {
                   errorCallback(SDK.SAMPLES._getError(req.responseXML));
               }
           }
       },
       _getError: function (faultXml) {
           ///<summary>
           /// Parses the WCF fault returned in the event of an error.
           ///</summary>
           ///<param name="faultXml" Type="XML">
           /// The responseXML property of the XMLHttpRequest response.
           ///</param>
           var errorMessage = "Unknown Error (Unable to parse the fault)";
           if (typeof faultXml == "object") {
               try {
                   var bodyNode = faultXml.firstChild.firstChild;
                   //Retrieve the fault node
                   for (var i = 0; i < bodyNode.childNodes.length; i++) {
                       var node = bodyNode.childNodes[i];
                       //NOTE: This comparison does not handle the case where the XML namespace changes
                       if ("s:Fault" == node.nodeName) {
                       for (var j = 0; j < node.childNodes.length; j++) {
                           var faultStringNode = node.childNodes[j];
                           if ("faultstring" == faultStringNode.nodeName) {
                               errorMessage = faultStringNode.text;
                               break;
                           }
                       }
                       break;
                   }
               }
           }
           catch (e) { };
        }
        return new Error(errorMessage);
     },
 __namespace: true
};






Now the ConvertQuoteToSalesOrderRequest


if (typeof (SDK) == "undefined")
   { SDK = { __namespace: true }; }
       //This will establish a more unique namespace for functions in this library. This will reduce the 
       // potential for functions to be overwritten due to a duplicate name when the library is loaded.
       SDK.SAMPLES = {
           _getServerUrl: function () {
               ///<summary>
               /// Returns the URL for the SOAP endpoint using the context information available in the form
               /// or HTML Web resource.
               ///</summary>
               var OrgServicePath = "/XRMServices/2011/Organization.svc/web";
               var serverUrl = "";
               if (typeof GetGlobalContext == "function") {
                   var context = GetGlobalContext();
                   serverUrl = context.getServerUrl();
               }
               else {
                   if (typeof Xrm.Page.context == "object") {
                         serverUrl = Xrm.Page.context.getServerUrl();
                   }
                   else
                   { throw new Error("Unable to access the server URL"); }
                   }
                  if (serverUrl.match(/\/$/)) {
                       serverUrl = serverUrl.substring(0, serverUrl.length - 1);
                   } 
                   return serverUrl + OrgServicePath;
               }, 
           ConvertQuoteToSalesOrderRequest: function () {
               var requestMain = ""
               requestMain += "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\">";
               requestMain += "  <s:Body>";
               requestMain += "    <Execute xmlns=\"http://schemas.microsoft.com/xrm/2011/Contracts/Services\" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">";
               requestMain += "      <request i:type=\"b:ConvertQuoteToSalesOrderRequest\" xmlns:a=\"http://schemas.microsoft.com/xrm/2011/Contracts\" xmlns:b=\"http://schemas.microsoft.com/crm/2011/Contracts\">";
               requestMain += "        <a:Parameters xmlns:c=\"http://schemas.datacontract.org/2004/07/System.Collections.Generic\">";
               requestMain += "          <a:KeyValuePairOfstringanyType>";
               requestMain += "            <c:key>QuoteId</c:key>";
               requestMain += "            <c:value i:type=\"d:guid\" xmlns:d=\"http://schemas.microsoft.com/2003/10/Serialization/\">6ede549d-a5be-e011-8d99-1cc1de7955db</c:value>";
               requestMain += "          </a:KeyValuePairOfstringanyType>";
               requestMain += "          <a:KeyValuePairOfstringanyType>";
               requestMain += "            <c:key>ColumnSet</c:key>";
               requestMain += "            <c:value i:type=\"a:ColumnSet\">";
               requestMain += "              <a:AllColumns>false</a:AllColumns>";
               requestMain += "              <a:Columns xmlns:d=\"http://schemas.microsoft.com/2003/10/Serialization/Arrays\">";
               requestMain += "                <d:string>salesorderid</d:string>";
               requestMain += "                <d:string>totalamount</d:string>";
               requestMain += "              </a:Columns>";
               requestMain += "            </c:value>";
               requestMain += "          </a:KeyValuePairOfstringanyType>";
               requestMain += "        </a:Parameters>";
               requestMain += "        <a:RequestId i:nil=\"true\" />";
               requestMain += "        <a:RequestName>ConvertQuoteToSalesOrder</a:RequestName>";
               requestMain += "      </request>";
               requestMain += "    </Execute>";
               requestMain += "  </s:Body>";
               requestMain += "</s:Envelope>";
               var req = new XMLHttpRequest();
               req.open("POST", SDK.SAMPLES._getServerUrl(), true)
               // Responses will return XML. It isn't possible to return JSON.
               req.setRequestHeader("Accept", "application/xml, text/xml, */*");
               req.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
               req.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/xrm/2011/Contracts/Services/IOrganizationService/Execute");
               var successCallback = null;
               var errorCallback = null;
               req.onreadystatechange = function () { SDK.SAMPLES.ConvertQuoteToSalesOrderResponse(req, successCallback, errorCallback); };
               req.send(requestMain);
           },
       ConvertQuoteToSalesOrderResponse: function (req, successCallback, errorCallback) {
               ///<summary>
               /// Recieves the assign response
               ///</summary>
               ///<param name="req" Type="XMLHttpRequest">
               /// The XMLHttpRequest response
               ///</param>
               ///<param name="successCallback" Type="Function">
               /// The function to perform when an successfult response is returned.
               /// For this message no data is returned so a success callback is not really necessary.
               ///</param>
               ///<param name="errorCallback" Type="Function">
               /// The function to perform when an error is returned.
               /// This function accepts a JScript error returned by the _getError function
               ///</param>
               if (req.readyState == 4) {
               if (req.status == 200) {
               if (successCallback != null)
               { successCallback(); }
               }
               else {
                   errorCallback(SDK.SAMPLES._getError(req.responseXML));
               }
           }
       },
       _getError: function (faultXml) {
           ///<summary>
           /// Parses the WCF fault returned in the event of an error.
           ///</summary>
           ///<param name="faultXml" Type="XML">
           /// The responseXML property of the XMLHttpRequest response.
           ///</param>
           var errorMessage = "Unknown Error (Unable to parse the fault)";
           if (typeof faultXml == "object") {
               try {
                   var bodyNode = faultXml.firstChild.firstChild;
                   //Retrieve the fault node
                   for (var i = 0; i < bodyNode.childNodes.length; i++) {
                       var node = bodyNode.childNodes[i];
                       //NOTE: This comparison does not handle the case where the XML namespace changes
                       if ("s:Fault" == node.nodeName) {
                       for (var j = 0; j < node.childNodes.length; j++) {
                           var faultStringNode = node.childNodes[j];
                           if ("faultstring" == faultStringNode.nodeName) {
                               errorMessage = faultStringNode.text;
                               break;
                           }
                       }
                       break;
                   }
               }
           }
           catch (e) { };
        }
        return new Error(errorMessage);
     },
 __namespace: true
};





To understand how to parse the response please review my post on using the DOM parser.
Now you can call the SDK.SAMPLES.WinQuoteRequest  and  SDK.SAMPLES.ConvertQuoteToSalesOrderRequest functions from your form jscript handler.
Thats all there is to it!

I hope this helps!

3 comments:

  1. Hello Jamie,

    Thank you for this great post.
    I have tried to use these javascripts on a form but when i run the scripts i get an error: Object expected. Row 105.

    Can you help me with this error please?

    ReplyDelete
    Replies
    1. You should try debugging the jscript: http://mileyja.blogspot.com/2011/05/debugging-jscript-in-microsoft-dynamics.html

      Delete
  2. This comment has been removed by the author.

    ReplyDelete