Monday, September 17, 2012

PDQ in CRM 2011

Siebel CRM had an excellent concept of providing and customizing a default query that will be executed for each view. They are called Pre Defined Queries shortly PDQs. We can either have public or private PDQs

I was looking into MS CRM for similar concept and the only way is to create new views.
I don't want to create too many system views which will in turn become a management problem. (everything will be public and not all the users want to have those views)

We can however solve this problem by allowing the users to create a user view and then share it with the team. By this way all the users in that particular team will see that user defined view which they can use.
This is indeed better than Siebel solution.

You can find below on how to create a user defined view and how to share it with a team.

Use the advanced find button to create a new user saved view and save the new view.

After creating and testing the view using the Results button, click in the Saved Views button
Click on the share button to share the view with either users or teams.


Select the privileges. By default Read privileges is selected. 

Note: This still required the shared team/users have access to the entity that you are sharing. If they don't have access they cannot see it. So you cannot bypass the security layer posted by MS CRM.

Job Done




Wednesday, September 12, 2012

Dynamic filters

For all those who have worked in excel, the filter method on the column header is a life saver.
CRM 2011 offers such a nice feature of filtering records within any view.

Click on the filter icon in the ribbon and the filter appears on the columns header.
Click on the filter icon to continue filtering.


My View and My Teams View in CRM 2011

Hi,

I have been working with Siebel CRM before and I have been pretty much used to My Views and My Teams views in Siebel. (Position based visibility on records)
We used to have position (team) owned entities like Account, Contact, Opportunity and user owned entities like Activities and SR. Also in case of activities and service request quite often we need to look into your teams activities and teams requests. Although they are user owned entities in Siebel, we used the reporting logic to display this managerial view.

Similarly if we want to achieve a similar kind of functionality in CRM 2011, it's quite simple.

Use the advanced query editor to define the query as below.
Select the owner as the current user OR equal to the current user's teams


Also if you have used Siebel, when you are in Activities view, you will also see a view called "My Contact's activities" or "My Company's Activities". We can achieve the same in CRM 2011 as below. This is quite a powerful feature.


Note: Since these queries are resource intensive use with CAUTION. Use other filter conditions to filter more records. For example, I have used to filter only open activities.

See the next episode here

Monday, September 10, 2012

Testing of FetchXml

I have come across few instances where I want to test the custom written fetch xml query to test against the CRM. The reason being you cannot test aggregate queries in CRM and if you need to be sure to test the output before implementation, here you go with the solution. You can see the output so that it's easy before implementing the actual query.
Create a VS 2010 project (form based application).
Design the form by creating the following

Type
Name
Label
Button
buttonClearInput
Clear Input
Button
buttonClearOutput
Clear Output
Button
convert
Convert
TextBox
FetchXmlReq
Fetch Xml
TextBox
FetchXmlResp
Fetch Xml Response








        private void buttonClearInput_Click(object sender, EventArgs e)
        {
            FetchXmlReq.Text = "";
        }

        private void buttonClearOutput_Click(object sender, EventArgs e)
        {
            FetchXmlResp.Text = "";
        }

        private void convert_Click(object sender, EventArgs e)
        {
            string fetchXml = FetchXmlReq.Text;
            string fetchXmlResp =fetchXml;
            fetchXmlResp = testFetch(fetchXml);
            FetchXmlResp.Text = fetchXmlResp;
        }





private string testFetch(string fetchXml)
        {
            Uri organizationUri = new Uri("http://servername:port/SSBCapgemini/XRMServices/2011/Organization.svc");
            Uri homeRealmUri = null;
            ClientCredentials credentials = new ClientCredentials();

            credentials.Windows.ClientCredential.Domain = "domain";
            credentials.Windows.ClientCredential.UserName = "username";
            credentials.Windows.ClientCredential.Password = "Password";
            String fetchXmlresp = string.Empty;
            Guid _accountid = Guid.Empty;
            OrganizationServiceProxy orgProxy = new OrganizationServiceProxy(organizationUri, homeRealmUri, credentials, null);
            IOrganizationService _service = (IOrganizationService)orgProxy;
            try
            {
              
                /*
                string estimatedvalue_avg = @"  <fetch version='1.0' mapping='logical' distinct='false' aggregate='true'>
                                                  <entity name='ssb_stock'>
                                                    <attribute name='ssb_market_value' aggregate='SUM' alias='sum_market_value'/>
                                                    <attribute name='ssb_cost_value' aggregate='SUM' alias='sum_cost_value'/>
                                                    <filter>
                                                      <condition attribute='ssb_contact' operator='eq' value='{AE6A2F6E-13B9-8451-3775-0E328DF08230}' />
                                                    </filter>
                                                  </entity>
                                                </fetch>";
                //*/
                string estimatedvalue_avg = fetchXml;

                EntityCollection estimatedvalue_avg_result = _service.RetrieveMultiple(new FetchExpression(estimatedvalue_avg));

                foreach (var c in estimatedvalue_avg_result.Entities)
                {
                    decimal aggregate1 = ((Money)((AliasedValue)c.Attributes["sum_market_value"]).Value).Value;
                    decimal aggregate2 = ((Money)((AliasedValue)c.Attributes["sum_cost_value"]).Value).Value;
                    fetchXmlresp += "Sum of Market value: " + aggregate1 + Environment.NewLine;
                    fetchXmlresp += "Sum of Cost value: " + aggregate2;

                }

                /*
                // Display the results.
                fetchXmlresp += @"List all stocks matching specified parameters";
                fetchXmlresp += @"===============================================";
                foreach (var e in entityResults.Entities)
                {
                    fetchXmlresp += String.Format("Stock ID: {0}\n", e.Id;
                }


                fetchXmlresp += "<End of Listing>\n\n";
                 */
            }
            catch (Exception ex)
            {
               fetchXmlresp += ex.Message;
            }
           
            return fetchXmlresp;
        }

Set Auto numbering Format CRM 2011

You can control the prefix and suffix to certain extent for system entities in CRM 2011.
Navigate to Settings ->Administration ->Auto-Numbering.


Note: This option is available only for system entities. If you want to use autonumbering for custom entities or use a different format for system entities, refer to this post "Auto number in CRM 2011"

Hide Getting Started Pane/ Get CRM for Outlook Pane

You can easily hide the Get started pane of Get Outlook extension.
It's quite annoying when you keep getting this.

Admin setting for Get Started Pane
Turn of this option in the System setting under Administration.
Optionally if you want to enable this option and the individual users can turn off using the below option


To turn off Get Outlook client message



Friday, September 07, 2012

Fetch XML and Javascript

I had a requirement to show the financial summary of the customers.

I tried different options and ended up using Javascript.

I created an new button in the ribbon and then called the javascript to update the values using fetchxml.
Call the "GetOverview" function in the ssb_contact_links.js file.


Upload the below Javascript as webresources and then link these libraries to the form so that we can call those functions.


// JScript source code
//===============================================================================================
//ssb_FetchXmlLib.js
var XMLHTTPSUCCESS = 200;
var XMLHTTPREADY = 4;
function FetchUtil(sOrg, sServer) {
    this.org = sOrg;
    this.server = sServer;
    if (sOrg == null) {
        if (typeof (ORG_UNIQUE_NAME) != "undefined") {
            this.org = ORG_UNIQUE_NAME;
        }
    }
    if (sServer == null) {
        this.server = window.location.protocol + "//" + window.location.host;
    }
}
FetchUtil.prototype._ExecuteRequest = function (sXml, sMessage, fInternalCallback, fUserCallback) {
    var xmlhttp = new XMLHttpRequest();
    xmlhttp.open("POST", this.server + "/XRMServices/2011/Organization.svc/web", (fUserCallback != null));
    xmlhttp.setRequestHeader("Accept", "application/xml, text/xml, */*");
    xmlhttp.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
    xmlhttp.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/xrm/2011/Contracts/Services/IOrganizationService/Execute");
    if (fUserCallback != null) {
        //asynchronous: register callback function, then send the request.
        var crmServiceObject = this;
        xmlhttp.onreadystatechange = function () {
            fInternalCallback.call(crmServiceObject, xmlhttp, fUserCallback)
        };
        xmlhttp.send(sXml);
    } else {
        //synchronous: send request, then call the callback function directly
        xmlhttp.send(sXml);
        return fInternalCallback.call(this, xmlhttp, null);
    }
}
FetchUtil.prototype._HandleErrors = function (xmlhttp) {
    /// <summary>(private) Handles xmlhttp errors</summary>
    if (xmlhttp.status != XMLHTTPSUCCESS) {
        var sError = "Error: " + xmlhttp.responseText + " " + xmlhttp.statusText;
        alert(sError);
        return true;
    } else {
        return false;
    }
}
FetchUtil.prototype.Fetch = function (sFetchXml, fCallback) {
    /// <summary>Execute a FetchXml request. (result is the response XML)</summary>
    /// <param name="sFetchXml">fetchxml string</param>
    /// <param name="fCallback" optional="true" type="function">(Optional) Async callback function if specified. If left null, function is synchronous </param>
    var request = "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\">";
    request += "<s:Body>";
    request += '<Execute xmlns="http://schemas.microsoft.com/xrm/2011/Contracts/Services">' + '<request i:type="b:RetrieveMultipleRequest" ' + ' xmlns:b="http://schemas.microsoft.com/xrm/2011/Contracts" ' + ' xmlns:i="http://www.w3.org/2001/XMLSchema-instance">' + '<b:Parameters xmlns:c="http://schemas.datacontract.org/2004/07/System.Collections.Generic">' + '<b:KeyValuePairOfstringanyType>' + '<c:key>Query</c:key>' + '<c:value i:type="b:FetchExpression">' + '<b:Query>';
    request += CrmEncodeDecode.CrmXmlEncode(sFetchXml);
    request += '</b:Query>' + '</c:value>' + '</b:KeyValuePairOfstringanyType>' + '</b:Parameters>' + '<b:RequestId i:nil="true"/>' + '<b:RequestName>RetrieveMultiple</b:RequestName>' + '</request>' + '</Execute>';
    request += '</s:Body></s:Envelope>';
    return this._ExecuteRequest(request, "Fetch", this._FetchCallback, fCallback);
}
FetchUtil.prototype._FetchCallback = function (xmlhttp, callback) {
    ///<summary>(private) Fetch message callback.</summary>
    //xmlhttp must be completed
    if (xmlhttp.readyState != XMLHTTPREADY) {
        return;
    }
    //check for server errors
    if (this._HandleErrors(xmlhttp)) {
        return;
    }
    var sFetchResult = xmlhttp.responseXML.selectSingleNode("//a:Entities").xml;
    var resultDoc = new ActiveXObject("Microsoft.XMLDOM");
    resultDoc.async = false;
    resultDoc.loadXML(sFetchResult);
    //parse result xml into array of jsDynamicEntity objects
    var results = new Array(resultDoc.firstChild.childNodes.length);
    for (var i = 0; i < resultDoc.firstChild.childNodes.length; i++) {
        var oResultNode = resultDoc.firstChild.childNodes[i];
        var jDE = new jsDynamicEntity();
        var obj = new Object();
        for (var j = 0; j < oResultNode.childNodes.length; j++) {
            switch (oResultNode.childNodes[j].baseName) {
                case "Attributes":
                    var attr = oResultNode.childNodes[j];
                    for (var k = 0; k < attr.childNodes.length; k++) {
                        // Establish the Key for the Attribute
                        var sKey = attr.childNodes[k].firstChild.text;
                        var sType = "";
                        // Determine the Type of Attribute value we should expect
                        for (var l = 0; l < attr.childNodes[k].childNodes[1].attributes.length; l++) {
                            if (attr.childNodes[k].childNodes[1].attributes[l].baseName == 'type') {
                                sType = attr.childNodes[k].childNodes[1].attributes[l].text;
                            }
                        }
                        switch (sType) {
                            case "a:OptionSetValue":
                                var entOSV = new jsOptionSetValue();
                                entOSV.type = sType;
                                entOSV.value = attr.childNodes[k].childNodes[1].text;
                                obj[sKey] = entOSV;
                                break;
                            case "a:EntityReference":
                                var entRef = new jsEntityReference();
                                entRef.type = sType;
                                entRef.guid = attr.childNodes[k].childNodes[1].childNodes[0].text;
                                entRef.logicalName = attr.childNodes[k].childNodes[1].childNodes[1].text;
                                entRef.name = attr.childNodes[k].childNodes[1].childNodes[2].text;
                                obj[sKey] = entRef;
                                break;
                            case "a:AliasedValue":
                                var entAV = new jsAliasedValue();
                                //entAV.type = sType;
                                entAV.name = attr.childNodes[k].childNodes[1].childNodes[0].text;
                                entAV.logicalName = attr.childNodes[k].childNodes[1].childNodes[1].text;
                                entAV.value = attr.childNodes[k].childNodes[1].childNodes[2].text;
                                obj[sKey] = entAV;
                                break;
                            default:
                                var entCV = new jsCrmValue();
                                entCV.type = sType;
                                entCV.value = attr.childNodes[k].childNodes[1].text;
                                obj[sKey] = entCV;
                                break;
                        }
                    }
                    jDE.attributes = obj;
                    break;
                case "Id":
                    jDE.guid = oResultNode.childNodes[j].text;
                    break;
                case "LogicalName":
                    jDE.logicalName = oResultNode.childNodes[j].text;
                    break;
                case "FormattedValues":
                    var foVal = oResultNode.childNodes[j];
                    for (var k = 0; k < foVal.childNodes.length; k++) {
                        // Establish the Key, we are going to fill in the formatted value of the already found attribute
                        var sKey = foVal.childNodes[k].firstChild.text;
                        jDE.attributes[sKey].formattedValue = foVal.childNodes[k].childNodes[1].text;
                    }
                    break;
            }
        }
        results[i] = jDE;
    }
    //return entities
    if (callback != null) callback(results);
    else return results;
}
function jsAliasedValue(sLogicalName, sName, sValue, sFormattedValue) {
    this.value = sValue;
    this.logicalName = sLogicalName;
    this.name = sName;
    this.type = 'AliasedValue';
    this.formattedValue = sFormattedValue;
}
function jsDynamicEntity(gID, sLogicalName) {
    this.guid = gID;
    this.logicalName = sLogicalName;
    this.attributes = new Object();
}
function jsCrmValue(sType, sValue) {
    this.type = sType;
    this.value = sValue;
}
function jsEntityReference(gID, sLogicalName, sName) {
    this.guid = gID;
    this.logicalName = sLogicalName;
    this.name = sName;
    this.type = 'EntityReference';
}
function jsOptionSetValue(iValue, sFormattedValue) {
    this.value = iValue;
    this.formattedValue = sFormattedValue;
    this.type = 'OptionSetValue';
}

//===============================================================================================
//ssb_fetchXml.js

// JScript source code
function fetchOnLoadSync() {
    var _oService;
    var _sOrgName = "OrgName";
    var _sServerUrl = Xrm.Page.context.getServerUrl();
    // Get the ID of the Customer
    var sCustGUID = Xrm.Page.data.entity.getId();
    var entityName = Xrm.Page.data.entity.getEntityName();
    var linkEntity = "";

    if (entityName == "contact") linkEntity = "ssb_contact";
    if (entityName == "account") linkEntity = "ssb_account";
    var msg = "";
    msg += "Logical Entity Name = " + entityName + "\n";
    msg += "Guid of the current record = " + sCustGUID + "\n";
    msg += "Link Entity Name = " + linkEntity + "\n";
    //alert(sCustGUID);
    var sFetch =    "<fetch version='1.0' mapping='logical' distinct='false' aggregate='true'>"+
                      "<entity name='ssb_stock'>"+
                        "<attribute name='ssb_market_value' aggregate='SUM' alias='sum_market_value'/>" +
                        "<attribute name='ssb_cost_value' aggregate='SUM' alias='sum_cost_value'/>" +
                        "<attribute name='ssb_stock_name' aggregate='count' alias='count_of_stock'/>" +
                        "<filter>"+
                          "<condition attribute='" + linkEntity + "' operator='eq' value='"+ sCustGUID + "' />"+
                        "</filter>"+
                      "</entity>"+
                    "</fetch>";
    _oService = new FetchUtil(_sOrgName, _sServerUrl);
    var res = _oService.Fetch(sFetch);
    //for (x in res) {
    // for (y in res[x]) {
            //alert("Type = " + res[0].attributes["sum_market_value"].type);
           // alert("Value = " + res[0].attributes["sum_market_value"].value);
            //alert("Formatted Value = " + res[0].attributes["sum_market_value"].formattedValue);
         //   alert(res[x].attributes[y].value);
     //   }
    // }
    msg += "Type = " + res[0].attributes["sum_market_value"].type + "\n";
    msg += "Market Value = " + res[0].attributes["sum_market_value"].value + "\n";
    msg += "Market Value Formatted = " + res[0].attributes["sum_market_value"].formattedValue;
    msg += "Cost Value = " + res[0].attributes["sum_cost_value"].value + "\n";
    msg += "Cost Value Formatted = " + res[0].attributes["sum_cost_value"].formattedValue;
    msg += "Count of Records = " + res[0].attributes["count_of_stock"].value + "\n";
    //alert(msg);
   return (res[0].attributes["sum_market_value"].value );
}

//=======================================================================
//ssb_contact_links.js

function GetOverview()
{
//<script src="ssb_FetchXml.js"></script>
//alert("Overview Start");
//var fetch: ScriptName;
//fetch = this.GetComponent("ssb_FetchXml");
//alert (Xrm.Page.getAttribute("ssb_sum_of_stocks").getValue());
var marketValue =Number( fetchOnLoadSync());
//alert (marketValue )
Xrm.Page.getAttribute("ssb_sum_of_stocks").setValue(marketValue);
var idag = new Date();
//alert (idag);
Xrm.Page.getAttribute("ssb_overview_updated_date").setValue(idag);
//alert("Overview Refreshed");
Xrm.Page.data.entity.save();

}