Tuesday 6 September 2016

Utilize Custom Action to Help Filtering The Lookup View in CRM Form

Overview

Sometimes in our project, we have requirement to filter lookup based on some conditions and it can be achieved using addCustomView or addCustomFilter function.

And sometimes it is just not too easy to do it in Javascript or the complex fetch xml, so in my blog I just want to share another method to get the filtered result same as you wanted, that is using Javascript + Custom Action!

Detail

Following my previous post: http://missdynamicscrm.blogspot.sg/2014/08/crm-2013-using-addcustomfilter-to-get-filtered-lookup-field-based-on-linked-entity.html

So, considering you have this filter:
image

To get the result as per expected, you can use Custom Action.

Steps:

1. You need to create a custom action that give you output, either STRING or ENTITYCOLLECTION

2. Inside the custom action code, if you want to return string, you can use comma delimited concept, or using | as delimiter, or you can just return the Final XML Filter already.

If you use EntityCollection, you might need to parse it again.

3. Then create a javascript that can call the Custom Action, you can use this method for easy way:
http://www.magnetismsolutions.com/blog/paulnieuwelaar/2015/08/12/Call-Action-in-CRM-2015-Easily-from-JavaScript-Library

4. Then you get the result as parameter, you can just set it to the filter = “the Result” (if you use Final XML Filter as the Output) or you parse the GUID if you use the comma or | delimited concept, or if you use EntityCollection then you need to parse it back.

5. See the result

Basically, you just need to get this result:

image

Either you just easily using String as output or other method it is up to u.

But the point here is we can use Custom Action for solving complex filtering and remember that we can use impersonation also to get the data you want if it is related to the other entities as well, imagine if we also need to have multiple entities involved then it might be easier if we use Custom Action as we just replace the DLL if there is any other changes using Plugin Registration Tool.

Hope this helps!

Thanks,
Aileen

Modify the Delete (Dustbin icon) Button in the CRM Subgrid

Overview

Sometimes we need to modify the Delete button in the CRM Subgrid, example:

1. For preventing users to perform the delete button (but you dont want to just disable it)

2. Call another function or call custom function that needs client site programming (We can do plugin onDelete or onAssociate, but in case you want to show it in the client site)

3. To do impersonation

The Code

function modifyRibbon() {
    modifySubgridDeleteButtonEventHandler("subgrid_name_in_the_form", deleteSubgridRecord, true);
    discountStructureControl();
}

function deleteSubgridRecord() {
    alert("test");
}

function modifySubgridDeleteButtonEventHandler(subgridName, functionToCall, passGridControl) {
    try {
        //to store the original function ones
        var originalFunctionDeleteRecordSubgrid = Mscrm.GridCommandActions.deleteRecords;
        debugger;
        //add new standard subgrid
        Mscrm.GridCommandActions.deleteRecords = function (selectedControl, selectedControlSelectedItemReferences, selectedEntityTypeCode) {
            //if (typeof (gridControl.get_id).toString().toLowerCase() == "undefined") {} //no need since I replaced by the previous line
            if (selectedControl.get_id() != subgridName) {
                originalFunctionDeleteRecordSubgrid(selectedControl, selectedControlSelectedItemReferences, selectedEntityTypeCode);
            }
            else {
                if (passGridControl) {
                    functionToCall(selectedControl);
                }
                else {
                    functionToCall();
                }
            }
        }
    }
    catch (e) {

    }
}

Result

*After clicking the ‘Delete’ button

image

Note: This method is overwriting the CRM functions and it works for CRM 2013, for CRM 2015/2016, this function [Mscrm.GridCommandActions.deleteRecords] might have been changed, so need to find out the current function name based on your CRM Version. And again, it means it is unsupported Smile

Thanks!

Wednesday 17 August 2016

Insert Label After the Field in CRM Form using Javascript

Hi all,
Long time I did not blog any post due to workload.
And this is my first post in this month (I know it is end of the month already)
This post basically a quick one because I have the requirement.

So, the users just want to have guidance when they entering inputs in the system.

Which we can have these solutions:

1. Tooltip (using CRM Field description)

See my post here:
http://missdynamicscrm.blogspot.sg/2014/05/crm-20112013-power-of-description.html

2. Using HTML Web Resource

This is a supported way and very useful as well. But it will make the form slower because will load the web resource and how many web resource you should create and insert. Well, you can create 1 common .html then pass the label text as the parameter

3. Using other way

So, unfortunately if those 2 solutions still not the best choice, we could go to another way using the help of CSS and Javascript.

*This is unsupported way…

And here is the Code

The Code


function insertLabelToField(fieldname, text, objSpan) {
    var elemDiv = document.getElementById(fieldname + "_c");
    //var tbl = document.createElement('table');     //elemDiv.parentNode.insertBefore(tbl, elemDiv.nextSibling);
    //var tr = tbl.insertRow();     //var td1 = tr.insertCell();     //td1.appendChild(elemDiv);     var td1 = elemDiv;     //td1.style.borderRight = "2px solid #c1c7c4";     td1.style.paddingBottom = "5.5px";
    var spanPref = null;
    if (!objSpan) {         spanPref = document.createElement('span');                 //you can change the color condition here         if (attr(fieldname).getRequiredLevel() == "required") {             spanPref.style.color = '#e31a1a';         }         else {             spanPref.style.color = '#4b4c54';         }         spanPref.style.fontSize = '10.51px';         spanPref.style.fontStyle = 'italic';     }     else {         spanPref = objSpan;     }     spanPref.id = 'spanPref_' + fieldname;         td1.appendChild(spanPref);     document.getElementById('spanPref_' + fieldname).innerText = text; }

How to Call

 
insertLabelToField(“fieldname”, "(Please describe me)");


And here is the Result

image

Hope this helps.

Thanks

Wednesday 10 August 2016

Modify Subgrid Ribbon Behaviour Using Form Script

Introduction

A Quick post, just want to post how to Modify Subgrid button behaviour to always “Create New child record”, instead of “Add Existing record”.

This will use the similar method like described in the previous post before, but it does not call the custom funtion, it just will “redirect” the function to your “Create new record”.

Note: This is an unsupported way, for the supported way you can do two things:

1. Make the parent lookup field name to ‘Required’
2. Modify the Ribbon of the subgrid and Hide the ‘Add Existing’ button
*For your reference – > Nice post by Gareth: https://garethtuckercrm.com/2013/11/19/fixing-the-sub-grid-user-experience-in-crm-2013/

But, those will apply to the subgrid regardless the Condition, probably for the second method, you could apply some Enable/Display Rule on the subgrid ribbon.

But, if you want to do it only for specific entity and based on Parent Form condition (can be based on parent fields or even other entity values), then there is another way to do this that I will put the code in this post.

The Code

Ok, so back to the code.

Here is the code:
//function to insist create new record form instead of Add Existing
function openNewRecordFormFromSubgridButton(subgridName, passGridControl) {
    debugger;
    try {
        //to store the original function ones
        var originalFunctionAddNewStandard = Mscrm.GridRibbonActions.addNewFromSubGridStandard;
        var originalFunctionAddExistingStandard = Mscrm.GridRibbonActions.addExistingFromSubGridStandard;
        var originalFunctionAssociated = Mscrm.GridRibbonActions.addExistingFromSubGridAssociated;

        var primaryControl = document.getElementById('crmForm');
        var parentEntityId = Xrm.Page.data.entity.getId();
        var parentEntityTypeCode = Xrm.Page.data.entity.getEntityName();

        //add existing standard subgrid
        Mscrm.GridRibbonActions.addExistingFromSubGridStandard = function (gridTypeCode, gridControl) {
            if (gridControl.get_id() != subgridName) {
                originalFunctionAddExistingStandard(gridTypeCode, gridControl);
            }
            else {
                var primaryControl = document.getElementById('crmForm');
                originalFunctionAddNewStandard(gridTypeCode, parentEntityTypeCode, parentEntityId, null, gridControl);
            }
        }
  
        //and even possible for the N:N (but this is a rare scenario)
        //add associate subgrid (N:N)
        Mscrm.GridRibbonActions.addExistingFromSubGridAssociated = function (gridTypeCode, gridControl) {
            if (gridControl.get_id() != subgridName) {
                originalFunctionAssociated(gridTypeCode, gridControl);
            }
            else {
                var primaryControl = document.getElementById('crmForm');
                originalFunctionAddNewStandard(gridTypeCode, parentEntityTypeCode, parentEntityId, null, gridControl);
            }
        }
    }
    catch (e) {

    }
}

How to call:

openNewRecordFormFromSubgridButton("mysubgrid_name", true);

Result

*Previous
image
As we can see it opens lookup to look to the Existing record, it is 1:N, the record is almost possible already linked to another record, so it will make the users confused when they open this lookup.
*After
image

*And I never set any Required field to the parent lookup value, because this field is NOT always mandatory field, so it is conditional only.

Note: Use this method if you want to control the subgrid plus button based on some conditions and using the Form on Load script (easier), and it only works for the subgrid inside the form that load the function, other subgrid from another parent entity that does not have this function will not be affected, that is the advantage of using this method, to only affect subgrid of the child based on some parent entities and the function will be placed in the Parent Entity Form OnLoad and this is unsupported way.

“This is for dynamically control the subgrid behaviour through parent form onload functions based on some conditions logic, especially for 1:N relationship that usually already linked to another parent record, so that it is supposed to insist to open new Form, instead of asking the user to choose another existing record.

Hope this helps!

Thanks

Tuesday 31 May 2016

Modify CRM View Query or Filter Criteria on Demand Dynamically using Plugin Retrieve

Hi guys,

Just want to share how we can modify the View Filter Criteria/Condition dynamically.

Introduction

Recently I have requirement that I need to create a View in CRM that there is no way to construct it from Advanced Find and make it dynamic.

For example,
I need to have query that:
1. The Date parameter is must be changed accodingly based on the today date and
2. Another parameter must get the value from a Configuration Entity
Ok, for number 1, we can force the users to always go to the View and change the date accordingly, but how about number 2?

So, my use case:

Show me the list of Customers that due date is in the next 21 days?
Meanwhile, 21 here is must be dynamically obtained from another Configuration entity.

So, this is nearly impossible, because even we use Advanced Find there is no way to use variable as parameter in the filter criteria that we need to query from another entity that is NOT related at all!
As we know that Advanced Find Query, only has limited operator and also it cannot Query from another entity, only limited to the related entities.

Use Case

So, to go to the code, I need to tell the scenario first, and to make it simpler, I just use this use Case:
I want to get all Active Customers that birthday is this Month.

This month is May for example.
So, I have a custom field = Birthday Month

Which I have auto-populated this field before with the Date Birth component (in another plugin).
So, now I need to always update the View to always Query to the:

Status = Active and Birthday Month = 5

And for the Next Month (June) should be:
Status = Active and Birthday Month = 6

Which last Month for April
Status = Active and Birthday Month = 4

Those number 5, 6, and 4 are supposed to be generated dynamically.

And I can’t use the This-Month operator because I dont have May 2016 Data, since I save the DOB, not updating every customer BOD every year plus 1 year.

You can also add another requirement, such as must be a Member Customer with Annual Income more than X, which X is you taken from Configuration entity.

What you need is just a new fetch XML or Query Expression that you can just convert use this request!


QueryExpressionToFetchXmlRequest req = new QueryExpressionToFetchXmlRequest();
req.Query = qenew;
QueryExpressionToFetchXmlResponse resp = (QueryExpressionToFetchXmlResponse)service.Execute(req);

But, for this use case, my point is jut to point out to you how to modify the Query just as per you wish (sorry as per Users’ whish Smile)

The Current View

image

So, for the post here, I just need to replace the Query from the Existing View (in your case, you might need to create a new View)

image

Then I got the savedqueryid from this view

The Code

public void Execute(IServiceProvider serviceProvider)

{
            #region must have


            IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));


            IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));


            // Create service with context of current user
            IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);


            //create tracing service
            ITracingService tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));


            #endregion


            if (context.OutputParameters.Contains("BusinessEntity"))
            {
                var retrievedResult = (Entity)context.OutputParameters["BusinessEntity"];
                Entity entity = retrievedResult;
                string fetch = string.Empty;


                //the below GUID, you can do advance query to make this as non-hardcoded one, you can find by name as well, for example
                //just for my use case here, I post here as final GUID of the 'Active Customer' view GUID
                //in ACTUAL case, do not use harcoded one because it will change in different environment unless for Out of The Box Entities View
                if ((Guid)entity["savedqueryid"] == new Guid("00000000-0000-0000-00AA-000010001004")) 
                {
                    QueryExpression qenew = new QueryExpression("contact");


                    ConditionExpression activeCon = new ConditionExpression()
                    {
                        AttributeName = "statecode",
                        Operator = ConditionOperator.Equal,
                        Values = { 1 }
                    };


                    int currentMonth = DateTime.Now.Month; //this is the variable that dynamically you need to insert in


                    ConditionExpression birthdayCon = new ConditionExpression()
                    {
                        AttributeName = "ags_birthdaymonth",
                        Operator = ConditionOperator.Equal,
                        Values = { currentMonth }
                    };


                    qenew.Criteria.Conditions.Add(activeCon);
                    qenew.Criteria.Conditions.Add(birthdayCon);


                    //for easy fetch XML I use the Query Expression then convert to FetchXML
                    //but you can always use the directly generated fetchXML and also can get from Advanced Find as well
                    QueryExpressionToFetchXmlRequest req = new QueryExpressionToFetchXmlRequest();
                    req.Query = qenew;
                    QueryExpressionToFetchXmlResponse resp = (QueryExpressionToFetchXmlResponse)service.Execute(req);


                    //work with newly formed fetch string
                    string myfetch = resp.FetchXml;


                    //change the existing
                    entity["name"] = "Active Customer Birthday this Month"; 
                    entity["fetchxml"] = myfetch;
                }
            }
}



Code Explanation

*For the GUID of savedqueryid section, I got the ID and I implement in my code

image

*In your case, you need to do query, do not hardcode, please see my comment in the code as well.
For the Query Expresison, this is the place for you to change the logic as per your current requirement


image

Then for the Fetch XML

image

As the title mentioned, Then you need to register as POST event for RETRIEVE message, entity = savedquery.
Refresh the Advanced Find or View to see the result.

Result

As we can see that now, the Active Customer View (The View that we just now changed the fetchXML dynamically), now has this Query

image


You can also try to tweak your code, by just add additional conditional over the existing view by retrieving the fetch xml

string baseFetchXML = enSavedQuery["fetchxml"].ToString();

Then use this request to convert to Query Expression easily

FetchXmlToQueryExpressionResponse

Example of what I Did:



Remember, you can also add another Parameters.

So use this method for Querying:
1. Query that different from the static query from the View
2. Query that requires dynamic Date/Datetime variables
3. Query that needs condition obtained from another non-related entity
4. Query with variable from another Entity, let’s say Configuration Entity
5. Another Impossible Query using standard static Advanced Find
6. Also can do query that involving Current User, Security Role, or Team Query
7. Query requires Operator like “Does-Not-Equal Today” then you can utilize the “ON-OR-BEFORE” or “ON-ON-AFTER”
8. Query for “NOT IN”, then this one you can utilize the “DOES NOT EQUAL” function

But remember, you still need to use  FetchXML in the End.

Hope this helps!
Thanks

Friday 13 May 2016

Filter N to N (Many to Many) Subgrid Lookup for Existing Records in CRM Without Modifying the Ribbon Action

Introduction and Post References

Basically, in this post, I just want to show how to filter the N to N lookup in the subgrid in CRM Form, without even modifying the Global Subgrid Ribbon in CRM.

image

In my previous post: http://missdynamicscrm.blogspot.sg/2015/07/filter-nn-subgrid-in-crm-2013-using.html, I have explained about a way how to filter the N to N Subgrid Lookup, but that is needed to modify the ribbon action globally and you might need to set another condition to actually skip is you need to filter only on specific entity, but your grid will be appearing in many entities, such as:

You want to filter the Contact lookup in, if you modify the subgrid ribbon, in every contact subgrid, the filter will be applied and you might need extra code of criteria to actually limit the  execution.
Good thing about that if your entity only used for single entity and you don’t need to specify is that only for subgrid or associated view, then modifying the ribbon is a better solution. however, if you want to filter only subgrid in specific entity or form, you can actually using another way.

Like you can refer to my posts:


So, in this post, basically I try to combine the ways to become one solution, to filter the N to N lookup that only applicable once the user load the form using single javascript.  This is unsupported customization, but, remember, modifying the N to N view also needs unsupported customization, so yeah, to reach the requirement we have to go further for unsupported customization eventhough strongly I also not recommend.

The Code

So, I just try to combine the code becoming:

*For CRM 2013

function modifyRibbon(subgridName) {
    try {
        //to store the original function ones
        var originalFunctionAddNewStandard = Mscrm.GridRibbonActions.addNewFromSubGridStandard;
        var originalFunctionAddExistingStandard = Mscrm.GridRibbonActions.addExistingFromSubGridStandard;
        var originalFunctionAssociated = Mscrm.GridRibbonActions.addExistingFromSubGridAssociated;
        //add new standard subgrid
        Mscrm.GridRibbonActions.addNewFromSubGridStandard = function (gridTypeCode, parentEntityTypeCode, parentEntityId, primaryControl, gridControl) {
            if (gridControl != null) {
                if (gridControl.get_id() != subgridName) {
                    originalFunctionAddNewStandard(gridTypeCode, parentEntityTypeCode, parentEntityId, primaryControl, gridControl);
                }
                else {
                    originalFunctionAddNewStandard(gridTypeCode, gridControl);
                    filterTrainerProfile(gridTypeCode, gridControl);
                }
            }
        }

        //add existing standard subgrid
        Mscrm.GridRibbonActions.addExistingFromSubGridStandard = function (gridTypeCode, gridControl) {
            if (gridControl != null) {
                if (gridControl.get_id() != subgridName) {
                    originalFunctionAddExistingStandard(gridTypeCode, gridControl);
                }
                else {
                    originalFunctionAddExistingStandard(gridTypeCode, gridControl);
                    filterTrainerProfile(gridTypeCode, gridControl);
                }
            }
        }
        //add associate subgrid (N:N)
        Mscrm.GridRibbonActions.addExistingFromSubGridAssociated = function (gridTypeCode, gridControl) {
            if (gridControl != null) {
                if (gridControl.get_id() != subgridName) {
                    originalFunctionAssociated(gridTypeCode, gridControl);
                }
                else {
                    originalFunctionAssociated(gridTypeCode, gridControl);
                    filterTrainerProfile(gridTypeCode, gridControl);
                }
            }
        }
    }
    catch (ex) {
        alert(ex);
    }
}

*For CRM 2015

function modifyRibbon(subgridName) {
    try {
        //to store the original function ones
         var originalFunctionAddNewStandard = Mscrm.GridCommandActions.addNewFromSubGridStandard;
         var originalFunctionAddExistingStandard = Mscrm.GridCommandActions.addExistingFromSubGridStandard;
         var originalFunctionAssociated = Mscrm.GridCommandActions.addExistingFromSubGridAssociated;
        //add new standard subgrid
        Mscrm.GridRibbonActions.addNewFromSubGridStandard = function (gridTypeCode, parentEntityTypeCode, parentEntityId, primaryControl, gridControl) {
            if (gridControl != null) {
                if (gridControl.get_id() != subgridName) {
                    originalFunctionAddNewStandard(gridTypeCode, parentEntityTypeCode, parentEntityId, primaryControl, gridControl);
                }
                else {
                    originalFunctionAddNewStandard(gridTypeCode, gridControl);
                    filterTrainerProfile(gridTypeCode, gridControl);
                }
            }
        }

        //add existing standard subgrid
        Mscrm.GridRibbonActions.addExistingFromSubGridStandard = function (gridTypeCode, gridControl) {
            if (gridControl != null) {
                if (gridControl.get_id() != subgridName) {
                    originalFunctionAddExistingStandard(gridTypeCode, gridControl);
                }
                else {
                    originalFunctionAddExistingStandard(gridTypeCode, gridControl);
                    filterTrainerProfile(gridTypeCode, gridControl);
                }
            }
        }
        //add associate subgrid (N:N)
        Mscrm.GridRibbonActions.addExistingFromSubGridAssociated = function (gridTypeCode, gridControl) {
            if (gridControl != null) {
                if (gridControl.get_id() != subgridName) {
                    originalFunctionAssociated(gridTypeCode, gridControl);
                }
                else {
                    originalFunctionAssociated(gridTypeCode, gridControl);
                    filterTrainerProfile(gridTypeCode, gridControl);
                }
            }
        }
    }
    catch (ex) {
        alert(ex);
    }
}

* So the filterTrainerProfile(gridTypeCode, gridControl); is your custom function to filter the view, some as you filter and create new custom view that I also have example in my previous post:
http://missdynamicscrm.blogspot.sg/2015/07/filter-nn-subgrid-in-crm-2013-using.html.

Remember, to get the GUID of the list record to show, you can always using CRM Javascript or in CRM 2013 and above you can use smarter way that is to combine with Custom Action, since Custom Action can be called through Javascript.

http://missdynamicscrm.blogspot.sg/search?q=custom+action

Hope this helps! Jiayou!

Thursday 5 May 2016

'Workflow' entity doesn't contain attribute with Name = 'Workflowid' error during Importing Solution

Hi, just a quick one, if you receive this error:

image

When importing solution, please check this following setting:
LookupNameMatchesDuringImport
In your current environment.
By default, it is false.

Change this back to False to resolve the error, but remember, it will be potentially create a duplicate workflow after that, so you better clear them.

The reason of being duplicate is because they are workflow and workflow name is possible not unique, but the ID is.

*To change this setting easily, especially if you are using CRM Online, please use this way:
https://orgdborgsettings.codeplex.com/

image

As you can see, my previous setting was True, which causes the issue
Hope this helps!
Thank you

Monday 2 May 2016

Make a user in CRM ‘Joining More than 1 Business Unit’, enabling Users to See outside their Parent-BU Child Cluster Group records

Hi friends, as we know that Dynamics CRM can support the security access very well, it has the ownership feature, that is based on combination of Business Unit hierarchy and Security Role. But there is always limitation, such as, one User cannot be assigned to more than one Business Unit.

And the security role privileges are so strict, right if you have designed Parent-Child user access level, then the user inside cannot see records owned by other BU outside their parent-child cluster, so, the way is either you assign the Organization level or using Sharing, but the idea using sharing is can make the structure little bit unstructured, not easy to track shared/unshared records in single view unless we create a report or do advanced SQL Query.

I write this blog just because I have a case, that I am pretty sure this could be one of your experience in your CRM-ing journey.

So, here is my scenario that lead me into this idea.

The Scenario

Understand the Business Unit Hierarchy

Imagine, I have this hierarchy

image

Which applicable rule is very common requirement in Sales that each BU cannot do intervention each other, but they can see the records within their BU for collaboration. So, using this CRM Security Role, I can easily fulfill this by either provide the Business Unit Level or Parent Business Unit level privilege.

So, in CRM I have these Business Units

CRM Busines Unit Records

User join BU-1.1 cpy

Now, Understand the Users

In CRM, I would have this list of Users

User join BU-1 cpy

Come to The Another More Complex Rule

Now, I have another requirement:

The Product HQ Team is a stand-alone Business Unit, not a Sales-related, it is purely doing RnD and Product Management, but it supports all Regional Sales, so, once it has Record, example Potential Customer known by one of the Product Manager, or a Product related record that supports Sales, the Regional Sales should be able to View it.

So, I get an example, the User from West, Shawn Owen is a salesperson and Michael Lee is from Product HQ.

Once, Michael created a new Account, Shawn, should be able to view it, because the Organization needs him to follow up, it is not Michael job.


image

What’s Happening

Now, we already implemented a Security Roles that each Users are assigned to the Regional Business Unit with access = Parent-Child Level at max to prevent seeing each other record.

We also have a Regional Manager sitting in the Region area, which is correct. And Product HQ is a separated Business Unit without intervention.

All records:

User join BU-2

We know that when Shawn Owen is online and log in (see the top right logged in username), he will only see his own records and his WEST Business Unit teammate records.

User join BU-4

And when Michael Lee login

User join BU-5

Yes, they cannot see each other, which is correct for first rule, but we have another requirement to let the Regional users to see Product HQ newly created Account.

So, expected, once Shawn Owen is online, he will see other 2 records owned by “Product HQ”.

The Workaround

What we can do without tweaking is by let Product HQ user as owner, sharing the records to the Regional BU Teams or individual Users.

So, I share the record to Shawn and West

User join BU-6

Now, Shawn Owen is online and log in, he will be able to see the record that just now I share

User join BU-7

But, it is troublesome and every record must be shared, or you can do programmatically which is easy, this is one of the workaround as well.

Now I only share 1 record, I need to share another 1 record to make all those two records owned by Product HQ to be viewed by Shawn Owen.

This is not what I want to share in this article, so just get a new idea utilizing the concept of User, Team, and Business Unit.

The Final Solution

We know that we cannot grant Organization access to the Sales users nor can assign Shawn Owen in two different Business Units, so here is the solution.

Shawn Owen wants to see the record owned by Product HQ, as we know that all Business Unit in CRM always has a Default Team, and we have a concept that every user will join that default Team, so if we want to make a user to have “virtually” joining two different Business Units, then we need to make him joining the teams, but we cannot make a Users to manually joining the Default Business Unit Team in CRM.

So, come out an idea that we need to create our Custom Team manually.

User join BU-8

I create the Team and assign the Business Unit to the Product HQ, a Business Unit that I want the user to see the BU owned records.

Then, as part of my experiement, one of the users from Sales, I need to make him joining the team.

User join BU-9

Then, it is not enough, I need to assign the Security Role to the team.

User join BU-10..1jpg

With Security Role detail:

User join BU-12

Note:
And this is very important, you need to assign a Security Role to team with Business Unit access as well, eventhough YOU HAVE ASSIGNED THE PRIVILEGE TO THE USER SECURITY ROLE, it is not enough, if you want to play with Team concept of ownership!!

And now in Shawn perspective

User join BU-13

He now is joining two Team

And, now see the result once he login….

The Result

User join BU-11

As we can see once SHAWN OWEN from WEST login, he can see records owned by his Team (WEST) and also by Product HQ (eventhought Shawn is not part of Product HQ Business Unit).

He is virtually joining the Product HQ Business Unit, so long the records are owned by Product HQ Default Team or Product HQ Custom Team and the Team has Business Unit access, User access is not enough!

Hope this helps.
Thanks

Thursday 21 April 2016

Dynamics CRM Online Multi Language: How to Apply and Change it?

Hi all,

A Quick post about how to change the CRM Online language, we all know that CRM can support multiple languages and for Dynamics CRM OnPremise, we need to install the languages, download then install them, but how about CRM Online?

Sometimes we use CRM Online as Demo as well and we might need something to show to customer that yeah, CRM supports multiple language.

Here we go

Step 1

Go to Settings –> Administration –> Languages

Add language1

Step 2

Manage the languages, you can Add or Remove available Languages by ticking or un-ticking the Checkboxes.

Add language2

*Pretty easy right?

Step 3

Apply and then Wait…It will take some times for enabling the Languages you chose..

language1

Step 4

Now, you can change either System Settings or Personal Options Language
You can set the Language from Personal Options

language2

And OK

Result

Then can see now the difference

language5

language3cpy

language4cpy

is very straight-forward configuration, CRM Online is making our life easier as well.

Hope this helps!
Thanks

Thursday 31 March 2016

Intercept Column/Field Value in CRM View using RetrieveMultiple Plugin C#

In the previous post, I explained about how to modify the lookup field display using Plugin in CRM.

Now, I just want to show you how to modify also other column and I give a use case here is to display the column as Calculated Field.

Well, in CRM 2015 and above Microsoft introduced 2 new types of field: Calculated and Roll-up field, but what if your organization still using earlier version and you already created a field without using that type (do you care to re-create again?) or you want user not to always refresh it.

So, here I want to give you use case in simple scenario, that is to display “Age” field dynamically based on Birthdate of Contact versus current date.

Use Case

Now you have a custom field: Age, as Integer or Whole Number, but all blank.

You have choice: Always update this using batch job that will run everyday or you show as a report dynamically.

So, this is the combination of those choices, you do not need to create report, but you just need to intercept the Plugin to show the data that you want.

So here is before you apply the plugin:

*Age is blank
image

The Code

And here is the code:

 public void Execute(IServiceProvider serviceProvider)
        {
            #region must to have

            IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));

            IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));

            // Create service with context of current user
            IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);

            //create tracing service
            ITracingService tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));

            #endregion

            //this is to modify the Age column

            if (context.OutputParameters.Contains("BusinessEntityCollection"))
            {
                var retrievedResult = (EntityCollection)context.OutputParameters["BusinessEntityCollection"];
                foreach (Entity entity in retrievedResult.Entities)
                {                 
                    if (entity.Contains("birthdate"))
                    {
                        var birthday = entity.GetAttributeValue<DateTime>("birthdate");
                        int age = CalculateAgeYear(birthday);
                        //modify the Age field
                    entity.Attributes["pa_age"] = age;
                    }
                    
                }
            }
        }

You can modify any field so long you put the correct type of object as the value based on the each field type

Result

And here is the result

image

Yes, you can display this in the view.

What if you want to display in the form?
You need to tweak the plugin and use the “Retrieve” message, instead.

Or you can use this as well
http://stackoverflow.com/questions/15048688/dynamics-crm-2011-plugin-retrieve-and-retrieve-multiple

*Thanks Pedro, my friend.

Which Retrieve can also be used for form and also RetrieveMultiple also.

Well, this is not only for one field, is can be for many fields..Including lookup that I explained in my other previous post.

And the good thing also you can display in any view so long the field is exist.

But the disadvantage is you cannot use this as keyword to search or filter, because it is actually not stored in the database.

Example:

Active Contacts View

image

So by using this capability, you can extend this for example to display the combination of 1 to N records!

For example: Total Number of related cases, total dollar of Revenue (from Opportunity), total child Contacts, total number of days of open cases, any aging fields, any rollup fiel as well, and so on..

And you do not even need plugin or workflow to always update this field everytine got any changes in the source for calculated field or any new or removed Associated records changes as well, or creating batch job or recreating your field to be using CRM 2015 rollup and calculated fields..Amazing right..

Hope this helps!
Thanks

Wednesday 30 March 2016

Develop CRM SSRS Report in Local using “Windows Authentication” without really using the Joined Domain Windows Account

Hi all,

Just want to share how to develop SSRS Report that connect to the CRM Database, but using Windows Authentication.

Background

Sometimes we need to develop custom report because report wizard cannot support the complex report and complex UI or filter or grouping, so that is the reason why we still need to rely on this SSRS.

As we know that using FilteredView is the recommended way query CRM Database that using Security Layer applied on the query, but this FilteredView we can only run if we use the valid CRM User, which is not other than using Windows AD Account registered in AD and CRM. Then, we found problem that mostly developers are using their own local Visual Studio, of course we cannot use the server to install VS and let developers to use that imagine we have more than 10 developers trying to access and remote desktop to server? So troublesome.

image

image

And here are the list that what developers usually do to develop report:

1. Access the Server (remote Desktop), installing VS and develop there, using Windows Authentication, so that you can do query using FilteredView as usual

It is a good idea to settle everything, but will make burden on the server and cannot imagine if many developers want to develop and access at the same time, who will kick and be kicked everytime.

2. Other way is we use SQL Authentication using “sa” account to access, but this can be troublesome because as mentioned before, FilteredView, you can only access using CRM AD User. What you need for this is just access to the SQL Server Database.

3. Then, try another way is you restore the database locally to your SQL Server Express probably, then run the query locally, but, again, you cannot use FilteredView in your Query. This also needs you to do regular backup if there is changes in the database schema

image

For point number 2 and 3, you can do that and can run the report so long you have access to the Database, but the bad thing is you cannot run FilteredView and you must using standard View then before you deploy to CRM you must manually adding the “filtered” keyword before your View name and you cannot run the report view the data act on behalf of a CRM User based on the security because you are not CRM user running that time.

Solution

As we know that solution number 1 is good for faster development without manually changing, but might result other weakness in other areas, so we need to combine those 3 items.

Now this is what you need to do:

1. Run the cmd (CTRL + R) then type “cmd”
2. And run this command:
runas /netonly /user:yourdomainname\yourusername "C:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\IDE\devenv.exe"
3. Example:

image

Then it will prompt you to fill in password:

image

Result

I found this solution is quite useful because we can develop and run preview the report locally without remote desktop-ing to server and can use the recommend FilteredView query.
Then here is the result:

*Before:

Keep loading database names

image

And in the end prompting this error message or when you click the “Test Connection” as well:

image

*After

Now we can successfully login and can retrieve the database

image

Hope this helps!