Monday, January 19, 2009

Case Responsible Contact To Email and other Activities

A typical customer request is for email created from a Case to default to send to the Responsible Contact instead of its default behavior which is to send it to the Customer.

Since the Responsible Contact isn't even on the Case form by default and a customer could be a contact, MS chose this default behavior. However most of my installations to date have an Account as the Customer and one of many Contacts related to that Account as a Responsible Contact.

This is actually something very easy to change.

Please Note: This is an unsupported modification, and any patch could overwrite this.

There is a JavaScript file, cases.js, in the following folder.

C:\Program Files\Microsoft Dynamics CRM Server\CRMWeb\_static\CS\cases

It contains a function called locAddActTo that fills the To: activity party variables that are sent in the QueryString to the activity like the URL below.

http://crmserver1:5555/MicrosoftCRM/activities/email/edit.aspx?
pId={E0F2E676-7FE2-DD11-9AE8-0003FF517B20}
&pType=112
&pName=CaseTitleGoesHere
&partyid={0EDF3D7E-E3E0-DD11-A5F9-0003FF517B20}
&partytype=1
&partyname=AccountNameGoesHere
&partyaddressused=&contactInfo=

What we care about are the partyid, partytype and partyname arguments.

function locAddActTo(iActivityType, sContentId)
{
var sParentId   = null;
var sParentType = null;
var sPartyId   = null;
var sPartyType = null;
var sPartyName = null;
var sPartyLocation = null;


sParentId   = crmFormSubmit.crmFormSubmitId.value;
sParentType = crmFormSubmit.crmFormSubmitObjectType.value;


if (iActivityType != Task)
{
var customerId = crmForm.all.customerid.DataValue;
if (!IsNull(customerId))
{
if (!IsNull(customerId[0]))
{
sPartyId   = customerId[0].id;
sPartyType = customerId[0].type;
}
}
sPartyName = crmForm.customerid.parentElement.previousSibling.innerText;

sPartyLocation = "";
}

As you can see this information is being pulled from the case form customer field crmForm.all.customerid.DataValue

So all we have to do is change this to crmForm.all.responsiblecontactid.DataValue

and change the sPartyName assignment.

sPartyName = crmForm.customerid.parentElement.previousSibling.innerText;
to
sPartyName = customerId[0].name;

You can just replace the above function with the one below if you would like.

function locAddActTo(iActivityType, sContentId)
{
var sParentId   = null;
var sParentType = null;
var sPartyId   = null;
var sPartyType = null;
var sPartyName = null;
var sPartyLocation = null;


sParentId   = crmFormSubmit.crmFormSubmitId.value;
sParentType = crmFormSubmit.crmFormSubmitObjectType.value;


if (iActivityType != Task)
{
var customerId = crmForm.all.responsiblecontactid.DataValue;
if (!IsNull(customerId))
{
if (!IsNull(customerId[0]))
{
sPartyId   = customerId[0].id;
sPartyType = customerId[0].type;
sPartyName = customerId[0].name;
}
}


sPartyLocation = "";
}

After you have saved the change, run  iisreset to flush the cache.

That's it.

Sunday, January 18, 2009

CRM 4.0 Custom Workflows

Custom Workflows in MS CRM 4.0 are very simple to deploy and are pretty simple to create especially if you are familiar with Plugin-in development. 

Before you can create a workflow you need to have the Workflow Extensions installed. This is more of a concern if you are running VS2005. If you are running VS 2008 SP1, you should have everything you need already.

If you have an assembly that you are already deploying for plug-ins you can even add a workflow to that assembly so that you have fewer deployment assemblies.

The following custom workflow was created for a customer who wanted to have a matching customer sales relationship created for an account whenever an opportunity role was assigned to a contact.

namespace OneCRMPro { // In the workflow editor // "OneCRMPro" is going to show up at the bottom of the Add Step pick list // "Create CustomerRelationship" will show up as item off of OneCRMPro. [PersistOnClose] [CrmWorkflowActivity("Create CustomerRelationship", "OneCRMPro")] public partial class CreateCustomerRelationship : SequenceActivity { // These DependencyProperty entries will show up in the Set Properties editor public static DependencyProperty RoleProperty = DependencyProperty.Register("Role", typeof(Lookup), typeof(CreateCustomerRelationship)); [CrmInput("Sales Role")] [CrmReferenceTarget("relationshiprole")] public Lookup Role { get { return (Lookup)GetValue(RoleProperty); } set { SetValue(RoleProperty, value); } } public static DependencyProperty CustomerProperty = DependencyProperty.Register("Customer", typeof(Lookup), typeof(CreateCustomerRelationship)); [CrmInput("Customer Contact")] [CrmReferenceTarget("contact")] public Lookup Customer { get { return (Lookup)GetValue(CustomerProperty); } set { SetValue(CustomerProperty, value); } } // End Set Properties /// <summary> /// Execute is called when the custom workflow is invoked /// </summary> /// <param name="executionContext"></param> /// <returns></returns> protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext) { //Get a workflow context var contextService = executionContext.GetService(typeof(IContextService)) as IContextService; var workflowContext = contextService.Context; CreateRole(workflowContext); return ActivityExecutionStatus.Closed; } /// <summary> /// Create a matching sales customer relationship role /// </summary> /// <param name="context"></param> private void CreateRole(IWorkflowContext context) { try { // Get Guids of Customer and RelationshipRole Guid contactId = Customer.Value; Guid salesRoleId = Role.Value; // get a local service ICrmService service = context.CreateCrmService(true); // Grab the contact involved var cols = new ColumnSet(new [] {"parentcustomerid"}); var thisContact = (contact)service.Retrieve(EntityName.contact.ToString(), contactId, cols); // Create a cooresponding customer relationship var role = new customerrelationship { customerid = thisContact.parentcustomerid, partnerid = CrmTypes.CreateCustomer(EntityName.contact.ToString(), contactId), partnerroleid = CrmTypes.CreateLookup(EntityName.relationshiprole.ToString(), salesRoleId) }; service.Create(role); } catch (SoapException) { // Handle error } catch (Exception) { // Handle error } } } }

Before you can use your workflow it needs to be strongly signed and registered.  Its assembly is registered in the same way as a plugin, but it doesn't require registering it at the entity or event level. If you don't have the plugin Registration Tool you can find it here. Plugin Deployment Tool

image

When you create a new Workflow you can now select this custom Workflow when you "Add Step" and pick the following at the bottom of the list.

OneCRMPro   >   Create CustomerRelationship

Now when you "Set Properties", you will see below how the DependencyProperty values will show up below.

The CrmInput keyword specifies the Property Name and the CrmReferenceTarget keyword specifies the expected attribute type to be mapped.

image

That's about it. Publish your workflow and try it out!