"use strict"; (function () { 'use strict'; angular.module('changeModule') .directive('calendar', ['$log', 'events', '$filter', 'editTicketDatesService', 'collisionModel', '$compile', 'relationModel', '$document', '$state', '$stateParams', 'configurationModel', 'categoriesService', 'metadataModel', '$timeout', '$rootScope', function ($log, events, $filter, editTicketDatesService, collisionModel, $compile, relationModel, $document, $state, $stateParams, configurationModel, categoriesService, metadataModel, $timeout, $rootScope) { return { restrict: 'E', templateUrl: 'views/change/calendar.html', replace: true, controller: ['$scope', function ($scope) { var self = this; $scope.model = this.model = { expanded: false, calendarViews: ['book', 'day', 'week', 'month'], showWeekends: true, calendarTypes: [ { id: 'changes', selected: true }, { id: 'outages', selected: true }, { id: 'businessEvents', selected: true } ], context: $scope.context, editMode: $scope.editMode, isNew: $scope.isNew, currentRequestResourceId: "CurrentCRQ", thisCrqSortWeight: 1, collisionCrqSortWeight: 10, collisionOutageSortWeight: 60, collisionBusinessEventSortWeight: 80, addressedCollisions: {}, zoomInDisabled: false, zoomOutDisabled: false }; $scope.filters = { config: angular.copy(configurationModel.get('change.collisionFilter')), selected: [] }; var changeMetadata; metadataModel.getMetadataByType(EntityVO.TYPE_CHANGE).then(function (metadata) { changeMetadata = metadata; var filterDictionary = _.indexBy($scope.filters.config, 'name'); _.forEach(changeMetadata.statuses, function (status) { filterDictionary.ticketSpecificStatuses.options.push({ name: $filter('localizeLabel')(status.name, 'status', EntityVO.TYPE_CHANGE), type: 'constant', criteria: { name: 'ticketSpecificStatuses', value: [status.name] } }); }); _.forEach(changeMetadata.priorities, function (priority) { filterDictionary.priorities.options.push({ name: $filter('localizeLabel')(priority.name, 'priority', EntityVO.TYPE_CHANGE), type: 'constant', criteria: { name: 'priorities', value: [priority.name] } }); }); }); $scope.getProductCategoriesByCompany = function (searchText) { var categoryName = 'product', params = { entityType: EntityVO.TYPE_CHANGE, categoryType: categoryName, searchText: searchText, criteria: { company: { name: '%' } } }; return categoriesService.searchCategories(params).then(function (result) { var category = _.find(changeMetadata.categories, function (category) { return category.name === categoryName; }); return _.map(result.items, function (item) { if (item.length) { var serializedValue = _.compact(item).join(' > '), tiers = {}; for (var i = 0; i < category.listOfTiers.length; i++) { tiers[category.listOfTiers[i].name] = item[i]; } return { value: serializedValue, attributeMap: { categorizations: [{ name: categoryName, tiers: tiers }] } }; } }); }); }; var handling = { resize: { enabled: 'Update', disabled: 'Disabled' }, move: { enabled: 'Update', disabled: 'Disabled' }, select: { enabled: 'Enabled', disabled: 'Disabled' } }; this.handling = handling; $scope.model.selectedCalendarView = $scope.model.calendarViews[0]; $scope.toggle = function () { $scope.model.expanded = !$scope.model.expanded; $log.log('change calendar expanded: ' + $scope.model.expanded); $scope.$emit(events.TOGGLE_CHANGE_CALENDAR, $scope.model.expanded); }; $scope.$watch('model.selectedCalendarView', function (newValue) { $log.log("selected calendar view: " + newValue); if (newValue) { $state.go('^.' + newValue); } }); $scope.$watch('filters.selected.length', function () { loadCollisions(true); }); $scope.$watch('model.showWeekends', function (newValue) { $log.log("show weekends: " + newValue); }); $scope.$watch('model.calendarTypes', function (newValue) { $log.log("selected calendar types: " + JSON.stringify(newValue)); }, true); $scope.$watch('context', function () { $scope.model.context = $scope.context; }); $scope.$watch('model.context.scheduledStartDate', function () { loadCollisions(false); }); $scope.$watch('model.context.scheduledEndDate', function () { loadCollisions(false); }); $scope.$watch('model.context.linkedCIs', function () { loadCollisions(false); }); $scope.$watch('model.showWeekends', function () { $scope.$emit(events.CHANGE_COLLISION_SHOW_WEEKENDS, { showWeekends: $scope.model.showWeekends }); }); var off = $rootScope.$on(events.CHANGE_COLLISION_STATUS_CHANGED, function (event, addressedCollisions) { self.applyAddressedCollisions(addressedCollisions); }); $scope.$on('$destroy', function () { off(); }); $scope.zoom = function (increment) { $log.log("zoom: " + increment); $scope.$broadcast(events.ZOOM_LEVEL_CHANGED, increment); }; this.rescheduleChangeRequest = function (calendarInstance, start, end) { if (start instanceof Date) { start = new DayPilot.Date(start, true); } if (end instanceof Date) { end = new DayPilot.Date(end, true); } if (start && end && calendarInstance.lastEventStartDate && calendarInstance.lastEventEndDate && start.equals(calendarInstance.lastEventStartDate) && end.equals(calendarInstance.lastEventEndDate)) { return; } var shouldUpdate = false, event; if (calendarInstance.currentRequestEventId && (event = calendarInstance.events.find(calendarInstance.currentRequestEventId))) { shouldUpdate = true; calendarInstance.events.remove(event); } if (start && end) { calendarInstance.lastEventStartDate = start; calendarInstance.lastEventEndDate = end; calendarInstance.currentRequestEventId = DayPilot.guid(); shouldUpdate = true; event = new DayPilot.Event({ start: start, end: end, id: calendarInstance.currentRequestEventId, sort: [$scope.model.thisCrqSortWeight], resource: $scope.model.currentRequestResourceId, text: $filter('i18n')('create.change.wizard.dates.thisCRQ', $scope.model.context.displayId ? $scope.model.context.displayId : "").trim() }); calendarInstance.events.add(event); } else { calendarInstance.lastEventStartDate = null; calendarInstance.lastEventEndDate = null; } return shouldUpdate; }; this.updateCalendarHandling = function (calendarInstance) { if ($scope.model.context) { var interactionDisabled = editTicketDatesService.scheduledStartDateDisabled($scope.model.context, $scope.model.editMode); calendarInstance.eventResizeHandling = interactionDisabled ? handling.resize.disabled : handling.resize.enabled; calendarInstance.eventMoveHandling = interactionDisabled ? handling.move.disabled : handling.move.enabled; calendarInstance.timeRangeSelectedHandling = interactionDisabled ? handling.select.disabled : handling.select.enabled; calendarInstance.update(); } }; this.removeEvents = function (calendar, where) { var list = calendar.events.list ? calendar.events.list.slice() : []; for (var i = 0; i < list.length; i++) { var event = calendar.events.find(list[i].id); if (where(event)) { calendar.events.remove(event); } } }; this.collidesWithCurrentCRQ = function (context) { return this.intervalsOverlap({ start: $scope.model.context.scheduledStartDate, end: $scope.model.context.scheduledEndDate }, { start: context.scheduledStartDate, end: context.scheduledEndDate }); }; this.fitsInCalendarViewport = function (calendar, context) { return this.intervalsOverlap({ start: calendar.visibleStart(), end: calendar.visibleEnd() }, { start: context.scheduledStartDate, end: context.scheduledEndDate }); }; this.intervalsOverlap = function (t, s) { if (!t.start || !t.end || !s.start || !s.end) { return false; } t.start = toMs(t.start); t.end = toMs(t.end); s.start = toMs(s.start); s.end = toMs(s.end); return s.end >= t.start && s.start <= t.end; }; function toMs(date) { var result = date; if (date instanceof DayPilot.Date) { result = date.toDateLocal().getTime(); } if (date instanceof Date) { result = date.getTime(); } return result; } this.changeRequestFilterSelected = function () { return $scope.model.calendarTypes[0].selected; }; this.outageFilterSelected = function () { return $scope.model.calendarTypes[1].selected; }; this.businessEventsFilterSelected = function () { return $scope.model.calendarTypes[2].selected; }; this.onEventClicked = function (args) { if (!(args.e.data.changeRequest || args.e.data.outage)) { return; } var eventEl = $(args.div); if (args.div.hasTooltop) { return; } var newScope = $scope.$new(), content; eventEl.attr("title", null); if (args.e.data.changeRequest) { newScope.changeRequest = args.e.data.changeRequest; content = $compile('

')(newScope); } else if (args.e.data.outage) { newScope.outage = args.e.data.outage; content = $compile('

')(newScope); } $scope.$apply(); var popoverOffsetX = -197, popoverOffsetY = 3; eventEl.qtip({ content: content, show: { event: 'click', solo: true }, hide: { event: 'click unfocus' }, position: { target: 'mouse', adjust: { mouse: false, x: popoverOffsetX, y: popoverOffsetY } } }); eventEl.qtip('api').show(args.originalEvent); args.div.hasTooltop = true; }; this.collisionCount = function (changeRequest) { return this.collidesWithCurrentCRQ(changeRequest) ? changeRequest.additionalInformation && changeRequest.additionalInformation.collisionCount : 0; }; function buildFilterCriteria() { var filters = angular.copy($scope.filters.selected); var filterCriteria = {}; _.forEach(filters, function (filter) { var criteria = filter.criteria, isMultiSelect = _.isArray(criteria.value), name = criteria.name; if (filterCriteria[name]) { if (isMultiSelect) { filterCriteria[name] = _.union(filterCriteria[name], criteria.value); } else { filters.splice(_.findIndex(filters, { name: filter.name }), 1); filterCriteria[name] = criteria.value; } } else { filterCriteria[name] = criteria.value; } }); return filterCriteria; } function loadCollisions(skipDiff) { if ($scope.model.loadingCollisions || !needLoadingCollisions(skipDiff)) { return; } if (canLoadCollisions()) { var extensionDays = 31, context = $scope.model.context, start = new Date(moment(context.scheduledStartDate).subtract(extensionDays, 'days').valueOf()), end = new Date(moment(context.scheduledEndDate).add(extensionDays, 'days').valueOf()), linkedCIs = context.linkedCIs ? _.cloneDeep(context.linkedCIs) : []; $log.log("Loading collisions: start=" + start + "; end=" + end + "; CIs=" + _.pluck(context.linkedCIs, "id").join()); $scope.model.loadingCollisions = true; if (context.impactedService && context.impactedService.reconciliationId) { if (!_.find(linkedCIs, { id: context.impactedService.reconciliationId })) { linkedCIs.push({ id: context.impactedService.reconciliationId }); } } collisionModel .getListOfCollisionsByDate(context.id, start, end, linkedCIs, buildFilterCriteria()) .then(function (data) { $scope.model.loadingCollisions = false; $scope.displayingCollisions = false; $scope.model.collisions = data; $scope.model.collisions.scheduledStartDate = context.scheduledStartDate; $scope.model.collisions.scheduledEndDate = context.scheduledEndDate; $scope.model.collisions.linkedCIs = linkedCIs; }) .catch(function () { $scope.model.loadingCollisions = false; }); } else { $scope.model.collisions = null; } } function needLoadingCollisions(skipDiff) { var context = $scope.model.context, collisions = $scope.model.collisions, hasPrerequisites = context && collisions && context.scheduledStartDate && context.scheduledEndDate && context.linkedCIs && context.linkedCIs.length && collisions.scheduledStartDate && collisions.scheduledEndDate && collisions.linkedCIs && collisions.linkedCIs.length, infoMatch = hasPrerequisites && context.scheduledStartDate.getTime() === collisions.scheduledStartDate.getTime() && context.scheduledEndDate.getTime() === collisions.scheduledEndDate.getTime() && linkedCIsEqual(context.linkedCIs, collisions.linkedCIs) && !skipDiff; return hasPrerequisites ? !infoMatch : true; } function linkedCIsEqual(contextCIs, collisionCIs) { if (contextCIs.length !== collisionCIs.length) { return false; } var contextIds = _.pluck(contextCIs, "id"), collisionIds = _.pluck(collisionCIs, "id"), difference = _.difference(_.sortBy(contextIds), _.sortBy(collisionIds)); return difference.length === 0; } function canLoadCollisions() { var context = $scope.model.context; return context.scheduledStartDate && context.scheduledEndDate && context.scheduledEndDate.getTime() > context.scheduledStartDate.getTime(); } function loadLinkedCIs() { if (!$scope.model.editMode || $scope.model.isNew) { return; } var contextId = $scope.model.context.id; relationModel.getRelations(contextId, EntityVO.TYPE_CHANGE).finally(function () { $scope.model.loadingCollisions = false; $scope.model.context.linkedCIs = _.filter(angular.copy(relationModel.cache[contextId]), { type: EntityVO.TYPE_ASSET }) || []; }); } this.displayCollisions = function (params) { if ($scope.displayingCollisions) { return; } $scope.displayingCollisions = true; $timeout(function () { removeCollisions(params); if ($scope.model.collisions) { displayChangeRequests(params); displayOutages(params); displayBusinessEvents(params); } params.updateCalendar(); $scope.displayingCollisions = false; }); }; function removeCollisions(params) { self.removeEvents(params.calendar, function (event) { return event.resource() !== $scope.model.currentRequestResourceId; }); } function displayChangeRequests(params) { if (!self.changeRequestFilterSelected()) { return; } var resources = params.resources, changeRequests, changeRequest, resourceId, eventData, event; changeRequests = self.visibleChangeRequests(params.calendar); for (var i = 0, j = 1; i < changeRequests.length; i++, j++) { changeRequest = changeRequests[i]; resourceId = null; if (resources) { if (j >= resources.length) { resourceId = j.toString(); resources.push({ name: "", id: resourceId }); } else { resourceId = resources[j].id; } } var additionalClassName = self.collidesWithCurrentCRQ(changeRequest) && !configurationModel.disableCollisionManagement && !self.model.addressedCollisions[changeRequest.id] ? params.changeCollisionCss : params.changeNonCollisionCss; eventData = { start: new DayPilot.Date(new Date(changeRequest.scheduledStartDate), true), end: new DayPilot.Date(new Date(changeRequest.scheduledEndDate), true), id: changeRequest.displayId, text: changeRequest.displayId, additionalClassName: additionalClassName, sort: [$scope.model.collisionCrqSortWeight + i], changeRequest: _.cloneDeep(changeRequest) }; if (resourceId) { eventData.resource = resourceId; } if (!configurationModel.disableCollisionManagement && !self.model.addressedCollisions[changeRequest.id]) { eventData.collisionCount = self.collisionCount(changeRequest); } event = new DayPilot.Event(eventData); params.calendar.events.add(event); } } this.visibleChangeRequests = function (calendar) { return !$scope.model.collisions ? [] : _.filter($scope.model.collisions.changeRequests, function (context) { return self.fitsInCalendarViewport(calendar, context); }); }; function displayOutages(params) { if (!self.outageFilterSelected()) { return; } var resources = params.resources, resourceId, outages, outage, eventData, event; outages = self.visibleOutages(params.calendar); if (params.outagesIndex && resources) { if (params.outagesIndex >= resources.length) { resourceId = params.outagesIndex.toString(); resources.push({ name: "", id: resourceId }); } else { resourceId = resources[params.outagesIndex].id; } } for (var i = 0; i < outages.length; i++) { outage = outages[i]; var additionalClassName = self.collidesWithCurrentCRQ(outage) && !configurationModel.disableCollisionManagement ? params.outageCollisionCss : params.outageNonCollisionCss; eventData = { start: new DayPilot.Date(new Date(outage.scheduledStartDate), true), end: new DayPilot.Date(new Date(outage.scheduledEndDate), true), id: outage.displayId, text: outage.displayId, additionalClassName: additionalClassName, sort: [$scope.model.collisionOutageSortWeight + i], outage: _.cloneDeep(outage) }; if (resourceId) { eventData.resource = resourceId; } event = new DayPilot.Event(eventData); params.calendar.events.add(event); } } this.visibleOutages = function (calendar) { return !$scope.model.collisions ? [] : _.filter($scope.model.collisions.outages, function (context) { return self.fitsInCalendarViewport(calendar, context); }); }; function displayBusinessEvents(params) { if (!self.businessEventsFilterSelected()) { return; } var resources = params.resources, resourceId, businessEvents, businessEvent, eventData, event; businessEvents = self.visibleBusinessEvents(params.calendar); if (params.businessEventsIndex && resources) { if (params.businessEventsIndex >= resources.length) { resourceId = params.businessEventsIndex.toString(); resources.push({ name: "", id: resourceId }); } else { resourceId = resources[params.businessEventsIndex].id; } } for (var i = 0; i < businessEvents.length; i++) { businessEvent = businessEvents[i]; var additionalClassName = self.collidesWithCurrentCRQ(businessEvent) && !configurationModel.disableCollisionManagement ? params.businessEventCollisionCss : params.businessEventNonCollisionCss; eventData = { start: new DayPilot.Date(new Date(businessEvent.scheduledStartDate), true), end: new DayPilot.Date(new Date(businessEvent.scheduledEndDate), true), id: businessEvent.id, text: businessEvent.title, toolTip: businessEvent.title, additionalClassName: additionalClassName, sort: [$scope.model.collisionBusinessEventSortWeight + i], businessEvent: _.cloneDeep(businessEvent) }; if (resourceId) { eventData.resource = resourceId; } event = new DayPilot.Event(eventData); params.calendar.events.add(event); } } this.visibleBusinessEvents = function (calendar) { return !$scope.model.collisions ? [] : _.filter($scope.model.collisions.businessEvents, function (context) { return self.fitsInCalendarViewport(calendar, context); }); }; this.applyAddressedCollisions = function (addressedCollisions) { self.model.addressedCollisions = {}; _.forEach(addressedCollisions, function (collision) { self.model.addressedCollisions[collision.id] = !collision.ciCount; }); }; if ($scope.collisions && $scope.collisions.changeList) { self.applyAddressedCollisions($scope.collisions.changeList); } loadLinkedCIs(); } ] }; } ]); })();