You are currently browsing the tag archive for the ‘CRM Online 2011’ tag.

Other than using Bing, you can easily integrate other geo-location sources. One of the most enterprise level solution is of course Esri Maps. They have a very good solution for mapping. They can also be pricy, so if you are looking for a solution on the cheap, and are not afraid of doing very little coding, read on.

Google Maps provides a very robust mapping API. You can find more details HERE.

To integrate this into your existing CRM, you will use an iFrame, and a custom HTML web resource. Your HTML will include a div tag with an id of “geolocation”. You can use another id, we’re using “geolocation” in the context of this example. Basically, your div will look like so:

<div id="geolocation" style="width: 800px; height: 500px;"></div>

Define the style width and height as needed to render properly within the iFrame at a standard screen resolution set at the enterprise level.

Pass the context to the iFrame, so you can retrieve the address fields. You will use these to build the address string you pass to retrieve the longitude and latitude.

With the new v.3 of the maps API, you do not need a API Key anymore, which makes it a lot easier to move your solution between various environments, and to have the solution internal only (no IFD).

When calling the API, if your CRM is set to use HTTPS, use the HTTPS Google API URL, otherwise use the HTTP address.

Add in the header of your HTML web resource the reference to the JS library:

<script src="http://maps.googleapis.com/maps/api/js?v=3.exp&sensor=false"></script>

In this example I am using HTTP.

In your HTML web resource, first function you want to call on page load is a function that takes the value of the address fields, and concatenates it all into a string. It will be along the lines of:

var _address = street + city + province + country + postalCode;

check and use only the fields where you have a value populated.

Once you have this address as a string, pass it to the first function that converts the address into latitude and longitude. This will look as follows:

ParseAddress: function(address) {
    var url = "
http://maps.googleapis.com/maps/api/geocode/json?" +
    "address=" + address +
    "&sensor=false"

    var xhr = base.createCORSRequest(‘GET’, url);
    if (!xhr) {
        alert(‘CORS not supported’);
        return;
    }

    // Response handlers.
    xhr.onload = function () {
        var text = xhr.responseText;
        var _start = text.indexOf("\"location\" :") + 12;
        var _location = text.substring(_start, text.indexOf("}", _start) + 1);

        geoCode.Initialize(_location);
    };

    xhr.onerror = function () {
        $("#message-area").append("Woops, there was an error retrieving server data.");
    };

    xhr.send();
},

This example is using CORS. CORS stands for “Cross-Origin Resource Sharing”, and for more details on CORS see the W3C documentation HERE.

This function makes a call to the Google API and on successful retrieval of data, calls another function called Initialize, passing the trimmed returned string that includes the location lat and lng.

The Initialize function takes this location, builds the map representation, sets the zoom on it, and drops a pin at the location provided by the coordinates:

Initialize: function (_location) {

    var jsonData = $.parseJSON(_location);
    var _bGeoLocation = new Array();
    _bGeoLocation = jsonData;
 

    var myOptions = {
        zoom: 16,
        center: new google.maps.LatLng(_bGeoLocation .lat, _bGeoLocation .lng),
        mapTypeId: google.maps.MapTypeId.ROADMAP
    }
    // Draw the map
    var mapObject = new google.maps.Map(document.getElementById(“geolocation”), myOptions);
    // Place the marker
    new google.maps.Marker({ map: mapObject, position: userLatLng });

}

Et voila, now you have a map with a pin on it pointing to the record’s address.

image

Enjoy!

Advertisements

I’ve run into this situation recently, and I would think this is a bug.

Let’s say you are a System Customizer, and you have a read-only form and an edit form for an entity, say Account. Now you try to create a Workflow for Accounts, and when you get to add an Update Record step, and click on the Set Properties button, you are presented with the read-only form. Annoying  enough.

The reason for this behavior is because Dynamics CRM remembers the last form type you have used. If the last time you accessed an Account you’ve used the read-only form, you are presented in the Workflow with that same form.

To avoid that, go into an Account, and change the form to the edit form. Save and go back to your Workflow. Now you should be getting the proper edit form.

Hopefully we see a fix for this soon.

Enjoy!

Every now and then, if your scripts are performing form changes, and you try to navigate without saving, you will be prompted to save. But what if you want to see exactly what was changed on a large form?

The following function can show you the modified fields:

function checkIsDirty()
{
    var _mesage = "";
    Xrm.Page.data.entity.attributes.forEach(
        function(attribute, index)
        {
            if(attribute.getIsDirty() == true)
            {
                message += attribute.getName() + "\n";
            }
        }
    );
    alert("These fields have been modified: \n" + message);
}

You could put this on a ribbon button, and allow the form user to check at any time what was changed since the last save. Alternatively, you could highlight the fields with a different background color rather than bring up an alert message.

Enjoy!

The following form types are available in Dynamics CRM 2011:

Value Form Type
0 Undefined
1 Create
2 Update
3 Read Only
4 Disabled
5 Quick Create (Deprecated)
6 Bulk Edit
11 Read Optimized

 

To get the Form Type in JScript, use the following syntax:

var _formType = Xrm.Page.ui.getFormType();

I’ve been doing this for so many times, I thought I’d share it with the new CRM customizers out there, as it’s a neat way of getting something done without delving too deep into development and plugins.

This approach uses JScript to capture the Save event on a form, and to divert it on a different course of action.

First off, let’s begin with the basics. A condition on the form will triggers this action. In most cases I want to allows the form save, but in special circumstances I want to block it and perform a different action. We’ll be doing this on the Contact form.

So let’s go ahead and add a Two Options field on the form. I named it “new_isspecialcustomer”.

The following function, associated with the OnSave event, checks for the value on my newly created field, and decides on the action.

function StopSave(context)

{

var _isSpecialSelection = null;

var _isSpecial = Xrm.Page.getAttribute("new_isspecialcustomer");

if(_isSpecial != null)

{

_isSpecialSelection = _isSpecial.getValue();

}

if(_isSpecialSelection == false)

{

alert("You cannot save your record while the Customer is not a friend!");

context.getEventArgs().preventDefault();

}

}

Good. I assume you know by now how to create a web resource, add the JScript function to it, etc…

So, the preventDefault() stops the Save. What the user sees is an alert like this:

clip_image002

and the form remains opened and unsaved.

As presented previously in another blog post (this one to be more precise), you can write a function as such to start a workflow:

function launchWorkflow(dialogID, typeName, recordId)
{
  var serverUri = Mscrm.CrmUri.create(‘/cs/dialog/rundialog.aspx’);

  window.showModalDialog(serverUri + ‘?DialogId=’ + dialogID + ‘&EntityName=’ + typeName +
    ‘&ObjectId=’ + recordId, null, ‘width=615,height=480,resizable=1,status=1,scrollbars=1);

  // Reload form
  window.location.reload(true);
}

Now it’s starting to take shape. Now all we have to do is call this function from our previous one. Where you prevent the save, add a call to launchWorkflow. The parameters are as follows:

· GUID of the Workflow or Dialog

· The type name of the entity

· The ID of the record

And voila! Now you hijacked the save, performed your own actions, and didn’t have to fire up Visual Studio to write a plugin.

Just as a note, it’s probably more efficient to do this in a plugin, but I use this approach for a lot of quick demos I put together in a relatively short time. Gives a new meaning to the “Quick and Dirty” phrase.

Enjoy!

One interesting case I had just the other day made me look like a fool. Just like in many cases, when you have issues connecting your Outlook to a CRM instance, there are a few basic steps you follow in trying to debug the issue. There is available a troubleshooting wizard at the following URL:

http://rc.crm.dynamics.com/rc/2011/en-us/online/5.0/outlook-troubleshooting.aspx

While this is helpful, one other place to go look at is the folder holding the logs on the client machine. these logs are located at various locations, depending on the version of Windows you are running and type of log you are searching. These are:

Configuration log files

Windows Path
XP C:\Document and Settings\user\Local Settings\Application Data\Microsoft\MSCRM\Logs
Vista & 7 C:\Users\user\AppData\Local\Microsoft\MSCRM\Logs

Setup log files

Windows Path
XP C:\Document and Settings\user\Application Data\Microsoft\MSCRM\Logs
Vista & 7 C:\Users\user\AppData\Roaming\Microsoft\MSCRM\Logs

 

All fine by me, but the situation I had had to do with the fact that the Outlook client was refusing to connect to my CRM instance while using IE it worked just fine. The Sync to Outlook was enable in the security role, and all looked just fine.

Finally, one of my colleagues stumbled upon an interesting piece of information. In the security role the users that could not connect had assigned, on the Business Management tab, under User Settings, we only had Read across the Organization enabled. Changing the Write setting to User allowed now all Outlook clients for the users of this group to connect.

image

Enjoy!

For the sake of standardizing the format for all system postal codes, I have implemented the following script to re-format the user input of postal codes for both US and Canada. The purpose is to take the user’s input, and re-arrange it to follow a standard format.

The comments are self-explanatory.

// Function to format postal code
// for both Canadian and US postal codes
function FormatPostalCode(context)
{
  var oField = context.getEventSource().getValue();
  var sTmp;
 
  if(typeof(oField) != "undefined" && oField != null)
  {
    // check for US ZIP code
    if(oField.match(/^[0-9]{5}$/))
    {
        context.getEventSource().setValue(oField);
        return true;
    }

    // check for Canadian postal code
    sTmp = oField.toUpperCase();
    if (sTmp.match(/^[A-Z][0-9][A-Z][0-9][A-Z][0-9]$/))
    {
        sTmp = sTmp.substr(0,3) + " " + sTmp.substr(3,3);
        context.getEventSource().setValue(sTmp);
        return true;
    }
    if (sTmp.match(/^[A-Z][0-9][A-Z].[0-9][A-Z][0-9]$/))
    {
        context.getEventSource().setValue(sTmp);
        return true;
    }

    // alert("Incorrect ZIP/Postal Code format.");
    // postal code could be any other country, so leave as is
  }
}

Add the function in a Web Resource, and reference it on the postal code’s OnChange event. As the script uses the context, make sure you select the “Pass execution context as first parameter” option.

image

Enjoy!

Integrating CRM Accounts with LinkedIn is a very easy step. I have found numerous examples on doing this by using a Web Resource, but I don’t like the way it displays in a frame. It looks anything but professional.

So I decided there’s got to be a better way of doing this.

First off, I am using the jQuery library, so you will need to add that to your resources. It will make it so much easier to select elements on the page.

I have added an on load function to the account:

function Account_onLoad()
{
    var nvs_AccountName = Xrm.Page.getAttribute("name").getValue();
   
    $("#name").after(function() {
        var script = ‘<script src="
http://platform.linkedin.com/in.js" type="text/javascript"></script>’ +
        ‘<script type="IN/CompanyProfile" data-id="’ + nvs_AccountName +
        ‘" data-format="inline" data-related="false"></script>’;
        return script;
    });
}

I am looking for the Account name, as populated in the Name field, and retrieving the LinkedIn profile based on that. I have re-arranged the form to provide the space for the section that will be inserted right below the Account Name field:

image

In the Form Properties, add your references as below:

image

once you have all that set up, try opening an account, and you will get the following view:

image

No more iFrames, it just looks like it belongs there.

The beauty of this approach is also the fact that, if an account is not found on LinkedIn, no problem, the screen will look like so:

image

NOTE: Please be aware that you will get the following prompt:

image

Add LinkedIn to your browser’s trusted sites. In the Trusted Sites configuration, click on Custom level.

image

Set Display Mixed Content to Enable.

image

This setting can be pushed through a group policy. For more details see KB2625928.

Enjoy!

As you probably know, the Product has a non-sufficient form for most customers. Let’s have a look at the actual form, and see what’s missing:

image

Now, from what we see, the List Price is what is being used in the system calculations as the price to sell. The Standard Cost could be the MSRP, and the Current Cost is the price your company pays for the product, or the total cost to produce. This is minimalistic, so let’s look at a scenario:

Your company ABC Inc. buys products, and installs them for customers (let’s say Doors & Windows). How do we apply the form to this model?

First off, you have MSRP (Standard Cost). To that apply a standard discount percentage (needs to be added custom), to result in your Current Cost. To that you apply a percentage for S&H, and any other necessary fees, resulting in your List Price.

Since we do have the Description section, which includes the Vendor details, I think this was the scenario envisioned.

Otherwise, on to the manufacturing scenario:

Your company XYZ Inc. produces paper weights. Applying the form to this model:

Materials Cost + Labor Cost + Other expenses incurred = Current Cost

Current Cost + X Amount (expressed as % in many cases) = List Price

The Vendor becomes an interesting concept now. Either XYZ Inc. is the vendor, in which case the field becomes irrelevant, or you have multiple vendors/distributors, in which case you add the same product for each vendor, so you can track against vendors.

As I was saying, the Product is an entity where you should spend some time to map it correctly to the business model used.

Starting to customize a CRM without considering all the aspects could potentially force you in a corner when you get to the Product entity customization.

Enjoy!

One way or another, eventually you will get a request to change the default amount calculations as built in CRM. This could be either by adding a tax percentage (after all, all provinces or states use a fixed tax rate expressed as a percentage), or to handle some other kind of discount by a specific category of customers. Another common example is to apply tax at the Opportunity or Quote level rather than doing it for each product line added. After all, who wants to do the same task 10, 15 or even 20 times when you can do it just once?

If the approach is by using JScript, it may seem like a trivial task. Retrieve the values filled in by users, put your formulas in, an voila… almost perfect. Except, some fields do not retrain the value you pushed in. Even though the calculation on the screen looks correct, when you close and save your form, the totals are being re-calculated by the system, and your formulas are ignored.

In order to identify these cases, you can look at the MSDN documentation. Following is a sub-set that should help you with any calculations you need to override on the Opportunity, Quote and Invoice. The URLs are pointing to the original extended documentation on MSDN.

Opportunity

http://msdn.microsoft.com/en-us/library/gg328229.aspx

DisplayName

SchemaName

Type

IsValidForUpdate

Account

AccountId

Lookup

FALSE

Actual Revenue

ActualValue

Money

TRUE

Actual Revenue (Base)

ActualValue_Base

Money

FALSE

Contact

ContactId

Lookup

FALSE

Opportunity Discount Amount

DiscountAmount

Money

TRUE

Opportunity Discount Amount (Base)

DiscountAmount_Base

Money

FALSE

Opportunity Discount (%)

DiscountPercentage

Decimal

TRUE

Est. Revenue

EstimatedValue

Money

TRUE

Est. Revenue (Base)

EstimatedValue_Base

Money

FALSE

Exchange Rate

ExchangeRate

Decimal

FALSE

Freight Amount

FreightAmount

Money

TRUE

Freight Amount (Base)

FreightAmount_Base

Money

FALSE

Revenue

IsRevenueSystemCalculated

Boolean

TRUE

Opportunity

OpportunityId

Uniqueidentifier

FALSE

Price List

PriceLevelId

Lookup

TRUE

Pricing Error

PricingErrorCode

Picklist

TRUE

Total Amount

TotalAmount

Money

FALSE

Total Amount (Base)

TotalAmount_Base

Money

FALSE

Total Pre-Freight Amount

TotalAmountLessFreight

Money

FALSE

Total Pre-Freight Amount (Base)

TotalAmountLessFreight

_Base

Money

FALSE

Total Discount Amount

TotalDiscountAmount

Money

FALSE

Total Discount Amount (Base)

TotalDiscountAmount_Base

Money

FALSE

Total Detail Amount

TotalLineItemAmount

Money

FALSE

Total Detail Amount (Base)

TotalLineItemAmount_Base

Money

FALSE

Total Line Item Discount Amount

TotalLineItemDiscountAmount

Money

FALSE

Total Line Item Discount Amount (Base)

TotalLineItemDiscountAmount

_Base

Money

FALSE

Total Tax

TotalTax

Money

FALSE

Total Tax (Base)

TotalTax_Base

Money

FALSE

Currency

TransactionCurrencyId

Lookup

TRUE

NOTE: On the Opportunity, it may seem misleading, but the Discount (%) and Discount fields work independent of each other. By that I mean that you can add a Discount (%) and a Discount, and they will both be subtracted from Product Totals.

Quote

http://msdn.microsoft.com/en-us/library/gg309335.aspx

DisplayName

SchemaName

Type

IsValidForUpdate

Potential Customer

CustomerId

Customer

TRUE

Quote Discount Amount

DiscountAmount

Money

TRUE

Quote Discount Amount (Base)

DiscountAmount_Base

Money

FALSE

Quote Discount (%)

DiscountPercentage

Decimal

TRUE

Exchange Rate

ExchangeRate

Decimal

FALSE

Freight Amount

FreightAmount

Money

TRUE

Freight Amount (Base)

FreightAmount_Base

Money

FALSE

Name

Name

String

TRUE

Opportunity

OpportunityId

Lookup

TRUE

Payment Terms

PaymentTermsCode

Picklist

TRUE

Price List

PriceLevelId

Lookup

TRUE

Total Amount

TotalAmount

Money

FALSE

Total Amount (Base)

TotalAmount_Base

Money

FALSE

Total Pre-Freight Amount

TotalAmountLessFreight

Money

FALSE

Total Pre-Freight Amount (Base)

TotalAmountLessFreight_Base

Money

FALSE

Total Discount Amount

TotalDiscountAmount

Money

FALSE

Total Discount Amount (Base)

TotalDiscountAmount_Base

Money

FALSE

Total Detail Amount

TotalLineItemAmount

Money

FALSE

Total Detail Amount (Base)

TotalLineItemAmount_Base

Money

FALSE

Total Line Item Discount Amount

TotalLineItemDiscountAmount

Money

FALSE

Total Line Item Discount Amount (Base)

TotalLineItemDiscountAmount

_Base

Money

FALSE

Total Tax

TotalTax

Money

FALSE

Total Tax (Base)

TotalTax_Base

Money

FALSE

Currency

TransactionCurrencyId

Lookup

TRUE

 

Invoice

http://msdn.microsoft.com/en-us/library/gg309547.aspx

DisplayName

SchemaName

Type

IsValidForUpdate

Account

AccountId

Lookup

FALSE

Customer

CustomerId

Customer

TRUE

Invoice Discount Amount

DiscountAmount

Money

TRUE

Invoice Discount Amount (Base)

DiscountAmount_Base

Money

FALSE

Invoice Discount (%)

DiscountPercentage

Decimal

TRUE

Exchange Rate

ExchangeRate

Decimal

FALSE

Freight Amount

FreightAmount

Money

TRUE

Freight Amount (Base)

FreightAmount_Base

Money

FALSE

Invoice

InvoiceId

Uniqueidentifier

FALSE

Invoice ID

InvoiceNumber

String

FALSE

Payment Terms

PaymentTermsCode

Picklist

TRUE

Price List

PriceLevelId

Lookup

TRUE

Pricing Error

PricingErrorCode

Picklist

TRUE

Priority

PriorityCode

Picklist

TRUE

Total Amount

TotalAmount

Money

FALSE

Total Amount (Base)

TotalAmount_Base

Money

FALSE

Total Pre-Freight Amount

TotalAmountLessFreight

Money

FALSE

Total Pre-Freight Amount (Base)

TotalAmountLessFreight

_Base

Money

FALSE

Total Discount Amount

TotalDiscountAmount

Money

FALSE

Total Discount Amount (Base)

TotalDiscountAmount_Base

Money

FALSE

Total Detail Amount

TotalLineItemAmount

Money

FALSE

Total Detail Amount (Base)

TotalLineItemAmount_Base

Money

FALSE

Total Line Item Discount Amount

TotalLineItemDiscountAmount

Money

FALSE

Total Line Item Discount Amount (Base)

TotalLineItemDiscountAmount

_Base

Money

FALSE

Total Tax

TotalTax

Money

FALSE

Total Tax (Base)

TotalTax_Base

Money

FALSE

Currency

TransactionCurrencyId

Lookup

FALSE

 

Of course, you could write a plugin to do this, but if you need a quick and dirty solution, maybe for a presentation, or when going for a more agile approach, JScript is your friend.

Also, you will find that, for some fields, you will end-up hiding the original field, and creating your custom field to store the calculated values. When you do so, do not forget to hide the original and add your custom field in all the related views. The last thing you want is your invoice showing a price, but when you look at all the invoices for that month, to show a different amount in the view. Same goes for reporting, make sure that when you have a calculated field, you use that in the report.

Enjoy!

This is just a quick observation. You might be tempted, for the sake a clarity, to create an additional field next to the Contact phone number to capture a phone extension.

image

Even though it might seem like a very good idea, after all a lot of companies do still use phone extensions for each employee, when users will synchronize with Outlook, this field will of course not be mapped to anything in Outlook. Obviously, it’s a custom field, so it makes sense.

Anyway, the only way to store an extension and have it mapped between CRM and Outlook is to mark it in the phone number field. Try to use a convention across the company, so you can handle that in a formatting script.

I am still surprised at how many people still mark extensions with the following format:

(999) 999-9999 x999

or

(999) 999-9999 ext999

One note, for the sake of telephony, if you use a comma [,] instead of the [x], the cell phones will know to interpret that as a pause, and, once dialed, should be able to dial the number, followed by a pause and then the extension.

Make it easy for the mobile users to dial phone numbers!

Enjoy!

You might be reading this topic, and think it’s just scripting, and just like any other form. What’s different?

Now, most fields we can script against without a problem. But let’s focus on the Extended Amount field. There is a system calculation that takes place, and if you try to enable the field for editing on the form, you will realize that you can’t.

Funny enough though, assigning a value to the field in JavaScript “almost” works.

var calculatedValue = … ; // put your formula here

Xrm.Page.getAttribute(“extendedamount”).setValue(parseFloat(calculatedValue));

Don’t be fooled by the fact that the field value indeed updates, because that’s only on the screen. Once you save the Quote Product, the value automatically gets re-calculated based on the built-in formula, and your result is off.

The way to override that value is by writing a plugin, but that’s besides the point of this post. I was looking at alternatives using JS. In some circumstances you just can’t get the result you want without writing a plugin, but for easy things, you could basically take the following approach:

  • Create a new Unit Price field on the form.
  • Hide the default Unit Price field.
  • Do your calculations based on the custom Unit price field, update the unit price (adding discounts and other adjustments you want to make), and populate the new value in the default Unit Price field (the one you have hidden).
  • Using this new price, you can take advantage of the system calculation, and make sure the Extended Price gets updated.

image

For a Write In product, this is not a problem, but for an Existing product, this will only work if Override Price is selected. Otherwise, the calculated value is based on the price as it’s defined in the price list associated product.

Enjoy!

Microsoft Business Solutions MVP

Check out my course [Video]

Configuring and Extending Dynamics 365 Customer Engagement

Configuring and Extending Dynamics 365 Customer Engagement

Check out my course [Video]

Getting Started with Dynamics 365 Customer Engagement

Reviewed Book

Implementing Microsoft Dynamics 365 for Finance and Operations

Implementing Microsoft Dynamics 365 for Finance and Operations

Reviewed Book

Microsoft Dynamics 365 Extensions Cookbook

Microsoft Dynamics 365 Extensions Cookbook

Check out my Book

Microsoft Dynamics CRM 2016 Customization - Second Edition

Microsoft Dynamics CRM 2016 Customization - Second Edition

Check out my Book

Microsoft Dynamics CRM Customization Essentials

Microsoft Dynamics CRM Customization Essentials

Check out my Book

Microsoft Dynamics CRM 2011 Scripting Cookbook

Microsoft Dynamics CRM 2011 Scripting Cookbook

Reviewed Book

Microsoft Dynamics CRM 2011: Dashboards Cookbook

Microsoft Dynamics CRM 2011: Dashboards Cookbook

Enter your email address to subscribe to this blog and receive notifications of new posts by email.

Join 599 other followers

Follow Dynamics 365 Wizardry on WordPress.com