"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 = _.first(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 () { ticket.SLA.slaProgressBarValid = true; }, 50); ticket.SLA.items = _.sortBy(ticket.SLA.items, 'endTime'); var lastSlaItem = _.last(ticket.SLA.items); var now = moment(), slaStartTime = moment(_.first(ticket.SLA.items).startTime), slaEndTime = moment(_.last(ticket.SLA.items).endTime), closedTime = ticket.resolvedDate ? moment(ticket.resolvedDate) : (ticket.completedDate ? moment(ticket.completedDate) : (lastSlaItem && lastSlaItem.overallStopTime ? moment(lastSlaItem.overallStopTime) : (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: ticket.isOngoing() && (!lastSlaItem || lastSlaItem.measurementStatus !== SLAVO.MEASUREMENT_STATUS_MET), timeToLimit: now.from(slaEndTime, true), isSlaPassed: now.isAfter(slaEndTime), autoRefresh: true }, { reachedPercent: Math.round(timeFromSlaStart.tillClosedDate / timeFromSlaStart.tillSlaEnd * 100), condition: ticket.isOngoing() && (lastSlaItem && lastSlaItem.measurementStatus === SLAVO.MEASUREMENT_STATUS_MET), 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: ticket.isPaused(), 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: 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; } return ticket.SLA.items[0].downStartTime ? moment(ticket.SLA.items[0].downStartTime) : ''; } else { /* multiple SLA */ var oneOrMorePaused = false, oneOrMoreMet = false, allPaused = false; _.forEach(ticket.SLA.items, function (item) { if (item.measurementStatus === SLAVO.MEASUREMENT_STATUS_PENDING) { oneOrMorePaused = true; } else if (item.measurementStatus === SLAVO.MEASUREMENT_STATUS_MET) { oneOrMoreMet = true; } allPaused = allPaused ? allPaused : (item.measurementStatus === SLAVO.MEASUREMENT_STATUS_PENDING) || (item.measurementStatus === SLAVO.MEASUREMENT_STATUS_MET); }); allPaused = allPaused && oneOrMorePaused; //There should be at least one in paused state and others could be in met state. 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()) { 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.isPaused()) { denominator = ticket.SLA.slaEndTime.diff(ticket.SLA.slaStartTime); } else if (_.last(ticket.SLA.items).measurementStatus === SLAVO.MEASUREMENT_STATUS_MET) { 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, slaItemEndTime = 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); /* set tooltip line 2 */ if (now.isAfter(slaItemEndTime)) { /* original due */ messageOne = $filter('i18n')('sla.due') + ' ' + convertDateToLocale(slaItemEndTime); } else { /* due in */ 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: /* now paused */ messageTwo = $filter('i18n')('sla.now.paused'); item.iconClass = 'pause_circle_o'; break; case SLAVO.MEASUREMENT_STATUS_MET: /* achieved early */ messageTwo = $filter('i18n')('sla.achieved') + ' ' + slaItemOverallStopTime.from(slaItemEndTime, true) + ' ' + $filter('i18n')('sla.early') + ' (' + convertDateToLocale(slaItemOverallStopTime) + ')'; break; case SLAVO.MEASUREMENT_STATUS_MISSED: /* achieved late */ messageTwo = $filter('i18n')('sla.achieved') + ' ' + slaItemOverallStopTime.from(slaItemEndTime, true) + ' ' + $filter('i18n')('sla.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.achieved') + ' ' + ticket.SLA.closedTime.from(slaItemEndTime, true) + ' ' + $filter('i18n')('sla.late') + ' (' + convertDateToLocale(ticket.SLA.closedTime) + ')'; item.iconClass = 'cross_circle_o'; } else { /* achieved early */ messageTwo = $filter('i18n')('sla.achieved') + ' ' + ticket.SLA.closedTime.from(slaItemEndTime, true) + ' ' + $filter('i18n')('sla.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 += "' + $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(), slaFound; if (ticket.isResolved()) { ticket.SLA.reachTime = $filter('i18n')('sla.cancelled') + ' ' + calendarEndTime; if (ticket.SLA.closedTime.isBefore(ticket.SLA.slaEndTime)) { ticket.SLA.divClassType = 'success'; } else { ticket.SLA.divClassType = 'danger'; } } else if (ticket.isCancelled()) { ticket.SLA.reachTime = $filter('i18n')('sla.cancelled') + ' ' + calendarEndTime; ticket.SLA.divClassType = 'closed'; } else if (ticket.SLA.allPaused) { ticket.SLA.reachTime = $filter('i18n')('sla.now.paused'); slaFound = _.find(ticket.SLA.items, function (item) { return moment(item.downStartTime).isBefore(moment(item.endTime)) && (item.measurementStatus != SLAVO.MEASUREMENT_STATUS_MET); }); if (slaFound) { if (slaFound.warningDate) { ticket.SLA.divClassType = moment(slaFound.downStartTime).isBefore(moment(slaFound.warningDate)) ? 'success' : 'warning'; } else { ticket.SLA.divClassType = 'danger'; } } else { ticket.SLA.divClassType = 'danger'; } } else { if (ticket.SLA.slaRenderType === 'partial') { ticket.SLA.reachTime = $filter('i18n')('sla.target.date.is') + ' ' + calendarEndTime; 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(); if (slaFound.warningDate) { ticket.SLA.divClassType = now.isBefore(moment(slaFound.warningDate)) ? 'success' : 'warning'; } else { ticket.SLA.divClassType = 'danger'; } } else if (_.last(ticket.SLA.items).measurementStatus === SLAVO.MEASUREMENT_STATUS_MET) { ticket.SLA.reachTime = $filter('i18n')('sla.achieved') + ' ' + moment(_.last(ticket.SLA.items).overallStopTime).calendar(); ticket.SLA.divClassType = 'success'; } else { ticket.SLA.reachTime = $filter('i18n')('sla.passed') + ' ' + moment(_.last(ticket.SLA.items).endTime).calendar(); ticket.SLA.divClassType = 'danger'; } } } }; function convertDateToLocale(date) { if (date.toDate) { date = date.toDate(); } return $filter('date')(date, 'mediumDate') + ' ' + $filter('date')(date, 'shortTime'); } }]); }());