Pages

Showing posts with label Customizations. Show all posts
Showing posts with label Customizations. Show all posts

Monday, November 17, 2014

Convert Org Owned Entity to User Owned

A while back I discussed the pros and cons of user vs. organization owned entities. I concluded as follows:

The theory might all be well and good, but let's say you're presently in the predicament described above i.e. you're in production and have come across a requirement that means that you'll need to convert an entity from organization-owned to user-owned - what are the steps to achieve this? In the next post I'll attempt to address this with a step by step walk through.
Well it's about time I made good on that promise... So here goes -

Caution: This is not for the fainthearted. Especially in CRM Online installations where you can't really make a backup (as concluded in the above referenced post - when in doubt go with user-owned entities...).

Step 1 - Export all the data

You need to save all your data linked to the entity you wish to update. Assuming this is a CRM Online instance you can use the export to Excel option (in on-premise there are of course other options). This includes (but not necessarily limited to) the following:

  • Export Main  Entity




  • Export Notes - All columns
  • Export Activities - All columns
  • Export data from other 1:N relationships that you wish to recreate/relate

You need to resign your self to the fact that you will lose information stored in system attributes such as created/modified by/on.

Generally speaking, restoring N:1 relationships is not a problem because they'll be recreated with the re-import.

However with the 1:N relationship as in the case of notes and activities there is a level of effort involved so you'll need to make a judgment call of whether it is worth it. Same applies for N:N relationships (perhaps even more so since there is no way to re-import using native tools. Bottom line if there is a lot of dependent data this exercise is likely going to be too painful and you'll need to consider other options such as duplicating the entity and migrating the data over with all the relationships (tools such as Scribe can help here).



Step 2 - Create and Export Solution


Retains all customizations - views, forms etc.

  • Export a solution containing the entity you wish to modify and workflows on that entity 
  • Export a solution with everything but entity (including workflows, security roles)....
  • Make a copy of the exported zip
  • Unzip the solution, and open customizations.xml in a text editor
  • Replace the following section... 
<attribute PhysicalName="OrganizationId">
              <Type>lookup</Type>
              <Name>organizationid</Name>
              <LogicalName>organizationid</LogicalName>
              <RequiredLevel>none</RequiredLevel>
              <ImeMode>auto</ImeMode>
              <ValidForReadApi>1</ValidForReadApi>
              <IsCustomField>0</IsCustomField>
              <IsAuditEnabled>1</IsAuditEnabled>
              <IsSecured>0</IsSecured>
              <IntroducedVersion>1.0</IntroducedVersion>
              <IsCustomizable>1</IsCustomizable>
              <IsRenameable>1</IsRenameable>
              <CanModifySearchSettings>1</CanModifySearchSettings>
              <CanModifyRequirementLevelSettings>1</CanModifyRequirementLevelSettings>
              <CanModifyAdditionalSettings>1</CanModifyAdditionalSettings>
              <ReferencedEntityObjectTypeCode>1019</ReferencedEntityObjectTypeCode>
              <LookupTypes />
              <displaynames>
                <displayname description="Organization Id" languagecode="1033" />
              </displaynames>
              <Descriptions>
                <Description description="Unique identifier for the organization" languagecode="1033" />
              </Descriptions>
            </attribute>
              
  • With:
<attribute PhysicalName="OwnerId">
              <Type>owner</Type>
              <Name>ownerid</Name>
              <LogicalName>ownerid</LogicalName>
              <RequiredLevel>systemrequired</RequiredLevel>
              <DisplayMask>ValidForAdvancedFind|ValidForForm|ValidForGrid|RequiredForForm</DisplayMask>
              <ImeMode>auto</ImeMode>
              <ValidForReadApi>1</ValidForReadApi>
              <ValidForCreateApi>1</ValidForCreateApi>
              <IsCustomField>0</IsCustomField>
              <IsAuditEnabled>1</IsAuditEnabled>
              <IsSecured>0</IsSecured>
              <IntroducedVersion>1.0.0.0</IntroducedVersion>
              <IsCustomizable>1</IsCustomizable>
              <IsRenameable>1</IsRenameable>
              <CanModifySearchSettings>1</CanModifySearchSettings>
              <CanModifyRequirementLevelSettings>1</CanModifyRequirementLevelSettings>
              <CanModifyAdditionalSettings>1</CanModifyAdditionalSettings>
              <LookupStyle>single</LookupStyle>
              <LookupTypes>
                <LookupType id="00000000-0000-0000-0000-000000000000">8</LookupType>
                <LookupType id="00000000-0000-0000-0000-000000000000">9</LookupType>
              </LookupTypes>
              <displaynames>
                <displayname description="Owner" languagecode="1033" />
              </displaynames>
              <Descriptions>
                <Description description="Owner Id" languagecode="1033" />
              </Descriptions>
            </attribute>
            <attribute PhysicalName="OwnerIdName">
              <Type>nvarchar</Type>
              <Name>owneridname</Name>
              <LogicalName>owneridname</LogicalName>
              <RequiredLevel>systemrequired</RequiredLevel>
              <ImeMode>auto</ImeMode>
              <ValidForReadApi>1</ValidForReadApi>
              <IsCustomField>0</IsCustomField>
              <IsAuditEnabled>0</IsAuditEnabled>
              <IsLogical>1</IsLogical>
              <IsSecured>0</IsSecured>
              <IntroducedVersion>1.0.0.0</IntroducedVersion>
              <IsCustomizable>1</IsCustomizable>
              <IsRenameable>1</IsRenameable>
              <CanModifySearchSettings>1</CanModifySearchSettings>
              <CanModifyRequirementLevelSettings>1</CanModifyRequirementLevelSettings>
              <CanModifyAdditionalSettings>1</CanModifyAdditionalSettings>
              <Format>text</Format>
              <MaxLength>100</MaxLength>
              <Length>320</Length>
              <Descriptions>
                <Description description="Name of the owner" languagecode="1033" />
              </Descriptions>
            </attribute>
            <attribute PhysicalName="OwnerIdType">
              <Type>int</Type>
              <Name>owneridtype</Name>
              <LogicalName>owneridtype</LogicalName>
              <RequiredLevel>systemrequired</RequiredLevel>
              <DisplayMask>ObjectTypeCode</DisplayMask>
              <ImeMode>disabled</ImeMode>
              <ValidForReadApi>1</ValidForReadApi>
              <ValidForCreateApi>1</ValidForCreateApi>
              <IsCustomField>0</IsCustomField>
              <IsAuditEnabled>1</IsAuditEnabled>
              <IsSecured>0</IsSecured>
              <IntroducedVersion>1.0.0.0</IntroducedVersion>
              <IsCustomizable>1</IsCustomizable>
              <IsRenameable>1</IsRenameable>
              <CanModifySearchSettings>1</CanModifySearchSettings>
              <CanModifyRequirementLevelSettings>1</CanModifyRequirementLevelSettings>
              <CanModifyAdditionalSettings>1</CanModifyAdditionalSettings>
              <Format></Format>
              <MinValue>-2147483648</MinValue>
              <MaxValue>2147483647</MaxValue>
              <Descriptions>
                <Description description="Owner Id Type" languagecode="1033" />
              </Descriptions>
            </attribute>
            <attribute PhysicalName="OwnerIdYomiName">
              <Type>nvarchar</Type>
              <Name>owneridyominame</Name>
              <LogicalName>owneridyominame</LogicalName>
              <RequiredLevel>systemrequired</RequiredLevel>
              <ImeMode>auto</ImeMode>
              <ValidForReadApi>1</ValidForReadApi>
              <IsCustomField>0</IsCustomField>
              <IsAuditEnabled>0</IsAuditEnabled>
              <IsLogical>1</IsLogical>
              <IsSecured>0</IsSecured>
              <IntroducedVersion>1.0.0.0</IntroducedVersion>
              <IsCustomizable>1</IsCustomizable>
              <IsRenameable>1</IsRenameable>
              <CanModifySearchSettings>1</CanModifySearchSettings>
              <CanModifyRequirementLevelSettings>1</CanModifyRequirementLevelSettings>
              <CanModifyAdditionalSettings>1</CanModifyAdditionalSettings>
              <Format>text</Format>
              <MaxLength>100</MaxLength>
              <Length>320</Length>
              <YomiOf>OwnerIdName</YomiOf>
              <Descriptions>
                <Description description="Yomi name of the owner" languagecode="1033" />
              </Descriptions>
            </attribute>
            <attribute PhysicalName="OwningBusinessUnit">
              <Type>lookup</Type>
              <Name>owningbusinessunit</Name>
              <LogicalName>owningbusinessunit</LogicalName>
              <RequiredLevel>none</RequiredLevel>
              <ImeMode>auto</ImeMode>
              <ValidForReadApi>1</ValidForReadApi>
              <IsCustomField>0</IsCustomField>
              <IsAuditEnabled>0</IsAuditEnabled>
              <IsSecured>0</IsSecured>
              <IntroducedVersion>1.0.0.0</IntroducedVersion>
              <IsCustomizable>1</IsCustomizable>
              <IsRenameable>1</IsRenameable>
              <CanModifySearchSettings>1</CanModifySearchSettings>
              <CanModifyRequirementLevelSettings>1</CanModifyRequirementLevelSettings>
              <CanModifyAdditionalSettings>1</CanModifyAdditionalSettings>
              <ReferencedEntityObjectTypeCode>10</ReferencedEntityObjectTypeCode>
              <LookupStyle>single</LookupStyle>
              <LookupTypes />
              <displaynames>
                <displayname description="Owning Business Unit" languagecode="1033" />
              </displaynames>
              <Descriptions>
                <Description description="Unique identifier for the business unit that owns the record" languagecode="1033" />
              </Descriptions>
            </attribute>


  • Replace the following...
<OwnershipTypeMask>UserOwned</OwnershipTypeMask>
  • With:
<OwnershipTypeMask>UserOwned</OwnershipTypeMask>

  • Replace the following section (be sure to update "your_entity" with the name of your entity)...
<EntityRelationship Name="organization_your_entity">
      <EntityRelationshipType>OneToMany</EntityRelationshipType>
      <IsCustomizable>1</IsCustomizable>
      <IntroducedVersion>1.0</IntroducedVersion>
      <ReferencingEntityName>your_entity</ReferencingEntityName>
      <ReferencedEntityName>Organization</ReferencedEntityName>
      <CascadeAssign>NoCascade</CascadeAssign>
      <CascadeDelete>NoCascade</CascadeDelete>
      <CascadeReparent>NoCascade</CascadeReparent>
      <CascadeShare>NoCascade</CascadeShare>
      <CascadeUnshare>NoCascade</CascadeUnshare>
      <ReferencingAttributeName>OrganizationId</ReferencingAttributeName>
      <RelationshipDescription>
        <Descriptions>
          <Description description="Unique identifier for the organization" languagecode="1033" />
        </Descriptions>
      </RelationshipDescription>
      <field name="organizationid" requiredlevel="none" imemode="auto">
        <IsCustomizable>1</IsCustomizable>
        <IsRenameable>1</IsRenameable>
        <CanModifySearchSettings>1</CanModifySearchSettings>
        <CanModifyRequirementLevelSettings>1</CanModifyRequirementLevelSettings>
        <IsSecured>0</IsSecured>
        <IsAuditEnabled>1</IsAuditEnabled>
        <displaynames>
          <displayname description="Organization Id" languagecode="1033" />
        </displaynames>
      </field>
    </EntityRelationship>

  • With:
    <EntityRelationship Name="owner_your_entity">
      <EntityRelationshipType>OneToMany</EntityRelationshipType>
      <IsCustomizable>1</IsCustomizable>
      <IntroducedVersion>1.0.0.0</IntroducedVersion>
      <ReferencingEntityName>your_entity</ReferencingEntityName>
      <ReferencedEntityName>Owner</ReferencedEntityName>
      <CascadeAssign>NoCascade</CascadeAssign>
      <CascadeDelete>NoCascade</CascadeDelete>
      <CascadeReparent>NoCascade</CascadeReparent>
      <CascadeShare>NoCascade</CascadeShare>
      <CascadeUnshare>NoCascade</CascadeUnshare>
      <ReferencingAttributeName>OwnerId</ReferencingAttributeName>
      <RelationshipDescription>
        <Descriptions>
          <Description description="Owner Id" languagecode="1033" />
        </Descriptions>
      </RelationshipDescription>
      <field name="ownerid" requiredlevel="systemrequired" imemode="auto" lookupstyle="single" lookupbrowse="0" lookuptypes="8, 9">
        <IsCustomizable>1</IsCustomizable>
        <IsRenameable>1</IsRenameable>
        <CanModifySearchSettings>1</CanModifySearchSettings>
        <CanModifyRequirementLevelSettings>1</CanModifyRequirementLevelSettings>
        <IsSecured>0</IsSecured>
        <DisplayMask>ValidForAdvancedFind|ValidForForm|ValidForGrid|RequiredForForm</DisplayMask>
        <IsAuditEnabled>1</IsAuditEnabled>
        <displaynames>
          <displayname description="Owner" languagecode="1033" />
        </displaynames>
      </field>
    </EntityRelationship>
    <EntityRelationship Name="team_your_entity">
      <EntityRelationshipType>OneToMany</EntityRelationshipType>
      <IsCustomizable>1</IsCustomizable>
      <IntroducedVersion>1.0.0.0</IntroducedVersion>
      <ReferencingEntityName>your_entity</ReferencingEntityName>
      <ReferencedEntityName>Team</ReferencedEntityName>
      <CascadeAssign>NoCascade</CascadeAssign>
      <CascadeDelete>NoCascade</CascadeDelete>
      <CascadeReparent>NoCascade</CascadeReparent>
      <CascadeShare>NoCascade</CascadeShare>
      <CascadeUnshare>NoCascade</CascadeUnshare>
      <ReferencingAttributeName>OwningTeam</ReferencingAttributeName>
      <RelationshipDescription>
        <Descriptions>
          <Description description="Unique identifier for the team that owns the record." languagecode="1033" />
        </Descriptions>
      </RelationshipDescription>
      <field name="owningteam" requiredlevel="none" imemode="auto" lookupstyle="single" lookupbrowse="0">
        <IsCustomizable>1</IsCustomizable>
        <IsRenameable>1</IsRenameable>
        <CanModifySearchSettings>1</CanModifySearchSettings>
        <CanModifyRequirementLevelSettings>1</CanModifyRequirementLevelSettings>
        <IsSecured>0</IsSecured>
        <IsAuditEnabled>0</IsAuditEnabled>
        <displaynames>
          <displayname description="Owning Team" languagecode="1033" />
        </displaynames>
      </field>
    </EntityRelationship>
    <EntityRelationship Name="user_your_entity">
      <EntityRelationshipType>OneToMany</EntityRelationshipType>
      <IsCustomizable>1</IsCustomizable>
      <IntroducedVersion>1.0.0.0</IntroducedVersion>
      <ReferencingEntityName>your_entity</ReferencingEntityName>
      <ReferencedEntityName>SystemUser</ReferencedEntityName>
      <CascadeAssign>NoCascade</CascadeAssign>
      <CascadeDelete>NoCascade</CascadeDelete>
      <CascadeReparent>NoCascade</CascadeReparent>
      <CascadeShare>NoCascade</CascadeShare>
      <CascadeUnshare>NoCascade</CascadeUnshare>
      <ReferencingAttributeName>OwningUser</ReferencingAttributeName>
      <RelationshipDescription>
        <Descriptions>
          <Description description="Unique identifier for the user that owns the record." languagecode="1033" />
        </Descriptions>
      </RelationshipDescription>
      <field name="owninguser" requiredlevel="none" imemode="auto" lookupstyle="single" lookupbrowse="0">
        <IsCustomizable>1</IsCustomizable>
        <IsRenameable>1</IsRenameable>
        <CanModifySearchSettings>1</CanModifySearchSettings>
        <CanModifyRequirementLevelSettings>1</CanModifyRequirementLevelSettings>
        <IsSecured>0</IsSecured>
        <IsAuditEnabled>0</IsAuditEnabled>
        <displaynames>
          <displayname description="Owning User" languagecode="1033" />
        </displaynames>
      </field>
    </EntityRelationship>
  • Finally, update your entity name so it can exist side by side (temporarily) with the entity you will be replace. For example, find and replace all your_entity with your_entity2.
  • Similarly, replace the entity description with a replacement value so you'll be able to distinguish from the front end.
      <Name LocalizedName="Your Entity2" OriginalName="Your Entity2">snt_Your Entity2</Name>
      <ObjectTypeCode>10016</ObjectTypeCode>
      <EntityInfo>
        <entity Name="your_entity2">
          <LocalizedNames>
            <LocalizedName description="Your Entity2" languagecode="1033" />
          </LocalizedNames>
          <LocalizedCollectionNames>
            <LocalizedCollectionName description="Your Entity 2" languagecode="1033" />

  • Save Customization.xml and copy back into exported zip

Step 3 - Import Customization

  • Import modified zip file to recreate entity
  • Import other solution file created
  • Verify forms, views etc.
  • Verify entity is user-owne
Note: If you get an invalid reference error when importing you'll need to remove the reference (could be a sub-grid or jscript web resource reference) and you'll need to manually add back after the import.

Step 4 - Import Data

Use the data import wizard to re-import the data to your new duplicated entity.


Step 5 - Post Steps

Once you're satisfied with the state of affairs of your new entity you can apply the post steps which includes:

  • Delete old entity - This can get quite hairy. Deleting an entity requires removing all kinds of dependencies. Make sure you export entities, workflows, security roles etc. and keep careful manual track of any thing that needs to be manually reset

  • Update forms - you may need to update the forms with removed references that caused import customization errors. 
  • Update security roles
  • Update workflow

Tuesday, March 18, 2014

Run Report button missing on Forms

We noticed that certain forms in a CRM 2013 upgraded installation were missing the embedded "Run Report" option in the command bar. It appears in some forms but not in others... with no apparent rhyme or reason. For example, below is a screenshot of the upgraded opportunity form:



Anyone see the Run Report option? Neither do I... and the reason cannot be attributed to there not being any context sensitive reports for opportunities. There indeed are!


Yet... it does appear in the command menu for the grid view. Go figure.

I found this post which pointed me in the direction of the resolution. It references two options for resolution. One by directly modifying the export XML for the entity. And the second by using the Ribbon Workbench tool.

The first solution is the way to go. Quicker and easier and does the job. That is, you just need to add the following command definition to the RibbonDiffXml of the entity.

<CommandDefinition Id="Mscrm.ReportMenu.Form">
    <EnableRules>
      <EnableRule Id="Mscrm.FormStateNotNew" />
    </EnableRules>
    <DisplayRules>
      <DisplayRule Id="Mscrm.ReadReport" />
    </DisplayRules>
    <Actions />
</CommandDefinition>

After importing and publishing, the reports have returned to their rightful place (ok I know the report icon is still missing but that is a battle for another day).



Monday, February 3, 2014

Debugging Workflow Plugins in CRM Online

The Plugin Registration Tool has some important features to enable the effective troubleshooting of workflow plugins (and plugins in general). The following posts contain useful walkthroughs of these debugging features:

Debugging standard plugins:


Debugging workflow plugins:

But sometimes I find that adding good old-fashioned trace statements to the plugin code can also come in very handy.

The following illustrates this form of debugging for workflow plugins:

  1. Add the ITracingService reference
  2. Output trace information at various points
  3. Throw an exception to force the trace information to be output


The plugin will fail as you have forced an error by throwing an exception. And you can now go into the failed workflow and view the trace information that was outputted. 


Monday, January 27, 2014

Workflow FetchXml Query

Out of the box, workflow does a pretty good job of allowing you to specify test conditions to perform the appropriate branching logic. For example, say you wanted to determine whether a contact's parent account has a credit hold - well... configuration-wise requirements don't get much simpler than this.



Now let's add a wrinkle. Let's say you have an account hierarchy such that you wish to determine whether the contact's grandparent account (i.e. the parent account of the contact parent account) has a credit hold in order to make the necessary branching decision. This minor change in requirement of just pushing the test condition up one level cannot be achieved with the out of the box workflow tools. This is because the workflow Check Condition (and for that matter Wait Condition too) can only interrogate attributes of the current record and of parent records. Any relationship beyond that definition cannot be queried using the standard CRM workflow UI tools.

Fortunately we have a way of breaking out of this box using workflow plugins. I have created a plugin and published it on CodePlex that allows you to pass in a FetchXml query that will be evaluated. The plugin will return to the workflow whether the said FetchXml query returns results and can therefore be used to take necessary branching logic.

Let's use the example above to illustrate how this works.

First define a FetchXml query that will perform the evaluation that you are looking to do (you can use the Advanced Find "Download Fetch XML" to help you with constructing this query). In our case this would be as follows:

<fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="true">  <entity name="contact">    <attribute name="fullname" />    <attribute name="telephone1" />    <attribute name="contactid" />    <order attribute="fullname" descending="false" />    <filter type="and">      <condition attribute="contactid" operator="eq" value="{0}" />    </filter>    <link-entity name="account" from="accountid" to="accountid" alias="ae">      <link-entity name="account" from="accountid" to="parentaccountid" alias="af">        <filter type="and">          <condition attribute="creditonhold" operator="eq" value="1" />        </filter>      </link-entity>    </link-entity>  </entity></fetch>

NB: If you wish to pass in the current record's context as one of the filter criteria make sure that the following is included in FetchXml's condition clause (the attribute should be primary key of the entity that this workflow is running against):

<condition attribute="contactid" operator="eq" value="{0}" />

Now create a workflow and add the FetchXmlQuery step:



Pass in the FetchXml as a parameter to the FetchXmlQuery plugin:


And then define the rest of your branching logic based the results of this query.

If you have configured this correctly, you should be able to see the following results:


  • When the contact's grandparent account has a credit hold



  • When the contact's grandparent account does not have a credit hold


Monday, December 30, 2013

CRM 2013 Notes Control only showing notes

CRM 2013 has a handy new feature for displaying activities, notes, and posts in a single control. This is very easy to configure by just selecting the "Notes" control in the form designer.


You can subsequently also define the default view for this control in the properties. As an aside, it's also recommended to uncheck the "Display label on the form" check box as it will display better without this.



So far so good. Except for one small wrinkle - the "Activities" and "Posts" tabs for this control do not show...


I found a solution to this issue which is outlined in this blog post. Although it does appear to be an issue even for forms that have already been "upgraded". Case in point - the form that I was working with was a new form created in CRM 2013 for an existing entity and the issue still appeared.

Anyway, as described in that post, the issue is fixed by modifying FormPresentation node for the form in question from 0 to 1.


After importing and publishing, the 3 tabs for this control display as expected.



Friday, December 27, 2013

CRM Manipulation Library Date Calculation Issue

Speaking of the CRM Manipulation Library solution, I encountered an issue with the date calculation logic. Specifically with regards to the somewhat misleadingly named "Add Days" function. Misleading in that if you look at the function, you will notice that in addition to the capability of adding days, you can also use this to add years, months, weeks, hours and minutes to a given date.


This is an important clarification as adding a month to date is not always going to be the same as adding 30 days to a date. Similarly for years (as in the case of a leap year). And obviously being able to specify the more granular hour and minute parameters is also helpful. We probably could have worked around not having the week parameter but it does make life a little easier by not having to perform additional calculations.

Anyway, the issue that I encountered is that ironically if you do not specify a non-zero "Days To Add" value and just supply one of the other parameters, the calculation will not work. That is, if we were to specify parameters in the screenshot above (where we're trying to add years to the date) it would not work.

Luckily there is a simple workaround solution. Below is the same function call except we trick it by subtracting a day value and then cancel it out by passing in the equivalent days in the week parameter.

If you needed to use the week parameter, then you could similarly pass in 24 hours and cancel it out by passing -1 to the day parameter.


Thursday, December 26, 2013

Workflow Plugin CRM Online Compatibility

There are a lot of useful free add-ons available for Dynamics CRM that can add some serious customization features. The issue in many cases is that these add-ons were only released for the on-premise version of CRM.

For example, take the very useful CRM Manipulation Library which contain some important calculation capabilities. I especially find the Date Utilities to come in handy as there is often a requirement to calculate an off-set of days or business days as part of workflow logic. But when you try import the solution into a CRM Online instance you will receive an error that looks something like the following:



The good news is that this can be made compatible with CRM Online by following these steps:


  1. Unzip the solution into a folder
  2. Open the customizations.xml file
  3. Perform a search for the IsolationMode node
  4. Change IsolationMode value from 1 to 2
  5. Update the original zip file with the modified file


Once you have done so you should be able to import it into CRM Online. Applying this trick does not necessarily mean that the plugin will work as it might fail on something when trying to execute. However it also just may function without a problem which is indeed the case with the CRM Manipulation Library. So it's definitely worth a shot. If your plugin does not work after performing this tweak, you'll have to do some debugging at the code level.

Tuesday, December 24, 2013

CRM 2013 Form Design Guideline

This post is a work in progress and is far from being a definitive stance. Rather it is somewhat exploratory/tentative/philosophical based on experimentation using the CRM 2013 form designer. It may well be tweaked over time based on additional findings, user feedback etc.

In previous versions of CRM, the "default" design for forms was pretty straight forward. That is, when configuring a form, you could feel fairly "safe" in terms of selecting the "tab with one section and two columns" design option in terms of your starting point.


Sure on occasion when you knew you were adding lots of check box controls you might elect a 3 or 4 column section or conversely a 1-column section for a sub-grid. But the point is that you felt fairly certain you couldn't go too wrong in picking the "tab with one section and two columns" option in terms of how the form would render i.e. you were pretty certain that all fields would be easily visible and if the form was lengthy, you could easily add new tabs to simplify form navigation.

Maybe it's just me but I'm finding that I'm struggling a little bit more with the CRM 2013 forms. Suddenly we have a whole lot more form real estate to work with such that forms that used the previous "tab with one section and two columns" standard layout (2011, 4.0, 3.0, 1.2) , look rather sparse when viewing in CRM 2013. For example take a look at the upgraded contact form in 2013.


This is not helped by the fact that Microsoft has essentially eliminated the tab "jump" navigation option such that you have a lot of "scroll and hunt" in upgraded forms. As an aside, I personally don't know if eliminating the tab navigation is a long term decision - it seems to me that this could easily have been incorporated without compromising the look and feel of the 2013 form layout (e.g. by having a drop down where the general tab is showing or just having the tab names going from right to left at the top of the page). In contrast, they have indeed enabled a jump menu which enables navigating to different forms, so perhaps the long term design objective is to break up long forms into multiple mini-forms. Not quite sure at this point. But for now, I have to say that the lack of incorporating this tab navigation "bridge" to aid in navigation for forms upgraded from previous versions of CRM eludes me.

Anyway, the point of this post is essentially to be able to define a new "default" for CRM 2013 forms. There are definitely a lot more form design options - a simple comparison between the 2011 and 2013 forms illustrates this. We jump from having 2 tabs options to 6 tab options - this increases the form design options exponentially.


And while there are benefits to having more options there are also downsides to having so many choices. And correspondingly I've been finding that I've tended to vacillate a lot more than I did when working with previous versions of CRM. And so I've been looking for guidance as to how to go about thinking about form design in CRM 2013. But the most I've found is the following diagram which is Microsoft's take on form ergonomics.



And while this is helpful, it doesn't really address the overall issue described above. Therefore the objective of this exercise is to essentially take a stance on "default" form designs. That is, just as with previous versions of CRM, I'd like to have a fallback design that I can feel confident in taking based on the "you can't go too wrong" starting point premise. And when you reduce the requirement down, I think you come to the following basic guidelines:

  1. The form should not look too sparse with all the new form real estate i.e. take advantage of the new space that is available on the form such that the need for scrolling down is greatly reduced.
  2. Bring sub-grids that will be commonly accessed to the body of the form so that most of what the user will need will be right in front of them (I'll refer to these as "prominent" sub-grids).
  3. Consider the laptop/tablet experience where monitors are smaller - some things that fit nicely on your maximized form on your desktop monitor will be cut off on these somewhat smaller screen resolution sizes.

We can also take a look at how Microsoft designed the core entity forms - account, contact, lead, opportunity - to hopefully draw some inspiration (as presumably they also took into account the above considerations).

When all is said and done, I will tentatively be employing the following "default" form design strategy:

  • Any form where notes/activities is something that is relevant/tracked - I'll follow the guideline that Microsoft has used which is the "3-Column Varied Width" for the first form tab with the notes/activities in the central column using the new activity, notes and post control. This doesn't leave a generous amount of space for the other "prominent" sub-grids which we'll place in the third column but we'll live with that.



  • Any form that doesn't have notes/activities but has other prominent sub-grids - I'll be employing the "2-Column Varied Width" for the first form tab. The right column will be for the prominent sub-grids with a more generous allocation of space.



  • Any other form sections that are just a listing of fields - I'll be employing the "3-Column Equal Width" tab option.



That's pretty much it. This more or less throws out the  "tab with one section and two columns" which was the staple of previous versions of CRM - it just looks way too sparse in CRM 2013. In fact, as a general guideline it throws out having any section with more than one column (all the examples shown above have tabs with multiple sections, not sections with multiple columns - a tad confusing). As if you do add additional columns to the sections shown above, then you're bound to run into issues with fields being cut off when the actual form renders.

When you boil it down, it means that I will be limiting myself to the 3 form options below when designing the initial CRM form (using the 3 points mentioned above as a general guideline to determine when to use each option).



... with perhaps a few exceptions here and there for very specific requirements. And then of course, forms can subsequently always be tweaked using one of the other myriad options based on user feedback.

As I mentioned at the outset - somewhat philosophical - but this guideline helps me take quick and clinical decisions in terms of form design that I can confidently feel will will function as a very good starting point.

Monday, December 23, 2013

CRM 2013 Option Set Length

There are a few, let's say, particularities of the CRM 2013 UI experience. Some of them take getting a little getting used to such as the new way of navigating the system whereas others seem likely to be early release features that are destined to be "tweaked" in early product rollup releases... at least one would hope so!

A good example of this is the length of the drop down for option sets. Take for example, the following form:


As can be seen the drop down for the title is only showing two options at a time. Considering that there are at least 20 values in this option set, this experience is not likely to go down well with end users... and nor should it.

The way to get round this design issue is to add some spacers into the form as follows:


And after publishing, the form renders in a little bit more of a presentable fashion:



The downside is that when the option set is not in its drop down state there's a lot of unnecessary white space on the form.



So we have a workaround to this behavior but I find it to difficult to understand why it was designed in this way (maybe someone can enlighten me?). The way it worked in all previous versions seems much better and hopefully this will be rectified shortly (at which point we'll likely have to go through all those forms and remove the spacers which were added as a workaround - so hopefully sooner rather than later).

Tuesday, April 30, 2013

Contact Address Solution

In my previous post I provided some background with regards to the challenge of managing account and contact addresses in CRM. I also provided a solution for handling a simple scenario where the contact's account only ever has a single address. But the reality in most cases is more involved than that.

We will be walking through the scenario described at the end of my previous post i.e.
It would therefore be better to stick to a single account with multiple addresses and allow for the contact records to be associated to one of these account addresses. In the next post, I will present an elegant approach to handling this requirement.
The objective of this solution is to provide the flexibility of a custom lookup solution while not compromising any features/functions in the system by ensuring that the default (out of the box) address fields are populated with the address information.

This requirement covers a number of configuration scenarios and therefore may be useful as a good overall "configuration" tutorial. Topics referenced:

  • Default vs. Custom entities - when to resort to custom
  • Use case for the "Related Records Filtering" configuration option
  • REST API
  • Distribute Workflow
  • Plugin requirement

First of all, we need to create a new address entity. The reason we do so is because the ability to leverage the default More Addresses entity is limited. Specifically, the system does not allow custom relationships to be created against More Addresses which is a key requirement to the solution we are trying to implement. So first of all, we create a custom entity like the one shown and in order to eliminate user confusion we rename and remove the default More Addresses from the account and contact forms.


 Next we create an N:1 relationship with Account - this essentially replaces the More Addresses link in the account entity.


... and a 1:N relationship with Contact - this is going to allow for an address selector at the contact level.


We now add the relationship field created in the step above to the contact form. In the "Related Records Filtering" section we need to filter it to the Parent Account. This will limit the addresses shown in the lookup dialog to addresses defined at the parent account level.



The next thing we need to do is to add some jscript to the form. The objective here is to retrieve the address fields from the address which has been selected and copy it to the corresponding address fields on the contact record. The jscript below will perform the following logic:

  • Copy/Clear Address fields based on address selected from lookup using the REST API (note this use the retrieveEntityByID function described in this post)
  • Make fields read only if address is selected via the "picker" (the idea here is that you can enter an ad-hoc address if you want at the contact level, but if you select from one of the lookup values then the address values will be controlled by the official parent address records).

function AddressPicker_OnChange() {

 var entity = "snt_address";
 var entityid = Xrm.Page.getAttribute("snt_addressid").getValue();
 var fields = "?$select=snt_Street1,snt_Street2,snt_Street3,snt_City,snt_State,snt_Zip,snt_Country"
 
 if (entityid != null) {
  entityData = retrieveEntityById(entity,entityid[0].id,fields,UpdateAddressFields);
 } else {
  Xrm.Page.getAttribute("address1_line1").setValue(null);
  Xrm.Page.getAttribute("address1_line2").setValue(null);
  Xrm.Page.getAttribute("address1_line3").setValue(null); 
  Xrm.Page.getAttribute("address1_city").setValue(null);
  Xrm.Page.getAttribute("address1_stateorprovince").setValue(null); 
  Xrm.Page.getAttribute("address1_postalcode").setValue(null); 
  Xrm.Page.getAttribute("address1_country").setValue(null);   
 }

 Xrm.Page.getAttribute("address1_line1").setSubmitMode("always");
 Xrm.Page.getAttribute("address1_line2").setSubmitMode("always");
 Xrm.Page.getAttribute("address1_line3").setSubmitMode("always");
 Xrm.Page.getAttribute("address1_city").setSubmitMode("always");
 Xrm.Page.getAttribute("address1_stateorprovince").setSubmitMode("always");
 Xrm.Page.getAttribute("address1_postalcode").setSubmitMode("always");
 Xrm.Page.getAttribute("address1_country").setSubmitMode("always"); 
 
 AddressRender();
}

function UpdateAddressFields(entityData) {
 if (entityData != null) {  
  Xrm.Page.getAttribute("address1_line1").setValue(entityData.snt_Street1);
  Xrm.Page.getAttribute("address1_line2").setValue(entityData.snt_Street2);
  Xrm.Page.getAttribute("address1_line3").setValue(entityData.snt_Street3);
  Xrm.Page.getAttribute("address1_city").setValue(entityData.snt_City);
  Xrm.Page.getAttribute("address1_stateorprovince").setValue(entityData.snt_State);
  Xrm.Page.getAttribute("address1_postalcode").setValue(entityData.snt_Zip);
  Xrm.Page.getAttribute("address1_country").setValue(entityData.snt_Country);
 } 
}

function AddressRender() {
  -- Add function to form onload function
  var disabled;
  if (Xrm.Page.getAttribute("snt_addressid").getValue() != null)
   disabled = true;
  else
   disabled = false;
   
  Xrm.Page.getControl("address1_line1").setDisabled(disabled);
  Xrm.Page.getControl("address1_line2").setDisabled(disabled);
  Xrm.Page.getControl("address1_line3").setDisabled(disabled);
  Xrm.Page.getControl("address1_city").setDisabled(disabled);
  Xrm.Page.getControl("address1_stateorprovince").setDisabled(disabled);
  Xrm.Page.getControl("address1_postalcode").setDisabled(disabled);
  Xrm.Page.getControl("address1_country").setDisabled(disabled);  

}

For the sake of illustration, after the above jscript is implemented, when an address record is selected the fields are copied as shown:


Now we need to implement logic that will keep addresses in sync. For example, if the address shown in the screenshot is changed and there are 5 contacts that are linked to this address, then we want to make sure that the address fields are propagated to all 5 contact records. This is fairly easy to accomplish using the distribute workflow. The screenshots below show the 2 workflows required in order to configure this option.


The last piece of the puzzle requires a plugin. Basically at this juncture we can define addresses against the account entity that will appear in the lookup for contacts tied to this account:


But what about the address defined on the account itself? This in essence is the Primary address of the account and we also want it available for selection for the contacts. Therefore the plugin logic required here is to automatically maintain the PRIMARY address as shown above based on the values entered on the account form (as additional validation, you should also define jscript on the address entity to make the fields read only when the address name = PRIMARY and prevent manually keying in address with this name i.e. reserve PRIMARY for the plugin only).

The end result? A lookup on the contact form that allows you to select from the specified account addresses.