using System;
using Microsoft.Xrm.Sdk;
using Vrp.Crm.Base.Handler;
using Vrp.Crm.Base.Tracing;
using Vrp.Crm.Base.Logging;
using dyn.Common.Handler;
using dyn.CaseManagement.Common;
using dyn.Base.Scheduling;
using dyn.CaseManagement.Business.Objects;
using PluginStage = dyn.Base.Handler.PluginStage;
using Microsoft.Crm.Sdk.Messages;
using System.Collections.Generic;
using Microsoft.Xrm.Sdk.Query;
namespace dyn.CaseManagement
{
public class IncidentResolutionsProcessorPluginExecutor : PluginExecutor
{
public IncidentResolutionsProcessorPluginExecutor(IServiceProvider serviceProvider, IPluginExecutionContext pluginExecutionContext, ITracingService crmTracing, string userId, IOrganizationService service, Tracer tracer)
: base(serviceProvider, pluginExecutionContext, crmTracing, userId, service, tracer)
{
}
public override void Execute()
{
try
{
var context = pluginExecutionContext;
tracer.AddInfo(string.Format("{0} - {1}", CaseManagementConstants.Module, context.MessageName));
Entity targetEntity = PluginHelper.GetEntityFromContext(context);
if (context.Stage == PluginStage.PreOperation)
{
if (targetEntity == null)
{
return;
}
CaseHandler caseHandler = new CaseHandler(service, userId, CaseManagementConstants.Module);
Guid incidentId = Guid.Empty;
if (targetEntity.Attributes.Contains(IncidentResolutionEntityAttributeNames.RegardingIncidentIdFieldName))
{
EntityReference entityReference = targetEntity.Attributes[IncidentResolutionEntityAttributeNames.RegardingIncidentIdFieldName] as EntityReference;
if (entityReference != null && entityReference.LogicalName == IncidentEntityAttributeNames.EntityName)
{
incidentId = entityReference.Id;
}
}
if (incidentId != Guid.Empty)
{
Entity caseEntity = new Entity
{
Id = incidentId,
LogicalName = IncidentEntityAttributeNames.EntityName
};
if (caseEntity != null)
{
//get Case Configuration Case resolution Duration 24x7 or business hours
bool isDurationTypeBusinessHours = GetCaseProcessConfigResolutionDuration(caseHandler, caseEntity.Id.ToString());
//Validate
if (ValidateCaseResolution(caseHandler, incidentId.ToString()))
{
string subject = String.Empty;
string description = String.Empty;
//Get resolution Subject
if (
targetEntity.Attributes.Contains(
IncidentResolutionEntityAttributeNames.SubjectFieldName))
{
subject =
targetEntity.Attributes[IncidentResolutionEntityAttributeNames.SubjectFieldName]
.ToString();
}
//Get resolution Description
if (targetEntity.Attributes.Contains(
IncidentResolutionEntityAttributeNames.DescriptionFieldName)
&&
targetEntity.Attributes[IncidentResolutionEntityAttributeNames.DescriptionFieldName] !=
null)
{
description =
targetEntity.Attributes[
IncidentResolutionEntityAttributeNames.DescriptionFieldName].ToString();
}
//Set Resolution Subject on Case Entity
if (
caseEntity.Attributes.Contains(
IncidentEntityAttributeNames.ResolutionSubjectFieldName))
{
caseEntity.Attributes.Remove(IncidentEntityAttributeNames.ResolutionSubjectFieldName);
}
//Subject is mandatory field so no need to check for empty or null
caseEntity.Attributes.Add(IncidentEntityAttributeNames.ResolutionSubjectFieldName,
subject);
//Set Resolution description on Case Entity
if (
caseEntity.Attributes.Contains(
IncidentEntityAttributeNames.ResolutionDescriptionFieldName))
{
caseEntity.Attributes.Remove(
IncidentEntityAttributeNames.ResolutionDescriptionFieldName);
}
caseEntity.Attributes.Add(IncidentEntityAttributeNames.ResolutionDescriptionFieldName,
description);
if (
caseEntity.Attributes.Contains(
IncidentEntityAttributeNames.ActualResolutionTimeFieldName))
{
caseEntity.Attributes.Remove(
IncidentEntityAttributeNames.ActualResolutionTimeFieldName);
}
caseEntity.Attributes.Add(IncidentEntityAttributeNames.ActualResolutionTimeFieldName,
DateTime.Now);
//var resolutionDuration =CrmTypesHelper.GetIntValue(targetEntity, IncidentResolutionEntityAttributeNames.TimeSpentFieldName);
//caseEntity.Attributes.Add(IncidentEntityAttributeNames.ResolutionDurationFieldName, resolutionDuration + " Minutes");
TimeSpan ts;
if (isDurationTypeBusinessHours)
{
ts = CalculateCaseresolutionDuration(caseHandler, incidentId.ToString());
}
else
{
ts = DateTime.UtcNow - GetCaseCreatedOnDateTime(caseHandler, incidentId.ToString());
}
var resolutionDuration = string.Format("{0:%d} days, {0:%h} hours, {0:%m} minutes, {0:%s} seconds", ts);
caseEntity.Attributes.Add(IncidentEntityAttributeNames.ResolutionDurationFieldName, resolutionDuration);
caseHandler.Update(caseEntity);
}
}
}
}
}
catch (Exception e)
{
ExceptionHelper.LogException(e, userId, CaseManagementConstants.Module, "Error occured", service);
tracer.AddError("Error occured in Case Management, Change Category Task Generator Plugin" + e.Message);
throw;
}
}
public DateTime GetCaseCreatedOnDateTime(CaseHandler caseHandler, string incidentId)
{
var caseEntity = caseHandler.GetCaseById(incidentId, new[]
{
IncidentEntityAttributeNames.CreatedOnFieldName
});
var result = CrmTypesHelper.GetDateTimeValue(caseEntity, IncidentEntityAttributeNames.CreatedOnFieldName);
return result;
}
public bool GetCaseProcessConfigResolutionDuration(CaseHandler caseHandler, string incidentId)
{
TimeManager TimeManager = new TimeManager(service, tracer, userId, CaseManagementConstants.Module);
bool result = false;
try
{
var caseEntity = caseHandler.GetCaseById(incidentId, new[]
{
IncidentEntityAttributeNames.ProcessConfigurationIdFieldName,IncidentEntityAttributeNames.OwnerIdFieldName,IncidentEntityAttributeNames.Entitlement
});
var ConfigID = CrmTypesHelper.GetEntityReferenceId(caseEntity, IncidentEntityAttributeNames.ProcessConfigurationIdFieldName);
ProcessTaskConfigurationHandler caseProcessHandler = new ProcessTaskConfigurationHandler(service, userId, CaseManagementConstants.Module);
Entity caseProcessConfig = caseProcessHandler.GetCaseProcessConfigurationById(ConfigID);
if (caseProcessConfig.Attributes.Contains(CaseProcessConfigurationEntityAttributeNames.CaseResolutionDuration))
{
if (((OptionSetValue)caseProcessConfig[CaseProcessConfigurationEntityAttributeNames.CaseResolutionDuration]).Value == 2)
{
result = true; // Business Hours
}
}
}
catch (Exception e)
{
ExceptionHelper.LogException(e, userId, CaseManagementConstants.Module, "Error occured", service);
tracer.AddError("Error occured in Case Management, GetCaseProcessConfigResolutionDuration:" + e.Message);
throw;
}
return result;
}
public TimeSpan CalculateCaseresolutionDuration(CaseHandler caseHandler, string incidentId)
{
TimeSpan BusinessHoursDuration;
var workingDays = new List<DayOfWeek>();
try
{
TimeManager TimeManager = new TimeManager(service, tracer, userId, CaseManagementConstants.Module);
var caseEntity = caseHandler.GetCaseById(incidentId, new[]
{
IncidentEntityAttributeNames.ProcessConfigurationIdFieldName,IncidentEntityAttributeNames.OwnerIdFieldName,IncidentEntityAttributeNames.Entitlement
});
var ConfigID = CrmTypesHelper.GetEntityReferenceId(caseEntity, IncidentEntityAttributeNames.ProcessConfigurationIdFieldName);
var OwnerID = CrmTypesHelper.GetEntityReferenceId(caseEntity, IncidentEntityAttributeNames.OwnerIdFieldName);
Guid OwnerGuid = new Guid(OwnerID);
Guid ConfigGUID = new Guid(ConfigID);
SLAConfigObject slaConfig = new SLAConfigObject(service, userId, CaseManagementConstants.Module, tracer);
SLAObject sla = slaConfig.GetCaseProcessConfigurationSLAbyConfigId(ConfigGUID, Guid.Empty, OwnerGuid);
var entitlementGuid = CrmTypesHelper.GetEntityReferenceId(caseEntity,
IncidentEntityAttributeNames.Entitlement);
Guid calendarGuid = TimeManager.GetCalendarFromSLA(sla.SLAId);
Entity BCalendar = GetCalendar(calendarGuid);
EntityCollection calendarRules = BCalendar.GetAttributeValue<EntityCollection>(CalendarColumnNames.CalendarRules);
var firstRulePattern = calendarRules[0].GetAttributeValue<string>(CalendarColumnNames.Pattern);
Guid innerCalendarId = calendarRules[0].GetAttributeValue<EntityReference>(CalendarColumnNames.InnerCalendarId).Id;
Entity innerCalendar = service.Retrieve(CalendarColumnNames.EntityName, innerCalendarId, new ColumnSet(true));
EntityCollection innnerCalendarRule = innerCalendar.GetAttributeValue<EntityCollection>(CalendarColumnNames.CalendarRules);
var duration = 0;
var offset = 0;
duration = innerCalendar.GetAttributeValue<EntityCollection>(CalendarColumnNames.CalendarRules).Entities[0].GetAttributeValue<int>(CalendarColumnNames.Duration);
offset = innerCalendar.GetAttributeValue<EntityCollection>(CalendarColumnNames.CalendarRules).Entities[0].GetAttributeValue<int>(CalendarColumnNames.Offset);
DateTime CaseCreateTime = GetCaseCreatedOnDateTime(caseHandler, incidentId.ToString());
var ResolutionDate = DateTime.UtcNow;
List<TimeSlot> timeSlots = TimeManager.GetAvailableTimeSlotsForCalendar(calendarGuid, CaseCreateTime, DateTime.UtcNow);
double totalSpanSeconds = 0;
foreach (var slot in timeSlots)
{
double slotSpan = 0;
if (CaseCreateTime > slot.End)
slotSpan = 0;
else if (CaseCreateTime > slot.Start && ResolutionDate < slot.End)
slotSpan = (ResolutionDate - CaseCreateTime).TotalSeconds;
else if (CaseCreateTime > slot.Start && CaseCreateTime < slot.End && ResolutionDate > slot.End)
slotSpan = (slot.End - CaseCreateTime).TotalSeconds;
else if (ResolutionDate > slot.Start && ResolutionDate < slot.End)
slotSpan = (ResolutionDate - slot.Start).TotalSeconds;
else
slotSpan = (slot.End - slot.Start).TotalSeconds;
totalSpanSeconds += slotSpan;
}
TimeSpan TotalDuration = DateTime.UtcNow - CaseCreateTime;
workingDays = GetPatternDays(firstRulePattern);
List<Day> workingHours = BuildWorkingHoursFromPattern(workingDays, offset, offset + duration);
var timeBetween = new Duration(CaseCreateTime, DateTime.UtcNow);
timeBetween.RemoveNonWorkingMinutes(workingHours);
double responseTime = Math.Round(TimeSpan.FromMinutes(timeBetween.GetTotalMinutes()).TotalHours, 2);
BusinessHoursDuration = new TimeSpan(0, 0, Convert.ToInt32(totalSpanSeconds));
var resolutionDuration = string.Format("{0:%d} days, {0:%h} hours, {0:%m} minutes, {0:%s} seconds", BusinessHoursDuration);
}
catch (Exception e)
{
ExceptionHelper.LogException(e, userId, CaseManagementConstants.Module, "Error occured", service);
tracer.AddError("Error occured in Case Management, CalculateCaseresolutionDuration:" + e.Message);
throw;
}
return BusinessHoursDuration;
}
private Entity GetCalendar(Guid calendarId)
{
ColumnSet cols = new ColumnSet(true);
return service.Retrieve(CalendarColumnNames.EntityName, calendarId, cols);
}
internal static List<DayOfWeek> GetPatternDays(string pattern)
{
var days = new List<DayOfWeek>();
string split = Array.Find(pattern.Split(';'), s => s.Contains("BYDAY"));
int index = split.IndexOf("BYDAY=");
string[] daysStr = split.ToString().Remove(index, 6).Split(',');
if (daysStr.Length > 0)
{
var dayMappings = BuildDayMapping();
foreach (var day in daysStr)
{
if (dayMappings.ContainsKey(day))
days.Add(dayMappings[day]);
}
}
return days.Count > 0 ? days : null;
}
static Dictionary<string, DayOfWeek> BuildDayMapping()
{
Dictionary<string, DayOfWeek> dayMapping = new Dictionary<string, DayOfWeek>(7);
dayMapping.Add("MO", DayOfWeek.Monday);
dayMapping.Add("TU", DayOfWeek.Tuesday);
dayMapping.Add("WE", DayOfWeek.Wednesday);
dayMapping.Add("TH", DayOfWeek.Thursday);
dayMapping.Add("FR", DayOfWeek.Friday);
dayMapping.Add("SA", DayOfWeek.Saturday);
dayMapping.Add("SU", DayOfWeek.Sunday);
return dayMapping;
}
internal static List<Day> BuildWorkingHoursFromPattern(List<DayOfWeek> days, int start, int end)
{
var workingDays = new List<Day>(7);
var weekDays = BuildWeekDays();
foreach (var day in days)
workingDays.Add(new Day(day, start, end));
foreach (var weekDay in weekDays)
if (!days.Contains(weekDay))
// Add an empty Day with no minutes.
workingDays.Add(new Day(weekDay));
return workingDays;
}
static List<DayOfWeek> BuildWeekDays()
{
return new List<DayOfWeek>() {
DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday,
DayOfWeek.Thursday, DayOfWeek.Friday, DayOfWeek.Saturday, DayOfWeek.Sunday };
}
public bool ValidateCaseResolution(CaseHandler caseHandler, string incidentId)
{
bool result = false;
if (caseHandler != null)
{
var caseEntityCheck = caseHandler.GetCaseById(incidentId, new[]
{
IncidentEntityAttributeNames.SubCaseTypeCodeFieldName,
IncidentEntityAttributeNames.CustomerSatisfactionFieldName,
IncidentEntityAttributeNames.RootCauseFieldName,
IncidentEntityAttributeNames.RootCauseDescriptionFieldName,
IncidentEntityAttributeNames.InFavorOfFieldName,
IncidentEntityAttributeNames.ResolutionSubjectFieldName
});
var caseSubType = CrmTypesHelper.GetOptionSetValue(caseEntityCheck,
IncidentEntityAttributeNames.SubCaseTypeCodeFieldName);
if (caseSubType != 1) // Not equal to Customer Information Update
{
var resolutionSubject = CrmTypesHelper.GetStringValue(caseEntityCheck,
IncidentEntityAttributeNames.ResolutionSubjectFieldName);
if (resolutionSubject == CaseManagementConstants.SubjectAutoResolved)
return true;
var errMsg = "";
var satisfaction = CrmTypesHelper.GetOptionSetValue(caseEntityCheck,
IncidentEntityAttributeNames.CustomerSatisfactionFieldName);
if (satisfaction == 0)
errMsg = errMsg + @"Please select customer Satisfaction. \n";
var rootCause = CrmTypesHelper.GetEntityReferenceName(caseEntityCheck,
IncidentEntityAttributeNames.RootCauseFieldName);
if (string.IsNullOrWhiteSpace(rootCause))
errMsg = errMsg + @"Please select Root Cause. \n";
var rootCauseDesc = CrmTypesHelper.GetStringValue(caseEntityCheck,
IncidentEntityAttributeNames.RootCauseDescriptionFieldName);
if (string.IsNullOrWhiteSpace(rootCauseDesc))
errMsg = errMsg + @"Please select Root Cause Description. \n";
var inFavorOf = CrmTypesHelper.GetOptionSetValue(caseEntityCheck,
IncidentEntityAttributeNames.InFavorOfFieldName);
if (inFavorOf == 0)
errMsg = errMsg + @"Please select In Favor Of. \n";
if (!string.IsNullOrWhiteSpace(errMsg))
throw new InvalidPluginExecutionException(errMsg);
else
{
result = true;
}
}
}
return result;
}
}
//Murthy BEGIN - CALCULATE DUration
sealed class CalendarColumnNames
{
public const string EntityName = "calendar";
public const string HolidayScheduleCalendar = "holidayschedulecalendarid";
public const string CalendarRules = "calendarrules";
public const string CalendarId = "calendarid";
public const string InnerCalendarId = "innercalendarid";
public const string Pattern = "pattern";
public const string Duration = "duration";
public const string Offset = "offset";
//
}
public sealed class Minute
{
public int Index;
/// <summary>
/// Default constructor for <see cref="Minute"/>.
/// </summary>
/// <param name="index">The minute's offset from midnight in minutes
/// e.g. a <see cref="Index"/> of 60 would represent the minute at 01:00am for a <see cref="Day"/>.</param>
public Minute(int index)
{
Index = index;
}
}
public sealed class Day
{
public DayOfWeek DayOfWeek;
public int DayOfMonth;
public int Month;
public int Year;
public List<Minute> Minutes;
public TimeSpan WorkingHours;
public Day(DayOfWeek dayOfWeek)
{
DayOfWeek = dayOfWeek;
Minutes = new List<Minute>();
}
public Day(DayOfWeek dayOfWeek, int start = 0, int end = 1440)
{
DayOfWeek = dayOfWeek;
SetMinutes(start, end);
}
public Day(DateTime dateTime, int start = 0, int end = 1440)
{
DayOfWeek = dateTime.DayOfWeek;
DayOfMonth = dateTime.Day;
Month = dateTime.Month;
Year = dateTime.Year;
SetMinutes(start, end);
}
public Day(DayOfWeek dayOfWeek, int day, int month, int year)
{
DayOfWeek = dayOfWeek;
DayOfMonth = day;
Month = month;
Year = year;
SetMinutes();
}
/// <summary>
/// Initialises the minutes in a day, either using an offset for start and end
/// or setting all 1440 minutes (default).
/// </summary>
/// <param name="start">Offset from midnight in minutes.</param>
/// <param name="end">Offset from midnight in minutes.</param>
void SetMinutes(int start = 0, int end = 1440)
{
var minutes = new List<Minute>(1440);
for (int i = start; i < end; i++)
minutes.Add(new Minute(i));
Minutes = minutes;
}
public bool IsSameDate(DateTime dt)
{
bool isSameDate = false;
DateTime thisDay = new DateTime(this.Year, this.Month, this.DayOfMonth);
if (dt != null)
isSameDate = dt.Date == thisDay.Date;
return isSameDate;
}
}
public sealed class Duration
{
public List<Day> Days;
public DateTime Start;
public DateTime End;
public Duration(List<Day> days)
{
Days = days;
}
public Duration(DateTime start, DateTime end)
{
Start = start;
End = end;
SetDays();
}
/// <summary>
/// Get sum total of all minutes of all days within the <see cref="Duration"/>.
/// </summary>
/// <returns></returns>
public int GetTotalMinutes()
{
int count = 0;
foreach (var day in Days)
count += day.Minutes.Count;
return count;
}
/// <summary>
/// Initialises <see cref="Days"/>.
/// Begins with <see cref="Start"/>, ends with <see cref="End"/> and initialises all days between the two.
/// </summary>
void SetDays()
{
var daysInBetween = (End - Start).Days;
Days = new List<Day>(daysInBetween);
var dayOfWeek = Convert.ToInt16(Start.DayOfWeek);
Days.Add(new Day(Start, Convert.ToInt16(Start.TimeOfDay.TotalMinutes)));
for (int i = 0; i < daysInBetween; i++)
{
var nextDate = Start.AddDays(i + 1);
if (nextDate.Date != End.Date)
Days.Add(new Day(nextDate.DayOfWeek, nextDate.Day, nextDate.Month, nextDate.Year));
}
Days.Add(new Day(End, 0, Convert.ToInt16(End.TimeOfDay.TotalMinutes)));
}
/// <summary>
/// Sets <see cref="Start"/> equal to the smaller value of <see cref="Start"/> and <see cref="End"/>.
/// </summary>
/// <param name="inverted">Outputs true if <see cref="Start"/> and <see cref="End"/> has to be swapped, otherwise false.</param>
//void SetStartDate(out bool inverted)
//{
// inverted = false;
// if (Start > End)
// {
// Helpers.Common.Swap(ref Start, ref End);
// inverted = true;
// }
//}
/// <summary>
/// Removes all minutes from the <see cref="Duration"/> which are not included in <paramref name="businessHours"/> parameter
/// i.e. are not considered 'working' minutes.
/// </summary>
/// <param name="businessHours">Representation of minutes in days which are working minutes e.g. 09:00 - 17:00.</param>
public void RemoveNonWorkingMinutes(List<Day> businessHours)
{
var startMinutes = Start.TimeOfDay.TotalMinutes;
var endMinutes = End.TimeOfDay.TotalMinutes;
if (Start.Date == End.Date && Days.Count == 2)
Days.RemoveAt(1);
foreach (var day in Days)
{
var businessDay = businessHours.Find(x => x.DayOfWeek == day.DayOfWeek);
var minutesToRemove = new List<Minute>();
foreach (var minute in day.Minutes)
{
if (businessDay.Minutes.FindAll(m => m.Index == minute.Index).Count <= 0)
minutesToRemove.Add(minute);
if (day.IsSameDate(Start))
if (minute.Index < startMinutes)
minutesToRemove.Add(minute);
else if (day.IsSameDate(End))
if (minute.Index >= endMinutes)
minutesToRemove.Add(minute);
}
foreach (var minuteToRemove in minutesToRemove)
try
{
day.Minutes.Remove(minuteToRemove);
}
catch { continue; } // Lazy. Expecting IndexOutOfBounds..
}
}
}
//Murthy END - CALCULATE DUration
}