"use strict"; (function () { 'use strict'; angular.module('myitsmApp') .service('slaCalculatorService', ['$resource', '$log', '$filter', '$timeout', '$q', function ($resource, $log, $filter, $timeout, $q) { var resource = $resource('/smartit/rest/sla/:id', {}, { getSLAById: { method: 'GET', isArray: true } }); this.getAndCalculateSLA = function (ticket) { var that = this; return resource.getSLAById({ id: ticket.id }).$promise.then(function (result) { var slaJSON = result[0].items; if (_.isUndefined(slaJSON)) { return; } if (_.isEmpty(slaJSON) && ticket.status && ticket.serviceTargets && ticket.serviceTargets.length) { slaJSON = ticket.serviceTargets; } that.processServiceTarget(ticket, slaJSON); }).catch(function () { $log.error('Error getting sla information.'); }); }; this.processServiceTarget = function (ticket, slaJSON) { var that = this, SLA = slaJSON.map(function (sla) { return new SLAVO().build(sla); }), firstSLAItem = _.head(SLA), SLAConditions = [ { run: function () { if (!ticket.SLA) { ticket.SLA = { items: SLA, slaRenderType: 'full' }; } else { ticket.SLA.items = SLA; } that.calculate(ticket); }, condition: firstSLAItem.slaType === 'sla' || _.isEmpty(firstSLAItem.slaType) }, { run: function () { ticket.SLA = { items: SLA, slaRenderType: 'partial' }; that.calculate(ticket); }, condition: firstSLAItem.slaType === 'targetdate' || firstSLAItem.slaType === 'scheduleddate' }, { run: function () { ticket.SLA = { slaTextValid: true, divClassType: 'partial', reachTime: $filter('i18n')('sla.ticket.created.on') + ' ' + convertDateToLocale(firstSLAItem.endTime) }; }, condition: firstSLAItem.slaType === 'createdate' } ]; _.find(SLAConditions, 'condition').run(); }; this.calculate = function (ticket) { /* wait for 50ms to let angular complete dirty checking */ $timeout(function () { //As per discussion SLA story for below ticket types SLA bar will be hidden if ticket is cancelled. //Reason to hide : SLA bar is stopped at modified date and ticket can be modified even after cancel from mid-tier. if ((ticket.type === EntityVO.TYPE_PROBLEM || ticket.type === EntityVO.TYPE_KNOWNERROR) && ticket.status.value === 'Cancelled') { ticket.SLA.slaProgressBarValid = false; } else { ticket.SLA.slaProgressBarValid = true; } }, 50); ticket.SLA.items = _.sortBy(ticket.SLA.items, ['endTime', 'statusColor']); var lastSlaItem = _.last(ticket.SLA.items); var now = moment(), slaStartTime = moment(_.head(ticket.SLA.items).startTime), slaEndTime = moment(_.last(ticket.SLA.items).endTime), closedTime; if (ticket.SLA.slaRenderType === 'full') { _.forEach(ticket.SLA.items, function (item) { if (!closedTime && item.overallStopTime) { closedTime = moment(item.overallStopTime); } else if (closedTime && item.overallStopTime) { closedTime = moment(item.overallStopTime).isAfter(closedTime) ? moment(item.overallStopTime) : closedTime; } }); if (!closedTime) { closedTime = now; } } else { closedTime = ticket.modifiedDate ? moment(ticket.modifiedDate) : now; } //completedDate for WO, resolvedDate for incident and fetching overallStopTimefrom last SLA for change. if (ticket.isResolved() && ticket.type === EntityVO.TYPE_CHANGE) { if (ticket.SLA.slaRenderType !== 'partial' && ticket.SLA.items && ticket.SLA.items.length) { closedTime = moment(_.last(ticket.SLA.items).overallStopTime); } else { closedTime = ticket.actualEndDate ? moment(ticket.actualEndDate) : now; } } var downStartTime = setDownStartTime(ticket), timeFromSlaStart = { tillNow: now.diff(slaStartTime), tillSlaEnd: slaEndTime.diff(slaStartTime), tillClosedDate: closedTime.diff(slaStartTime), tillDownStartTime: downStartTime ? downStartTime.diff(slaStartTime) : '' }, slaCalculator = [ { reachedPercent: Math.round(timeFromSlaStart.tillNow / timeFromSlaStart.tillSlaEnd * 100), condition: (lastSlaItem.slaType !== 'sla' && !ticket.isClosed()), timeToLimit: now.from(slaEndTime, true), isSlaPassed: now.isAfter(slaEndTime), autoRefresh: true }, { reachedPercent: Math.round(timeFromSlaStart.tillNow / timeFromSlaStart.tillSlaEnd * 100), condition: (lastSlaItem.slaType === 'sla') && (!ticket.SLA.allMet && !ticket.SLA.allPaused), timeToLimit: now.from(slaEndTime, true), isSlaPassed: now.isAfter(slaEndTime), autoRefresh: true }, { reachedPercent: Math.round(timeFromSlaStart.tillClosedDate / timeFromSlaStart.tillSlaEnd * 100), condition: (lastSlaItem.slaType === 'sla') && ticket.SLA.allMet, timeToLimit: closedTime.from(slaEndTime, true), isSlaPassed: closedTime.isAfter(slaEndTime), autoRefresh: true }, { reachedPercent: timeFromSlaStart.tillDownStartTime ? Math.round(timeFromSlaStart.tillDownStartTime / timeFromSlaStart.tillSlaEnd * 100) : Math.round(timeFromSlaStart.tillNow / timeFromSlaStart.tillSlaEnd * 100), condition: (lastSlaItem.slaType === 'sla') && ticket.SLA.allPaused, timeToLimit: downStartTime ? downStartTime.from(slaEndTime, true) : now.from(slaEndTime, true), isSlaPassed: downStartTime ? downStartTime.isAfter(slaEndTime) : now.isAfter(slaEndTime), autoRefresh: false }, { reachedPercent: Math.round(timeFromSlaStart.tillClosedDate / timeFromSlaStart.tillSlaEnd * 100), condition: lastSlaItem.slaType !== 'sla' && ticket.isClosed(), timeToLimit: closedTime.from(slaEndTime, true), isSlaPassed: closedTime.isAfter(slaEndTime), autoRefresh: false } ]; var slaFound = _.find(slaCalculator, 'condition'); if (slaFound) { ticket.SLA.reachedPercent = slaFound.reachedPercent; ticket.SLA.timeToLimit = slaFound.timeToLimit; ticket.SLA.isSlaPassed = slaFound.isSlaPassed; ticket.SLA.autoRefresh = slaFound.autoRefresh; ticket.SLA.slaStartTime = slaStartTime; ticket.SLA.slaEndTime = slaEndTime; ticket.SLA.closedTime = closedTime; } processSLATooltip(ticket); processSLAOthersCalendar(ticket); return $q.when(ticket); }; var setDownStartTime = function (ticket) { ticket.SLA.allPaused = false; if (ticket.SLA.slaRenderType === 'partial') { /* no SLM available */ return ''; } else { /* SLM is available */ if (ticket.SLA.items.length === 1) { /* one SLA */ if (ticket.SLA.items[0].measurementStatus === SLAVO.MEASUREMENT_STATUS_PENDING) { ticket.SLA.allPaused = true; } else if ((ticket.SLA.items[0].measurementStatus === SLAVO.MEASUREMENT_STATUS_MET) || (ticket.SLA.items[0].measurementStatus === SLAVO.MEASUREMENT_STATUS_MISSED)) { ticket.SLA.allMet = true; } return ticket.SLA.items[0].downStartTime ? moment(ticket.SLA.items[0].downStartTime) : ''; } else { /* multiple SLA */ var oneOrMorePaused = false, oneOrMoreMet = false, allPaused = true, allMet = true; _.forEach(ticket.SLA.items, function (item) { if (item.measurementStatus === SLAVO.MEASUREMENT_STATUS_PENDING) { oneOrMorePaused = true; allMet = false; } else if ((item.measurementStatus === SLAVO.MEASUREMENT_STATUS_MET) || (item.measurementStatus === SLAVO.MEASUREMENT_STATUS_MISSED)) { oneOrMoreMet = true; } else { allPaused = false; allMet = false; } }); allPaused = allPaused && oneOrMorePaused; //There should be at least one in paused state and others could be in met state. allMet = allMet && oneOrMoreMet; //There should be at least one item in Met / Missed but achieved status ticket.SLA.allMet = allMet; if (allPaused && !oneOrMoreMet) { /* all SLA stopped when ticket is in pending status */ ticket.SLA.allPaused = true; return moment(_.find(ticket.SLA.items, { 'measurementStatus': SLAVO.MEASUREMENT_STATUS_PENDING }).downStartTime); } else if (allPaused) { ticket.SLA.allPaused = true; return moment(_.find(ticket.SLA.items, { 'measurementStatus': SLAVO.MEASUREMENT_STATUS_PENDING }).downStartTime); } else { /* at least one SLA didn't stop */ _.each(ticket.SLA.items, function (item) { if (item.measurementStatus === SLAVO.MEASUREMENT_STATUS_PENDING) { item.iconClass = 'pause_circle_o'; } }); return ''; } } } }; var processSLATooltip = function (ticket) { var now = moment(), denominator; if (ticket.isClosed() && ticket.SLA.slaRenderType !== 'full') { denominator = (ticket.SLA.closedTime).isAfter(ticket.SLA.slaEndTime) ? (ticket.SLA.closedTime).diff(ticket.SLA.slaStartTime) : ticket.SLA.slaEndTime.diff(ticket.SLA.slaStartTime); } else if (ticket.SLA.allPaused) { denominator = ticket.SLA.slaEndTime.diff(ticket.SLA.slaStartTime); } else if (ticket.SLA.allMet) { denominator = (ticket.SLA.closedTime).isAfter(ticket.SLA.slaEndTime) ? (ticket.SLA.closedTime).diff(ticket.SLA.slaStartTime) : ticket.SLA.slaEndTime.diff(ticket.SLA.slaStartTime); } else { //the SLTs will get placed on the bar � spread out so that the final one is assumed to be the end of the bar //if the time passes beyond the last SLA then the �end time� of the bar will be �now� and the bubbles will adjust denominator = now.isAfter(ticket.SLA.slaEndTime) ? now.diff(ticket.SLA.slaStartTime) : ticket.SLA.slaEndTime.diff(ticket.SLA.slaStartTime); } _.each(ticket.SLA.items, function (item) { var messageOne, messageTwo, defaultWarningPercentage = 90, // DRSMX-77574: metMissedAmount added to fix SLA bar issue. slaItemEndTime = (item.metMissedAmount && item.measurementStatus === SLAVO.MEASUREMENT_STATUS_MISSED) ? moment(item.metMissedAmount) : moment(item.endTime), slaItemOverallStopTime = item.overallStopTime ? moment(item.overallStopTime) : ''; item.position = slaItemEndTime.diff(ticket.SLA.slaStartTime) / denominator; item.timeToLimit = item.measurementStatus === SLAVO.MEASUREMENT_STATUS_PENDING ? moment(item.downStartTime).from(slaItemEndTime, true) : now.from(slaItemEndTime, true); if (item.slaType !== 'sla') { if (ticket.isResolved()) { item.slaStatusClass = ticket.SLA.isSlaPassed ? 'sla-icon_color-red' : 'sla-icon_color-green'; } else { item.slaStatusClass = ticket.SLA.isSlaPassed ? 'sla-icon_color-red' : (ticket.SLA.reachedPercent < defaultWarningPercentage ? 'sla-icon_color-green' : 'sla-icon_color-orange'); } } /* set tooltip line 2 */ if (now.isAfter(slaItemEndTime)) { /* original due */ messageOne = $filter('i18n')('sla.due') + ' ' + convertDateToLocale(slaItemEndTime); } else { if (item.endTime === '' || item.endTime === null || item.endTime === undefined) { messageOne = $filter('i18n')('sla.due.date') + ' ' + $filter('i18n')('common.labels.unknown'); } else { messageOne = $filter('i18n')('sla.due.in') + ' ' + item.timeToLimit + ' (' + convertDateToLocale(slaItemEndTime) + ')'; } } /* set tooltip line 3 */ if (ticket.SLA.slaRenderType === 'full') { switch (item.measurementStatus) { case SLAVO.MEASUREMENT_STATUS_PENDING: item.iconClass = 'pause_circle_o'; break; case SLAVO.MEASUREMENT_STATUS_MET: /* achieved early */ if (slaItemOverallStopTime) { messageTwo = $filter('i18n')('sla.status.achieved', slaItemOverallStopTime.from(slaItemEndTime, true)) + ' ' + $filter('i18n')('sla.status.early', convertDateToLocale(slaItemOverallStopTime)); } break; case SLAVO.MEASUREMENT_STATUS_MISSED: /* achieved late */ if (slaItemOverallStopTime) { messageTwo = $filter('i18n')('sla.status.achieved', slaItemOverallStopTime.from(slaItemEndTime, true)) + ' ' + $filter('i18n')('sla.status.late', convertDateToLocale(slaItemOverallStopTime)); } break; } } else { item.tooltip = ''; item.iconClass = 'circle_o'; if (ticket.isPaused()) { messageTwo = $filter('i18n')('sla.now.paused'); } else if (ticket.isResolved()) { if (ticket.SLA.isSlaPassed) { /* achieved late */ messageTwo = $filter('i18n')('sla.status.achieved', ticket.SLA.closedTime.from(slaItemEndTime, true)) + ' ' + $filter('i18n')('sla.status.late', convertDateToLocale(ticket.SLA.closedTime)); item.iconClass = 'cross_circle_o'; } else { /* achieved early */ messageTwo = $filter('i18n')('sla.status.achieved', ticket.SLA.closedTime.from(slaItemEndTime, true)) + ' ' + $filter('i18n')('sla.status.early', convertDateToLocale(ticket.SLA.closedTime)); item.iconClass = 'check_circle_o'; } } else if (ticket.isCancelled()) { if (ticket.SLA.isSlaPassed) { item.iconClass = 'cross_circle_o'; } else { item.iconClass = 'circle_o'; } } } var iconHtml = ""; if (item.iconClass === 'cross_circle_o') { item.SLAStatus = $filter('i18n')('sla.passed'); iconHtml = ' ' + '' + item.SLAStatus + ''; } else if (item.iconClass === 'check_circle_o') { item.SLAStatus = $filter('i18n')('sla.achieved'); iconHtml = ' ' + '' + item.SLAStatus + ''; } else if (item.iconClass === 'pause_circle_o') { item.SLAStatus = $filter('i18n')('sla.now.paused'); iconHtml = ' ' + '' + item.SLAStatus + ''; } /* set tooltip */ item.tooltip = '

' + item.title + '

' + '

' + messageOne + iconHtml + '

'; item.tooltipText = item.title + ' ' + messageOne; if (messageTwo) { item.tooltip += '

' + messageTwo + '

'; item.tooltipText = item.tooltipText + ' ' + messageTwo; } item.SLADetailTooltipText = { 'title': item.title, 'message': messageOne }; if (messageTwo) { item.SLADetailTooltipText.message += " " + messageTwo; } }); var timeObject = {}; _.each(ticket.SLA.items, function (item) { if (!_.isUndefined(timeObject[item.endTime])) { timeObject[item.endTime].tooltipHtml += "
" + item.tooltip; timeObject[item.endTime].tooltipCount += 1; } else { timeObject[item.endTime] = { 'tooltipHtml': item.tooltip, 'tooltipCount': 1 }; } }); ticket.SLA.items.map(function (ele) { ele.tooltipCount = timeObject[ele.endTime].tooltipCount; if (timeObject[ele.endTime].tooltipCount > 2) { ele.tooltip = ele.tooltip + '

' + $filter('i18n')('sla.tooltip.showMore', (timeObject[ele.endTime].tooltipCount - 1)) + '

' + '

' + $filter('i18n')('sla.tooltip.clickToSeeDetails') + '

'; } else { ele.tooltip = timeObject[ele.endTime].tooltipHtml; } }); }; var processSLAOthersCalendar = function (ticket) { var now = moment(), defaultWarningPercentage = 90, calendarEndTime = ticket.SLA.slaEndTime.calendar({ sameElse: 'lll' }), slaFound; if (ticket.SLA.allPaused) { ticket.SLA.reachTime = $filter('i18n')('sla.now.paused'); slaFound = _.find(ticket.SLA.items, function (item) { return item.measurementStatus === SLAVO.MEASUREMENT_STATUS_PENDING; }); if (slaFound) { if (moment(slaFound.downStartTime).isAfter(moment(slaFound.endTime))) { ticket.SLA.divClassType = 'danger'; } else if (slaFound.warningDate) { ticket.SLA.divClassType = moment(slaFound.downStartTime).isBefore(moment(slaFound.warningDate)) ? 'success' : 'warning'; } else { ticket.SLA.divClassType = 'success'; } } else { ticket.SLA.divClassType = 'danger'; } } else { if (ticket.SLA.slaRenderType === 'partial') { ticket.SLA.reachTime = $filter('i18n')('sla.target.date.is') + ' ' + calendarEndTime; if (ticket.isResolved()) { ticket.SLA.divClassType = ticket.SLA.isSlaPassed ? 'danger' : 'success'; } else { ticket.SLA.divClassType = ticket.SLA.isSlaPassed ? 'danger' : (ticket.SLA.reachedPercent < defaultWarningPercentage ? 'success' : 'warning'); } } else { // find the first SLA which now is before its endTime and that are not met slaFound = _.find(ticket.SLA.items, function (item) { return now.isBefore(moment(item.endTime)) && (item.measurementStatus != SLAVO.MEASUREMENT_STATUS_MET); }); if (slaFound) { ticket.SLA.reachTime = $filter('i18n')('sla.next.sla.is') + ' ' + moment(slaFound.endTime).calendar({ sameElse: 'lll' }); } else if (ticket.SLA.allMet) { ticket.SLA.reachTime = $filter('i18n')('sla.achieved') + ' ' + moment(_.last(ticket.SLA.items).overallStopTime).calendar({ sameElse: 'lll' }); } else { var lastSLAPassed = ticket.SLA.items.filter(function (item) { return item.measurementStatus === SLAVO.MEASUREMENT_STATUS_MISSED_GOAL; }); lastSLAPassed = (lastSLAPassed && _.last(lastSLAPassed)) || _.last(ticket.SLA.items); ticket.SLA.reachTime = $filter('i18n')('sla.passed') + ' ' + moment(lastSLAPassed.endTime).calendar({ sameElse: 'lll' }); } //Consider worst case scenario to show the SLA bar color slaFound = _.find(ticket.SLA.items, function (item) { return (item.measurementStatus === SLAVO.MEASUREMENT_STATUS_MISSED) || (item.measurementStatus === SLAVO.MEASUREMENT_STATUS_MISSED_GOAL); }); if (slaFound) { ticket.SLA.divClassType = 'danger'; } else { slaFound = _.find(ticket.SLA.items, function (item) { return (item.measurementStatus === SLAVO.MEASUREMENT_STATUS_PENDING) && moment(item.downStartTime).isAfter(moment(item.endTime)); }); if (slaFound) { ticket.SLA.divClassType = 'danger'; } else { slaFound = _.find(ticket.SLA.items, function (item) { return ((item.measurementStatus === SLAVO.MEASUREMENT_STATUS_PENDING) && moment(item.downStartTime).isAfter(moment(item.warningDate))) || ((item.measurementStatus !== SLAVO.MEASUREMENT_STATUS_PENDING && item.measurementStatus !== SLAVO.MEASUREMENT_STATUS_MET) && now.isAfter(item.warningDate)); }); if (slaFound) { ticket.SLA.divClassType = 'warning'; } else { ticket.SLA.divClassType = 'success'; } } } } } }; function convertDateToLocale(date) { if (date.toDate) { date = date.toDate(); } return $filter('datePreConfigTimezone')(date, 'mediumDate') + ' ' + $filter('datePreConfigTimezone')(date, 'shortTime'); } }]); }());