{Quick tip} : Determine if OpportunityClose dialog is opened for “Close as Won” or “Close as Lost” in Dynamics 365 Unified interface.

As many of us know, for unified interface now we can customize the “opportunityclose” entity. A feature awaited for a very long time and finally it’s here.

If you are not aware, you can now customize the dialog box that pops up when you click “Close as Won” and “Close as Lost” button on the opportunity. The entity is “opportunityclose” entity. For more details, please refer the below links

https://docs.microsoft.com/en-us/dynamics365/sales-enterprise/enable-opportunity-close-customization

https://docs.microsoft.com/en-us/dynamics365/sales-enterprise/customize-opportunity-close-experience

So you can customize the opportunityclose form and add your custom fields as well. But the same form fires both when you click “Close as won” or “Close as lost” button. My customer had a requirement where based on the button clicked, appropriate fields need to show up and certain business logic need to be performed when the “Opportunity Close” form is loading.

Just when I was about to pose before my customer as “know it all person”, Dynamics 365 again intervened and put me in proper place. So I was back to drawing boards. The first thing that struck me was – “How is this quick create form getting launched?”. In the ribbon workbench I evaluated “Close as won” button and found the below action.

image

Now we are going to modify the Opportunity system library script file. Ahhhhhhhhh..Just joking.

The important thing to note here is the “Boolean parameter” which is passed to the function. So the key takeaway from here is the system function is taking this boolean parameter as input and then passing this parameter to the Quick create form of opportunity close.

Now things are sorted out. A quick guess – this parameter must be passed as a Querystring parameter to the Quick create form. And hell yeah! I was right.

function formLoad (e) {
        debugger;
        var isCloseAsLostClicked = false;

        var fc = e.getFormContext();
        var gc = Xrm.Utility.getGlobalContext();

        var queryString = gc.getQueryStringParameters();
        isCloseAsLostClicked = queryString["param_won"];
// value will be true for won and false for lost button click.    

}

I have highlighted the lines of code here which will help you determine whether “Close as Won” or “Close as Lost” was clicked.

Once you have that information, it’s up to you to utilize this information. Cool isn’t it?

Before I end, this will work for Unified interface only as the feature of customizing opportunityclose entity is supported only for Unified interface.

P.S – Haven’t found this documented in MS docs. So obviously can’t vouch whether this parameter will always be there. As of now validated for multiple environments and scenarios and it worked just fine. Would be happy for alternatives.

Hope this helps!

Debajit Dutta

(Dynamics MVP)

For consultation/ corporate training visit www.xrmforyou.com or reach out to us at info@xrmforyou.com

Our product offerings:

Role based views for Dynamics 365 (http://www.xrmforyou.com/role-based-views.html)

CRM-Sharepoint Attachment uploader and metadata manager (http://www.xrmforyou.com/sharepoint-integrator.html)

Record Cloner for Dynamics 365 (http://www.xrmforyou.com/record-cloner.html)

Multiselect picklist for Dynamics 365 (http://www.xrmforyou.com/multi-select-picklist.html)

Advertisements

How to use Promise to evaluate your ribbon enable rules with asynchronous Xrm.WebApi methods in Dynamics 365 Unified interface.

We all have been using Xrm.WebApi methods isn’t it? After all why not? They are wonderful. You no longer need to write lengthy XmlHttpRequests and parse the raw JSON results back. Xrm.WebApi methods does the hard part of converting the raw JSON outputs into strongly typed objects.

Not sure if anyone is still there but if you are new to Xrm.WebApi methods, below is the perfect link to get you started.

https://docs.microsoft.com/en-us/powerapps/developer/model-driven-apps/clientapi/reference/xrm-webapi

Just when you are about to think that Xrm.WebApi methods have successfully ticked all the boxes, comes the ribbon rules. Xrm.WebApi rules are asynchronous.

Suppose you have a requirement where you need to show a ribbon button only if user is in role of Salesperson. For that we write one function “userHasRole” with the code below

 function userHasRole() {

       var isUserAdmin = false;

        Xrm.WebApi.retrieveMultipleRecords("role", "?$select=roleid&$filter=name eq 'salesperson'").then( 
            function success(result) { 
                 var userSettings = Xrm.Utility.getGlobalContext().userSettings; 
                var securityRoles = userSettings.securityRoles;

                var isUserAdmin = false; 
                 for (var i = 0; i < result.entities.length; i++) { 
                     var roleId = result.entities[i].roleid;

                    if (securityRoles.indexOf(roleId) !== -1) { 
                         isUserAdmin = true; 
                        break; 
                    } 
                } 
            }, 
            function (error) { 
                console.log(error.message); 
            } 
         );

return isUserAdmin; 
}

The problem with this code is very evident. Before Xrm.WebApi return the results, the function would return false. Off course you can modify this function using window level objects and then using Xrm.Page.ui.refreshRibbon() to refresh the ribbon after Xrm.WebApi returns results. And perhaps we all have done that so far.

While the above method would certainly work, you may think like – “Is this the only way?” Certainly not. If you are using Unified interface (only option left for online users after Oct 2020), you have a better way to achieve the same. Unified interface rules support returning a Promise rather than boolean for asynchronous rule evaluation. Let’s see how we can re-write the same rule using Promise. And surprisingly, many of us working in Dynamics 365 day in and day out are not aware of this.

function userHasRole() { 
    return new Promise(function (resolve, reject) {

        Xrm.WebApi.retrieveMultipleRecords("role", "?$select=roleid&$filter=name eq 'salesperson'").then( 
            function success(result) { 
                var userSettings = Xrm.Utility.getGlobalContext().userSettings; 
                var securityRoles = userSettings.securityRoles;

                var isUserAdmin = false; 
                for (var i = 0; i < result.entities.length; i++) { 
                    var roleId = result.entities[i].roleid;

                    if (securityRoles.indexOf(roleId) !== -1) { 
                        isUserAdmin = true; 
                         break; 
                    } 
                 }

                // return true or false 
                resolve(isUserAdmin); 
            }, 
            function (error) { 
                reject(error.message); 
                console.log(error.message); 
            } 
        ); 
    }); 
}

Not much change in the code. All you need to do is return a Promise the resolve method of which should return true or false depending on business logic. And one very important to remember is that not only with Xrm.WebApi methods but also if you are using XMLHttp async, it would work just fine.

Put your code within the Promise and everything works like charm.

Any functionality before we implement, we should be aware of the limitations of the same. So we have a couple of them here.

  • This only works for Unified Interface. If you are using WebClient this won’t work
  • If the rule does not evaluate within 10 seconds, the rule would resolve with false value

Before I end, you may be asking where should can I find this in Microsoft documentation. Well if you are a big fan of Microsoft docs like me, here you go – https://docs.microsoft.com/en-us/dynamics365/customerengagement/on-premises/developer/customize-dev/define-ribbon-enable-rules#custom-rule

Hope this helps!

Debajit Dutta

(Dynamics MVP)

For consultation/ corporate training visit www.xrmforyou.com or reach out to us at info@xrmforyou.com

Our product offerings:

Role based views for Dynamics 365 (http://www.xrmforyou.com/role-based-views.html)

CRM-Sharepoint Attachment uploader and metadata manager (http://www.xrmforyou.com/sharepoint-integrator.html)

Record Cloner for Dynamics 365 (http://www.xrmforyou.com/record-cloner.html)

Multiselect picklist for Dynamics 365 (http://www.xrmforyou.com/multi-select-picklist.html)

Dynamics 365 integration with SmartSheet using Flows–What’s there and what’s not.

A dry spell of a month and here I am back to my favourite way of connecting with community which is writing one more blog post.

So here I got a new requirement from my customer. Basically they are using Smartsheet and Dynamics 365 and the simple requirement is whenever any changes are made in the smartsheet (row added or updated), some updates need to happen back in Dynamics 365.

Although I haven’t used Smartsheet connector in Flow, I was just aware of it and my initial hunch was like this would a walk in the park for flows. But like so many other times, this time also it didn’t quite turnout like the way we expected.

The first scenario was “When a row is added to a smartsheet”, I should be able to sync those changes to Dynamics 365 instance. So I started creating and flow and searched for Smartsheet connector. And the trigger is just there.

“When a new row is created”

image

I select that trigger and connect to the smartsheet.

image

As you can see from the above screenshot, I provided the smartsheet I want connect to and also mentioned the columns that I want to sync to. “Requestor” and “Business Group” are columns in the smartsheet as seen from the below screenshot.

image

The next step is to identify the values of the fields entered by the user. Now this one can be tricky if you are new here. For this demo I will use the Initialize variable action and take the value of the Requestor and Business Group in those variables. But before we do that, we need to understand how does it return the data.

So I go ahead and add a new row to the Smartsheet. As expected within a minute the flow fired. Let’s inspect the body of the flow here.

image

As seen from the above screenshot, it returns cells collection. The value highlighted here is for the" “Business Group” field.

To take this value and assign it a variable you can use the below expression.

triggerBody()[‘cells’][0].displayValue

image

In this way you can use variables for other values and then use CDS connector to update CRM.

Now comes the update part. Surprisingly there is no trigger for when a row is updated in Smartsheet.

image

We have something – ‘When a sheet is update’ but unfortunately that does not return the updated rows. Infact it returns the whole sheet data. Kind of stuck and hence had to resort to custom development of consuming SmartSheet API’s.

Would be delighted if I have some inputs here!

Hope this helps next time you get a requirement on this.

Debajit Dutta

(Dynamics MVP)

For consultation/ corporate training visit www.xrmforyou.com or reach out to us at info@xrmforyou.com

Our product offerings:

Role based views for Dynamics 365 (http://www.xrmforyou.com/role-based-views.html)

CRM-Sharepoint Attachment uploader and metadata manager (http://www.xrmforyou.com/sharepoint-integrator.html)

Record Cloner for Dynamics 365 (http://www.xrmforyou.com/record-cloner.html)

Multiselect picklist for Dynamics 365 (http://www.xrmforyou.com/multi-select-picklist.html)