Friday 20 June 2014

Dynamics CRM: How to Add working days by calculating business closure and weekends


Recently I received a requirement to set the Incident Auto Closure date to 'CreatedOn' + 7 working days. To add working days to 'CreatedOn' date, we need to exclude all the weekend and business closures. I came up with the following solution to achieve the required outcome.
Add the following Class to your project to calculate working day and business closures.

using System;
using System.Collections.Generic;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;

namespace HayerCrmPackage.Plugins
{
    public class AddWorkingDaysClass
    {
        /// 
        /// Add working days to a specific date by calculating Business Closure and weekends
        /// 
        public static DateTime AddWorkingAndBusinessClosureDays(DateTime specificDate, int workingDaysToAdd, 
            IOrganizationService service, IPluginExecutionContext context)
        {
            var currentDate = specificDate;
            var businessClosures = GetBusinessClosureCalendarRules(context, service);

            // Calculate the working days by taking out the weekends
            int completeWeeks = workingDaysToAdd / 5;
            DateTime date = specificDate.AddDays(completeWeeks * 7);
            workingDaysToAdd = workingDaysToAdd % 5;
            for (int i = 0; i < workingDaysToAdd; i++)
            {
                date = date.AddDays(1);
                while (!IsWorkingDayOfWeek(date))
                {
                    date = date.AddDays(1);
                }
            }

            // Calculate the working days by taking out Business Closures
            for (var i = currentDate; i <= date; i = i.AddDays(1))
            {
                if (i.DayOfWeek == DayOfWeek.Saturday || i.DayOfWeek == DayOfWeek.Sunday)
                    continue;

                foreach (var closure in businessClosures)
                {
                    var startDate = (DateTime)closure["effectiveintervalstart"];
                    var endDate = (DateTime)closure["effectiveintervalend"];
                    var range = new DateRange(startDate, endDate);

                    if (range.Includes(i))
                    {
                        date = date.AddDays(1);

                        if (date.DayOfWeek == DayOfWeek.Saturday)
                        {
                            date = date.AddDays(2);
                            i = i.AddDays(1);
                        }
                        else if (date.DayOfWeek == DayOfWeek.Sunday)
                        {
                            date = date.AddDays(1);
                        }
                    }
                }
            }
            return date;
        }

        /// 
        /// Get Business Closure Calendar Rules
        /// 
        private static IEnumerable<Entity> GetBusinessClosureCalendarRules(IPluginExecutionContext context,
            IOrganizationService service)
        {
            // Get Organization Business Closure Calendar Id
            var organization = service.Retrieve("organization", context.OrganizationId, new ColumnSet("businessclosurecalendarid"));

            var query = new QueryExpression("calendar")
            {
                ColumnSet = new ColumnSet(true),
                Criteria = new FilterExpression()
            };

            // Add condition to get Get Calander where CalanderId is equal to Organization's businessclosurecalendarid
            query.Criteria.AddCondition(new ConditionExpression("calendarid", ConditionOperator.Equal, organization["businessclosurecalendarid"].ToString()));

            // Get Calendar
            var businessClosureCalendar = service.RetrieveMultiple(query).Entities[0];

            // Return the Calendar rules
            return businessClosureCalendar != null ? businessClosureCalendar.GetAttributeValue<EntityCollection>("calendarrules").Entities : null;
        }

        private static bool IsWorkingDayOfWeek(DateTime date)
        {
            var day = date.DayOfWeek;
            return day != DayOfWeek.Saturday && day != DayOfWeek.Sunday;
        }

        public interface IRange<T>
        {
            T Start { get; }
            T End { get; }
            bool Includes(T value);
        }

        public class DateRange : IRange<DateTime>
        {
            public DateRange(DateTime start, DateTime end)
            {
                Start = start;
                End = end;
            }

            public DateTime Start { get; private set; }
            public DateTime End { get; private set; }

            public bool Includes(DateTime value)
            {
                return (Start <= value) && (value < End);
            }
        }
    }
}



To consume above class in your plugin, use the following code:
using System;
using System.Linq;
using HayerCrmPackage.Plugins.Entities;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Client;
using Microsoft.Xrm.Sdk.Query;

namespace HayerCrmPackage.Plugins
{
    public class PreCaseCreate : Plugin
    {
        public PreCaseCreate()
            : base(typeof(PreCaseCreate))
        {
            base.RegisteredEvents.Add(new Tuple<int, string, string, Action<LocalPluginContext>>(20, "Create", "incident", new Action<LocalPluginContext>(ExecutePreCaseCreate)));            
        }

        protected void ExecutePreCaseCreate(LocalPluginContext localContext)
        {
            if (localContext == null)
            {
                throw new ArgumentNullException("localContext");                
            }

            var pluginContext = localContext.PluginExecutionContext;
            var service = localContext.OrganizationService;
            var context = new OrganizationServiceContext(service);

            var targetEntity = (pluginContext.InputParameters != null && pluginContext.InputParameters.Contains("Target"))
            ? (Entity)pluginContext.InputParameters["Target"]
            : null;

            // Converting Entity to Incident
            var incident = (targetEntity != null && targetEntity.LogicalName == Incident.EntityLogicalName) ? targetEntity.ToEntity<Incident>() : null;
            
            // If you want to update the number of working days to add, change the value of 2nd parameter below
            incident.new_AutoClosureDate = AddWorkingDaysClass.AddWorkingAndBusinessClosureDays(DateTime.Now, 7, service,
                        pluginContext);
        }
    }
}

Happy Coding

P. S. Hayer
(ਪ੍ਰੇਮਜੀਤ ਸਿੰਘ ਹੇਰ)

Ref:
 1. Adding Working Week Days to a Specific Date
 2. Check if Date Time is in Date Range
Please check my other (non-CRM) blog here: Programming Blogs

No comments :

Post a Comment