diff --git a/Client-Side Components/UI Scripts/Custom Change Schedule/README.md b/Client-Side Components/UI Scripts/Custom Change Schedule/README.md
new file mode 100644
index 0000000000..dfecace0d5
--- /dev/null
+++ b/Client-Side Components/UI Scripts/Custom Change Schedule/README.md
@@ -0,0 +1,36 @@
+# ๐งพ ServiceNow Change Schedule Enhancement
+### _(UI Scripts: `sn_chg_soc.change_soc`, `sn.chg_soc.config`, `sn.chg_soc.data`)_
+
+---
+
+## ๐ Overview
+
+This customization extends the **ServiceNow Change Schedule (Change Calendar)** functionality.
+The enhancement adds visibility and interactivity to the Change Calendar by including:
+
+- A **Short Description** column in the Change Schedule view.
+- A **configurable UI** allowing users to toggle visibility of columns such as _Configuration Item_, _Short Description_, and _Duration_.
+- Integration of additional data services for fetching and rendering change records with enhanced details.
+- A **Change Schedule button** that refreshes and displays these changes dynamically.
+
+The result is a more informative and user-friendly Change Schedule interface for Change Managers, CAB members, and ITSM users.
+
+---
+
+## ๐งฉ Architecture
+
+| Module | Description |
+|--------|-------------|
+| **`sn_chg_soc.change_soc`** | Main controller and directive for the Change Schedule Gantt Chart UI. Handles initialisation, rendering, zoom, and popovers. |
+| **`sn.chg_soc.config`** | Manages configuration settings for displayed columns and schedules (blackout, maintenance). Allows toggling visibility. |
+| **`sn.chg_soc.data`** | Provide the data on the gantt chat from the change records
+
+
+Requirement:
+As an ITIL user, you can click the Change Schedule button to navigate directly to the Change Schedule view.
+This allows you to see all planned changes and plan your own changes accordingly, especially useful for customers who do not have a well-established CMDB integrated with Discovery.
+
+
+
+
+
diff --git a/Client-Side Components/UI Scripts/Custom Change Schedule/change_soc.js b/Client-Side Components/UI Scripts/Custom Change Schedule/change_soc.js
new file mode 100644
index 0000000000..25c02cf500
--- /dev/null
+++ b/Client-Side Components/UI Scripts/Custom Change Schedule/change_soc.js
@@ -0,0 +1,1018 @@
+angular.module("sn.chg_soc.change_soc", [
+ "ngAria",
+ "sn.common",
+ "sn.common.glide",
+ "sn.angularstrap",
+ "sn.chg_soc.accessibility",
+ "sn.chg_soc.tooltip_overflow",
+ "sn.chg_soc.notification",
+ "sn.chg_soc.mousedown",
+ "sn.chg_soc.gantt",
+ "sn.chg_soc.data",
+ "sn.chg_soc.style",
+ "sn.chg_soc.config",
+ "sn.chg_soc.share",
+ "sn.chg_soc.landing_wizard",
+ "sn.chg_soc.context_menu",
+ "sn.chg_soc.snCreateNewInvite",
+ "sn.chg_soc.keyboard",
+ "sn.chg_soc.popover",
+ "sn.chg_soc.duration",
+ "sn.app_itsm.now.filter",
+ "sn.chg_soc.filter_control",
+ "sn.chg_soc.loading",
+ "sn.itsm.change.overflow"
+ ])
+ .constant("SOC", {
+ BLACKOUT: "blackout",
+ BLACKOUT_SPAN_COLOR: "#BDC0C4",
+ CHANGE_REQUEST: "change_request",
+ DATE_FORMAT: "%Y-%m-%d %H:%i:%s",
+ GET_CHANGE_SCHEDULE: "/api/sn_chg_soc/soc/changeschedule/",
+ GET_PARSE_QUERY: "/api/now/ui/query_parse/change_request?sysparm_query=",
+ ISO_WEEK: "isoWeek",
+ MAINT: "maint",
+ MAINT_SPAN_COLOR: "#BDDCFC",
+ STYLE_PREFIX: "soc_",
+ SYSPARM_ID: "sysparm_id",
+ ZOOM_LEVEL_PREF: "sn_chg_soc.change_soc_zoom_level",
+ COLUMN: {
+ SHORT_DESCRIPTION: "short_description",
+ CONFIG_ITEM: "config_item",
+ // DURATION: "duration",
+ NUMBER: "number"
+ },
+ STYLE_CLASS_MAP: {
+ soc_event_bar: "soc-event-bar",
+ soc_row_child: "soc-row-child",
+ soc_row_child_end: "soc-row-child-end",
+ soc_row_child_start: "soc-row-child-start",
+ soc_row_child_single: "soc-row-child-single"
+ },
+ KEYS: {
+ TABKEY: 9,
+ ENTER: 13,
+ ESCAPE: 27,
+ SPACE: 32,
+ LEFT_ARROW: 37,
+ UP_ARROW: 38,
+ RIGHT_ARROW: 39,
+ DOWN_ARROW: 40,
+ D: 68,
+ E: 69,
+ F: 70,
+ SLASH: 191
+ }
+ })
+ .config(["$httpProvider", "$locationProvider", function($httpProvider, $locationProvider) {
+ $locationProvider.html5Mode({
+ enabled: true,
+ requireBase: false
+ });
+ $httpProvider.interceptors.push("xhrInterceptor");
+ }])
+ .service("urlService", ["$location", "SOC", function($location, SOC) {
+ var urlService = this;
+
+ urlService.socId = $location.search()[SOC.SYSPARM_ID];
+
+ urlService.setChangeScheduleId = function() {
+ var params = $location.search();
+ urlService.socId = params[SOC.SYSPARM_ID];
+ };
+ }])
+ .service("clientService", ["dataService", function(dataService) {
+ var clientService = this;
+
+ clientService.filter = dataService.definition;
+ }])
+ .directive("changeSoc", ["urlService", "ganttChart", "ganttScale", "dataService", "i18n", "getTemplateUrl", "$templateRequest", "$templateCache", "$filter", "$compile", "$window", "SOC", "TextSearchService", "socNotification",
+ function(urlService, ganttChart, ganttScale, dataService, i18n, getTemplateUrl, $templateRequest, $templateCache, $filter, $compile, $window, SOC, TextSearchService, socNotification) {
+ return {
+ restrict: "A",
+ scope: false,
+ transclude: true,
+ template: "
",
+ link: function($scope, $element, $attrs, changeSoCCtrl) {
+ var position = {
+ delta: {
+ top: 0,
+ left: 0
+ },
+ original: {
+ top: 0,
+ left: 0
+ }
+ };
+ $scope.ganttInstance = ganttChart.getInstance(urlService.socId);
+ $scope.gantt = $scope.ganttInstance.gantt;
+
+ // destroy all popovers when resizing the window
+ angular.element(window).on("resize", function() {
+ angular.element(".popover.soc-task-popover").popover("destroy");
+ _handleDestroyPopover();
+ });
+
+ angular.element(window).on("keydown", function($event) {
+ if ($event.keyCode === SOC.KEYS.ESCAPE)
+ _handleDestroyPopover();
+ });
+
+ angular.element(window).on("click", function($event) {
+ var target = getTargetElement($event);
+ if (target === null)
+ _handleDestroyPopover(); // Clicking outside a gantt task
+ });
+
+ //size of gantt
+ $scope.$watch(function() {
+ return $element[0].offsetWidth + "." + $element[0].offsetHeight;
+ }, function() {
+ $scope.gantt.setSizes();
+ });
+
+ $scope.$watch("dataService.definition.condition.dryRun", function(newValue, oldValue) {
+ if (newValue)
+ angular.element(".control-left .filter-btn").addClass("dry-run");
+ else
+ angular.element(".control-left .filter-btn").removeClass("dry-run");
+ });
+ /**
+ * Marker config
+ */
+ $scope.gantt.config.show_markers = true;
+
+ /**
+ * Column config
+ */
+ var msgSelectRecord = i18n.getMessage("Show span start");
+ $scope.gantt.config.columns = [{
+ name: SOC.COLUMN.NUMBER,
+ label: i18n.getMessage("Number"),
+ align: "left",
+ tree: true,
+ width: 160,
+ min_width: 160,
+ resize: true,
+ template: function(content) {
+ return "" + content.number + "" +
+ "";
+ }
+ },
+ {
+ name: SOC.COLUMN.CONFIG_ITEM,
+ label: i18n.getMessage("Configuration Item"),
+ align: "left",
+ width: 220,
+ min_width: 220,
+ resize: true,
+ template: function (content) {
+ return "" +
+ content[SOC.COLUMN.CONFIG_ITEM] +
+ "";
+ }
+ },
+ {
+
+ name: SOC.COLUMN.SHORT_DESCRIPTION,
+ label: "Short Description",
+ align: "left",
+ tree: true,
+ width: 160,
+ min_width: 160,
+ resize: true,
+ template: function(content) {
+ return "" + content.short_description + "" +
+ "";
+ }
+
+ },
+ // {
+ // name: SOC.COLUMN.DURATION,
+ // label: i18n.getMessage("Duration"),
+ // align: "left",
+ // width: 130,
+ // min_width: 130,
+ // template: function(content) {
+ // return content.dur_display;
+ // },
+ // resize: true
+ // }
+ ];
+
+ /**
+ * Core Config
+ */
+ // internal date time format
+ $scope.gantt.config.xml_date = SOC.DATE_FORMAT;
+ // ARIA attributes
+ $scope.gantt.config.wai_aria_attributes = true;
+ // Keyboard navigation
+ $scope.gantt.config.keyboard_navigation = true;
+
+ /**
+ * Scrolling
+ */
+ // Prevents scrolling gantt on load of data
+ $scope.gantt.config.initial_scroll = false;
+
+ $scope.gantt.showTask = function(id) {
+ var task = this.getTask(id);
+ var taskSize = this.getTaskPosition(task, task.start, task.end);
+ var left = Math.max(taskSize.left - this.config.task_scroll_offset, 0);
+ var ganttVerScrollWidth = angular.element(".gantt_ver_scroll").width();
+ var ganttTaskWidth = angular.element(".gantt_task").width() - ganttVerScrollWidth;
+
+ if (Math.abs(this.getScrollState().x - taskSize.left) < ganttTaskWidth && (taskSize.left + taskSize.width) > this.getScrollState().x)
+ left = null;
+
+ var scrollStateTop = this.getScrollState().y;
+ var scrollStateBottom = scrollStateTop + this._scroll_sizes().y;
+ var visibleTaskTop = taskSize.top;
+ var visibleTaskBottom = taskSize.top + this.config.row_height;
+ var top = null;
+
+ if (visibleTaskTop < scrollStateTop)
+ top = visibleTaskTop;
+ else if (visibleTaskTop > scrollStateTop && (visibleTaskTop < scrollStateBottom && visibleTaskBottom < scrollStateBottom))
+ top = null;
+ else if (visibleTaskTop > scrollStateTop && visibleTaskBottom > scrollStateBottom)
+ top = visibleTaskBottom - scrollStateBottom + scrollStateTop;
+
+ this.scrollTo(left, top);
+ };
+
+ function isPopoverInViewport(el) {
+ var visibleArea = {
+ minWidth: angular.element(".gantt_grid_data").width() - 15, // 15px considering the arrow can be shifted to the right (still visible)
+ maxWidth: angular.element("body").width(),
+ minTop: angular.element(".gantt_data_area").offset().top,
+ maxTop: angular.element("body").height()
+ };
+ var currentArea = {
+ minWidth: el.offset().left,
+ maxWidth: el.offset().left + el.width(),
+ minTop: el.offset().top - 15, // 15px considering the arrow
+ maxTop: el.offset().top + el.height() + 15, // 15px considering the arrow
+ };
+ if (currentArea.minWidth > visibleArea.minWidth && currentArea.maxWidth < visibleArea.maxWidth &&
+ currentArea.minTop > visibleArea.minTop && currentArea.maxTop < visibleArea.maxTop)
+ return true;
+ return false;
+ }
+
+ function adjustPopover() {
+ popoverElement = angular.element(".popover.soc-task-popover");
+ if (position.delta.top - angular.element(".gantt_ver_scroll").scrollTop() === 0 && position.delta.left - angular.element(".gantt_hor_scroll").scrollLeft() === 0)
+ return;
+ if (popoverElement.hasClass("in")) {
+ var newPopoverPosition = {
+ top: position.original.top + position.delta.top - angular.element(".gantt_ver_scroll").scrollTop(),
+ left: position.original.left + position.delta.left - angular.element(".gantt_hor_scroll").scrollLeft()
+ };
+ popoverElement.offset(newPopoverPosition);
+ if (!isPopoverInViewport(popoverElement))
+ _handleDestroyPopover();
+ }
+ }
+
+ $scope.lastScrollTop = 0;
+ $scope.loadScrollTop = 0;
+ $scope.lazyLoading = false;
+ $scope.gantt.attachEvent("onGanttScroll", function(left, top) {
+ if ($scope.lazyLoading)
+ $scope.lastScrollTop = top;
+
+ if (dataService.count >= $window.NOW.sn_chg_soc.limit)
+ return;
+
+ var gridHeight = angular.element("div.gantt_ver_scroll").find("div").height();
+ var shouldLoad = top > ($scope.loadScrollTop + ((gridHeight - $scope.loadScrollTop) / 4));
+ adjustPopover();
+ if (!shouldLoad || $scope.isLoading() || !dataService.more || $scope.lazyLoading || top <= $scope.loadScrollTop || top <= $scope.lastScrollTop)
+ return;
+
+ $scope.loadScrollTop = top;
+ $scope.lazyLoading = true;
+ dataService.getChanges(urlService.socId).then(function(model) {
+ if (dataService.count >= $window.NOW.sn_chg_soc.limit)
+ socNotification.show("warning", i18n.format(i18n.getMessage("This schedule has exceeded the event limit. The first {0} events based on your order criteria will be displayed."), $window.NOW.sn_chg_soc.limit), 0);
+
+ // Need to provide the tasks so it can calc min/max
+ ganttScale.setDateRange(dataService.tasks.data);
+ ganttScale.configureScale();
+ $scope.gantt.clearAll();
+ ganttChart.addNowMarker(urlService.socId);
+ // these are the created tasks that will be added to the gantt
+ $scope.gantt.parse(dataService.tasks, "json");
+ $scope.lazyLoading = false;
+ });
+ });
+
+ /**
+ * Scales
+ */
+ // Only visible scale is rendered
+ $scope.gantt.config.smart_scales = true;
+ // Removes vertical borders on cells
+ $scope.gantt.config.show_task_cells = false;
+ $scope.gantt.config.scale_height = 60;
+ $scope.gantt.config.row_height = 40;
+ $scope.gantt.config.duration_unit = "hour";
+ $scope.gantt.config.duration_step = 1;
+ $scope.gantt.config.scale_unit = "day";
+ $scope.gantt.config.date_scale = "%j %M %Y";
+ $scope.gantt.config.subscales = [{
+ unit: "hour",
+ step: 1,
+ date: "%H:%i"
+ }];
+
+ /**
+ * UI Components
+ */
+ $scope.gantt.config.show_progress = false;
+ $scope.gantt.config.drag_links = false;
+ $scope.gantt.config.drag_move = false;
+ $scope.gantt.config.drag_resize = false;
+
+ /**
+ * Templates
+ */
+ // Configure use of icons in the gantt rows
+ $scope.gantt.templates.grid_open = function(item) {
+ return "";
+ };
+ $scope.gantt.templates.grid_folder = function(item) {
+ return "";
+ };
+ $scope.gantt.templates.grid_file = function(item) {
+ return "";
+ };
+ $scope.gantt.templates.grid_indent = function(item) {
+ return "";
+ };
+ $scope.gantt.templates.grid_row_class = function(start, end, task) {
+ return "";
+ };
+ $scope.gantt.templates.task_row_class = function(start, end, task) {
+ return "";
+ };
+ $scope.gantt.templates.task_class = function(start, end, task) {
+ return SOC.STYLE_CLASS_MAP.soc_event_bar;
+ };
+ $scope.gantt.templates.task_text = function(start, end, task) {
+ return "";
+ };
+
+ function getNode(node) {
+ if (node.hasClass("gantt_row"))
+ return angular.element(node.children(".gantt_cell")[0]);
+ if (!node.hasClass("gantt_cell") || node.hasClass("gantt_task_content") || node.hasClass("gantt_task_drag"))
+ node = node.parent();
+ return node;
+ }
+
+ function getTargetElement($event) {
+ var node = angular.element($event.target || $event.srcElement);
+ if ($event.type === "keydown")
+ return angular.element($event.target);
+ node = getNode(node);
+ if (node.hasClass("gantt_task_line"))
+ return node;
+ return null;
+ }
+
+ function handleOpenRecord() {
+ var task = $filter("filter")(dataService.tasks.data, {
+ id: this.targetId
+ })[0];
+ $window.location.href = task.table + ".do?&sys_id=" + task.sys_id +
+ "&sysparm_redirect=" + encodeURIComponent("sn_chg_soc_change_soc.do?sysparm_id=" + urlService.socId);
+ }
+
+ function _handleDestroyPopover() {
+ if (angular.element("[soc-popover]").length === 0)
+ return "";
+ angular.element("[soc-popover]").focus();
+ angular.element("[soc-popover]").attr("aria-expanded", "false");
+ angular.element("[soc-popover]").removeAttr("soc-popover");
+ angular.element(".popover.soc-task-popover").popover("destroy");
+ }
+
+ function _handleDestroyFlyout() {
+ $scope.$broadcast("sn.aside.change_soc_side.close");
+ }
+
+ function getTargetSelector($event, taskObj) {
+ if ($event.type !== "keydown") {
+ var selector = ".gantt_grid_data ." + $event.target.className;
+ var result = angular.element(selector);
+ var targetClass = (result.length > 0) ? ".gantt_grid" : ".gantt_task";
+ return (result.length > 0) ? targetClass + " [task_id='" + taskObj.id + "'] .gantt_cell:first" : targetClass + " .gantt_task_line[task_id='" + taskObj.id + "']";
+ } else
+ return ".gantt_grid [task_id='" + taskObj.id + "'] .gantt_cell:first";
+ }
+
+ function getX(target) {
+ var result = {
+ "start": 0,
+ "end": 0
+ };
+ var targetElement = {
+ "start": angular.element(target).offset().left,
+ "end": angular.element(target).offset().left + angular.element(target).width()
+ };
+ var visibleArea = angular.element(".gantt_task");
+ var visibleAreaLimits = {
+ "start": visibleArea.offset().left,
+ "end": visibleArea.offset().left + visibleArea.width()
+ };
+ result.start = (targetElement.start > visibleAreaLimits.start) ? targetElement.start : visibleAreaLimits.start;
+ result.end = (targetElement.end < visibleAreaLimits.end) ? targetElement.end : visibleAreaLimits.end;
+ return result.start + (result.end - result.start) / 2;
+ }
+
+ // Callback function used for building the popover template
+ function buildPopoverTemplate(taskObj, $event, popoverContent, popoverTemplate) {
+ var $popoverScope = $scope.$new(true);
+ $popoverScope.openRecord = i18n.getMessage("Open Record");
+ $popoverScope.handleOpenRecord = handleOpenRecord;
+ var targetSelector = getTargetSelector($event, taskObj);
+ var target = angular.element(targetSelector);
+ $popoverScope.targetId = taskObj.id;
+ popoverTemplate = $compile(popoverTemplate)($popoverScope);
+ target.attr("tabindex", "0");
+ target.attr("aria-expanded", "true");
+ target.attr("soc-popover", "opened");
+ var options = {
+ "container": "body",
+ "viewport": {
+ "selector": "body",
+ "padding": 20
+ },
+ "html": true,
+ "trigger": "manual",
+ "placement": "auto",
+ "title": taskObj.number + " - " + (taskObj.record.short_description ? taskObj.record.short_description.display_value : ""),
+ "content": popoverContent,
+ "template": popoverTemplate
+ };
+ target.popover(options);
+ if (targetSelector.indexOf("gantt_task") !== -1) {
+ target.data("bs.popover").options.atMouse = $event.pageX !== 0;
+ target.data("bs.popover").options.mousePos = {
+ "x": getX(target),
+ "y": $event.pageY
+ };
+ }
+ var action = angular.element(".popover.soc-task-popover").hasClass("in") ? "hidden" : "shown";
+ target.on(action + ".bs.popover", function($ev) {
+ if ($ev.type === "shown") {
+ _handleDestroyFlyout();
+ angular.element(".soc-btn-open-record").focus();
+ position.delta = {
+ top: angular.element(".gantt_ver_scroll").scrollTop(),
+ left: angular.element(".gantt_hor_scroll").scrollLeft()
+ };
+ position.original = {
+ top: angular.element(".popover.soc-task-popover").offset().top,
+ left: angular.element(".popover.soc-task-popover").offset().left
+ };
+ // Amend popover height if it is taller than remaining part of the window
+ var popoverElement = angular.element(".soc-task-popover");
+ var windowHeight = angular.element(window).height();
+ var maxHeight = windowHeight - popoverElement.offset().top;
+ if (popoverElement.height() > maxHeight)
+ popoverElement.height(maxHeight + "px");
+ } else
+ _handleDestroyPopover();
+ });
+ target.popover("toggle");
+ }
+
+ function getTooltipTextToDisplay() {
+
+ }
+
+ // Callback function used for building the popover content
+ function buildPopoverContent(taskObj, $event, popoverContent) {
+ $templateCache.remove(getTemplateUrl("sn_chg_soc_change_soc_popover_template.xml"));
+ var $popoverContentScope = $scope.$new(true);
+ $popoverContentScope.leftFields = taskObj.left_fields;
+ $popoverContentScope.rightFields = taskObj.right_fields;
+ $popoverContentScope.emptyValue = "[" + i18n.getMessage("Empty") + "]";
+ popoverContent = $compile(popoverContent)($popoverContentScope);
+ $templateRequest(getTemplateUrl("sn_chg_soc_change_soc_popover_template.xml")).then(buildPopoverTemplate.bind(this, taskObj, $event, popoverContent));
+ }
+
+ function openPopover(id, $event) {
+ var targetElement = getTargetElement($event);
+ var openedPopover = angular.element("[soc-popover]");
+ if (targetElement === null || openedPopover.length > 0) {
+ _handleDestroyPopover();
+ if (targetElement === null || openedPopover.attr("task_id") === id)
+ return;
+ }
+ _handleDestroyFlyout();
+ $event.stopPropagation();
+ var taskObj = $filter("filter")(dataService.tasks.data, {
+ "id": id
+ }, true)[0];
+ $templateRequest(getTemplateUrl("sn_chg_soc_change_soc_task_popover.xml")).then(buildPopoverContent.bind(this, taskObj, $event));
+ }
+
+ /**
+ * Events
+ **/
+ $scope.gantt.attachEvent("onTaskClick", function(id, $event) {
+ openPopover(id, $event);
+ return true;
+ });
+
+ $scope.gantt.attachEvent("onTaskDblClick", function(id, e) {
+ return false;
+ });
+
+ $scope.gantt.addShortcut("enter", function($event) {
+ openPopover(this.taskId, $event);
+ }, "taskRow");
+
+ $scope.gantt.addShortcut("tab", function($event) {}, "taskRow");
+
+ $scope.gantt.attachEvent("onTaskSelected", function(id, item) {
+ return true;
+ });
+
+ $scope.gantt.attachEvent("onBeforeTaskSelected", function(id, item) {
+ return true;
+ });
+
+ function getScheduleEvent(task, startDate, endDate, styleClass) {
+ startDate = $scope.gantt.date.parseDate(startDate, "xml_date");
+ endDate = $scope.gantt.date.parseDate(endDate, "xml_date");
+ var sizes = $scope.gantt.getTaskPosition(task, startDate, endDate);
+ var el = document.createElement("div");
+ el.className = "schedule-bar " + styleClass;
+ el.style.left = sizes.left + "px";
+ el.style.width = sizes.width + "px";
+ el.style.top = sizes.top + "px";
+ return el;
+ }
+
+ // Add task layer for blackout windows
+ $scope.ganttInstance.addTaskLayer(function(task) {
+ if (task.blackout_spans.length === 0 && task.maint_spans.length === 0)
+ return;
+ var wrapper = document.createElement("div");
+ if (dataService.definition.show_maintenance.value)
+ task.maint_spans.forEach(function(maintSpan) {
+ wrapper.appendChild(getScheduleEvent(task, maintSpan.start, maintSpan.end, "maint"));
+ });
+ if (dataService.definition.show_blackout.value)
+ task.blackout_spans.forEach(function(blackoutSpan) {
+ wrapper.appendChild(getScheduleEvent(task, blackoutSpan.start, blackoutSpan.end, "blackout"));
+ });
+ return wrapper;
+ });
+
+ $scope.gantt.attachEvent("onGanttRender", function() {
+ $element.find(".gantt_container").attr("role", "grid");
+ angular.element('[data-toggle="tooltip"]').tooltip('destroy');
+ angular.element(".tooltip[id^='tooltip']").remove();
+ $element.find('[data-toggle="tooltip"]').tooltip();
+ });
+
+ // Locale information must be associated with gantt object attached to window
+ $window.gantt.locale = {
+ date: {
+ month_full: [i18n.getMessage("January"),
+ i18n.getMessage("February"),
+ i18n.getMessage("March"),
+ i18n.getMessage("April"),
+ i18n.getMessage("May"),
+ i18n.getMessage("June"),
+ i18n.getMessage("July"),
+ i18n.getMessage("August"),
+ i18n.getMessage("September"),
+ i18n.getMessage("October"),
+ i18n.getMessage("November"),
+ i18n.getMessage("December")
+ ],
+ month_short: [i18n.getMessage("Jan"),
+ i18n.getMessage("Feb"),
+ i18n.getMessage("Mar"),
+ i18n.getMessage("Apr"),
+ i18n.getMessage("May"),
+ i18n.getMessage("Jun"),
+ i18n.getMessage("Jul"),
+ i18n.getMessage("Aug"),
+ i18n.getMessage("Sep"),
+ i18n.getMessage("Oct"),
+ i18n.getMessage("Nov"),
+ i18n.getMessage("Dec")
+ ],
+ day_full: [i18n.getMessage("Sunday"),
+ i18n.getMessage("Monday"),
+ i18n.getMessage("Tuesday"),
+ i18n.getMessage("Wednesday"),
+ i18n.getMessage("Thursday"),
+ i18n.getMessage("Friday"),
+ i18n.getMessage("Saturday")
+ ],
+ day_short: [i18n.getMessage("Sun"),
+ i18n.getMessage("Mon"),
+ i18n.getMessage("Tue"),
+ i18n.getMessage("Wed"),
+ i18n.getMessage("Thu"),
+ i18n.getMessage("Fri"),
+ i18n.getMessage("Sat")
+ ]
+ },
+ labels: {}
+ };
+
+ $scope.zoomIn = function() {
+ _handleDestroyPopover();
+ ganttScale.zoom(++$scope.ganttScale.level, urlService.socId);
+ };
+
+ $scope.zoomOut = function() {
+ _handleDestroyPopover();
+ ganttScale.zoom(--$scope.ganttScale.level, urlService.socId);
+ };
+
+ $scope.gantt.init($element[0]);
+ }
+ };
+ }
+ ])
+ .controller("ChangeSoCCtrl", ["$scope", "$document", "$timeout", "$window", "$location", "ganttChart", "styleService", "configService", "shareService", "dataService", "urlService", "ganttScale", "getTemplateUrl", "i18n", "SOC", "TextSearchService", "socNotification",
+ function($scope, $document, $timeout, $window, $location, ganttChart, styleService, configService, shareService, dataService, urlService, ganttScale, getTemplateUrl, i18n, SOC, TextSearchService, socNotification) {
+ var changeSoCCtrl = this;
+
+ changeSoCCtrl.share = {
+ canWrite: false
+ };
+
+ changeSoCCtrl.closeFlyout = function() {
+ $scope.$apply(function() {
+ $scope.$broadcast("sn.aside.change_soc_side.close");
+ });
+ };
+
+ $scope.loadingElements = {};
+ $scope.dataService = dataService;
+ $scope.ganttScale = ganttScale;
+ $scope.urlService = urlService;
+
+ $scope.pageLeft = function($event) {
+ if ($event && $event.keyCode !== SOC.KEYS.ENTER && $event.keyCode !== SOC.KEYS.SPACE)
+ return;
+ var gantt = ganttChart.getGantt(urlService.socId);
+ var left = gantt.getScrollState().x - angular.element("div.gantt_scale_cell").width();
+ gantt.scrollTo(left < 0 ? 0 : left, null);
+ };
+
+ $scope.pageRight = function($event) {
+ if ($event && $event.keyCode !== SOC.KEYS.ENTER && $event.keyCode !== SOC.KEYS.SPACE)
+ return;
+ var gantt = ganttChart.getGantt(urlService.socId);
+ var left = gantt.getScrollState().x + angular.element("div.gantt_scale_cell").width();
+ var scrollLength = angular.element("div.gantt_hor_scroll > div").width();
+ gantt.scrollTo(left > scrollLength ? scrollLength : left, null);
+ };
+
+ $scope.scrollToday = function() {
+ var gantt = ganttChart.getGantt(urlService.socId);
+ gantt.showDate(new Date());
+ };
+
+ $scope.openView = function(viewId, event) {
+ // We already have something open, need to deal with that first
+ if ($scope.activeAside === viewId) {
+ $scope.$broadcast("sn.aside.change_soc_side.close");
+ if (event)
+ event.target.blur();
+ } else {
+ var view;
+ switch (viewId) {
+ case "share":
+ view = getView(viewId, "sn_chg_soc_aside_share.xml");
+ break;
+ case "style":
+ view = getView(viewId, "sn_chg_soc_aside_style.xml");
+ break;
+ case "style_def":
+ view = getView(viewId, "sn_chg_soc_aside_style_page.xml", true);
+ break;
+ case "config":
+ view = getView(viewId, "sn_chg_soc_aside_config.xml");
+ break;
+ case "keyboard":
+ view = getView(viewId, "sn_chg_soc_aside_keyboard.xml");
+ break;
+ }
+ if (view !== undefined) {
+ angular.element(".sn-aside_right").show();
+ $scope.activeAside = viewId;
+ $scope.$broadcast("sn.aside.change_soc_side.open", view, "320px");
+ }
+ }
+ };
+
+ $scope.$on("sn.aside.change_soc_side.close", function() {
+ switch ($scope.activeAside) {
+ case "share":
+ angular.element("#share_side").focus();
+ break;
+ case "style":
+ angular.element("#style_side").focus();
+ break;
+ case "style_def":
+ angular.element("#style_side").focus();
+ break;
+ case "config":
+ angular.element("#config_side").focus();
+ break;
+ case "keyboard":
+ angular.element("#keyboard_side").focus();
+ break;
+ }
+ $scope.activeAside = "";
+ angular.element(".sn-aside_right").hide();
+ });
+
+ $scope.$on("sn.aside.change_soc_side.open_style", function(event, style) {
+ styleService.selectedStyle = style;
+ $scope.openView("style_def");
+ });
+
+ $scope.$on("sn.aside.change_soc_side.style_updated", function(event, result) {
+ if (result.style_sheet) {
+ var socStyleSheet = $document[0].getElementById("soc_span_style");
+ socStyleSheet.innerHTML = result.style_sheet;
+ }
+
+ if (result.records) {
+ var gantt = ganttChart.getGantt(urlService.socId);
+ for (var i = 0; i < dataService.tasks.data.length; i++)
+ if (result.records[dataService.tasks.data[i].id].style_rule)
+ dataService.tasks.data[i].style_class = SOC.STYLE_PREFIX + result.records[dataService.tasks.data[i].id].style_rule.sys_id;
+ gantt.parse(dataService.tasks, "json");
+ }
+ });
+
+ $scope.$on("sn.aside.change_soc_side.open_share", function(event, model) {
+ shareService.model = model;
+ $scope.openView("share");
+ });
+
+ $scope.$on("sn.aside.change_soc_side.open_share:closed", function(event, model) {
+ $scope.openView("share");
+ });
+
+ $scope.$on("sn.aside.change_soc_side.historyBack.completed", function(e, view) {
+ $scope.activeAside = view.title;
+ });
+
+ function getView(name, template, isChild) {
+ return {
+ scope: $scope,
+ title: name,
+ templateUrl: getTemplateUrl(template),
+ isChild: isChild
+ };
+ }
+
+ // Global keyboard shortcuts
+ $document.on("keydown", function(event) {
+ // Open keyboard help side
+ if (event.shiftKey && event.which == SOC.KEYS.SLASH && event.originalEvent.target.tagName !== "INPUT") {
+ $scope.$apply(function() {
+ if ($scope.activeAside === "keyboard") {
+ $scope.$broadcast("sn.aside.change_soc_side.close");
+ if (event)
+ event.target.blur();
+ } else {
+ $scope.activeAside = "keyboard";
+ $scope.$broadcast("sn.aside.change_soc_side.open", getView("keyboard", "sn_chg_soc_aside_keyboard.xml"), "320px");
+ }
+ });
+ }
+ });
+
+ var getChildTaskDividerClass = function(start, end, task) {
+ if (!task.parent)
+ return "";
+
+ var classStyle = " " + SOC.STYLE_CLASS_MAP.soc_row_child;
+
+ var nextTask = this.ganttChart.getNext(task.id);
+ nextTask = nextTask ? this.ganttChart.getTask(nextTask) : null;
+ var previousTask = this.ganttChart.getPrev(task.id);
+ previousTask = previousTask ? this.ganttChart.getTask(previousTask) : null;
+
+ // Only child task for a parent
+ if (previousTask && !previousTask.parent)
+ if (!nextTask || (nextTask && !nextTask.parent))
+ return classStyle += " " + SOC.STYLE_CLASS_MAP.soc_row_child_single;
+
+ // First child task for their parent
+ if (previousTask && !previousTask.parent && (nextTask && nextTask.parent))
+ return classStyle += " " + SOC.STYLE_CLASS_MAP.soc_row_child_start;
+
+ // Last child task for their parent
+ if (previousTask && previousTask.parent && ((nextTask && !nextTask.parent) || !nextTask))
+ return classStyle += " " + SOC.STYLE_CLASS_MAP.soc_row_child_end;
+
+ return classStyle;
+ };
+
+ var defineClassTemplate = function(start, end, task) {
+ var classStyle = "";
+
+ if (this.type)
+ classStyle += SOC.STYLE_CLASS_MAP[this.type];
+
+ classStyle += getChildTaskDividerClass.call({
+ ganttChart: this.ganttChart
+ }, null, null, task);
+
+ if (task.style_class)
+ classStyle += " " + task.style_class;
+
+ return classStyle;
+ };
+
+ var dateToStr = gantt.date.date_to_str(gantt.config.task_date);
+
+ function updateMarkerInterval(gantt, markerId) {
+ var today = gantt.getMarker(markerId);
+ today.start_date = new Date();
+ today.title = dateToStr(today.start_date);
+ gantt.updateMarker(markerId);
+ }
+
+ function addNowMarker(gantt) {
+ var markerId = gantt.addMarker({
+ start_date: new Date(),
+ css: "today-marker",
+ title: dateToStr(new Date()),
+ text: " "
+ });
+ setInterval(updateMarkerInterval(gantt, markerId), 1000 * 60);
+ }
+
+ function addScheduleSpanStyle(definition) {
+ var socStyleSheet = $document[0].createElement("style");
+ socStyleSheet.id = "soc_schedule_style";
+ $document[0].head.appendChild(socStyleSheet);
+
+ var maintColor = definition.maintenance_span_color.value ? definition.maintenance_span_color.value : SOC.MAINT_SPAN_COLOR;
+ var blackoutColor = definition.blackout_span_color.value ? definition.blackout_span_color.value : SOC.BLACKOUT_SPAN_COLOR;
+
+ var spanStyleSheet;
+ for (var i = 0; i < $document[0].styleSheets.length; i++)
+ if ($document[0].styleSheets[i].ownerNode.id === socStyleSheet.id) {
+ spanStyleSheet = $document[0].styleSheets[i];
+ break;
+ }
+
+ if (!spanStyleSheet)
+ return;
+
+ spanStyleSheet.insertRule("div.schedule-bar.maint {background-color: " + maintColor + ";}", 0);
+ spanStyleSheet.insertRule("div.schedule-bar.blackout {background-color: " + blackoutColor + ";}", 0);
+ }
+
+ changeSoCCtrl.initPage = function() {
+ dataService.initPage(urlService.socId).then(function() {
+ styleService.initStyle();
+
+ // Setup for share panel
+ changeSoCCtrl.share.canWrite = dataService.canWrite();
+
+ // Setup configuration panel
+ configService.showBlackoutOption = configService.showBlackoutSchedules = dataService.definition.show_blackout.value;
+ configService.showMaintOption = configService.showMaintSchedules = dataService.definition.show_maintenance.value;
+ configService.setChildRecords(dataService.child_table);
+
+ // Need to apply changes due to style info
+ var socStyleSheet = document.createElement("style");
+ socStyleSheet.id = "soc_span_style";
+ document.head.appendChild(socStyleSheet);
+ socStyleSheet.innerHTML = dataService.style.style_sheet;
+
+ addScheduleSpanStyle(dataService.definition);
+
+ var gantt = ganttChart.getGantt(urlService.socId);
+ gantt.templates.grid_row_class = defineClassTemplate.bind({
+ ganttChart: gantt,
+ type: "",
+ });
+ gantt.templates.task_row_class = getChildTaskDividerClass.bind({
+ ganttChart: gantt
+ });
+ gantt.templates.task_class = defineClassTemplate.bind({
+ ganttChart: gantt,
+ type: "soc_event_bar",
+ });
+ gantt.render();
+
+ // Need to provide the tasks so it can calc min/max
+ ganttScale.setDateRange(dataService.tasks.data);
+ ganttScale.configureScale();
+ gantt.clearAll();
+ addNowMarker(gantt);
+ // these are the created tasks that will be added to the gantt
+ gantt.parse(dataService.tasks, "json");
+ if (dataService.tasks.data.length > 0) {
+ gantt.showTask(dataService.tasks.data[0].id);
+ $scope.noResults = false;
+ } else
+ $scope.noResults = true;
+ }).catch(function(response) {
+ socNotification.show("error", response.data.error.message);
+ });
+ };
+
+ $scope.filter = {
+ filterConditions: ["number", "config_item", "Short Description", "children.number", "children.config_item"],
+ placeholderText: i18n.getMessage("Search Change Schedule")
+ };
+
+ function buildFilterData() {
+ var augmentedData = dataService.tasks.data;
+ dataService.tasks.data.forEach(function(obj, index) {
+ augmentedData[index].children = dataService.getChildren(obj.id);
+ });
+ return augmentedData;
+ }
+
+ $scope.$on("textSearch", function(event, textSearch) {
+ var filteredRecords = TextSearchService.getFilteredArray(buildFilterData(), textSearch);
+ ganttChart.updateGanttData(urlService.socId, filteredRecords);
+ $scope.noResults = filteredRecords.length === 0;
+ });
+
+ $scope.isLoading = function() {
+ return $scope.$parent.loading;
+ };
+
+ changeSoCCtrl.messages = {
+ "No records to display": i18n.getMessage("No records to display"),
+ "No records match the filter": i18n.getMessage("No records match the filter"),
+ "Change Schedule": i18n.getMessage("Change Schedule"),
+ "Close panel": i18n.getMessage("Close panel"),
+ "Configuration": i18n.getMessage("Configuration"),
+ "Share": i18n.getMessage("Share"),
+ "Open context menu": i18n.getMessage("Open context menu"),
+ "Filter": i18n.getMessage("Filter"),
+ "Keyboard Shortcuts": i18n.getMessage("Keyboard Shortcuts"),
+ "Search Change Schedule": i18n.getMessage("Search Change Schedule"),
+ "Span Styles": i18n.getMessage("Span Styles"),
+ "Today": i18n.getMessage("Today"),
+ "Zoom in": i18n.getMessage("Zoom in"),
+ "Zoom out": i18n.getMessage("Zoom out"),
+ "Page left": i18n.getMessage("Page left"),
+ "Page right": i18n.getMessage("Page right"),
+ "Show today": i18n.getMessage("Show today")
+ };
+
+ $scope.noResults = false;
+ var noResultsElem = "" + changeSoCCtrl.messages["No records to display"] + "
";
+
+ function noResults(newValue, oldValue) {
+ if (newValue === oldValue)
+ return;
+ if (newValue) {
+ angular.element("div.gantt_marker.today-marker").hide();
+ angular.element("div.gantt_task_scale").after(noResultsElem);
+ } else {
+ angular.element("div.gantt_marker.today-marker").show();
+ angular.element("div.no-results").remove();
+ }
+ }
+ $scope.$watch("noResults", noResults);
+ }
+ ])
+ .filter("objectKeys", [function() {
+ return function(anObject) {
+ if (!anObject)
+ return null;
+ return Object.keys(anObject);
+ };
+ }])
+ .filter("objectKeysLength", [function() {
+ return function(anObject) {
+ if (!anObject)
+ return null;
+ return Object.keys(anObject).length;
+ };
+ }]);
diff --git a/Client-Side Components/UI Scripts/Custom Change Schedule/config.js b/Client-Side Components/UI Scripts/Custom Change Schedule/config.js
new file mode 100644
index 0000000000..0a388521e6
--- /dev/null
+++ b/Client-Side Components/UI Scripts/Custom Change Schedule/config.js
@@ -0,0 +1,146 @@
+angular.module("sn.chg_soc.config", ["sn.common"])
+ .service("configService", ["dataService", "ganttChart", "urlService", "SOC", function(dataService, ganttChart, urlService, SOC) {
+ var configService = this;
+
+ configService.showConfigItem = true;
+ configService.showDuration = true;
+ configService.showShortDesc=true;
+ configService.showBlackoutOption = true;
+ configService.showBlackoutSchedules = true;
+ configService.showMaintOption = true;
+ configService.showMaintSchedules = true;
+
+ configService.childRecords = {};
+
+ configService.setChildRecords = function(childTables) {
+ for (var tableName in childTables)
+ configService.childRecords[tableName] = {
+ inputId: tableName + "Option",
+ label: childTables[tableName].__label,
+ name: tableName + "Show",
+ show: true,
+ change: updateChildRecords
+ };
+ };
+
+ function updateChildRecords(tableName) {
+ var gantt = ganttChart.getGantt(urlService.socId);
+ var ganttTasks = gantt.getTaskByTime();
+ for (var i = 0; i < ganttTasks.length; i++)
+ if (ganttTasks[i].parent && ganttTasks[i].table === tableName)
+ ganttTasks[i].__visible = configService.childRecords[tableName].show;
+ gantt.attachEvent("onBeforeTaskDisplay", function(id, task) {
+ if (task.parent)
+ return task.__visible;
+ return true;
+ });
+ gantt.templates.grid_open = gridOpen;
+ gantt.render();
+ }
+
+ function gridOpen(task) {
+ var gantt = ganttChart.getGantt(urlService.socId);
+ var children = gantt.getChildren(task.id);
+
+ for (var i = 0; i < children.length; i++) {
+ var childTask = gantt.getTask(children[i]);
+ if (childTask.__visible)
+ return "";
+ }
+
+ return "";
+ }
+ }])
+ .directive("socAsideConfig", ["getTemplateUrl", "configService", "ganttChart", "dataService", "objectKeysLengthFilter", "SOC", "i18n", function(getTemplateUrl, configService, ganttChart, dataService, objectKeysLengthFilter, SOC, i18n) {
+ "use strict";
+ return {
+ restrict: "A",
+ templateUrl: getTemplateUrl("sn_chg_soc_aside_config_body.xml"),
+ scope: {
+ socDefId: "="
+ },
+ controller: function($scope, objectKeysLengthFilter) {
+ // $scope.showConfigItem = configService.showConfigItem;
+ // $scope.showDuration = configService.showDuration;
+ $scope.showBlackoutOption = configService.showBlackoutOption;
+ $scope.showShortDesc = configService.showShortDesc;
+ $scope.showBlackoutSchedules = configService.showBlackoutSchedules;
+ $scope.showMaintOption = configService.showMaintOption;
+ $scope.showMaintSchedules = configService.showMaintSchedules;
+ $scope.childRecords = configService.childRecords;
+ $scope.objectKeysLengthFilter = objectKeysLengthFilter;
+ $scope.messages = {
+ "Child Records": i18n.getMessage("Child Records"),
+ "Columns": i18n.getMessage("Columns"),
+ "Configuration Item": i18n.getMessage("Configuration Item"),
+ "Short Description": i18n.getMessage("Short Description"),
+ "Duration": i18n.getMessage("Duration"),
+ "Related Records": i18n.getMessage("Related Records"),
+ "Schedules": i18n.getMessage("Schedules"),
+ "Blackout": i18n.getMessage("Blackout"),
+ "Maintenance": i18n.getMessage("Maintenance")
+ };
+
+ $scope.updateColumnLayout = function(columnId) {
+ var gantt = ganttChart.getGantt($scope.socDefId);
+ var column = gantt.getGridColumn(columnId);
+ if (SOC.COLUMN.CONFIG_ITEM === columnId) {
+ configService.showConfigItem = !configService.showConfigItem;
+ column.hide = !configService.showConfigItem;
+ } else if (SOC.COLUMN.DURATION === columnId) {
+ configService.showDuration = !configService.showDuration;
+ column.hide = !configService.showDuration;
+ } else if (SOC.COLUMN.SHORT_DESCRIPTION === columnId) {
+ configService.showShortDesc = !configService.showShortDesc;
+ column.hide = !configService.showShortDesc;
+ } else
+ return;
+ gantt.render();
+ };
+
+ function getScheduleEvent(task, startDate, endDate, styleClass) {
+ var gantt = ganttChart.getGantt($scope.socDefId);
+ startDate = gantt.date.parseDate(startDate, "xml_date");
+ endDate = gantt.date.parseDate(endDate, "xml_date");
+ var sizes = gantt.getTaskPosition(task, startDate, endDate);
+ var el = document.createElement("div");
+ el.className = "schedule-bar " + styleClass;
+ el.style.left = sizes.left + "px";
+ el.style.width = sizes.width + "px";
+ el.style.top = sizes.top + "px";
+ return el;
+ }
+
+ var scheduleTaskLayer = function(task) {
+ if ((!this.show_blackout && !this.show_maint) || (task.blackout_spans.length === 0 && task.maint_spans.length === 0))
+ return;
+ var wrapper = document.createElement("div");
+ if (this.show_blackout && dataService.definition.show_blackout.value)
+ task.blackout_spans.forEach(function(blackoutSpan) {
+ wrapper.appendChild(getScheduleEvent(task, blackoutSpan.start, blackoutSpan.end, "blackout"));
+ });
+ if (this.show_maint && dataService.definition.show_maintenance.value)
+ task.maint_spans.forEach(function(maintSpan) {
+ wrapper.appendChild(getScheduleEvent(task, maintSpan.start, maintSpan.end, "maint"));
+ });
+ return wrapper;
+ };
+
+ $scope.updateScheduleLayer = function() {
+ configService.showBlackoutSchedules = $scope.showBlackoutSchedules;
+ configService.showMaintSchedules = $scope.showMaintSchedules;
+ var ganttInstance = ganttChart.getInstance($scope.socDefId);
+ ganttInstance.removeTaskLayer();
+
+ if ($scope.showBlackoutSchedules || $scope.showMaintSchedules) {
+ ganttInstance.addTaskLayer(scheduleTaskLayer.bind({
+ show_blackout: $scope.showBlackoutSchedules,
+ show_maint: $scope.showMaintSchedules
+ }));
+ ganttInstance.gantt.render();
+ }
+ };
+ }
+ };
+ }]);
diff --git a/Client-Side Components/UI Scripts/Custom Change Schedule/data.js b/Client-Side Components/UI Scripts/Custom Change Schedule/data.js
new file mode 100644
index 0000000000..e521f41ba3
--- /dev/null
+++ b/Client-Side Components/UI Scripts/Custom Change Schedule/data.js
@@ -0,0 +1,272 @@
+angular.module("sn.chg_soc.data", [])
+ .service("dataService", ["$http", "$q", "$window", "i18n", "urlService", "ganttChart", "durationFormatter", "SOC", "$filter", function($http, $q, $window, i18n, urlService, ganttChart, durationFormatter, SOC, $filter) {
+ var dataService = this;
+
+ dataService.more = false;
+ dataService.count = 0;
+ dataService.child_table = {};
+ dataService.definition = {};
+ dataService.style = {
+ chg_soc_style_rule: {},
+ chg_soc_definition_style_rule: {},
+ chg_soc_def_child_style_rule: {},
+ style_sheet: ""
+ };
+ dataService.tasks = {
+ data: [],
+ links: []
+ };
+ dataService.allRecords = {};
+
+ function isValidDate(date) {
+ if (Object.prototype.toString.call(date) === "[object Date]" && !isNaN(date.getTime()))
+ return true;
+ return false;
+ }
+
+ function buildFields(record, selectedFieldsList, tableMeta) {
+ var result = [];
+ if (!selectedFieldsList)
+ return result;
+ var selectedFields = selectedFieldsList.split(",");
+ selectedFields.forEach(function(fieldName) {
+ if (fieldName && tableMeta[fieldName])
+ result.push({
+ column_name: fieldName,
+ label: tableMeta[fieldName].label,
+ display_value: record[fieldName].display_value,
+ value: record[fieldName].value,
+ });
+ });
+ return result;
+ }
+
+ function buildRecord(record, chgSocDef, tableMeta, styleRule, scheduleWindow) {
+ var ganttUtil = ganttChart.getGantt(urlService.socId);
+ var startDate = ganttUtil.date.parseDate(record[chgSocDef.start_date_field.value].display_value_internal, "xml_date");
+ var endDate = ganttUtil.date.parseDate(record[chgSocDef.end_date_field.value].display_value_internal, "xml_date");
+ // Check start/end dates are valid before adding the task to gantt chart
+ if (!isValidDate(startDate) || !isValidDate(endDate))
+ return;
+
+ var recordEvent = {
+ id: record.sys_id ? record.sys_id.value : "",
+ text: record.number ? record.number.display_value : "",
+ number: record.number ? record.number.display_value : "",
+ chg_soc_def: chgSocDef.sys_id.value,
+ config_item: record.cmdb_ci ? record.cmdb_ci.display_value : "",
+ start_date: startDate,
+ end_date: endDate,
+ dur_display: durationFormatter.buildDurationDisplay(startDate, endDate),
+ order: 0,
+ progress: 0,
+ table: record.sys_class_name ? record.sys_class_name.value : chgSocDef.table_name.value,
+ left_fields: buildFields(record, chgSocDef.popover_left_col_fields.value, tableMeta),
+ right_fields: buildFields(record, chgSocDef.popover_right_col_fields.value, tableMeta),
+ record: record,
+ blackout_spans: [],
+ maint_spans: [],
+ sys_id: record.sys_id ? record.sys_id.value : "",
+ short_description: record.short_description ? record.short_description.display_value : "",
+ __visible: true
+ };
+
+ if (styleRule && styleRule.sys_id)
+ recordEvent.style_class = SOC.STYLE_PREFIX + styleRule.sys_id;
+
+ if (scheduleWindow) {
+ if (chgSocDef.show_maintenance.value)
+ angular.forEach(scheduleWindow.maintenance, function (schedule) {
+ Array.prototype.push.apply(recordEvent.maint_spans, schedule.spans);
+ });
+ if (chgSocDef.show_blackout.value)
+ angular.forEach(scheduleWindow.blackout, function (schedule) {
+ Array.prototype.push.apply(recordEvent.blackout_spans, schedule.spans);
+ });
+ } else {
+ recordEvent.id = chgSocDef.sys_id.value + "_" + recordEvent.id;
+ recordEvent.parent = record[chgSocDef.reference_field.value].value;
+ }
+
+ dataService.allRecords[recordEvent.id] = {
+ style_rule: styleRule,
+ sys_id: record.sys_id ? record.sys_id.value : "",
+ table_name: record.sys_class_name ? record.sys_class_name.value : chgSocDef.table_name.value,
+ chg_soc_def: chgSocDef.sys_id.value
+ };
+
+ return recordEvent;
+ }
+
+ function buildItem(result, item) {
+ // Build change_request record
+ var record = buildRecord(result[item.table_name][item.sys_id], result.chg_soc_definition, result[item.table_name].__table_meta, item.style, item.schedule_window);
+ if (!record)
+ return;
+
+ dataService.tasks.data.push(record);
+
+ // Build related tasks
+ if (item.related)
+ for (var childSocDefId in item.related) {
+ var childRecords = item.related[childSocDefId];
+ for (var i = 0; i < childRecords.length; i++) {
+ var childRecord = buildRecord(result[childRecords[i].table_name][childRecords[i].sys_id], result.chg_soc_definition.__child[childSocDefId], result[childRecords[i].table_name].__table_meta, childRecords[i].style);
+ if (childRecord)
+ dataService.tasks.data.push(childRecord);
+ }
+ }
+ }
+
+ dataService.buildData = function(result) {
+ if (!result)
+ return;
+
+ dataService.more = result.__more;
+ dataService.count = result.__change_count;
+
+ // Start with the definition object
+ if (result.chg_soc_definition)
+ dataService.definition = result.chg_soc_definition;
+
+ // Ordered change requests with style and related records
+ if (result.__struct)
+ for (var i = 0; i < result.__struct.length; i++)
+ buildItem(result, result.__struct[i]);
+
+ // Find all child tables
+ for (var table in result)
+ if (result[table].__has_children)
+ dataService.child_table[table] = result[table].__table_meta;
+
+ // Set style rules and style sheet to the model
+ dataService.style.chg_soc_style_rule = result.chg_soc_style_rule;
+ dataService.style.chg_soc_definition_style_rule = result.chg_soc_definition_style_rule;
+ dataService.style.chg_soc_def_child_style_rule = result.chg_soc_def_child_style_rule;
+ dataService.style.style_sheet = result._css;
+ };
+
+ dataService.addData = function(result) {
+ dataService.more = result.__more;
+ dataService.count = result.__change_count;
+
+ if (result.__struct)
+ for (var i = 0; i < result.__struct.length; i++)
+ buildItem(result, result.__struct[i]);
+
+ for (var table in result)
+ if (result[table].__has_children)
+ dataService.child_table[table] = result[table].__table_meta;
+ };
+
+ dataService.initPage = function(chgSocDefId, condition) {
+ var deferred = $q.defer();
+ var url = SOC.GET_CHANGE_SCHEDULE + chgSocDefId;
+ var config = {};
+ config.params = {
+ sysparm_ck: $window.g_ck
+ };
+ if (condition)
+ config.params.condition = condition;
+ $http.get(url, config).then(function(response){
+ deferred.resolve(dataService.buildData(response.data.result));
+ }, function(response) {
+ deferred.reject(response);
+ });
+ return deferred.promise;
+ };
+
+ dataService.getChanges = function() {
+ var deferred = $q.defer();
+ var url = SOC.GET_CHANGE_SCHEDULE + dataService.definition.sys_id.value;
+ var config = {};
+ config.params = {
+ sysparm_ck: $window.g_ck,
+ count: dataService.count
+ };
+ if (dataService.definition.condition.dryRun)
+ config.params.condition = dataService.definition.condition.value;
+
+ $http.get(url, config).then(function(response){
+ deferred.resolve(dataService.addData(response.data.result));
+ }, function(response) {
+ deferred.reject(response);
+ });
+ return deferred.promise;
+ };
+
+ dataService.getChildren = function(parentId) {
+ var res = $filter("filter")(dataService.tasks.data, function(task) {
+ return task.parent === parentId;
+ });
+ return res;
+ };
+
+ dataService.destroyData = function() {
+ dataService.more = false;
+ dataService.count = 0;
+ dataService.child_table = {};
+ dataService.definition = {};
+ dataService.style = {
+ chg_soc_style_rule: {},
+ chg_soc_definition_style_rule: {},
+ chg_soc_def_child_style_rule: {},
+ style_sheet: ""
+ };
+ dataService.tasks = {
+ data: [],
+ links: []
+ };
+ dataService.allRecords = {};
+ };
+
+ dataService.parseQuery = function(condition) {
+ condition = condition + "";
+ var deferred = $q.defer();
+ var url = SOC.GET_PARSE_QUERY + condition;
+ var config = {};
+ config.params = {};
+ config.params.sysparm_ck = $window.g_ck;
+
+ $http.get(url, config).then(function(response) {
+ deferred.resolve(response.data.result);
+ }, function(response) {
+ deferred.reject(response);
+ });
+
+ return deferred.promise;
+ };
+
+ function checkSecurityObject() {
+ return dataService.definition && dataService.definition.__security;
+ }
+
+ dataService.canCreate = function() {
+ if (checkSecurityObject() && dataService.definition.__security.canCreate)
+ return dataService.definition.__security.canCreate;
+ return false;
+ };
+
+ dataService.canRead = function() {
+ if (checkSecurityObject() && dataService.definition.__security.canRead)
+ return dataService.definition.__security.canRead;
+ return false;
+ };
+
+ dataService.canWrite = function() {
+ if (checkSecurityObject() && dataService.definition.__security.canWrite)
+ return dataService.definition.__security.canWrite;
+ return false;
+ };
+
+ dataService.canDelete = function() {
+ if (checkSecurityObject() && dataService.definition.__security.canDelete)
+ return dataService.definition.__security.canDelete;
+ return false;
+ };
+
+ dataService.trackEvent = function(source) {
+ if ($window.GlideWebAnalytics && $window.GlideWebAnalytics.trackEvent)
+ $window.GlideWebAnalytics.trackEvent('com.snc.change_management.soc', 'Change Schedules', source, 0, 0);
+ };
+ }]);