Dynamics 365 – Adding products issue

This might be the dumbest blog in a while, but I am adding it in case others have problems with the same issue.

Recently I needed to add new products to the product table of Dynamics, and the buttons for adding the products/families were gone.

I of course had to look at the documentation in case there was something obvious I was missing. The documentation only wanted to ensure that I had a license + security role.

All of this was checking out on my side as I had a license and I was an administrator. Still, I was desperate trying to understand such a stupid issue that I made sure I had enough roles:

Eventually, I got some help from friends, and a huge thank you to Vivian Voss for pointing me in the right direction. First I was told to check the “obvious”.

Are you able to configure products via the old UI?

Vivian Voss

Well, turns out I was.

Solution?

Go to the Default Solution of configuration and make sure that “read only” was not on for the product table in the new UI😂😂🤷‍♂️🤷‍♀️🤷

The only thing that I can think of is an update from Microsoft changing this value for some odd reason. The fact that it is read-only in the Unified Client makes no sense, but hey. Not the first time something like that has happened.. hehe.

Just make sure this is OFF, Save changes, and then publish the table. You should be able to configure products again.

Custom Page – Open Custom Page with Ribbon Button

I am using Dynamics 365 as an example, but the process will be the same for Power Apps – Model Driven. Only difference is that Dynamics 365 has the Opportunity table that we are wanting to use.

If you want to learn more about Custom pages I suggest you look at the following posts:
Scott Durrow
Lisa Crosby
MCJ
Microsoft Custom Pages

I have also added the simple button setup to Github, if you just want to give it a go.
👉DOWNLOAD SIMPLE BUTTON HERE👈

Creating an app and adding the button

Start off by adding an app to a solution and giving it a proper name. In my case, I am choosing Sales Win, because I am creating a simple app for everyone to see the functionality. You could of course just add this to your existing app if that is what you want to do.

For the tricky part, you need to open the Command Bar. This is essentially Ribbon Workbench jr😉 In time I would only imagine that most of the Ribbon Workbench would be available here, but we are probably still a few years away from a complete transition here.

Choose the main form for this exercise, and then create a new Command. Command is actually a button. Why it’s called Command I’m not sure as we all are used to add buttons to a ribbon🤷‍♂️

On the right side, you can add an image, and I usually find mine through SVG’s online.
SVG’s for download <- a blog post I wrote about the topic. Add this as a webresource.

Now, we are going to have to create some JavaScript. It’s actually the only way to open a Custom Page. The JavaScript is pretty “simple”, and I will provide you with the copy-paste version.

JavaScript

🛑BEFORE YOU STOP READING BECAUSE OF JAVASCRIPT🛑

I will provide the JavaScript you need. Unfortunately, you will still need to understand how to copy-paste some script for stuff like custom pages to work.

The only parameter you have to change here if you are creating everything from scratch is:
name: “saleswin_saleswin_c0947” <- replace with the name of your custom page.

function CloseWON(formContext) {
    //Get Opportunity GUID and remove {}
    var recordGUID = formContext.data.entity.getId().replace(/[{}]/g, "");
    // Centered Dialog
    var pageInput = {
        pageType: "custom",
        name: "saleswin_saleswin_c0947", //Unique name of Custom page
        entityName: "opportunity",
        recordId: recordGUID,
    };
    var navigationOptions = {
        target: 2,
        position: 1,
        width: {value: 450, unit: "px"},
        height:{value: 550, unit: "px"}
    };
    Xrm.Navigation.navigateTo(pageInput, navigationOptions)
        .then(
            function () {
                // Called when the dialog closes
                formContext.data.refresh();
            }
        ).catch(
            function (error) {
                // Handle error
                alert("CANCEL");
            }
        );
}

This JavaScript opens up a custom page and sends in a GUID parameter (look at PageInput) to use within the page. This is just how Custom Pages work, so don’t try to do any magic here!

Other examples of how to load a custom page:
Microsoft Docs Custom Page

Hide-Show button

It’s important to hide or show the button at the correct times, and that is why you have to add logic. With the following function, the button is only visible when the Opportunity is in edit mode. When creating a new Opportunity you will not see this button.

Self.Selected.State = FormMode.Edit

Add a new page to your solution

A custom page is a lot like a Canvas App, but it’s not exactly the same. Not all functionality is the same as a Canvas App, so you need to get familiar with it first.

The first thing I noticed was the lack of multiple screens.

It seems not impossible to add more screens, but Microsoft has hidden this feature. The reason seems to be related to isolating a Screen to be a specific application. Might make sense, but not for my use case. It does make somewhat sense because a Custom Page can be opened from everywhere within Model Driven Apps. It’s actually not tied to anything (entity) at all. If you need to turn it on: 👇

Please don’t judge this Canvas App ATM. It will only get better over time 😂

I added labels and text boxes without any logic to them yet and added a data source for Opportunities. This is all for the next step of our configuration of the Sales Win dialog.

Last step (maybe the most important one)

We now need to add the custom page to the APP.

Make sure you UNCHECK the “Show in navigation”. Otherwise, you will see this page as a navigation option on the left side like account, contact, oppty etc.

Finally, publish in the ribbon editor

What have we achieved so far?

Once the button is pushed, the Custom Page loads. 🙏👍🎉

Dynamics 365 App for Outlook button

Tiny blog for a tiny button 😂 This is only relevant if you have users that work in Outlook Web. Every now and then I do encounter a few Apple users that prefer the Outlook Web, even though it works well with Outlook for Mac.

Outlook client

If you use the Outlook client you know the button from the ribbon. Click the button do load the client.

Outlook Web.

OOTB the Dynamics client is hidden once it is deployed for the user. Only way to find it is to open the actual email and choose the ellipsis

Great thing is that we can change the order of the buttons:)

Solution

Open the Outlook Web settings and choose “View All Outlook Settings”.

Find the Dynamics 365 button in the Customize Actions and click save.

You now have the button easily accessible 🤗

New Email Signature in preview. Is it really an upgrade? 🤷‍♂️

In the preview for release wave 2020 2 Microsoft is moving the signature to UI. While doing this, they have the possibility to brush up on some old tech. Almost every piece of the old Dynamics moved over has had been given at least a small upgrade, of not a total overhaul. If you want to read about my take on signature in Dynamics, head on over here.

Limitations of the old signature:
1. Personal signature only. Could not define a signature for the organization or group.
2. No images could be added

So what has changed?

Old

The old signature did not have a rich editor, and limited us to primitive text editing. Exactly like the old email editor.

If you tried to enter a picture, you would get an error message. For this example, I just wrote the text “no picture possible”.

The signature would be added to the email as expected if it was defined as default signature, but again with little formatting.

New

The new signature is available in the new UI

I added a picture of the blue color, just to demonstrate that there can be a picture in the signature.

Opening a new email will automatically add the signature as expected, but now with the picture included. 👍

Well?

Does the signature support images?
YES, it finally does.

It is stored as a Base64, so no need to consider where the actual image is stored (notes, azure, blob etc etc). Migration from system A – B would work fine.

Can we use the signature multiple users?
Well… Yes, but No.. It seems every signature is meant to be personal, but you can search for other signatures if you want to. But in regards to automatic signature added to email, you need to create one for each user.

Thoughts 🤷‍♂️

As of now, I could only find the signature in the Customer Service Hub, and that confuses me a bit. Why not start off by adding it to the Sales Hub. If the signatures are personal, why not let the sales people use this first. In customer service I would have thought that a organization signature would make more sense.

While this is a great update to the last signature experience, I hope they are able to add global signatures in the future for groups of users. This would make the administrative experience a little better when you have hundreds of users.

If you read my article on signatures, I provide a different way of handling the signature experience. I try to centralize the management of the signature for faster changes. The good thing is that we have both solutions available, so pick and choose what suits your need.

Maybe in the next version we might see some structural changes to the way signatures are connected to users/groups.

Dear MS Could you please just fix the following? Pretty Please🙏🥰

I love the new UI and the new timeline, but there were some choices made that never made sense. Specifically the navigation elements on the timeline.

Ideas – Please vote

https://experience.dynamics.com/ideas/idea/?ideaid=187baf8c-5fdd-ea11-bf21-0003ff68e671#

This is a OOTB case with a timeline. I want to open one of the emails to view content or reply. Either way I want to OPEN the activity.

Let’s click on the Email and see what happens…

NOPE… Only the preview of the email. Great functionality, but still not what I want.

What about the BIG Purple circle? That is the same logic as subgrids. This must be it?

NOPE… This opens the owner of the activity…. Why would anyone want this function? Seriously asking, because I have never understood why.

There it is!!!.. The tiny little OPEN button….

Subrids do it better

Check it out.. I click on the subgrid item, and magically it opens the actual record I am trying to see.

BOOM

This is more like it..

Dear MS… Please make the navigation look and feel the same as subgrids 🙂

Dynamics Customer Service form – Simple yet functional

This post is a part of a series of posts for Customer Service. The complete GUIDE can be found here

Before we complete it all I will clean the form a bit. The demo I will be focusing on is the B2B space. If you use the B2C part of support, you might not need any of this. Just continue to the next post:)

Before

It looks good out of the box, but it doesn’t provide a great amount of value.

  1. Business Process Flow on the top of this case is close to useless. It only indicates that you can have a process that looks nice. The steps don’t make sense, and as always with BPF…. You can’t create a step without a mandatory field present. 😒
  2. The first quick view shows a view of the company information. It takes up a lot of space, and in my opinion displays relatively irrelevant information about the customer. If you want quick info about customer, you can “hover over” the customer field.
  3. The right side is setup with reference panels. I wrote an article about loving them a while back, but Customers seem to prefer normal subgrids when actually navigating. Recent Cases is actually just a quick view, and that doesn’t do us much good. Try opening one of the cases, and you won’t see much. We will have to clean this up for it to actually work. We will also remove entitlements, because it’s too much of a hassle in a simple Customer Service scenario.
  4. I hide the tabs on top for now, because I simply don’t need them. If at one point you want to expand, you can reintroduce them.
  5. Subjects.. Are they staying or going. I am not sure what the future holds for this. It’s quite confusing. At some parts of history it seemed very central for Customer Service and Knowledge Articles. Then Categories came along. I would like some clear information what is what. For simplicity I will use subjects for demo. Personally not to happy about it.
  6. The customer field here is as confusing at the customer field was originally for Opportunity. If you are using Customer Service for B2B this has to be linked to an account and not contact. If you use B2C I guess you can just leave it and never really see it as a problem. In my cases I always have to change this to store Account, and then use the Contact field for Contact. Opportunity has managed to change from Customer field to Account & Contact. Don’t see why Case can’t do the same…

Cleanup

1. BPF – Business Process Flow removal

Start off by deactivating the BPF from customization. Then we have to delete all records of the process running. Open Advanced Find, and look for the table that has the same name as the Business Process Flow

PS: If you happen to have a demo setup, it might also include Field Service demo data. Then you have to repeat the process for “Case to Work Order Business Process”.

2. Quick View

Does not give any extra added value. I am replacing this function with a similar value, but different way of working. I open in form editor and remove.

3. Remove Entitlements in Reference Panel

First make sure to remove the Entitlements

Then make sure that the view is set to Recent Cases. Don’t trust that the name of the subgrid is correct. You actually have to make sure that you have the Recent Cases view here.

Create a new section for Recent Cases, and move the Recent Cases out of the Reference Panel. This way we can actually click on a case and open it from the Quick View.

4. Hide tabs

Just hide it. Don’t need it for now

5. Fix Customer field – B2B scenario

In the B2B scenario we need to make a few adjustments to the customer field, and add some onload logic for the agent. In the B2B scenario we need the customer data to be good, and therefore we can’t accept cases where the contact is unknown.

Check this article on how to fix the Customer/Contact fields

6. Subject

Remove unwanted subjects.

Result

Let’s just agree that this doesn’t look very exciting, but it does the job. It does the job quite well!

This is how I want it to look. I have hidden a bunch of components that Microsoft includes as standard Customer Service. Normally it is just a little too much. This will lead to an easy training and a simple customer service view for the agent.

Removing the Business process flow we loose a lot of colors, but this is for actual production. Not a fancy demo to excite someone buying.

Dynamics Customer Service – B2B scenario

This post is a part of a series of posts for Customer Service. The complete GUIDE can be found here

Many years ago Opportunity retired the Customer field and replaced it with Primary Contact and Primary Account fields. Case has not yet done this for reasons I can’t really understand. As you will see in this article we achieve the same result when adding the contact field, but the customer field is still a polymorphic lookup.

Email to Case could easily add to the contact field instead of the customer field. Field service is actually dependent on account being in the customer field for it to work properly.

This is how Case has to be setup to make sense in the B2B world:

If the Contact and Customer are known to the system, they will automatically populate. If the Contact is new to the system, the connection to the Account will be missing. In this case we need to alert the Agent, and ask them to update the contact record.

JavaScript

The following JavaScript is added to the Case form. It checks the Customer field to see if it is contact. If this is the case, it will check if the Contact has a Account connected. Most likely it will not have an Account, so we will be prompted “Do you wish to update the Contact?”. Answer YES here, and a quick view of the Contact will appear. Update Account, and then the JavaScript will do the rest for you. ✨MAGIC✨

var formContext = "";

function OnCrmPageLoad(executionContext) {
    formContext = executionContext.getFormContext();
    
    //
    //You don't need to change this. Just understand that forms have one the following states when opening
    //
    var FormTypes =
    {
        Undefined: 0,
        Create: 1,
        Update: 2,
        ReadOnly: 3,
        Disabled: 4,
        QuickCreate: 5,
        BulkEdit: 6
    }
    runAlways(formContext);
    switch (formContext.ui.getFormType()) {
        case FormTypes.Create: OnNewFormLoad(); break;
        case FormTypes.Update: OnUpdateFormLoad(); break;
        case FormTypes.ReadOnly: OnReadOnlyFormLoad(); break;
        case FormTypes.Disabled: OnDisabledFormLoad(); break;
        case FormTypes.QuickCreate: OnQuickCreateFormLoad(); break;
        case FormTypes.BulkEdit: OnBulkEditFormLoad(); break;
        case FormTypes.Undefined: alert("Error"); break;
    }
}

//
//I only use the RunAlways, OnNewFormLoad and OnUpdateFormLoad, but i keep the others here if i ever would need. 
//When looking at this you can always know what funtion is running. Easy to read and debug. 
//
function runAlways() { }
function OnNewFormLoad() {}
function OnUpdateFormLoad() {
    //
    //Clean up Contact Data. If contact has account, but account not in Customer field perform update. If Contact doen's have account ask for update
    //
    GetAccountInfo();
}
function OnReadOnlyFormLoad() { }
function OnDisabledFormLoad() { }
function OnQuickCreateFormLoad() { }
function OnBulkEditFormLoad() { }


//******************************************************************** */
//CUSTOM FUNCTIONS are added below here. Below this point you add all types of functions you need. 
//******************************************************************** */
function GetAccountInfo() {
    var CustomerField = formContext.getAttribute("customerid").getValue();
    if (CustomerField != null) {
        if (CustomerField[0].entityType == "contact") {
            var CustomerGUID = CustomerField[0].id;
            CustomerGUID = CustomerGUID.replace("{", "");
            CustomerGUID = CustomerGUID.replace("}", "");

            //
            //If the Customer Field contains a contact, I want to change this. I want the Customer Field to be an account. Step 1 is to find out if the contact has account registered.
            //
            Xrm.WebApi.online.retrieveRecord("contact", CustomerGUID, "?$select=_parentcustomerid_value").then(
                function success(result) {
                    var Id = "{" + result["_parentcustomerid_value"] + "}";
                    var Name = result["_parentcustomerid_value@OData.Community.Display.V1.FormattedValue"];
                    var LogicalName = result["_parentcustomerid_value@Microsoft.Dynamics.CRM.lookuplogicalname"];

                    //
                    //IF the contact has an account I move the Contact to Case Contact, and receive the Account from the Contact and enter it to Customer on Case.
                    //
                    if (LogicalName != null){
                        formContext.getAttribute("primarycontactid").setValue(CustomerField);
                        formContext.getAttribute("customerid").setValue([{ id: Id, name: Name, entityType: LogicalName }]);
                        formContext.data.entity.save();
                    }else{
                        //
                        //Promt if you want to open contact for update?
                        //https://carldesouza.com/how-to-implement-javascript-confirmation-dialogs-in-power-apps-and-dynamics-365/ - THANK YOU
                        //
                        var confirmStrings = { text:"Contact is not connected to Account. Please update!", title:"Data Update Recommended", confirmButtonLabel:"Open Contact", cancelButtonLabel: "Not Now" };
                        var confirmOptions = { height: 200, width: 450 };
                        Xrm.Navigation.openConfirmDialog(confirmStrings, confirmOptions).then(
                        function (success) {    
                            if (success.confirmed){
                                //
                                //If the user chooses to update, I open a small contact form, and make the user set the Account. 
                                //After Save&Close i recall this function, and then I update Account and Contact for case. 
                                //
                                Xrm.Navigation.navigateTo({pageType:"entityrecord", entityName:"contact", formType:2, formId:"e4206691-b1e3-4e9d-a23a-4865b9511091", entityId:CustomerGUID}, {target: 2, position: 1, width: {value: 20, unit:"%"},height: {value: 50, unit:"%"}}).then(
                                    function success() {
                                        GetAccountInfo();
                                    },
                                    function error() {
                                        alert("The system was not able to save the change. Please reload the page and try again");
                                    }
                                );
                            }else{
                                //Say or do something if the user doesn't update Contact
                            }
                        });

                    }
                },
                function (error) {
                    Xrm.Utility.alertDialog(error.message);
                }
            );
            
        }
    }

}

Result

Dynamics Email Subject

This post is a part of a series of posts for Customer Service. The complete GUIDE can be found here

Are you as excited as me now that things are coming together?!?!🙌😜 Probably not .. hehe

So the title might not sound exciting, but when you combine the work done in earlier posts with the work we will do in this post, you have got yourself a good setup for Email support system.

JavaScript

So we continue with the JavaScript from the last post, but here we will add a little something from the CASE. If you remember from the Autonumber post we now have a better CASE number that somewhat makes sense. Let’s use JavaScript to retrieve it.

Go all the way down to the last function

var formContext = "";

function OnCrmPageLoad(executionContext) {
    formContext = executionContext.getFormContext();
    
    //
    //You don't need to change this. Just understand that forms have one the following states when opening
    //
    var FormTypes =
    {
        Undefined: 0,
        Create: 1,
        Update: 2,
        ReadOnly: 3,
        Disabled: 4,
        QuickCreate: 5,
        BulkEdit: 6
    }
    runAlways(formContext);

    switch (formContext.ui.getFormType()) {
        case FormTypes.Create: OnNewFormLoad(); break;
        case FormTypes.Update: OnUpdateFormLoad(); break;
        case FormTypes.ReadOnly: OnReadOnlyFormLoad(); break;
        case FormTypes.Disabled: OnDisabledFormLoad(); break;
        case FormTypes.QuickCreate: OnQuickCreateFormLoad(); break;
        case FormTypes.BulkEdit: OnBulkEditFormLoad(); break;
        case FormTypes.Undefined: alert("Error"); break;
    }
}

//
//I only use the RunAlways, OnNewFormLoad and OnUpdateFormLoad, but i keep the others here if i ever would need. 
//When looking at this you can always know what funtion is running. Easy to read and debug. 
//On my OnNewFOrmLoad I am now calling a function "GetDefaultQueueAndSignature"
//
function runAlways() { }
function OnNewFormLoad() {
    //
    //On new form load we call get Signature and Queue
    //
    GetDefaultQueueAndSignature();
    //
    //For regarding we have to check that it contains ID before getting data, and we have to check that it is case
    //
    var RegardingObject = formContext.getAttribute("regardingobjectid").getValue();
    if (RegardingObject != null) {
        if (RegardingObject[0].entityType == "incident") {
            GetCaseIDSetSubject();
        }
    }
}
function OnUpdateFormLoad() {
    //
    //On new UpdateForm load we call get Signature and Queue
    //
    GetDefaultQueueAndSignature();
    //
    //For regarding we have to check that it contains ID before getting data, and we have to check that it is case
    //
    var RegardingObject = formContext.getAttribute("regardingobjectid").getValue();
    if (RegardingObject != null) {
        if (RegardingObject[0].entityType == "incident") {
            GetCaseIDSetSubject();
        }
    }
}
function OnReadOnlyFormLoad() { }
function OnDisabledFormLoad() { }
function OnQuickCreateFormLoad() { }
function OnBulkEditFormLoad() { }


//******************************************************************** */
//CUSTOM FUNCTIONS are added below here. Below this point you add all types of functions you need. 
//******************************************************************** */
function GetDefaultQueueAndSignature() {
    //Get User GUID and replace "{" and "}" with blanks. 
    var UserID = Xrm.Utility.getGlobalContext().userSettings.userId;
    UserID = UserID.replace("{", "");
    UserID = UserID.replace("}", "");

    //Get User Default Queue and Signature via WebApi
    Xrm.WebApi.online.retrieveRecord("systemuser", UserID, "?$select=_queueid_value&$expand=cs_Signature($select=cs_htmlsignature)").then(
        function success(result) {
            var Id = "{" + result["_queueid_value"] + "}";
            var Name = result["_queueid_value@OData.Community.Display.V1.FormattedValue"];
            var LogicalName = result["_queueid_value@Microsoft.Dynamics.CRM.lookuplogicalname"];
            if (result.hasOwnProperty("cs_Signature")) {
                var Signature = result["cs_Signature"]["cs_htmlsignature"];
            }
            if (LogicalName == null || Signature == null) {
                alert("User Record missing Queue and/or Signature");
                return;
            }

            //Set FROM lookup to queue
            formContext.getAttribute("from").setValue([{ id: Id, name: Name, entityType: LogicalName }]);
            //Set signature before current text in body
            var Body = formContext.getAttribute("description").getValue();
            if(Body != null){
                formContext.getAttribute("description").setValue("<br /><br />" + Signature + Body);
            }else{
                formContext.getAttribute("description").setValue("<br /><br />" + Signature);
            }
            
        },
        function (error) {
            Xrm.Utility.alertDialog(error.message);
        }
    );
}

function GetCaseIDSetSubject() {
    //Get Uswer GUID and replace "{" and "}" with blanks. 
    var CaseID = formContext.getAttribute("regardingobjectid").getValue()[0].id;
    CaseID = CaseID.replace("{", "");
    CaseID = CaseID.replace("}", "");

    Xrm.WebApi.online.retrieveRecord("incident", CaseID, "?$select=ticketnumber").then(
        function success(result) {
            var CaseNumber = result["ticketnumber"];
            var Subject = formContext.getAttribute("subject");
            //
            //Check if Subject contains data
            //
            if (Subject.getValue() != null) {
                var SubjectContainsID = Subject.getValue().includes(CaseNumber);
                //
                //IF subject does not contain casenumber, i add the casenumber to the subject
                //
                if (SubjectContainsID == false) {
                    formContext.getAttribute("subject").setValue(Subject.getValue() + " - " + CaseNumber);
                }
            } 
            //
            //This is a new email without a subject. Get the CaseNumber, and inform that topic has to be set
            //
            else {
                formContext.getAttribute("subject").setValue("[Insert Topic] - " + CaseNumber);
            }
        },
        function (error) {
            Xrm.Utility.alertDialog(error.message);
        }
    );

}

We are getting the CASE number from Case / Incident. Then we perform a few checks to see if there is an active subject in the email, and if the email contains the case number from before.

New Email

Let’s see what happens when I now open a new email from a case. Not as a reply to email, but as a new email thread.

Look at that. You have the subject pre populated with a notification that the customer agent should add a topic. This of course could be defaulted to whatever you want in the script, but I left it like this.

Existing Email reply

In this case we see what happens when I reply to an email a customer sent to my CRM system

The CRM Email will look like this. Now I hit reply

Now this is what the subject line should look like.

Conclusion

This small detail means the world of a difference for the people working with customer service and the customers. Whenever they are referring to a case, they can use the same case number. No more tracking token idiocrasy. When you use the global search in dynamics you get hits from the emails and the cases. That is what I call being just a little more efficient!

Please implement this as OOTB Microsoft.

As you can see my posts are now coming together. Next post includes a little extra for quality of data, before I add a video of how to use it.

Dynamics 365 Email – HTML Signature and Sender

This post is a part of a series of posts for Customer Service. The complete GUIDE can be found here

Every time I setup Customer Service I encounter the same 2 problems.
1. I want to send from a QUEUE
2. I need to automatically add a signature

Am I really the only one having these problems, or are we all doing the same silly tasks every time?

Standard Signature OOTB

You can enter a standard signature per user, but this signature does not support HTML formatting for some reason. The email you are writing to is in fact a HTML email. Signature that is injected is text into a HTML, but the signature editor is a simple text editor.

When we open the email, the default signature will appear.

PlussMinus
1. You can use standard MS functions to auto insert signature

2. Users can easily make changes to personal signature.


1. Does not support HTML formatting of signature

2. Every user can configure personal signature, and you can’t control it for company profiling.

3. If you work with customer service you have to 4-5 clicks to find the correct signature for Customer Service to enter.

Standard Sender FROM

Whenever you open an email in Dynamics it will default to your email address. Normally I would say this if fine, but why on earth would you want to do this when setting up a Customer Service system?

A user would have to change the FROM to the Customer service

Imagine having to do this every single time you want to answer an email in Dynamics Customer Service! I personally would go crazy.

Design

I solve this by creating a custom entity called Signature and use a standard field on User “Default Queue” to solve the 2 problems. This involves some javascript, but will provide everything you need. In my last post of this series, I will also include the managed and unmanaged files you need to do it yourself.

From the Email I will now retrieve the HTML signature and default sender for this contact.

1. HTML Signature

I found this little tool that generates an HTML signature. It seems like a cool little piece of free software that will create nice HTML signatures. The HTML you generate, can be copy pasted directly into the signature entity.

https://si.gnatu.re/

After messing about for a while I got the following result.

Example HTML

<table width="351" cellspacing="0" cellpadding="0" border="0"> <tr> <td style="vertical-align: top; text-align:left;color:#000000;font-size:12px;font-family:helvetica, arial;; text-align:left"> <span><span style="margin-right:5px;color:#000000;font-size:15px;font-family:helvetica, arial">Customer Service Team</span> <br><span style="margin-right:5px;color:#000000;font-size:12px;font-family:helvetica, arial">Super Duper Helper</span></span> <br> <span style="font:12px helvetica, arial;">E-mail&nbsp;<a href="mailto:support@support.com" style="color:#3388cc;text-decoration:none;">support@support.com</a></span> <br> <span style="font:12px helvetica, arial;">Telephone&nbsp;<span style="color:#000000">22222222</span></span> <br> <table cellpadding="0" cellpadding="0" border="0"><tr><td style="padding-right:5px"><a href="https://facebook.com/https://www.facebook.com/pointtakenas" style="display: inline-block;"><img width="40" height="40" src="https://s1g.s3.amazonaws.com/d927102059ee8fca87e6e3f49e77e9e9.png" alt="Facebook" style="border:none;"></a></td><td style="padding-right:5px"><a href="https://twitter.com/https://twitter.com/point_taken_" style="display: inline-block;"><img width="40" height="40" src="https://s1g.s3.amazonaws.com/83255843bbcec47d809669aff3b22691.png" alt="Twitter" style="border:none;"></a></td><td style="padding-right:5px"><a href="https://linkedin.com/company/https://www.linkedin.com/company/pointtaken-as" style="display: inline-block;"><img width="40" height="40" src="https://s1g.s3.amazonaws.com/85f05a94630abd611533bc2566751eb7.png" alt="LinkedIn (Company)" style="border:none;"></a></td><td style="padding-right:5px"><a href="https://youtube.com/https://www.youtube.com/channel/UC4rPzEDfHPp393oqOozUqaw" style="display: inline-block;"><img width="40" height="40" src="https://s1g.s3.amazonaws.com/a6f64a587978d338c0b8f6cc2d90bd0b.png" alt="YouTube" style="border:none;"></a></td><td style="padding-right:5px"><a href="https://instagram.com/https://www.instagram.com/pointtakenas/" style="display: inline-block;"><img width="40" height="40" src="https://s1g.s3.amazonaws.com/05f979c6025f3a336303ec824ebb815c.png" alt="Instagram" style="border:none;"></a></td></tr></table><a href="https://www.pointtaken.no" style="text-decoration:none;color:#3388cc;">www.pointtaken.no</a> </td> </tr> </table> 

Next you click on COPY HTML and open the Support Signature record (CUSTOM) on the user entity.

Just copy paste the HTML right inn to the text area. Save and Close

2. Javascript

Now you have to create a JavaScript file on the Email form. In the solution you can download in my last post you will find that I add a form to Email that you can use. I chose not to overwrite the default form.

var formContext = "";

function OnCrmPageLoad(executionContext) {
    formContext = executionContext.getFormContext();
    
    //
    //You don't need to change this. Just understand that forms have one the following states when opening
    //
    var FormTypes =
    {
        Undefined: 0,
        Create: 1,
        Update: 2,
        ReadOnly: 3,
        Disabled: 4,
        QuickCreate: 5,
        BulkEdit: 6
    }
    runAlways(formContext);

    switch (formContext.ui.getFormType()) {
        case FormTypes.Create: OnNewFormLoad(); break;
        case FormTypes.Update: OnUpdateFormLoad(); break;
        case FormTypes.ReadOnly: OnReadOnlyFormLoad(); break;
        case FormTypes.Disabled: OnDisabledFormLoad(); break;
        case FormTypes.QuickCreate: OnQuickCreateFormLoad(); break;
        case FormTypes.BulkEdit: OnBulkEditFormLoad(); break;
        case FormTypes.Undefined: alert("Error"); break;
    }
}

//
//I only use the RunAlways, OnNewFormLoad and OnUpdateFormLoad, but i keep the others here if i ever would need. 
//When looking at this you can always know what funtion is running. Easy to read and debug. 
//On my OnNewFOrmLoad I am now calling a function "GetDefaultQueueAndSignature"
//
function runAlways() { }
function OnNewFormLoad() {
    GetDefaultQueueAndSignature();
}
function OnUpdateFormLoad() {
    GetDefaultQueueAndSignature();
}
function OnReadOnlyFormLoad() { }
function OnDisabledFormLoad() { }
function OnQuickCreateFormLoad() { }
function OnBulkEditFormLoad() { }


//
//CUSTOM FUNCTIONS are added below here. Below this point you add all types of functions you need. 
//
function GetDefaultQueueAndSignature() {
    //Get User GUID and replace "{" and "}" with blanks. 
    var UserID = Xrm.Utility.getGlobalContext().userSettings.userId;
    UserID = UserID.replace("{", "");
    UserID = UserID.replace("}", "");

    //Get User Default Queue and Signature via WebApi
    Xrm.WebApi.online.retrieveRecord("systemuser", UserID, "?$select=_queueid_value&$expand=cs_Signature($select=cs_htmlsignature)").then(
        function success(result) {
            var Id = "{" + result["_queueid_value"] + "}";
            var Name = result["_queueid_value@OData.Community.Display.V1.FormattedValue"];
            var LogicalName = result["_queueid_value@Microsoft.Dynamics.CRM.lookuplogicalname"];
            if (result.hasOwnProperty("cs_Signature")) {
                var Signature = result["cs_Signature"]["cs_htmlsignature"];
            }
            if (LogicalName == null || Signature == null) {
                alert("User Record missing Queue and/or Signature");
                return;
            }

            //Set FROM lookup to queue
            formContext.getAttribute("from").setValue([{ id: Id, name: Name, entityType: LogicalName }]);
            //Set signature before current text in body
            var Body = formContext.getAttribute("description").getValue();
            if(Body != null){
                formContext.getAttribute("description").setValue("<br /><br />" + Signature + Body);
            }else{
                formContext.getAttribute("description").setValue("<br /><br />" + Signature);
            }
            
        },
        function (error) {
            Xrm.Utility.alertDialog(error.message);
        }
    );
}

Save and publish.

The Result is beautiful

We have now saved the service technician many clicks, and the signature looks great. Follow the next post to see how we manage the subject field to “master” the Smart Matching function.

Dynamics 365 Case Cascading Rules

This post is a part of a series of posts for Customer Service. The complete GUIDE can be found here

The OOTB cascading rules for Case are not optimal for the setup that I like to use. I will try to explain why, and at the same time show what I feel needs to be done for it to work better. Keyword is Parental Relationships that we have to STOP. If you wonder what I am talking about, below is the timeline from Case displaying activities.

What happens when you have parental relationship on, and you assign the case to another user, all of the related entities like Task, Emai, Phone Call etc will also be reassigned. Normally you you could ask why that would matter, but for CASE that is a real issue.

You also happen to change the “Modified On” data for the records leaving them potentially unsorted in a manner that will confuse the service agents. This applies to all activities, even the ones that have been closed as complete.

There are MANY other ways to sort activities, but Emails are almost always historical activities and happen at a given time. Tasks and Phone calls can be in the future. This is why I like modified on date, to keep a sort of chronological order. If possible the best sorting would be chronological order for completed activities, and due date sorted on top of that for open activities. Don’t think that is possible unfortunately.

Configuration

As you can see, the default behavior is Parental for Email, Phonecall and Task. I could have chosen more, but these are the entities I use the most in Case.

Do the following for all 3 relationships. Select Configurable Cascading and then Cascade None for the Assign. Microsoft DOCS cascading – If you wan to see more about Cascading Rules.

Why not NEW UI Config?

At the time of writing this blog, the configuration still isn’t possible for relationships. If you are familiar with configuring, the main point is just showing what I need to do for Dynamics.