"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();
}
]
};
}
]);
})();