Field Service – Another Booking Rule example

Below is another example of a Booking Rule as well as an interesting find.

This new Booking rule fetches the Characteristics from both the Resource and the Work Order and compares them to see if the Resource is valid to do the job.

First of all it will check if the Resource matches the Characteristics needed for the job and then will it check if their proficiency rating is equal or higher to the Work Order Requirement.

Full Code below:

function newBookingRule(sbContext) {

    var resourceSkills = [];
    var jobSkills = [];

    var ruleResult = {
        IsValid: false,
        Message: '',
        Type: 'error'
    };
    var resourceReqId = sbContext.newValues.ResourceRequirementId.replace(/[{}]/g, "");
    if (resourceReqId != null) {
        var req = new XMLHttpRequest();
        req.open("GET", encodeURI(Xrm.Page.context.getClientUrl() + "/api/data/v8.2/msdyn_requirementcharacteristics?$select=_msdyn_characteristic_value,_msdyn_ratingvalue_value&$expand=msdyn_ratingvalue($select=value)&$filter=_msdyn_resourcerequirement_value eq " + resourceReqId), 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 results = JSON.parse(req.response);
                for (var i = 0; i < results.value.length; i++) {
                    var _msdyn_characteristic_value = results.value[i]["_msdyn_characteristic_value"];
                    var msdyn_ratingvalue_NextLink = results.value[i]["msdyn_ratingvalue"].value;
                    jobSkills.push({
                        skill: _msdyn_characteristic_value,
                        rating: msdyn_ratingvalue_NextLink
                    });
                }
            } else {
                Xrm.Utility.alertDialog(req.statusText);
            }
        }
    }

    var resourceId = sbContext.newValues.ResourceId.replace(/[{}]/g, "");
    if (resourceId != null) {
        var req2 = new XMLHttpRequest();
        req2.open("GET", encodeURI(Xrm.Page.context.getClientUrl() + "/api/data/v8.2/bookableresourcecharacteristics?$select=_characteristic_value,_ratingvalue_value&$expand=RatingValue($select=value)&$filter=_resource_value eq " + resourceId), false);
        req2.setRequestHeader("Accept", "application/json");
        req2.setRequestHeader("Content-Type", "application/json;charset=utf-8");
        req2.setRequestHeader("OData-MaxVersion", "4.0");
        req2.setRequestHeader("OData-Version", "4.0");
        req2.setRequestHeader("Prefer", "odata.include-annotations=OData.Community.Display.V1.FormattedValue");
        req2.send();
        if (req2.readyState === 4) {
            req2.onreadystatechange = null;
            if (req2.status === 200) {
                var results2 = JSON.parse(req2.response);
                for (var j = 0; j < results2.value.length; j++) {
                    var _characteristic_value = results2.value[j]["_characteristic_value"];
                    var ratingValue_NextLink = results2.value[j]["RatingValue"].value;
                    resourceSkills.push({
                        skill: _characteristic_value,
                        rating: ratingValue_NextLink
                    });
                }
            } else {
                Xrm.Utility.alertDialog(req2.statusText);
            }
        }
    }

    check_characteristics(resourceSkills, jobSkills);

    if (jobSkills.length == 0) {
        ruleResult.IsValid = true;
        ruleResult.Message = "Success";
        ruleResult.Type = 'success';
    } else {
        ruleResult.IsValid = false;
        ruleResult.Message = 'Resource is not qualified for this job';
        ruleResult.Type = 'error';
    }
    return ruleResult;
}

function check_characteristics(a, b) {
    for (var i = 0, len = a.length; i < len; i++) {
        for (var j = 0, len2 = b.length; j < len2; j++) {
            if (a[i].skill === b[j].skill && a[i].rating >= b[j].rating) {
                b.splice(j, 1);
                len2 = b.length;
            }
        }
    }
}

An interesting thing that I found out is that you can also trigger the booking alert from the Schedule Assistant. Nothing that Interesting I know! But I found an interesting difference between how the sbContext.newValues.ResourceRequirementId brings back its Id.

When you drag and drop a Work Order on to the Schedule Board it brings back the ResourceRequirementId without curly braces. But if you use the Schedule Assistant it will bring it back with curly braces! Its important to know that this happens as if its not handled appropriately within the JavaScript it will cause an error when trying to Schedule a Work Order.

Schedule Board – (No Curly Braces)

Schedule Assistant – (Curly Braces)

So please make sure that you handle this by removing the braces by using this:

sbContext.newValues.ResourceRequirementId.<strong>replace(/[{}]/g, "");

My previous blog on Booking Rules for reference – https://www.daymandynamics.com/field-service-booking-rule-example/

Hopefully this has been helpful!

3 thoughts on “Field Service – Another Booking Rule example

  1. Hi Thomas,

    I am trying to use your code and the error message box being returned is blank.

    Please can you help!

    Rachel

    1. Hi Rachel, this post is quite old now so it could be out of date. You can send your code over or send over some screenshots to my email address? I will try and see if I can see what the problem is

      1. Hi Thomas,

        Sorry I didn’t see your reply, thank you for coming back to me.

        I have fixed the error message box issue but the problem I have now is that the code is always returning the warning that the resource doesn’t have the skills even when they do.

        This is what I’ve got:

        function newBookingRule4(sbContext) {

        var resourceSkills = [];
        var jobSkills = [];

        var ruleResult = {
        IsValid: false,
        Message: ”,
        Type: ‘warning’
        };
        var resourceReqId = sbContext.newValues.ResourceRequirementId.replace(/[{}]/g, “”);
        if (resourceReqId != null) {
        var req = new XMLHttpRequest();
        req.open(“GET”, encodeURI(Xrm.Page.context.getClientUrl() + “/api/data/v8.2/msdyn_requirementcharacteristics?$select=_msdyn_characteristic_value&;$filter=_msdyn_resourcerequirement_value eq ” + resourceReqId), 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 results = JSON.parse(req.response);
        for (var i = 0; i < results.value.length; i++) {
        var _msdyn_characteristic_value = results.value[i]["_msdyn_characteristic_value"];
        jobSkills.push({
        skill: _msdyn_characteristic_value
        });
        }
        }
        }
        }

        var resourceId = sbContext.newValues.ResourceId.replace(/[{}]/g, "");
        if (resourceId != null) {
        var req2 = new XMLHttpRequest();
        req2.open("GET", encodeURI(Xrm.Page.context.getClientUrl() + "/api/data/v8.2/bookableresourcecharacteristics?$select=_characteristic_value&;$filter=_resource_value eq " + resourceId), false);
        req2.setRequestHeader("Accept", "application/json");
        req2.setRequestHeader("Content-Type", "application/json;charset=utf-8");
        req2.setRequestHeader("OData-MaxVersion", "4.0");
        req2.setRequestHeader("OData-Version", "4.0");
        req2.setRequestHeader("Prefer", "odata.include-annotations=OData.Community.Display.V1.FormattedValue");
        req2.send();
        if (req2.readyState === 4) {
        req2.onreadystatechange = null;
        if (req2.status === 200) {
        var results2 = JSON.parse(req2.response);
        for (var j = 0; j < results2.value.length; j++) {
        var _characteristic_value = results2.value[j]["_characteristic_value"];
        resourceSkills.push({
        skill: _characteristic_value
        });
        }
        }
        }
        }

        function check_characteristics(a, b) {
        for (var i = 0, len = a.length; i < len; i++) {
        for (var j = 0, len2 = b.length; j < len2; j++) {
        if (a[i].skill === b[j].skill) {
        b.splice(j, 1);
        len2 = b.length;
        }
        }
        }
        }

        check_characteristics(resourceSkills, jobSkills);

        if (jobSkills.length == 0) {
        ruleResult.IsValid = true;
        ruleResult.Message = "Success";
        ruleResult.Type = 'success';
        } else {
        ruleResult.IsValid = false;
        ruleResult.Message = ("Resource Alert: Does not have required skills " );
        ruleResult.Type = 'warning';
        }
        return ruleResult;
        }

        Thank you so much.

        Rachel

Leave a Reply to Rachel Cancel reply

Your email address will not be published. Required fields are marked *