CRM, Microsoft CRM Dynamics, Microsoft Dynamics 365, Microsoft Dynamics CRM

Querying entity/attribute metadata in Dynamics 365 from browser

We all usually go to a solution or the customization area to see the entity metadata, may it be attributes or properties related to the entity like auditing etc.

There are also tools that a lot of developers use like the Entity metadata browser, which is a solution that you install in your environment and can browse through the metadata in an easier manner.

What I find most productive is to query the metadata from the browser itself by forming the URL specific to the entity I am looking for.

So let’s say I want to see what all attributes/fields which are available on the account entity, here’s the URL that I would use:

https://{Organization base URL}/api/data/v9.1/EntityDefinitions(LogicalName=’account’)?$select=LogicalName&$expand=Attributes($select=LogicalName;)

This is how the output would look like:

You can use any JSON formatter of your choice to better present the data if you want:

Hope it helped :-)!

Standard
CRM, Dynamics 365 Field Services, Microsoft CRM Dynamics, Microsoft Dynamics 365, Microsoft Dynamics 365 Field Service

Creating Booking Rules in Microsoft Dynamics 365 Field Services

Booking Rules are Javascript validations that you can attach to the Booking creation event which is triggered when a booking is created on the schedule board or the schedule assistant.

To create a Booking Rule,  Field Service > Administration, and then choose Booking Rules.

Capture.JPG

What we need to do is attach a JS web resource with the Booking rule. The JS web resource should have a function which takes in the context and is called when a Resource requirement is dragged and dropped on to the Schedule board or the assistant.

Let’s see some code, in this example we are restricting creating of a Booking on the basis of how many bookings are already being created for a Resource.

function validateRules(sbContext) {
debugger;
var ruleResult = {};
var resourceReqId = '';
if (sbContext.newValues.ResourceRequirementId != undefined) {
resourceReqId = sbContext.newValues.ResourceRequirementId;
}
else if (sbContext.newValues.ResourceScheduleSource != undefined && sbContext.newValues.ResourceScheduleSource == 690970004) {//SA
var rr = Xrm.Page.getAttribute('msdyn_resourcerequirement');
if (rr != null && rr.getValue() != null && rr.getValue().length > 0) {
resourceReqId = rr.getValue()[0].id;
}
}
if (resourceReqId == '') {
console.log('RR is null. Exiting..');

ruleResult.IsValid = true;
ruleResult.Message = “No RR”;
ruleResult.Type = ‘success’;
return ruleResult;
}

resourceReqId = resourceReqId.replace(‘{‘, ”).replace(‘}’, ”);
var resourceId = sbContext.newValues.ResourceId;
var startTime = sbContext.newValues.StartTime;
var endTime = sbContext.newValues.EndTime;
var hearingType = ”;

var req = new XMLHttpRequest();
req.open(“GET”, encodeURI(Xrm.Page.context.getClientUrl() + ‘/api/data/v8.2/msdyn_resourcerequirements?$filter=msdyn_resourcerequirementid eq ‘ + resourceReqId + ‘&$expand=new_wa_case($select=wa_hearingtype)’), false);
req.setRequestHeader(“Accept”, “application/json”);
req.setRequestHeader(“Content-Type”, “application/json;charset=utf-8”);
req.setRequestHeader(“OData-MaxVersion”, “4.0”);
req.setRequestHeader(“OData-Version”, “4.0”);
req.setRequestHeader(“Prefer”, “odata.include-annotations=OData.Community.Display.V1.FormattedValue”);
req.send();
if (req.readyState === 4) {
req.onreadystatechange = null;
if (req.status === 200) {
var result = JSON.parse(req.response);
hearingType = result.value[0].new_wa_case[‘wa_hearingtype’];

if (hearingType == null) {
hearingType = 953860001; // Motion as default
console.log(‘motion set as default’);
}
console.log(hearingType);

//————————————————
var dt = new Date(startTime).toISOString();
var st = dt.substr(0, dt.indexOf(‘T’)) + ‘T00:00:00Z’;
var et = dt.substr(0, dt.indexOf(‘T’)) + ‘T23:59:59Z’; //2018-07-17T11:59:59Z
console.log(dt); // today’s date
var req = new XMLHttpRequest();
req.open(“GET”,
encodeURI(Xrm.Page.context.getClientUrl() +
‘/api/data/v8.2/bookableresourcebookings?$select=bookableresourcebookingid,starttime,endtime&$expand=Resource($select=wa_jurytrialhearinglimit,wa_motion)&$filter=_resource_value eq ‘ +
resourceId +
‘ and wa_hearingtype eq ‘ +
hearingType +
‘ and starttime gt (‘ + st + ‘) and endtime lt (‘ + et + ‘)’),
false);
req.setRequestHeader(“Accept”, “application/json”);
req.setRequestHeader(“Content-Type”, “application/json;charset=utf-8”);
req.setRequestHeader(“OData-MaxVersion”, “4.0”);
req.setRequestHeader(“OData-Version”, “4.0”);
req.setRequestHeader(“Prefer”, “odata.include-annotations=OData.Community.Display.V1.FormattedValue”);
req.send();
if (req.readyState === 4) {
req.onreadystatechange = null;
if (req.status === 200) {
var res = JSON.parse(req.response);
var data = res.value;

ruleResult.IsValid = true;
ruleResult.Message = “Validation succeeded”;
ruleResult.Type = ‘success’;

if (data.length > 0 && data[0].Resource != null) { // Court Room
var juryLimit = data[0].Resource.wa_jurytrialhearinglimit;
var motionLimit = data[0].Resource.wa_motion;
console.log(data, data.length, juryLimit, motionLimit);
var isDone = false;
for (var i = 0; i = new Date(data[i].starttime) && startTime = juryLimit) {
ruleResult.IsValid = false;
ruleResult.Message = ‘Jury Hearing capacity reached. Bookings not allowed.’;
ruleResult.Type = ‘error’;
} else if (hearingType == 953860001 && data.length >= motionLimit) {
ruleResult.IsValid = false;
ruleResult.Message = ‘Motion Hearing capacity reached. Bookings not allowed.’;
ruleResult.Type = ‘error’;
}
}
}
} else {
console.log(req.statusText);
}
}
}
else {
console.log(req.statusText);
}
}
return ruleResult;
}

Standard
CRM, Dynamics 365 Field Services, Microsoft Dynamics 365, Microsoft Dynamics 365 Field Service

How to hide Initial public view from schedule board in D365 Field services

As you would know in D365 field services schedule board, we can configure multiple boards and configure different filters or one scenario which is very common is having a board for each Organization unit/Territory.

We have a request from customer that they want to hide the Default Initial Public view from the board. There’s an easy way to configure this in D365.

Go to Advanced find and search for “Schedule board Settings entity”:

Find the record with Tab Name: “Initial public view” and change the Share Type to System instead of Everyone.

This is going to hide the tab from the schedule board.

Capture6.JPG

 

 

Standard
CRM, Dynamics 365 Field Services, Microsoft CRM Dynamics, Microsoft Dynamics CRM

Not able to connect to D365 Tenant from Plugin registration tool

I recently created a new D365 CE Trial instance and was able to work with it.

However, when I tried connecting to it using Plugin registration tool, it failed to connect, giving an Authentication error(401).

Seems like there has been a recent change how Microsoft allows users to connect the CRM org.

Microsoft will support only TSL 1.2 or above going forward. So in case, you try connecting to your newly created org using an older version, you will get authentication issue.

Download the latest SDK tools using from the following link and try connecting again:

https://www.nuget.org/packages/Microsoft.CrmSdk.XrmTooling.PluginRegistrationTool

 

Standard
CRM, Dynamics 365 Field Services, Microsoft CRM Dynamics, Microsoft Dynamics CRM

Making custom entities Geocode enabled in Resco Mobile CRM App for Field Services

If you have worked with Resco Mobile CRM app for Field services, you would know that the Account entity which has Address fields (precisely Longitude and latitude fields) have Action buttons on the Map Section which allows the User to update Geocode or update the Address.

 

In our case, the Customer wanted the same experience for a custom entity. It worked out that there’s an easy way to customize this in Resco app.

 

Here are the prerequisites:

The entity must have floating point number fields whose names end with “latitude” and “longitude.
Also, the precision needs to be set to 5 decimal numbers, Maximum Value to 180.00000, Minimum Value to -180.00000 for the Longitude, and Maximum Value to 90.00000, Minimum Value to -90.00000 for Latitude

Next step is to have a detail tab named exactly “Address” on the edit form.

Finally, to use the GPS fields for showing the record’s position on the map, on the edit form, the user needs to add a Map item and specify GPS fields to be used.

That’s it, you now have the custom entity with Geocode actions enabled on it.

 

Standard
CRM, Microsoft CRM Dynamics, Microsoft Dynamics CRM

How to resolve access issues in D365

Many times you would bump into Users having issues with creating/updating records in D365 due to some permission issue and the system would generate a similar looking error message:

Error message

The error message is not explanatory so that one can figure out the entity for which the permission is missing. What we need to do here is to search for the  ObjectTypeCode online (In my case it’s 1141). Once we do a search and know the corresponding entity, we need to give necessary permissions for User’s security role (In my case I had to give Append and AppendTo permission). Once I gave the permissions, the User was able to create the record.

 

 

Standard
CRM, Microsoft CRM Dynamics, Microsoft Dynamics CRM

Universal scheduling in Dynamics 365 Field Services

The Schedule Board and Resource Scheduling Optimization are the main features of Field Services but they only work with Entities like Work Order and Projects.

If we want scheduling to work with other entities like Opportunities for example, then we can configure Field Services to work with such entities as well (Even custom entities can be configured to work with scheduling).

To configure an Entity to work with Field Service scheduling, Go to Field Services Administration:

Universal1

 

Universal2

In the above screenshot, we are continuing Case entity to work with Field Service Scheduling.

 

Standard
CRM, Microsoft CRM Dynamics, Microsoft Dynamics CRM

Custom Filter on lookup Dynamics 365

The usual method to go about creating a filtered lookup is using FetchXML condition and add “addPreSearch()” method on the field where the filter is required.

The obvious challenge with this approach is maintainability as there’s javascript involved.

In D365 you can now configure filters on a lookup in the following manner:

Go to field properties for the field you want to be filtered and under Related Records Filtering, check Only show records where. Select the filter condition and you are done 🙂

Filtered lookup D365

Standard
CRM, Microsoft CRM Dynamics, Microsoft Dynamics CRM

Activating a read-only record in CRM Dynamics

During dev around Orders and Products in CRM, once Order is submitted the usual practice is to make the record read-only.

As part of Order capturing we were also passing along a date time variable to a 3rd party service. As the service was failing and we wanted to unit test this scenario multiple times, it’d have been easy if we could modify the Order and try out the integration a few times rather than creating a new Order each time which is so unproductive.

The best solution we found is to create an On-Demand workflow, all it does is to change the status of the Order to one of the option sets which makes the record active.

Capture

Now, go to Order entity and run the On-demand to change the status of the record.

 

Standard
C#, CRM, Microsoft CRM Dynamics, Microsoft Dynamics CRM

TCP/IP based Integrations in CRM Dynamics On Prem

We recently had a requirement to Integrate with a System using MML(Man Machine Language) Commands with CRM Dynamics.

Sending MML commands to systems is done using TCP/IP protocol. Let’s first see the Sequence Diagram for the Integration:

sequencediagram

 

Here is the Source code for a Sample Console App we used for a POC, same code can be used from a Windows service or a Desktop application if required with few tweeks:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace EMATestingConsoleApp
{
    class Program
    {
        // Receiving byte array  
        static byte[] bytes = new byte[1024];
        static Socket senderSock;

        static void Main(string[] args)
        {
            Console.WriteLine("Connect to server? Press any key to continue connecting..");
            Console.ReadKey();

            try
            {
                // Create one SocketPermission for socket access restrictions 
                SocketPermission permission = new SocketPermission(
                    NetworkAccess.Connect,    // Connection permission 
                    TransportType.Tcp,        // Defines transport types 
                    "",                       // Gets the IP addresses 
                    SocketPermission.AllPorts // All ports 
                    );

                // Ensures the code to have permission to access a Socket 
                permission.Demand();

                var ipAddr = IPAddress.Parse(System.Configuration.ConfigurationManager.AppSettings["ipaddress"]);
                var port = Convert.ToInt32(System.Configuration.ConfigurationManager.AppSettings["port"]);
                // Creates a network endpoint 
                IPEndPoint ipEndPoint = new IPEndPoint(ipAddr, port);

                // Create one Socket object to setup Tcp connection 
                senderSock = new Socket(
                    ipAddr.AddressFamily,// Specifies the addressing scheme 
                    SocketType.Stream,   // The type of socket  
                    ProtocolType.Tcp     // Specifies the protocols  
                    );

                senderSock.NoDelay = false;   // Using the Nagle algorithm 

                // Establishes a connection to a remote host 
                senderSock.Connect(ipEndPoint);
                Console.WriteLine("Socket connected to " + senderSock.RemoteEndPoint.ToString());

                Console.WriteLine("Enter command to be sent to EMA");
                

                try
                {
                    // Sending message 
                    //<Client Quit> is the sign for end of data 
                    string theMessageToSend = Console.ReadLine();
                    byte[] msg = Encoding.Unicode.GetBytes(theMessageToSend + "<Client Quit>");

                    // Sends data to a connected Socket. 
                    int bytesSend = senderSock.Send(msg);

                    ReceiveDataFromServer();

                
                }
                catch (Exception exc) { Console.WriteLine(exc.ToString()); }

                Console.ReadLine();
            }
            catch (Exception exc) { Console.WriteLine(exc.ToString()); }

        }

        private static void ReceiveDataFromServer()
        {
            try
            {
                // Receives data from a bound Socket. 
                int bytesRec = senderSock.Receive(bytes);

                // Converts byte array to string 
                String theMessageToReceive = Encoding.Unicode.GetString(bytes, 0, bytesRec);

                // Continues to read the data till data isn't available 
                while (senderSock.Available > 0)
                {
                    bytesRec = senderSock.Receive(bytes);
                    theMessageToReceive += Encoding.Unicode.GetString(bytes, 0, bytesRec);
                }

                Console.WriteLine("The server reply: " + theMessageToReceive);
                Console.WriteLine("Press any key to disconnect");
                Console.ReadKey();

                // Disables sends and receives on a Socket. 
                senderSock.Shutdown(SocketShutdown.Both);

                //Closes the Socket connection and releases all resources 
                senderSock.Close();
            }
            catch (Exception exc) { Console.WriteLine(exc.ToString()); }
        }

      

    }
}

 

 

 

Standard