Name
global.OCRotation
Description
Core On-Call Rotation logic build a data structure which is used to display the calendar. The code builds up the GlideAJAXSchedulePage object that stores a list of span items. Works for the Gwt (legacy), see the OCRotationV2 script include to support other calendars.
Script
var OCRotation = Class.create();
OCRotation.prototype = {
/*
* Keeps track of when an a value (such as the start date) has
* been changed and a call to buildRotas is required
*/
_isDirty: true,
_inclusiveEndDate: true,
SHIFT_STATE: {
DRAFT: "draft"
},
SPAN_FETCH_PREFERENCE: {
ALL: "all", // It will fetch all the active rotas or inactive rotas in draft state
DRAFT_ONLY: "draft", // It will fetch all the inactive rotas in draft state only
NONE: "" // fetch only the active rotas
},
initialize: function(schedulePage) {
this.log = new GSLog("com.snc.on_call_rotation.log.level", this.type);
if (this.log.atLevel(GSLog.DEBUG))
this.log.debug("[initialize] START");
this.timer = new OCTimer(this.type);
this.legacySpanSuport = gs.getProperty("com.snc.on_call_rotation.support_legacy_spans", "false") == "false" ? false : true;
this.editRotaMsg = gs.getMessage('Edit rota');
this.viewRotaMsg = gs.getMessage('View rota');
this.sm = GlideSecurityManager.get();
this.arrayUtils = new ArrayUtil();
if (!JSUtil.nil(schedulePage)) {
this.setSchedulePage(schedulePage);
this.setStartDate(this.getSchedulePage().getParameter("start"));
this.setEndDate(this.getSchedulePage().getParameter("end"));
}
// make sure we have some dates
if (JSUtil.nil(this.getStartDate()))
this.setStartDate();
if (JSUtil.nil(this.getEndDate()))
this.setEndDate();
// check whether to compress adjacent spans or not
if (JSUtil.nil(this.getCompressTimeMap()))
this.setCompressTimeMap(true);
if (JSUtil.nil(this.getExcludeTimeOff()))
this.excludeTimeOff = true;
if (JSUtil.nil(this.getSpanFetchPreference()))
this.spanFetchPreference = this.SPAN_FETCH_PREFERENCE.NONE;
if (this.log.atLevel(GSLog.DEBUG))
this.log.debug("[initialize] END");
},
_init: function() {
if (this.log.atLevel(GSLog.DEBUG)) {
this.log.debug("[_init] startDate: " + this.getStartDate().getValue() + ", endDate: " + this.getEndDate().getValue() + ", timezone: " + this.getTimezone());
this.timer.start("[_init]");
}
if (JSUtil.nil(this.getSchedulePage()))
this.setSchedulePage(new SchedulePage());
var timezone = this.getTimezone();
var start = new GlideScheduleDateTime(this.getStartDate().getDisplayValue());
if (this.log.atLevel(GSLog.DEBUG))
this.log.debug("[_init] start converted: " + start);
start.setTimeZone(timezone);
var end = new GlideScheduleDateTime(this.getEndDate().getDisplayValue());
if (this.log.atLevel(GSLog.DEBUG))
this.log.debug("[_init] end converted: " + end);
end.setTimeZone(timezone);
if (this.log.atLevel(GSLog.DEBUG))
this.log.debug("[_init] start: " + start + " (" + start.getTimeZone().getID() + "), end: " + end + " (" + end.getTimeZone().getID() + "), timezone: " + this.getTimezone());
if (JSUtil.nil(this.getSchedulePage().getPage()))
this.getSchedulePage().setPage(new GlideAJAXSchedulePage(start, end, timezone));
this.getSchedulePage().clear();
this.getSchedulePage().getPage().setDateRange(start, end);
this.getSchedulePage().getPage().setTimeZone(timezone);
this.getSchedulePage().getPage().setCompressTimeMap(this.getCompressTimeMap());
if (this.log.atLevel(GSLog.DEBUG))
this.timer.stop("[_init]");
},
isDirty: function() {
return this._isDirty;
},
setDirty: function(isDirty) {
this._isDirty = isDirty;
return this;
},
getStartDate: function() {
return this.startDate;
},
/* Set the start date used when building rotas
*
* @param *optional* startDate [String]: When the time spans should start from as
* a string in the yyyy-MM-dd format. A default is applied if not provided which
* is the first day of the previous month.
*
* @param *optional* isDateTime [boolean]: set to true, if startDate has both Date and time. false, if it has only Date
*/
setStartDate: function(startDate, isDateTime) {
if (JSUtil.nil(startDate)) {
if (this.log.atLevel(GSLog.DEBUG))
this.log.debug("[setStartDate] no startDate set to current day minus one month");
var currentDateTime = new GlideDateTime();
currentDateTime.addMonthsLocalTime(-1);
currentDateTime.setDayOfMonthLocalTime(1);
startDate = new GlideDateTime();
startDate.setDisplayValueInternal(currentDateTime.getLocalDate() + " 00:00:00");
} else {
var temp = new GlideDateTime();
if (!isDateTime)
temp.setDisplayValueInternal(startDate + " 00:00:00");
else
temp.setDisplayValueInternal(startDate);
startDate = temp;
}
if (this.log.atLevel(GSLog.DEBUG))
this.log.debug("[setStartDate] startDate: " + startDate);
this.startDate = startDate;
this.setDirty(true);
return this;
},
getEndDate: function() {
return this.endDate;
},
/* Set the end date used when building rotas
*
* @param *optional* endDate [String]: When the time spans should end as a string
* in the yyyy-MM-dd format. A default is applied if not provided which is the
* last day of the next month.
*
* @param *optional* isDateTime [boolean]: set to true, if endDate has both Date and time. false, if it has only Date
*/
setEndDate: function(endDate, inclusive, isDateTime) {
this._inclusiveEndDate = JSUtil.nil(inclusive) ? true : inclusive;
if (JSUtil.nil(endDate)) {
if (this.log.atLevel(GSLog.DEBUG))
this.log.debug("[setEndDate] no endDate set to current day plus one month");
var currentDateTime = new GlideDateTime();
currentDateTime.addMonthsLocalTime(1);
currentDateTime.setDayOfMonthLocalTime(31);
endDate = new GlideDateTime();
endDate.setDisplayValueInternal(currentDateTime.getLocalDate() + " 23:59:59");
} else {
var temp = new GlideDateTime();
if (!isDateTime)
temp.setDisplayValueInternal(endDate + " 23:59:59");
else
temp.setDisplayValueInternal(endDate);
endDate = temp;
}
if (!this._inclusiveEndDate)
endDate.addDaysLocalTime(-1);
if (this.log.atLevel(GSLog.DEBUG))
this.log.debug("[setEndDate] endDate: " + endDate);
this.endDate = endDate;
this.setDirty(true);
return this;
},
getTimezone: function() {
if (JSUtil.nil(this.timezone))
this.timezone = GlideSession.get().getTimeZoneName();
return this.timezone;
},
setTimezone: function (timezone) {
this.timezone = timezone;
this.setDirty(true);
return this;
},
setExcludeTimeOff: function(excludeTimeOff) {
this.excludeTimeOff = excludeTimeOff;
return this;
},
getCompressTimeMap: function() {
return this.compressTimeMap;
},
setCompressTimeMap: function(compressTimeMap) {
this.compressTimeMap = compressTimeMap;
return this;
},
getExcludeTimeOff: function() {
return this.excludeTimeOff;
},
getGroupIds: function() {
return this.groupIds;
},
/* Set the Group ID used when building rotas
*
* groupIds [String]: A comma seperated list of sys_user_group
* sys_ids to filter the cmn_rota records fetched
*/
setGroupIds: function(groupIds) {
// Make sure this is always a String
this.groupIds = groupIds + "";
this.setDirty(true);
return this;
},
getRotaIds: function() {
return this.rotaIds;
},
/* Set the Rota IDs used when building rotas
*
* rotaIds [String]: A comma seperated list of cmn_rota sys_ids
* to filter the returned spans
*/
setRotaIds: function(rotaIds) {
this.rotaIds = rotaIds;
this.setDirty(true);
return this;
},
getRosterIds: function() {
return this.rosterIds;
},
/* Set the Roster IDs used when building rotas
*
* rosterIds [String]: A comma seperated list of cmn_rota_roster
* sys_ids to filter the returned spans
*/
setRosterIds: function(rosterIds) {
this.rosterIds = rosterIds;
this.setDirty(true);
return this;
},
getUserIds: function() {
return this.userIds;
},
/* Set the User IDs used when building rotas
*
* userIds [String]: A comma seperated list of sys_user sys_ids
* to filter the returned spans
*/
setUserIds: function(userIds) {
this.userIds = userIds;
this.setDirty(true);
return this;
},
getSchedulePage: function() {
this.schedulePage.getPage().setLegacy(this._isLegacy());
return this.schedulePage;
},
setSchedulePage: function(page) {
this.schedulePage = page;
var groupId = this.schedulePage.getParameter("group");
if (!JSUtil.nil(groupId))
this.setGroupIds(groupId + "");
this.setDirty(true);
return this;
},
getSpanFetchPreference: function () {
return this.spanFetchPreference;
},
setSpanFetchPreference: function(preference) {
if (JSUtil.notNil(preference))
this.spanFetchPreference = preference;
return this;
},
/*
* Get a GlideRecord for the cmn_rota table which is filtered by Rota IDs,
* Group IDs, Roster IDs and User IDs. The GlideRecord will only contain
* cmn_rota records which match all the criteria.
*
* @param *optional* rotaIds [String]: A comma separated list of cmn_rota sys_ids
*
* @param *optional* groupIds [String]: A comma separated list of sys_user_group sys_ids
* which are referred to by cmn_rota records
*
* @param *optional* rosterIds [String]: A comma separated list of cmn_rota_roster sys_ids
*
* @param *optional* userIds [String]: A comma separated list of sys_user sys_ids which
* are referred to by cmn_rota_member records
*
* @return [GlideRecord]: A queried GlideRecord
*/
getRotaGr: function(rotaIds, groupIds, rosterIds, userIds) {
if (this.log.atLevel(GSLog.DEBUG))
this.timer.start("[getRotaGr]");
var filteredRotaIds = "";
if (!JSUtil.nil(userIds))
filteredRotaIds = this._getRotaIdsFromMembers(userIds, rosterIds, rotaIds, groupIds);
else if (!JSUtil.nil(rosterIds))
filteredRotaIds = this._getRotaIdsFromRosters(rosterIds, rotaIds, groupIds);
else
filteredRotaIds = this._getRotaIdsFromRotas(rotaIds, groupIds);
var rotaGr = new GlideRecord("cmn_rota");
rotaGr.addQuery("sys_id", "IN", filteredRotaIds);
rotaGr.query();
if (this.log.atLevel(GSLog.DEBUG)) {
this.log.debug("[getRotaGr] rota count = " + rotaGr.getRowCount() + ", query = " + rotaGr.getEncodedQuery());
this.timer.stop("[getRotaGr]");
}
return rotaGr;
},
/*
* Builds the schedule spans and stores them within the Schedule Page object.
*/
buildRotas: function() {
if (this.log.atLevel(GSLog.DEBUG)) {
this.log.debug("[buildRotas]");
this.timer.start("[buildRotas]");
}
this._init();
var rotaGr = this.getRotaGr(this.getRotaIds(), this.getGroupIds(), this.getRosterIds(), this.getUserIds());
if (this.log.atLevel(GSLog.DEBUG))
this.log.debug("[buildRotas] rota count = " + rotaGr.getRowCount());
var userIds = {}; // gather a list of unique member sys_user ids
var rotaIds = [];
while (rotaGr.next()) {
var canWrite = rotaGr.canWrite();
var rotaId = rotaGr.sys_id + "";
rotaIds.push(rotaId);
this.log.debug("[buildRotas] editRights = " + canWrite + ", rotaId = " + rotaId);
var rotaMsg = canWrite ? this.editRotaMsg : this.viewRotaMsg;
if (this.log.atLevel(GSLog.DEBUG))
this.timer.start("[_buildUserSpan.addSchedule]");
var items = this.getSchedulePage().addSchedule(rotaGr.schedule, this._getEventBgColor(rotaId, "rota"), null, canWrite);
if (this.log.atLevel(GSLog.DEBUG))
this.timer.stop("[_buildUserSpan.addSchedule]");
var groupId = rotaGr.group + "";
var rotaLink = rotaGr.getLink(true);
for (var i = 0; i < items.size(); i++) {
var rotaItem = items.get(i);
rotaItem.setGroupId(groupId);
rotaItem.addMenuURL(rotaMsg, rotaLink);
rotaItem.setRotaId(rotaId);
if (!rotaItem.getType())
rotaItem.setType("rota");
}
var memberGr = this.getRosterMembersGr(rotaId, this.getRosterIds(), this.getUserIds());
while (memberGr.next()) {
this._buildMember(rotaId, memberGr, canWrite);
userIds[memberGr.member+""] = true;
}
}
var userIdArr = this._getKeys(userIds);
// Include users who provide coverage, may have left the group
var rssGr = new GlideRecord('roster_schedule_span');
rssGr.addQuery('roster.rota', 'IN', rotaIds);
if(userIdArr.length)
rssGr.addQuery('user', 'NOT IN', userIdArr);
rssGr.addEncodedQuery(this._getDateLimitedEncQuery('type=on_call'));
rssGr.query();
while (rssGr.next())
userIds[rssGr.user + ""] = true;
userIdArr = this._getKeys(userIds);
this._getMembersInfo(null, userIdArr);
this.setDirty(false);
if (this.log.atLevel(GSLog.DEBUG))
this.timer.stop("[buildRotas]");
return this;
},
_getKeys: function(obj) {
var keys = [];
for (var key in obj)
keys.push(key);
return keys;
},
/*
* Get a GlideRecord for the cmn_rota_member table which is filtered by Rota IDs,
* Roster IDs and User IDs. The GlideRecord will only contain cmn_rota_member records
* which match all the criteria.
*
* @param *optional* rotaIds [String]: A comma separated list of cmn_rota sys_ids
*
* @param *optional* rosterIds [String]: A comma separated list of cmn_rota_roster sys_ids
*
* @param *optional* userIds [String]: A comma separated list of sys_user sys_ids which
* are referred to by cmn_rota_member records
* @param *optional* groupIds [String]: A comma separated list of Group [sys_user_group] sys_ids which
* are referred to by the Rota [cmn_rota] record
*
* @return [GlideRecord]: A queried GlideRecord of cmn_rota_member records matching all the parameters
*/
getRosterMembersGr: function(rotaIds, rosterIds, userIds, groupIds) {
if (this.log.atLevel(GSLog.DEBUG))
this.timer.start("[getRosterMembersGr]");
var gr = new GlideRecord("cmn_rota_member");
if (!JSUtil.nil(rosterIds)) {
if (this.log.atLevel(GSLog.DEBUG))
this.log.debug("[getRosterMembersGr] rosterSysIds=" + rosterIds);
gr.addQuery("roster", "IN", rosterIds);
}
if (!JSUtil.nil(userIds)) {
if (this.log.atLevel(GSLog.DEBUG))
this.log.debug("[getRosterMembersGr] userSysIds=" + userIds);
gr.addQuery("member", "IN", userIds);
}
if (!JSUtil.nil(rotaIds)) {
if (this.log.atLevel(GSLog.DEBUG))
this.log.debug("[getRosterMembersGr] rotaIds=" + rotaIds);
gr.addQuery("roster.rota", "IN", rotaIds);
}
if (!JSUtil.nil(groupIds)) {
if (this.log.atLevel(GSLog.DEBUG))
this.log.debug("[getRosterMembersGr] groupIds=" + groupIds);
gr.addQuery("roster.rota.group", "IN", groupIds);
}
gr.addQuery("roster.active", "true");
gr.orderBy("roster.order");
gr.orderBy("order");
gr.query();
if (this.log.atLevel(GSLog.DEBUG))
this.log.debug("[getRosterMembersGr] encodedQuery=" + gr.getEncodedQuery());
if (this.log.atLevel(GSLog.DEBUG))
this.timer.stop("[getRosterMembersGr]");
return gr;
},
_buildMember: function(rotaId, memberGr, canWrite) {
if (this.log.atLevel(GSLog.DEBUG)) {
this.log.debug("[_buildMember] rotaId=" + rotaId + " memberGr.sys_id=" + memberGr.sys_id);
this.timer.start("[_buildMember]");
}
var rosterId = memberGr.roster + "";
var rosterName = memberGr.roster.name + "";
if (this.log.atLevel(GSLog.DEBUG))
this.log.debug("[_buildMember] rosterId: " + rosterId);
var memberId = memberGr.member + "";
var memberName = "";
// Check for condition if the member record has been archived
if (!memberGr.member.name && memberGr.member.getDisplayValue())
memberName = gs.getMessage('{0} (Archived)', [memberGr.member.getDisplayValue()]);
else
memberName = memberGr.member.name + '';
var userGr = this._getUser(memberGr.member+ '');
var memberEmail = (!this.checkAccess || userGr.email.canRead()) ? userGr.email + '' : '';
var memberPhone = '';
if (!this.checkAccess) {
memberPhone = (userGr.mobile_phone + '' != '') ? userGr.mobile_phone + '' : userGr.phone + '';
} else if ((userGr.mobile_phone + '' != '') && userGr.mobile_phone.canRead()){
memberPhone = userGr.mobile_phone + '';
} else if (userGr.phone.canRead()) {
memberPhone = userGr.phone + '';
}
var memberActive = memberGr.member.active;
var groupId = memberGr.roster.rota.group + '';
// Get the member's rotation schedule and remove this user's time off
var memberSchedule = new GlideSchedule(memberGr.rotation_schedule + "");
if (this.getExcludeTimeOff())
this._excludeTimeOff(memberSchedule, memberGr.member, groupId);
this._excludeCoverage(memberSchedule, rosterId);
// Get the user's name
var name;
if (userGr) {
name = userGr.name + " (" + memberGr.roster.name + ")";
if(!memberName)
memberName = userGr.name;
}
else
name = memberName + " (" + memberGr.roster.name + ")";
var eventBgColor = this._getEventBgColor(rotaId, "roster");
if (this.log.atLevel(GSLog.DEBUG))
this.log.debug("[_buildMember] name: " + name + " eventBgColor:" + eventBgColor);
var item = this.getSchedulePage().addScheduleObject(memberSchedule, name, eventBgColor, true);
if (!JSUtil.nil(rotaId))
item.setRotaId(rotaId);
if (!JSUtil.nil(rosterId))
item.setRosterId(rosterId);
if (!JSUtil.nil(rosterName))
item.setRosterName(rosterName);
if (!JSUtil.nil(memberId))
item.setUserId(memberId);
if (!JSUtil.nil(memberName))
item.setUserName(memberName);
if (!JSUtil.nil(memberEmail))
item.setUserEmail(memberEmail);
if (!JSUtil.nil(memberPhone))
item.setUserContactNumber(memberPhone);
if (!JSUtil.nil(memberActive))
item.setUserActive(memberActive);
if (!JSUtil.nil(memberGr)) {
item.setSysId(memberGr.getUniqueValue());
item.setTable(memberGr.getTableName());
item.setGroupId(this._getMemberGroups(memberGr.member));
}
item.setOrder(memberGr.roster.order + "");
item.setEditable(canWrite);
item.setDescription(gs.getMessage("On-call rotation"));
item.setType("roster");
if (this.log.atLevel(GSLog.DEBUG)) {
this.log.debug("[_buildMember] item: " + item);
this.timer.stop("[_buildMember]");
}
},
_getUser: function(userSysId) {
if (!this._userCache) {
this._userCache = {};
}
if (!this._userCache[userSysId]) {
var userGr = new GlideRecord('sys_user');
userGr.setWorkflow(false); // inactive users are not visible to non-admins
userGr.get(userSysId);
this._userCache[userSysId] = userGr;
}
return this._userCache[userSysId];
},
_excludeTimeOff: function(memberSchedule, userID, groupId) {
if (this.log.atLevel(GSLog.DEBUG))
this.timer.start("[_excludeTimeOff]");
var gr = new GlideRecord("sys_user");
gr.setWorkflow(false); // inactive users are not visible to non-admins
// Get groups encoded query
var groupEncQuery = "groupISEMPTY";
if (groupId)
groupEncQuery += "^ORgroup.sys_id=" + groupId;
if (gr.get(userID)) {
var scheduleID = gr.schedule;
if (scheduleID) {
var grSpan = this._getRosterScheduleSpan(this._getDateLimitedEncQuery("schedule=" + scheduleID + "^type=" + "time_off" + "^" + groupEncQuery));
if (this.log.atLevel(GSLog.DEBUG)) {
this.log.debug("[excludeTimeOff] encQuery: " + grSpan.getEncodedQuery());
this.log.debug("[excludeTimeOff] getRowCount: " + grSpan.getRowCount());
}
// Exclude this schedule span according to its own time-zone so that repeats and repeat-until is factored correctly
while (grSpan.next())
memberSchedule.excludeTimeSpan(grSpan, (grSpan.schedule ? grSpan.schedule.time_zone + "" : ""));
}
}
if (this.log.atLevel(GSLog.DEBUG))
this.timer.stop("[_excludeTimeOff]");
},
_excludeCoverage: function(schedule, rosterSysId) {
if (this.log.atLevel(GSLog.DEBUG))
this.timer.start("[_excludeCoverage]");
if (!rosterSysId.isNil()) {
var gr = this._getRosterScheduleSpan(this._getDateLimitedEncQuery("roster=" + rosterSysId + "^type=" + "on_call" + "^" + this._getGroupLimitedEncQuery()));
if (this.log.atLevel(GSLog.DEBUG)) {
this.log.debug("[_excludeCoverage] getRowCount: " + gr.getRowCount());
this.timer.start("[schedule.addTimeSpansExcluded]");
}
// Exclude this schedule span according to its own time-zone so that repeats and repeat-until is factored correctly
while (gr.next())
schedule.excludeTimeSpan(gr, (gr.schedule ? gr.schedule.time_zone + "" : ""));
if (this.log.atLevel(GSLog.DEBUG))
this.timer.stop("[schedule.addTimeSpansExcluded]");
}
if (this.legacySpanSuport)
this._legacyRosterScheduleSpans(schedule, this.getGroupIds());
if (this.log.atLevel(GSLog.DEBUG))
this.timer.stop("[_excludeCoverage]");
},
// Backwards compatible search (i.e. non-roster_schedule_span entries)
_legacyRosterScheduleSpans: function(schedule, groupSysIds) {
if (this.log.atLevel(GSLog.DEBUG))
this.timer.start("[_legacyRosterScheduleSpans]");
var gr = new GlideRecord("cmn_schedule_span");
gr.addQuery("group", "IN", groupSysIds);
gr.addQuery("type", "on_call");
gr.addQuery("sys_class_name", "!=", "roster_schedule_span");
gr.addQuery("schedule.type", "!=", "roster");
gr.query();
schedule.addTimeSpansExcluded(gr);
if (this.log.atLevel(GSLog.DEBUG))
this.timer.stop("[_legacyRosterScheduleSpans]");
},
_getMembersInfo: function(rotaId, userIds) {
if (this.log.atLevel(GSLog.DEBUG))
this.timer.start("[_getMembersInfo]");
//Get users from group
var groupMemberGr = new GlideRecord("sys_user_grmember");
groupMemberGr.setWorkflow(false);
groupMemberGr.addQuery("group", "IN", this.getGroupIds() + "");
groupMemberGr.query();
while (groupMemberGr.next()) {
if (!this.arrayUtils.contains(userIds, groupMemberGr.getValue("user")))
userIds.push(groupMemberGr.getValue("user"));
}
// Get member info for all users of the groups + members of rota (userIds)
if (!JSUtil.nil(userIds)) {
var gr = new GlideRecord("sys_user");
// inactive users are not visible to non-admins
gr.setWorkflow(false);
gr.addQuery("sys_id", "IN", userIds.join(","));
gr.query();
if (this.log.atLevel(GSLog.DEBUG))
this.log.debug("[_getMembersInfo] encoded query: " + gr.getEncodedQuery());
while (gr.next()) {
if (this.log.atLevel(GSLog.DEBUG))
this.log.debug("[_getMembersInfo] checking schedule for user name=" + gr.name);
if (gr.schedule != '') {
if (this.log.atLevel(GSLog.DEBUG))
this.log.debug("[_getMembersInfo] found schedule for user");
this._getMemberInfo(gr, rotaId);
}
}
}
if (this.log.atLevel(GSLog.DEBUG))
this.timer.stop("[_getMembersInfo]");
},
_getMemberInfo: function(userGr, rotaId) {
if (this.log.atLevel(GSLog.DEBUG))
this.timer.start("[_getMemberInfo]");
this._timeOffScheduleSpan(userGr, rotaId);
this._timeOffInApprovalScheduleSpan(userGr, rotaId);
this._overrideScheduleSpan(userGr);
if (this.log.atLevel(GSLog.DEBUG))
this.timer.stop("[_getMemberInfo]");
},
_overrideScheduleSpan: function(userGr) {
if (this.log.atLevel(GSLog.DEBUG))
this.timer.start("[_overrideScheduleSpan]");
var userName = userGr.name + "";
var scheduleSysId = userGr.schedule + "";
var overrideColor = this.getSchedulePage().getColor(2);
var gr = this._getScheduleSpanWithoutUserFilter(scheduleSysId, "on_call");
if (this.log.atLevel(GSLog.DEBUG))
this.log.debug("[_overrideScheduleSpan] gr row count: " + gr.getRowCount());
while (gr.next())
this._buildUserSpan(gr, this._getCoverageName(userName, gr.sys_class_name, gr.sys_id), overrideColor, gr.roster.rota + "", userGr, "override");
if (this.log.atLevel(GSLog.DEBUG))
this.timer.stop("[_overrideScheduleSpan]");
},
_timeOffScheduleSpan: function(userGr, rotaId) {
if (this.log.atLevel(GSLog.DEBUG))
this.timer.start("[_timeOffScheduleSpan]");
this._buildTimeOffScheduleSpan(userGr, rotaId, "time_off", this.getSchedulePage().getColor(1), gs.getMessage("Time off"), "timeoff", false);
if (this.log.atLevel(GSLog.DEBUG))
this.timer.stop("[_timeOffScheduleSpan]");
},
_timeOffInApprovalScheduleSpan: function(userGr, rotaId) {
if (this.log.atLevel(GSLog.DEBUG))
this.timer.start("[_timeOffInApprovalScheduleSpan]");
this._buildTimeOffScheduleSpan(userGr, rotaId, "time_off_in_approval", "transparent", gs.getMessage("Time off - In approval"), "time_off_in_approval", true);
if (this.log.atLevel(GSLog.DEBUG))
this.timer.stop("[_timeOffInApprovalScheduleSpan]");
},
_buildTimeOffScheduleSpan: function(userGr, rotaId, type, color, message, field, checkApproval) {
if (this.log.atLevel(GSLog.DEBUG))
this.timer.start("[_buildTimeOffScheduleSpan]");
if (this.log.atLevel(GSLog.DEBUG)) {
this.log.debug("[_buildTimeOffScheduleSpan] rotaId: " + rotaId);
this.log.debug("[_buildTimeOffScheduleSpan] type: " + type);
this.log.debug("[_buildTimeOffScheduleSpan] color: " + color);
this.log.debug("[_buildTimeOffScheduleSpan] message: " + message);
this.log.debug("[_buildTimeOffScheduleSpan] field: " + field);
this.log.debug("[_buildTimeOffScheduleSpan] checkApproval: " + checkApproval);
}
var userName = userGr.name + "";
if (this.log.atLevel(GSLog.DEBUG))
this.log.debug("[_buildTimeOffScheduleSpan] userName: " + userName);
var scheduleSysId = userGr.schedule + "";
var gr = this._getScheduleSpan(scheduleSysId, type);
while (gr.next())
if (!checkApproval || (checkApproval && new OnCallSecurityNG().isTimeOffInApprovalSpanViewable(gr)))
this._buildUserSpan(gr, gs.getMessage("{0} ({1})", [userName, message]), color, rotaId, userGr, field);
if (this.log.atLevel(GSLog.DEBUG))
this.timer.stop("[_buildTimeOffScheduleSpan]");
},
/*
* Replica method of _getScheduleSpan, not considering the "user" filter as it already filters the record by user's schedule ID
*/
_getScheduleSpanWithoutUserFilter: function(scheduleSysId, type) {
if (this.log.atLevel(GSLog.DEBUG))
this.timer.start("[_getScheduleSpanWithoutUserFilter]");
var gr = this._getRosterScheduleSpan(this._getDateLimitedEncQuery("schedule=" + scheduleSysId + "^type=" + type + "^" + this._getGroupLimitedEncQuery()));
if (this.log.atLevel(GSLog.DEBUG)) {
this.log.debug("[_getScheduleSpan] row count: " + gr.getRowCount());
this.timer.stop("[_getScheduleSpan]");
}
return gr;
},
_getScheduleSpan: function(scheduleSysId, type) {
if (this.log.atLevel(GSLog.DEBUG))
this.timer.start("[_getScheduleSpan]");
var userLimitedQuery = "";
if (this.getUserIds() && this.getUserIds().length && this.getUserIds().length > 0)
userLimitedQuery = "^" + this._getUserLimitedEncQuery();
var gr = this._getRosterScheduleSpan(this._getDateLimitedEncQuery("schedule=" + scheduleSysId + "^type=" + type + "^" + this._getGroupLimitedEncQuery() + userLimitedQuery));
if (this.log.atLevel(GSLog.DEBUG)) {
this.log.debug("[_getScheduleSpan] row count: " + gr.getRowCount());
this.timer.stop("[_getScheduleSpan]");
}
return gr;
},
_getRosterScheduleSpan: function(encQuery) {
if (this.log.atLevel(GSLog.DEBUG))
this.log.debug("[_getRosterScheduleSpan] encQuery: " + encQuery);
var gr = new GlideRecord("roster_schedule_span");
gr.addEncodedQuery(encQuery);
gr.setWorkflow(false);
gr.query();
return gr;
},
_getDateLimitedEncQuery: function(limitedBy) {
var and = "^";
var or = "^NQ";
var startDateTime = this._formatDateStr(this.getStartDate().getUTCValue());
var endDateTime = this._formatDateStr(this.getEndDate().getUTCValue());
var startInEndIn = limitedBy + and + "start_date_time>=" + startDateTime + and + "end_date_time<=" + endDateTime;
var startOutEndIn = limitedBy + and + "start_date_time<=" + startDateTime + and + "end_date_time>=" + startDateTime + and + "end_date_time<=" + endDateTime;
var startInEndOut = limitedBy + and + "start_date_time>=" + startDateTime + and + "start_date_time<=" + endDateTime + and + "end_date_time>=" + endDateTime;
var startOutEndOut = limitedBy + and + "start_date_time<=" + startDateTime + and + "end_date_time>=" + endDateTime;
var repeatNotNull = limitedBy + and + "repeat_type!=null" + and + "repeat_until=00000000^ORrepeat_until=null^ORrepeat_until>=" + startDateTime.split("T")[0];
var encQuery = startInEndIn + or + startOutEndIn + or + startInEndOut + or + startOutEndOut + or + repeatNotNull;
if (this.log.atLevel(GSLog.DEBUG))
this.log.debug("[_getDateLimitedEncQuery] encQuery: " + encQuery);
return encQuery;
},
_getGroupLimitedEncQuery: function() {
var encQuery = "groupISEMPTY";
// Support legacy span records that do not have group values, hence null is queried
if (!JSUtil.nil(this.getGroupIds()) && this.getGroupIds().length > 0)
encQuery += "^ORgroup.sys_idIN" + this.getGroupIds();
if (this.log.atLevel(GSLog.DEBUG))
this.log.debug("[_getGroupLimitedEncQuery] encQuery: " + encQuery);
return encQuery;
},
_getUserLimitedEncQuery: function() {
var encQuery = "";
// Support legacy span records that do not have group values, hence null is queried
if (this.getUserIds() && this.getUserIds().length && this.getUserIds().length > 0)
encQuery += "user.sys_idIN" + this.getUserIds();
if (this.log.atLevel(GSLog.DEBUG))
this.log.debug("[_getUserLimitedEncQuery] encQuery: " + encQuery);
return encQuery;
},
/*
* Input: yyyy-MM-dd hh:mm:ss
* Output: yyyyMMddThhmmssZ
* e.g
* 2016-08-02 00:00:00
* 20160802T000000Z
*/
_formatDateStr: function(strDate) {
strDate = strDate + "";
strDate = strDate.replace(/\s+/g, "T");
strDate = strDate.replace(/\-/g, "");
strDate = strDate.replace(/\:/g, "");
strDate += "Z";
return strDate;
},
_getCoverageName: function(userName, tableName, rosterScheduleSpanId) {
if (this.log.atLevel(GSLog.DEBUG))
this.timer.start("[_getCoverageName]");
var coverageName = gs.getMessage("{0} (Coverage)", [userName]);
// see if we have a reference to a roster in there
if (tableName == 'roster_schedule_span') {
var gr = new GlideRecord("roster_schedule_span");
gr.addQuery("sys_id", rosterScheduleSpanId);
gr.query();
if (gr.next() && gr.roster.name != "")
coverageName = gs.getMessage("{0} ({1} Coverage)", [userName, gr.roster.name + ""]);
}
if (this.log.atLevel(GSLog.DEBUG)) {
this.log.debug("[_getCoverageName] coverageName: " + coverageName);
this.timer.stop("[_getCoverageName]");
}
return coverageName;
},
_getMemberGroups: function(userSysId) {
if (this.log.atLevel(GSLog.DEBUG))
this.timer.start("[_getMemberGroups]");
var groupSysIds = {};
var gr = new GlideRecord("sys_user_grmember");
gr.addActiveQuery();
gr.addQuery("user", userSysId);
gr.query();
while(gr.next())
groupSysIds[gr.group + ''] = gr.group + '';
var result = [];
for(var key in groupSysIds)
result.push(key);
if (this.log.atLevel(GSLog.DEBUG))
this.timer.stop("[_getMemberGroups]");
return result.join(",");
},
_buildUserSpan: function(gr, altName, color, rotaId, userGr, type) {
if (this.log.atLevel(GSLog.DEBUG)) {
this.log.debug("[_buildUserSpan] altName: " + altName + ", rotaId: " + rotaId);
this.timer.start("[_buildUserSpan]");
}
var memberId = userGr.sys_id + "";
var memberName = userGr.name + "";
var memberEmail = (!this.checkAccess || userGr.email.canRead()) ? userGr.email + '' : '';
var memberPhone = '';
if (!this.checkAccess) {
memberPhone = (userGr.mobile_phone + '' != '') ? userGr.mobile_phone + '' : userGr.phone + '';
} else if ((userGr.mobile_phone + '' != '') && userGr.mobile_phone.canRead()){
memberPhone = userGr.mobile_phone + '';
} else if (userGr.phone.canRead()) {
memberPhone = userGr.phone + '';
}
if (this.log.atLevel(GSLog.DEBUG))
this.timer.start("[_buildUserSpan.addScheduleSpan]");
var timeZone = this.getTimezone();
var spanTimeZone = gr.schedule.time_zone + "";
if (!JSUtil.nil(spanTimeZone))
timeZone = spanTimeZone;
var item = this.getSchedulePage().getPage().addScheduleSpan(gr, timeZone, "", color);
if (this.log.atLevel(GSLog.DEBUG))
this.timer.stop("[_buildUserSpan.addScheduleSpan]");
if (!JSUtil.nil(rotaId))
item.setRotaId(rotaId);
if (!JSUtil.nil(memberId))
item.setUserId(memberId);
if (!JSUtil.nil(memberName))
item.setUserName(memberName);
if (!JSUtil.nil(memberEmail))
item.setUserEmail(memberEmail);
if (!JSUtil.nil(memberPhone))
item.setUserContactNumber(memberPhone);
if (!JSUtil.nil(type))
item.setType(type);
item.setName(altName);
item.setDescription("");
// An override is linked to a roster, ensure same order is applied
if (gr.roster + "" && gr.roster.order + "")
item.setOrder(parseInt(gr.roster.order + "", 10));
else
item.setOrder(999999);
if (type == 'override') {
item.setRosterId(gr.roster.sys_id + "");
item.setRosterName(gr.roster.name + "");
}
if (this.log.atLevel(GSLog.DEBUG))
this.timer.stop("[_buildUserSpan]");
},
_getEventBgColor: function(rotaId, type) {
if (this.log.atLevel(GSLog.DEBUG))
this.timer.start("[_getEventBgColor]");
var eventBgColor;
if (this.getSchedulePage().getPage().isLegacy())
eventBgColor = type == "rota" ? this.getSchedulePage().getColor(rotaId) : this.getSchedulePage().darkenColor(this.getSchedulePage().getColor(rotaId));
else {
if (type === "rota") {
var rotaPalleteDarkColor = new OCCalendarUtils().getPalleteDarkColors(this.getSchedulePage().getColor(rotaId));
eventBgColor = (rotaPalleteDarkColor) ? rotaPalleteDarkColor : this.getSchedulePage().darkenColor(this.getSchedulePage().getColor(rotaId));
}
else
eventBgColor = this.getSchedulePage().getColor(rotaId);
}
if (this.log.atLevel(GSLog.DEBUG))
this.timer.stop("[_getEventBgColor]");
return eventBgColor;
},
saveCatchAllToRotationItems: function() {
if (this.log.atLevel(GSLog.DEBUG))
this.timer.start("[saveCatchAllToRotationItems]");
var ocr = new OnCallRotation();
var catchAllType;
var catchAllSysID;
var gr = new GlideRecord("v_rotation");
var ga = new GlideAggregate("v_rotation");
ga.groupBy("group");
ga.groupBy("start_date_time");
ga.groupBy("end_date_time");
ga.groupBy("rota");
ga.query();
while (ga.next()) {
catchAllType = ocr.getCatchAllType(ga.rota.toString());
catchAllSysID = ocr.getCatchAll(ga.rota.toString());
gr.initialize();
gr.current_user_id = gs.userID();
gr.group = ga.group;
gr.type = catchAllType;
gr.rota = ga.rota;
gr.start_date_time = ga.start_date_time;
gr.end_date_time = ga.end_date_time;
if (catchAllType == "all") {
var rotaMember = new GlideRecord("cmn_rota_member");
rotaMember.addQuery("roster", catchAllSysID);
rotaMember.query();
while (rotaMember.next()) {
gr.user = rotaMember.member;
gr.roster = catchAllSysID;
gr.insert();
}
} else if (catchAllType) {
var user = new GlideRecord("sys_user");
user.get(catchAllSysID);
gr.roster = "";
gr.user = user.sys_id;
gr.insert();
}
}
if (this.log.atLevel(GSLog.DEBUG))
this.timer.stop("[saveCatchAllToRotationItems]");
},
saveRotationItems: function(items, startGdt, endGdt) {
if (this.log.atLevel(GSLog.DEBUG))
this.timer.start("[saveRotationItems]");
var gr = new GlideRecord("v_rotation");
for (var i = 0; i < items.size(); i++) {
var item = items.get(i);
var type = item.getDataByKey("type") + "";
if (type == "roster")
continue;
var spans = item.getTimeSpans();
for (var j = 0; j < spans.size(); j++) {
var span = spans.get(j);
gr.initialize();
gr.current_user_id = gs.userID();
gr.group = item.getDataByKey("group");
gr.type = type;
gr.roster = item.getDataByKey("roster");
gr.rota = item.getDataByKey("rota");
gr.user = item.getDataByKey("user");
// Make sure date range falls within the report range
var sdt = span.getStart().getGlideDateTime();
var edt = span.getEnd().getGlideDateTime();
if (sdt.compareTo(startGdt) < 0)
sdt = startGdt;
if (edt.compareTo(endGdt) > 0)
edt = endGdt;
if (edt.compareTo(sdt) < 0)
continue;
gr.start_date_time = sdt.getDisplayValue() + "";
gr.end_date_time = edt.getDisplayValue() + "";
gr.insert();
}
}
this.saveCatchAllToRotationItems();
if (this.log.atLevel(GSLog.DEBUG))
this.timer.stop("[saveRotationItems]");
},
formattedReportUrl: function(start_date, end_date, groups) {
if (this.log.atLevel(GSLog.DEBUG)) {
this.log.debug("[formattedReportUrl] start_date: " + start_date + ", end_date: " + end_date + ", groups: " + groups);
this.timer.start("[formattedReportUrl]");
}
groups = (!JSUtil.nil(groups)) ? groups : "";
var redirect = 'schedule_formatted_report.do?sysparm_start_date=' + start_date + '&sysparm_end_date=' + end_date;
if (!JSUtil.nil(groups)) {
var glist = this._getGroupArr(groups);
if (glist.length > 199) {
glist = glist.slice(0, 199);
this.log.debug("[formattedReportUrl] groups exceed 200 truncating to first 200");
}
if (glist.length > 0 && !JSUtil.nil(glist.join(",")))
redirect += "&sysparm_groups=" + glist.join(",");
}
if (this.log.atLevel(GSLog.DEBUG)) {
this.log.debug("[formattedReportUrl] redirect: " + redirect);
this.log.debug("[formattedReportUrl] redirect length: " + redirect.length);
this.timer.stop("[formattedReportUrl]");
}
return redirect;
},
_getGroupArr: function(groups) {
if (JSUtil.nil(groups))
return [];
var glist = groups.split(",");
var index = glist.indexOf("--");
if (index > -1)
glist.splice(index, 1);
return glist;
},
v_rotation_handling: function(start_date, end_date) {
// groups is global variable and this part is for legacy support
this.vRotationHandling(start_date, end_date, groups);
},
vRotationHandling: function(start_date, end_date, groups) {
if (this.log.atLevel(GSLog.DEBUG)) {
this.log.debug("[vRotationHandling] start_date: " + start_date + ", end_date: " + end_date);
this.timer.start("[vRotationHandling]");
}
groups = (!JSUtil.nil(groups)) ? groups : "";
// set the date range to be from now to the end of next month
if (!start_date)
start_date = gs.now();
var start_gdt = new GlideDateTime();
start_gdt.setDisplayValue(start_date);
if (!end_date)
end_date = gs.now();
var end_gdt = new GlideDateTime();
end_gdt.setDisplayValue(end_date);
end_gdt.addSeconds(86399);
var tz = gs.getSession().getTimeZoneName();
var start = new GlideScheduleDateTime(start_gdt);
var end = new GlideScheduleDateTime(end_gdt);
start.setTimeZone(tz);
end.setTimeZone(tz);
var page = new GlideAJAXSchedulePage(start, end, tz);
var ocrCalculator = new OnCallRotationCalculator();
ocrCalculator.setPage(page);
ocrCalculator.activeRostersOnly = true;
var glist = this._getGroupArr(groups);
for (var i = 0; i < glist.length; i++)
if (!JSUtil.nil(glist[i]))
ocrCalculator.run(glist[i]);
ocrCalculator.removeRotation();
this.saveRotationItems(ocrCalculator.page.getItems(), start_gdt, end_gdt);
gs.addInfoMessage(gs.getMessage("On-call Rotation Schedule for {0} to {1}",[start_date, end_date]));
var redirect = "v_rotation_list.do?sysparm_query=^ORDERBYstart_date_time";
if (this.log.atLevel(GSLog.DEBUG)) {
this.log.debug("[vRotationHandling] redirect: " + redirect);
this.log.debug("[vRotationHandling] redirect length: " + redirect.length);
this.timer.stop("[vRotationHandling]");
}
return redirect;
},
_formQueryPerFetchPreference: function (gr) {
var stateField = "";
var activeField = "";
var table = gr.getRecordClassName();
switch (table) {
case "cmn_rota":
stateField = "state";
activeField = "active";
break;
case "cmn_rota_roster":
stateField = "rota.state";
activeField = "rota.active";
break;
case "cmn_rota_member":
stateField = "roster.rota.state";
activeField = "roster.rota.active";
break;
}
if (this.getSpanFetchPreference() == this.SPAN_FETCH_PREFERENCE.ALL) {
var qc = gr.addQuery = gr.addQuery(activeField, true);
var qcOR = qc.addOrCondition(activeField, false);
qcOR.addCondition(stateField, this.SHIFT_STATE.DRAFT);
} else if (this.getSpanFetchPreference() == this.SPAN_FETCH_PREFERENCE.DRAFT_ONLY) {
gr.addQuery(stateField, this.SHIFT_STATE.DRAFT);
gr.addQuery(activeField, false);
} else
gr.addQuery(activeField, true);
return gr;
},
_getRotaIdsFromRotas: function(rotaIds, groupIds) {
if (this.log.atLevel(GSLog.DEBUG))
this.timer.start("[_getRotaIdsFromRotas]");
var rotaGr = new GlideRecord("cmn_rota");
if (!JSUtil.nil(rotaIds))
rotaGr.addQuery("sys_id", "IN", rotaIds);
if (!JSUtil.nil(groupIds))
rotaGr.addQuery("group", "IN", groupIds);
this._formQueryPerFetchPreference(rotaGr);
rotaGr.query();
var rotaIdsArr = [];
while (rotaGr.next())
rotaIdsArr.push(rotaGr.sys_id + "");
if (this.log.atLevel(GSLog.DEBUG))
this.timer.stop("[_getRotaIdsFromRotas]");
return rotaIdsArr.join(",");
},
_getRotaIdsFromRosters: function(rosterIds, rotaIds, groupIds) {
if (this.log.atLevel(GSLog.DEBUG))
this.timer.start("[_getRotaIdsFromRosters]");
var rosterGr = new GlideRecord("cmn_rota_roster");
rosterGr.addQuery("sys_id", "IN", rosterIds);
if (!JSUtil.nil(rotaIds))
rosterGr.addQuery("rota", "IN", rotaIds);
if (!JSUtil.nil(groupIds))
rosterGr.addQuery("rota.group", "IN", groupIds);
rosterGr.addQuery("active", "true");
this._formQueryPerFetchPreference(rosterGr);
rosterGr.query();
var rotaIdsArr = [];
while (rosterGr.next())
rotaIdsArr.push(rosterGr.rota + "");
if (this.log.atLevel(GSLog.DEBUG))
this.timer.stop("[_getRotaIdsFromRosters]");
return rotaIdsArr.join(",");
},
_getRotaIdsFromMembers: function (userIds, rosterIds, rotaIds, groupIds) {
if (this.log.atLevel(GSLog.DEBUG))
this.timer.start("[_getRotaIdsFromMembers]");
var memberGr = new GlideRecord("cmn_rota_member");
memberGr.addQuery("member", "IN", userIds);
if (!JSUtil.nil(rosterIds))
memberGr.addQuery("roster", "IN", rosterIds);
if (!JSUtil.nil(rotaIds))
memberGr.addQuery("roster.rota", "IN", rotaIds);
if (!JSUtil.nil(groupIds))
memberGr.addQuery("roster.rota.group", "IN", groupIds);
this._formQueryPerFetchPreference(memberGr);
memberGr.addQuery("roster.active", "true");
memberGr.query();
var rotaIdsArr = [];
while (memberGr.next())
rotaIdsArr.push(memberGr.roster.rota + "");
if (this.log.atLevel(GSLog.DEBUG))
this.timer.stop("[_getRotaIdsFromMembers]");
return rotaIdsArr.join(",");
},
setCheckAccess: function(checkAccess) {
this.checkAccess = checkAccess;
return this;
},
_isLegacy: function() {
return true;
},
type: 'OCRotation'
};
Sys ID
afda4624d7001200fca223c7ce6103f9