You've probably heard of service level agreements before. They are the backbone and workhorse of the service desk industry. They are a way to measure the performance, not only of the helpdesk itself but almost every part of it and done correctly, yields a wealth of information. It's a pity then that there is no inbuilt way to handle this within Dynamics CRM, but I think I have a way that is pretty good all the same and will do the job regardless.
Now, forgive me if my javascript is a little clumsy in places, but this is my first effort at a complicated script, but it does the job nicely.
Right, lets get down to it. This method is not just a javascript routine, but a combination of various methods both native and alien to the CRM. By combining these elements together, you can come up with something that is pretty sophisticated.
Preparation
1) A new attribute called new_slabreachtime. Make this attribute a date/time value and add it to the Cases Form. This attribute will hold the actual SLA Date in a format that the CRM can access from a workflow procedure.
2) A new attribute called new_sladate. Make this attribute nvarchar. This entity will hold the friendly human-readable SLA date.
3) The following code pasted into the OnChange event of the 'prioritycode' attribute on the Cases Form.
1: //determine Priority value
2: var sPriority = document.crmForm.prioritycode.value;
3:
4: //determine status code
5: var sStatusCode = document.crmForm.statuscode.value;
6:
7:
8: //Determine number of hours to add for each Priority level
9:
10: if (sStatusCode == 10) //the Case cannot be closed already
11: {
12: alert("Error : You cannot set a Priority when the case is CLOSED. Please check and try again");
13: crmForm.prioritycode.focus();
14: return false;
15: }
16:
17: else if (sPriority == 1) //Critical cases have 4 hours
18: {
19: var sHour = 4
20: }
21: else if (sPriority == 2) //Next Business Day have 24 hours or plus 1 day
22: {
23: var sHour = 24
24: }
25: else if (sPriority == 3) //Non Urgent cases have 3 business days (24 x 3 = 72 max)
26: {
27: var sHour = 72
28: }
29: else if (sPriority == 4) // To-Do cases have 5 days max (24 x 5 = 120)
30: {
31: var sHour = 120
32: }
33: else if (sPriority == 5) // Scheduled tasks have 30 days max (24 x 30 = 720)
34: {
35: var sHour = 720
36: }
37: else if (sPriority == 0) //SLA Breach (system set)
38: {
39: //dont do anything at all. This is to stop the workflows balking when they update
40: //the priority level programmatically.
41: return false;
42: }
43:
44:
45: //determine SLA Breach Date/Time
46: var sBreachDate = slabreachtime(sHour)
47:
48:
49: //Now determine the hour of the breach time in whole numbers. This is necessary to
50: //decide whether the case is outside of business hours or not.
51:
52: var curHour = sBreachDate.getHours()
53:
54: //We need the Day Name to determine if the SLA Breach Date is either a Friday, Saturday or Sunday
55: var d=sBreachDate;
56: var weekday=new Array(7);
57: weekday[0]="Sunday";
58: weekday[1]="Monday";
59: weekday[2]="Tuesday";
60: weekday[3]="Wednesday";
61: weekday[4]="Thursday";
62: weekday[5]="Friday";
63: weekday[6]="Saturday"
64:
65: var sBreachDayName = weekday[d.getDay()];
66:
67:
68: //Now, we need to make sure that the NewBreachDate does not fall on a Saturday or Sunday
69: //if it does, then move the breach date to the next available working day
70:
71: if (sBreachDayName == "Saturday" || sBreachDayName == "Sunday")
72: {
73: alert("The SLA Breach Date will fall on a " + sBreachDayName + ". Advancing SLA Breach time to the next available working day");
74: if (sBreachDayName == "Saturday")
75: {
76: alert("advancing date...");
77: sBreachDate = recalcDate(48,sBreachDate);
78: //convert nasty UTC date to friendly one
79: crmForm.all.new_slabreachtime.DataValue = sBreachDate;
80: crmForm.new_sladate.value = FriendlyDate(sBreachDate);
81: return false;
82: }
83: else if (sBreachDayName == "Sunday")
84: {
85: alert("advancing date...");
86: sBreachDate = recalcDate(24,sBreachDate);
87: crmForm.all.new_slabreachtime.DataValue = sBreachDate;
88: crmForm.new_sladate.value = FriendlyDate(sBreachDate);
89: return false;
90: }
91:
92: }
93:
94:
95: //Now we need to check the time of the call against the Priority Level.
96: //Since Critical Cases (Priority 1) have a 4 hour SLA, the latest that they can
97: //be logged for completion is 1200 hours. If the logging day is a Friday, we
98: //need to advance the SLA Breach time to Monday but retain the Critical status.
99:
100: if (sPriority == 1)
101: {
102:
103: if (curHour >= 12 && sBreachDayName == "Friday")
104: {
105: //it is too late to set a CRITICAL Priority for that day
106: alert("WARNING : it is too late to complete a Critical case today. Advancing SLA Breach time to the next available working day");
107: sBreachDate = recalcDate(68,sBreachDate);
108: //convert nasty UTC date to friendly one
109: crmForm.all.new_slabreachtime.DataValue = sBreachDate;
110: crmForm.new_sladate.value = FriendlyDate(sBreachDate);
111: return false;
112: }
113: else if (curHour >= 12 && sBreachDayName != "Friday")
114: {
115: alert("WARNING : it is too late to complete a Critical case today. Please reset the Priority level to the Next Working Day");
116: crmForm.prioritycode.focus();
117: return false;
118: }
119: else if (curHour <= 12)
120: {
121: alert("Warning : This case must be completed by close of business today. You have until " + FriendlyDate(sBreachDate) + " to resolve this problem.");
122: crmForm.all.new_slabreachtime.DataValue = sBreachDate;
123: crmForm.new_sladate.value = FriendlyDate(sBreachDate);
124: return false;
125: }
126: //If the sPriority is Escallate then exit proceedure....
127: else if (sPriority == 0)
128: {
129: return false;
130: }
131: }//end if/else
132:
133:
134: //what to do if there are no exceptions
135: alert("Success! You have : " + sHour + " hours to complete this case, You must finish by : " + FriendlyDate(sBreachDate));
136: crmForm.all.new_slabreachtime.DataValue = sBreachDate;
137: crmForm.new_sladate.value = FriendlyDate(sBreachDate);
138: return false;
139:
140:
141: function slabreachtime(targethour){
142: //Function to calculate the breach time of the Case SLA
143: var oneMinute = 60 * 1000 // milliseconds in a minute
144: var oneHour = oneMinute * 60
145: var oneDay = oneHour * 24
146: var oneWeek = oneDay * 7
147:
148: var today = new Date()
149: var dateInMS = today.getTime() + oneHour * targethour
150: var targetDate = new Date(dateInMS)
151:
152: return targetDate;
153:
154: }
155:
156:
157: function recalcDate(targethour,today){
158: //Function to calculate the breach time of the Case SLA
159: var oneMinute = 60 * 1000 // milliseconds in a minute
160: var oneHour = oneMinute * 60
161: var oneDay = oneHour * 24
162: var oneWeek = oneDay * 7
163:
164: var dateInMS = today.getTime() + oneHour * targethour
165: var targetDate = new Date(dateInMS)
166:
167: return targetDate;
168:
169: }
170:
171:
172: //----------------------------------------------------------------->
173: //Functions controlling date/time conversions
174:
175:
176: //Date Time conversion from UTC to Friendly Format
177: function FriendlyDate(today){
178: var Days = new Array('Sunday','Monday','Tuesday','Wednesday',
179: 'Thursday','Friday','Saturday');
180:
181: //var today = sBreachDate;
182: var Year = takeYear(today);
183: var Month = leadingZero(today.getMonth()+1);
184: var DayName = Days[today.getDay()];
185: var Day = leadingZero(today.getDate());
186:
187: var Hours = leadingZero(today.getHours());
188:
189:
190:
191: var Minutes = leadingZero(today.getMinutes());
192: var Seconds = leadingZero(today.getSeconds());
193:
194: //Now assign the cleaned breach date/time to a variable for use
195: var FriendlyBreachDate = Hours + ':' + Minutes + ':' + Seconds + ' '
196: + DayName + ' ' + Day + '-' + Month + '-' + Year
197:
198: return FriendlyBreachDate;
199:
200: }
201:
202: function takeYear(theDate)
203: {
204: x = theDate.getYear();
205: var y = x % 100;
206: y += (y < 38) ? 2000 : 1900;
207: return y;
208: }
209:
210: function leadingZero(nr)
211: {
212: if (nr < 10) nr = "0" + nr;
213: return nr;
214: }
4) 7 new workflows attached to the Cases entity. These will do the real work for you within the CRM. I have named my workflows in the following way according to the values in the 'prioritycode' attribute and each one is specific to that. They are :
- Check Case Priority - self explanatory
- Case Priority ESLA Breach - (Priority = SLA Breach)
- Case Priority 4HR - (Priority 1 = critical, 4 hours)
- Case Priority NBD - (Priority 2 = Next Business Day)
- Case Priority 13D - (Priority 3 = 1 to 3 business days)
- Case Priority 35D = (Priority 4 = 3 to 5 business days)
- Case Priority 30D = (Priority 5 = 30 days plus)
4) The values of the 'prioritycode' attribute should be the following :
1: <select name="prioritycode">
2: <option value="0">Escalate - SLA Breach</option>
3: <option value="1">Critical - 4 hours</option>
4: <option value="2">Next Business Day</option>
5: <option value="3">Non Urgent - 1 to 3 B/Days</option>
6: <option value="4" >To Do - 3 to 5 days</option>
7: <option value="5">Scheduled Task - 30 days</option>
8: </select>
The above code is a HTML representation of what the CRM select box looks like.
Explanations
The idea is to calculate the SLA Breach date and time programmatically using Javascript and then paste the resulting value into the 'new_sladate' and 'new_slabreachtime' attributes on the cases form. Once this value has been established within the CRM, we can get the workflows to reference it and to perform actions based on it.
Dates are easy enough to call from within the code using the Date() function, but if you really want it to do something for you, you need to take into account the resulting problems....
Scenario A : The helpdesk agent raises a case with a priority of 4 hours at 16:30.
Result : Since the helpdesk does not work past 17:00, raising a case with a priority of critical with only 30 minutes left in the day is pretty useless. We need to stop this happening. The program could refuse to log the case since it is outside of working hours, but this defeats the object of the exercise. What we need is for the script to recognise this scenario and 'advance' the SLA to the next available working day but retain the 'Critical' status so that the helpdesk agents are immediately aware of the situation when they next come into work. In reality, a critical case would probably be handled that same day no matter what the time, but lets assume that the helpdesk are clock watching or working to rule.
Scenario B : The helpdesk agent raises a case with a priority of 1-3 days on a Wednesday afternoon. The SLA Breach time would therefore fall on a Saturday when the helpdesk is unavailable.
Result : The SLA is breached on a Saturday when no-one is available. Again, the date needs to be advanced to compensate.
None of these results are exactly what we need, so the code listed above will take care of these possibilities. Once the code has determined the breach date, it will be easy to make our workflows respond to these cues in the proper way.
Special Note on pasting dates into fields
We can paste our resulting date into 'new_slabreachtime' if the attribute were set to nvarchar. This is fine. But the CRM wont recognise it as a date and you wont be able to leverage the workflows using it. We need the new_slabreach time to be a 'date/time' field. This being the case, the date that you paste must be the raw UTC date as opposed to the friendly human converted date we paste into 'new_sladate'. Thus, the syntax to use when pasting into a date/time field would be :
1: crmForm.all.new_slabreachtime.DataValue = sBreachDate;
NOT
1: crmForm.new_slabreachtime.value = FriendlyDate(sBreachDate);
That being the case, the workflow will now recognise the field as a date and you can now set a checkcondition to find out if the value in 'new_slabreachtime' has been passed or not.
Workflows : Explanations
The workflows are an integral part of the SLA breach/check procedure. The first, CheckCasePriority checks the value in the 'prioritycode' attribute and determines which workflow to run. This is keyed to the 'new case' event. You could also make it run depending on whether prioritycode changes, but this would re-set the SLA breach date. Possible, but not what I want at the moment.
The CasePriority(x) workflows consist of instructions for the CRM on what to do for that case once the priority has been set. The first instruction on the workflow should be a 'wait' condition keyed to the 'new_slabreachtime' date/time field we have already discussed. My workflow will alert the service desk when there is one hour to run before breach. You can add various other conditions in here if you like such as 'raise an activity' or synchronous workflow to do something else whilst you are waiting.
The CasePriorityESLABreach workflow is a set of conditions to fulfil should the SLA be breached. In this case, a combination of check and wait conditions will result in the CRM sending warning emails to the service desk at 1,2 and 3 hours post breach.
Final Improvements
I want the service desk to view cases according to priority. This is very simple, just sort the apropriate view according to the 'priority' level.
I want the service desk to be able to view the SLA breach time in the 'view'. This is also very simple. Just add the new_sladate' attribute to the view matrix.
Hope this has been useful....
Recent Comments