Wednesday 30 January 2013

Clone a record in MS CRM 2011

Sometime in CRM there is a need to copy the existing record.For example there may be a need to clone the existing case so that service representative can update some fields on the cloned case.

Q.How can we achieve it in MS CRM 2011
A. It can be achieved either by JavaScript or Plugin.

We will try to achieve with JavaScript using "Relationships" mappings feature in MS CRM 2011.

Q:What we need to do?
  
1)Add a ribbon button called "Clone Case"
2)Creating a 1:N relationship with Case to Case then generating mappings.
3)On button click Open the URL of the cloned Case.


Implementation:

1)Add a ribbon button called "Clone Case"

Add a ribbon button called "Clone Case".You can use ribbon workbech for this on click of this button it will call a Javascript function called "cloneCase" in "My_CustomRibbonJavascript" webresource.
Or you can do manually like.

1)Create a solution add "Case" entity to it.
2)Export the Solution.
3)Extract the Solution.
4)Open Customizations.xml in Visual studio.
5)Replace <RibbonDiffXmlwith the below given XML
6)Take care of adding the icons as given in below XML in CRM as webresources before import.

<RibbonDiffXml>
  <CustomActions>
 
    <CustomAction Id="My.MSCRM.incident.form.Clone.Button.CustomAction" Location="Mscrm.Form.incident.MainTab.Collaborate.Controls._children" Sequence="0">
      <CommandUIDefinition>
        <Button Command="MSCRM.incident.form.Clone.Command" Id="MSCRM.incident.form.Clone.Button" Image32by32="$webresource:My_Clone32" Image16by16="$webresource:My_Clone16" LabelText="$LocLabels:MSCRM.incident.form.Clone.Button.LabelText" Sequence="0" TemplateAlias="o1" ToolTipTitle="$LocLabels:MSCRM.incident.form.Clone.Button.ToolTipTitle" ToolTipDescription="$LocLabels:MSCRM.incident.form.Clone.Button.ToolTipDescription" />
      </CommandUIDefinition>
    </CustomAction>
 
  </CustomActions>
  <Templates>
    <RibbonTemplates Id="Mscrm.Templates"></RibbonTemplates>
  </Templates>
  <CommandDefinitions>
 
    <CommandDefinition Id="MSCRM.incident.form.Clone.Command">
      <EnableRules/>
      <DisplayRules>
        <DisplayRule Id="MSCRM.incident.form.Clone.DisplayRule" />
      </DisplayRules>
      <Actions>
        <JavaScriptFunction FunctionName="cloneCase" Library="$webresource:My_CustomRibbonJavascript" />
      </Actions>
    </CommandDefinition>
 
  </CommandDefinitions>
  <RuleDefinitions>
    <TabDisplayRules />
    <DisplayRules>
 
      <DisplayRule Id="MSCRM.incident.form.Clone.DisplayRule">
        <FormStateRule State="Create" InvertResult="true" />
      </DisplayRule>
 
    </DisplayRules>
    <EnableRules/>
 
  </RuleDefinitions>
  <LocLabels>
 
    <LocLabel Id="MSCRM.incident.form.Clone.Button.LabelText">
      <Titles>
        <Title description="Clone Case" languagecode="1033" />
      </Titles>
    </LocLabel>
    <LocLabel Id="MSCRM.incident.form.Clone.Button.ToolTipDescription">
      <Titles>
        <Title description="Clone Case" languagecode="1033" />
      </Titles>
    </LocLabel>
    <LocLabel Id="MSCRM.incident.form.Clone.Button.ToolTipTitle">
      <Titles>
        <Title description="Clone Case" languagecode="1033" />
      </Titles>
    </LocLabel>
 
  </LocLabels>
</RibbonDiffXml>

2)Creating a 1:N relationship with Case to Case then generating mappings.

Open the Case enity relationship(1:N) and Create a relationship with Case.Open the created relationship and generate mappings.

Refer below screenshots











3)On button click Open the URL of the cloned Case.

Add a new webresource called "My_CustomRibbonJavascript" add the  "cloneCase" function to it


function GetContext() {
    var _context = null;
    if (typeof GetGlobalContext != "undefined")
        _context = GetGlobalContext();
    else if (typeof Xrm != "undefined")
        _context = Xrm.Page.context;
    return _context
}
function cloneCase() {
 
    if (Xrm.Page.data.entity.getId() == null) {
        alert('First save the record before Clone Case')
 
    }
    else {
        var CRMContext = GetContext();
        var serverUrl = CRMContext.getServerUrl();
        var caseid = Xrm.Page.data.entity.getId();
        caseid = caseid.replace('{''').replace('}''');
 
        //Below URL is for CRM online  
        var url = serverUrl + 'main.aspx?etc=112&extraqs=%3f_CreateFromId%3d%257b' + caseid + '%257d%26_CreateFromType%3d112%26etc%3d112%26pagemode%3diframe&pagetype=entityrecord';
 
        openNewWindow(url, 900, 600, 'toolbar=no,menubar=no,resizable=yes');
    }
 
 
}


Note:How to create the URL

Since Case is in 1:N relationship It will appear in case left navigation pane.Click on Case button and add the new Case.Copy the created URL and make it dynamic.

For Onpresmise MS CRM 2011 URL will be like this

var url = serverUrl + '/cs/cases/edit.aspx?_CreateFromType=112&_CreateFromId=' +
 Xrm.Page.data.entity.getId();



 Hope it helps someone somewhere :)

Regards,

Yusuf



Friday 11 January 2013

A Scenery

I spent two years in Kerala (India) for studies.I am very much impressed by the natural beauty of Kerala.I feel that in Kerala everywhere there is an opportunity to see the Scenery.


CRM 2011: Ribbon not loading in Mozilla Firefox and Chrome

Good news for MS CRM 2011 developer is that December 2012 Service Update for Microsoft Dynamics CRM 2011 (Polaris) will allows cross browser compatibility and some other splendid updates.

Now the chances for Javascipt errors will be more across different browsers.Now onwards we have to more careful while writing JavaScript Code.We need to make sure it will work different across other browsers besides IE as well.

We are working on Polaris CRM 2011 and customizing using Javascript.While testing functionality we came to know that Case ribbon is not being loaded.

Here comes the  Microsoft Dynamics CRM 2011 Custom Code Validation Tool  into picture.

What is it :"Custom Code Validation Tool to ensure their existing customizations will work with the next service release of CRM 2011, code named Polaris"

For more information read this blog.



After running this tool on Case JavaScript I got the erroneous code (marked in blue colour) which was causing the issue for the ribbon loading.I updated the JavaScript to fix that.

It's now working fine.Below is the screenshot of MS CRM 2011 running in Mozilla Firefox.



Hope it helps someone.

Regards,
Yusuf

Wednesday 9 January 2013

Speaking Lines !!!

Some time lines says more than words.Lines may be beautiful  if drawn meaningfully.
Enjoy the "Speaking Lines" drawn by me :)


Tuesday 8 January 2013

Override the Resolve Case System ribbon button functionality?



When you resolve a Case a pop up appears where you can provide some details regarding closing the Case.Here I will show how can  we overwrite the default functionality of the Resolve Case.


When you resolve a case it creates an case resolution activity.Which we need to take care while overwriting the default functionality.


Customization Aim:
To remove the "Resolve Case" dialog  box and change the name of the Resolve Case to 'Close Case'.

What to do ?:What things needs to be customized in order to achieve this.


1)We need to customize the ribbon definition of Case entity i.e  RibbonDiffXml.
2)We need to create one custom field on Case entity.
3)We need to write a JavaScript function.
4)We have to write a plugin which will take care of resolving the Case.

Note:
Case can be resolved from 'Form' level and 'HomePage grid'.We will take care of both the scenarios.


1.Customizing the Case ribbon button(HomePage grid and  Form):

1.1) Create a solution containing Case entity.Export it and save it.Extract the solution file.
        Open the "customizations.xml" search the <RibbonDiffXml> tag.
        Replace this with the following


<RibbonDiffXml>
        <CustomActions>
 
          <CustomAction Id="new.Mscrm.Form.incident.Resolve.CustomAction" Location="Mscrm.Form.incident.MainTab.Actions.Controls._children" Sequence="3">
            <CommandUIDefinition>
              <Button Alt="$Resources:Ribbon.Form.incident.MainTab.Actions.Resolve" Command="Mscrm.Form.incident.Resolve" Id="Mscrm.Form.incident.Resolve" Image32by32="/_imgs/ribbon/resolvecase32.png" Image16by16="/_imgs/ribbon/ResolveCase_16.png" LabelText="$LocLabels:Mscrm.Form.incident.Resolve.LabelText" Sequence="3" TemplateAlias="o1" ToolTipTitle="$Resources:Ribbon.Form.incident.MainTab.Actions.Resolve" ToolTipDescription="$Resources:Ribbon.Tooltip.ResolveCase" />
            </CommandUIDefinition>
          </CustomAction>
 
 
          <CustomAction Id="new.Mscrm.HomepageGrid.incident.Resolve.CustomAction" Location="Mscrm.HomepageGrid.incident.MainTab.Actions.Controls._children" Sequence="3">
            <CommandUIDefinition>
              <Button Alt="$Resources:Ribbon.Form.incident.MainTab.Actions.Resolve" Command="Mscrm.HomepageGrid.incident.Resolve" Id="Mscrm.HomepageGrid.incident.Resolve" Image32by32="/_imgs/ribbon/resolvecase32.png" Image16by16="/_imgs/ribbon/ResolveCase_16.png" LabelText="$LocLabels:Mscrm.HomepageGrid.incident.Resolve.LabelText" Sequence="3" TemplateAlias="o1" ToolTipTitle="$Resources:Ribbon.Form.incident.MainTab.Actions.Resolve" ToolTipDescription="$Resources:Ribbon.Tooltip.ResolveCase" />
            </CommandUIDefinition>
          </CustomAction>
 
        </CustomActions>
        <Templates>
          <RibbonTemplates Id="Mscrm.Templates"></RibbonTemplates>
        </Templates>
        <CommandDefinitions>
          <CommandDefinition Id="Mscrm.Form.incident.Resolve">
            <EnableRules>
              <EnableRule Id="Mscrm.CanChangeIncidentForm" />
            </EnableRules>
            <DisplayRules>
              <DisplayRule Id="Mscrm.CanChangeIncidentForm" />
              <DisplayRule Id="Mscrm.IncidentIsActive" />
            </DisplayRules>
            <Actions>
              <JavaScriptFunction FunctionName="resolveCase" Library="$webresource:new_CustomRibbonJavascript" />
            </Actions>
          </CommandDefinition>
          <CommandDefinition Id="Mscrm.HomepageGrid.incident.Resolve">
            <EnableRules>
              <EnableRule Id="Mscrm.SelectionCountExactlyOne" />
              <EnableRule Id="Mscrm.VisualizationPaneNotMaximized" />
            </EnableRules>
            <DisplayRules>
              <DisplayRule Id="Mscrm.CanChangeIncidentForm" />
            </DisplayRules>
            <Actions>
              <JavaScriptFunction FunctionName="resolveCaseFromGrid" Library="$webresource:new_CustomRibbonJavascript">
                <CrmParameter Value="SelectedControlSelectedItemIds" />
              </JavaScriptFunction>
            </Actions>
          </CommandDefinition>
 
        </CommandDefinitions>
        <RuleDefinitions>
          <TabDisplayRules />
          <DisplayRules>
 
            <DisplayRule Id="Mscrm.CanChangeIncidentForm">
              <EntityPrivilegeRule EntityName="incident" PrivilegeType="Write" PrivilegeDepth="Basic" />
              <EntityPrivilegeRule EntityName="incident" PrivilegeType="AppendTo" PrivilegeDepth="Basic" />
              <EntityPrivilegeRule EntityName="activitypointer" PrivilegeType="Create" PrivilegeDepth="Basic" />
              <EntityPrivilegeRule EntityName="activitypointer" PrivilegeType="Append" PrivilegeDepth="Basic" />
            </DisplayRule>
 
            <DisplayRule Id="Mscrm.IncidentIsActive">
              <FormStateRule State="Existing" />
            </DisplayRule>
 
          </DisplayRules>
          <EnableRules>
 
            <EnableRule Id="Mscrm.CanChangeIncidentForm">
              <FormStateRule State="Create" InvertResult="true" />
              <RecordPrivilegeRule PrivilegeType="Write" AppliesTo="PrimaryEntity" />
              <RecordPrivilegeRule PrivilegeType="AppendTo" AppliesTo="PrimaryEntity" />
            </EnableRule>
 
            <EnableRule Id="Mscrm.SelectionCountExactlyOne">
              <SelectionCountRule AppliesTo="SelectedEntity" Minimum="1" Maximum="1" />
            </EnableRule>
            <EnableRule Id="Mscrm.VisualizationPaneNotMaximized">
              <CustomRule FunctionName="Mscrm.RibbonActions.disableButtonsWhenChartMaximized" Library="/_static/_common/scripts/RibbonActions.js">
                <CrmParameter Value="SelectedControl" />
              </CustomRule>
            </EnableRule>
          </EnableRules>
        </RuleDefinitions>
        <LocLabels>
          <LocLabel Id="Mscrm.Form.incident.Resolve.LabelText">
            <Titles>
              <Title description="Close Case" languagecode="1033" />
            </Titles>
          </LocLabel>
          <LocLabel Id="Mscrm.HomepageGrid.incident.Resolve.LabelText">
            <Titles>
              <Title description="Close Case" languagecode="1033" />
            </Titles>
          </LocLabel>
 
        </LocLabels>
      </RibbonDiffXml>


2.Case form Customization:

Customize the Case entity create one radio button with logical name "new_iscaseresolved" having default value false.Hide this field on the form.We will make use of this field for writing our logic.


3.Write the Javascipt function:

Create a webresource  called "new_CustomRibbonJavascriptand add the following JavaScript functions to it.


function resolveCase() {
    try {
        var answer = confirm("Do you want to Close the Case?")
        if (answer) {
 
            Xrm.Page.getAttribute("new_iscaseresolved").setValue(true);
            Xrm.Page.data.entity.save();
 
        }
    }
    catch (err) { }
}
 
function resolveCaseFromGrid(CaseIdList) {
 
    try {
        if (CaseIdList != null) {
            var caseid = CaseIdList[0]
            caseid = caseid.replace('{''').replace('}''');
            var answer = confirm("Do you want to Close the Case?")
            if (answer) {
                if (checkCaseOpenActivities(caseid)) {
                    var ChangesToUpdate = {
                        new_IsCaseResolved: true
                    };
 
                    updateRecord(caseid, ChangesToUpdate, "IncidentSet");
 
 
                }
 
                else {
 
                    alert('Please close open activities before closing the Case');
                }
            }
        }
 
    }
    catch (err) { }
}
 
 
function checkCaseOpenActivities(caseId) {
    var ODataSelect = "ActivityPointerSet?$select=ActivityId&$filter=RegardingObjectId/Id eq guid'" + caseId + "' and StateCode/Value eq 0";
    var CaseActivities = GetDataUsingODataServiceWithJQuery(ODataSelect);
    if (CaseActivities != null) {
        if (CaseActivities.results.length > 0) {
            return false;
        }
        else {
            return true;
        }
    }
    else {
        return true;
    }
 
 
}

function GetContext() {
    var _context = null;
    if (typeof GetGlobalContext != "undefined")
        _context = GetGlobalContext();
    else if (typeof Xrm != "undefined")
        _context = Xrm.Page.context;
    return _context
}
 
 
DisplayError = function (cntrl, func, err) {
    alert("Control : " + cntrl + "\nODataCommonFunctions.: " + func + "\nError : " + err);
}
 
// ========== OData Serivce - JQuery Request Functions Start ==========
 
function GetDataUsingODataServiceWithJQuery(ODataSelect) {
    var resultOfGet = null;
    var CRMContext = GetContext();
    if (CRMContext != null) {
        var serverUrl = CRMContext.getServerUrl();
        var ODataEndpoint = "/XRMServices/2011/OrganizationData.svc";
        var ODataURL = serverUrl + ODataEndpoint + "/" + ODataSelect;
        try {
            $.ajax({
                type: "GET",
                contentType: "application/json; charset=utf-8",
                datatype: "json",
                async: false,
                url: ODataURL,
                beforeSend: function (XMLHttpRequest) { XMLHttpRequest.setRequestHeader("Accept""application/json"); },
                success: function (data, textStatus, XmlHttpRequest) {
                    resultOfGet = data.d;
                },
                error: function (XMLHttpRequest, textStatus, errorThrown) {
                    DisplayError("ODataService""Fetch", textStatus + " - " + JSON.parse(XMLHttpRequest.responseText).error.message.value);
                    //resultOfGet = null;
                }
            });
        } catch (e) {
            DisplayError("ODataService""Fetch", e.description);
            //resultOfGet = null;
        }
    }
    return resultOfGet;
}
function updateRecord(id, entityObject, odataSetName) {     var jsonEntity = window.JSON.stringify(entityObject);     // Get Server URL     var serverUrl = Xrm.Page.context.getServerUrl();     //The OData end-point     var ODATA_ENDPOINT = "/XRMServices/2011/OrganizationData.svc";     //Asynchronous AJAX function to Update a CRM record using OData     $.ajax({         type: "POST",         contentType: "application/json; charset=utf-8",         datatype: "json",         data: jsonEntity,         url: serverUrl + ODATA_ENDPOINT + "/" + odataSetName + "(guid'" + id + "')",         beforeSend: function (XMLHttpRequest) {             //Specifying this header ensures that the results will be returned as JSON.             XMLHttpRequest.setRequestHeader("Accept""application/json");             //Specify the HTTP method MERGE to update just the changes you are submitting.             XMLHttpRequest.setRequestHeader("X-HTTP-Method""MERGE");         },         success: function (data, textStatus, XmlHttpRequest) {             alert("Case resolved successfully");         },         error: function (XmlHttpRequest, textStatus, errorThrown) {             if (XmlHttpRequest && XmlHttpRequest.responseText) {                 alert("Error while updating " + odataSetName + " ; Error – " + XmlHttpRequest.responseText);             }         }     }); }



4.Writing the plugin:
Create a plugin as given below


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Crm.Sdk;
using Microsoft.Xrm.Sdk;
using System.ServiceModel;
using System.Diagnostics;
using System.Data.Services;
using System.Data.Services.Client;
using contoso;
using System.Xml;
using Microsoft.Crm.Sdk.Messages;
 
 
namespace Xrm.Case
{
   public class IncidentPostUpdate:IPlugin
    {
        public void Execute(IServiceProvider serviceProvider)
        {
            contoso.Incident IncRec;
 
            IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
 
 
            if (context.InputParameters.Contains("Target") && context.InputParameters["Target"is Entity)
            {
 
                //Verify that the entity represents an Case
                if (((Entity)context.InputParameters["Target"]).LogicalName != contoso.Incident.EntityLogicalName || context.MessageName != "Update")
                    return;
                else
                    IncRec = ((Entity)context.InputParameters["Target"]).ToEntity<contoso.Incident>();
            }
            else
            {
                return;
            }
 
            try
            {
                IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
                IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);
 
 
                using (var serenaContext = new SerenaContext(service))
                {
 
                    
                        if (context.Depth == 1)
                        {
 
                            if (IncRec.new_IsCaseResolved == true)
                            {
 
                                contoso.Incident inc = new Incident();
 
                                inc.new_IsCaseResolved = false;
                                inc.Id = IncRec.Id;
                                service.Update(inc);
 
                      
                                 List<contoso.ActivityPointer> caseOpenActivities = serenaContext.ActivityPointerSet
                                .Where(a => a.StateCode == ActivityPointerState.Open && a.RegardingObjectId.Id ==IncRec.Id)
                                .Select(a => new contoso.ActivityPointer
                                {
                                    Id = a.Id
                                }).ToList<contoso.ActivityPointer>();
 
                                 if (caseOpenActivities != null && caseOpenActivities.Count > 0)
                                 {
                                     throw new InvalidPluginExecutionException("Close all the Open activities associated with Case before closing the Case");
                                 }
                                 else
                                 { 
                                    // Create the incident's resolution.
                                    Entity caseResolution = new Entity("incidentresolution");
                                    caseResolution.Attributes.Add("incidentid"new EntityReference("incident", IncRec.Id));
                                    caseResolution.Attributes.Add("subject""Closed");
 
 
                                    // Close the incident with the resolution.         
 
                                    CloseIncidentRequest req = new CloseIncidentRequest();
                                    req.IncidentResolution = caseResolution;
                                    req.RequestName = "CloseIncident";
                                    OptionSetValue o = new OptionSetValue();
                                    o.Value = 5;
                                    req.Status = o;
 
                                    CloseIncidentResponse resp = (CloseIncidentResponse)service.Execute(req);
                                 }
 
                             }
                        }
                                    
                    
                }
 
 
            }
            catch (InvalidPluginExecutionException)
            {
                throw;
            }
            catch (FaultException<OrganizationServiceFault> ex)
            {
                throw new InvalidPluginExecutionException("An error occurred in the plug-in.", ex);
            }
 
        }
    }
}


5.Register the plugin as per the below screen shots:




We are done with the customization to override the Resove case Please note some points.

1.In plugin I am using early bound approach you can go with late bound as you wish.
2.Please include "Json" and "jquery" file on Case form as they will we used by the "updateRecord" method.


Hope it helps you.


Regards,
Yusuf