Name
global.RotaScheduleEntryValidation
Description
Specific Validation for Rota Schedule Entries Called by Rota Schedule Item Validate (business rule) OCAddItem (script include)
Script
var RotaScheduleEntryValidation = Class.create();
/**
* Test validity of a Rota Schedule Entry
* - sets error messages, field error messages, accordingly
* - returns false if entry is not valid
*/
RotaScheduleEntryValidation.isValid = function(cmn_schedule_span) {
return new RotaScheduleEntryValidation(cmn_schedule_span).isValid();
};
RotaScheduleEntryValidation.prototype = {
initialize: function(cmn_schedule_span) {
this._log = new GSLog("com.snc.on_call_rotation.log.level", this.type);
this.cmn_schedule_span = cmn_schedule_span;
this._valid = true;
this._message = "";
this._showMessage = true;
},
setShowMessage: function(show) {
this._showMessage = show;
return this;
},
getMessage: function() {
return this._message;
},
isValid: function() {
this._ensureRosterNoGroup();
var onCallCommon = new OnCallCommon();
var groupId = this._getRotaGroupForSchedule(this.cmn_schedule_span.schedule + "");
if(!onCallCommon.isOverlapAllowed(groupId))
this._validateNoOverlapRota();
else if (this.cmn_schedule_span.type == 'on_call' && JSUtil.nil(this.cmn_schedule_span.roster)) {
var rotaGr = new GlideRecord('cmn_rota');
rotaGr.addQuery('schedule', this.cmn_schedule_span.schedule);
rotaGr.query();
if (rotaGr.next()) {
this._validateNoOverlapRota(rotaGr.getUniqueValue());
}
}
this._validateNoOverlapTimeOff();
if ( this.cmn_schedule_span.type == 'time_off' || this.cmn_schedule_span.type == 'time_off_in_approval' || (this.cmn_schedule_span.type == 'on_call' && !JSUtil.nil(this.cmn_schedule_span.roster))) {
this._validateCoverageTimeOffOverlap();
}
return this._valid;
},
_validateCoverageTimeOffOverlap: function() {
var rosterScheduleSpanGr = new GlideRecord('roster_schedule_span');
rosterScheduleSpanGr.addQuery("schedule.document", "sys_user");
rosterScheduleSpanGr.addQuery("schedule.document_key", this.cmn_schedule_span.schedule.document_key + "");
rosterScheduleSpanGr.addQuery("group", this.cmn_schedule_span.group);
if (this.cmn_schedule_span.type == 'time_off' || this.cmn_schedule_span.type == 'time_off_in_approval')
rosterScheduleSpanGr.addNotNullQuery('roster');
else
rosterScheduleSpanGr.addQuery("type", "!=", "on_call");
if (this.cmn_schedule_span.sys_id)
rosterScheduleSpanGr.addQuery("sys_id", "!=", this.cmn_schedule_span.sys_id + "");
var startDateTimeObj = new GlideScheduleDateTime(this.cmn_schedule_span.start_date_time.getGlideObject());
var endDateTimeObj = new GlideScheduleDateTime(this.cmn_schedule_span.end_date_time.getGlideObject());
startDateTimeObj.addSeconds(1);
endDateTimeObj.addSeconds(-1);
var startDateTime = startDateTimeObj.getValue() + "";
var endDateTime = endDateTimeObj.getValue() + "";
if (this._log.atLevel(global.GSLog.DEBUG))
this._log.debug("[_validateCoverageTimeOffOverlap] startDateTime: " + startDateTime + " endDateTime: " + endDateTime);
rosterScheduleSpanGr.addEncodedQuery(this._getDateLimitedEncQuery(rosterScheduleSpanGr.getEncodedQuery(), startDateTime, endDateTime));
rosterScheduleSpanGr.query();
if (this._log.atLevel(global.GSLog.DEBUG))
this._log.debug("[_validateCoverageTimeOffOverlap] encodedQuery: " + rosterScheduleSpanGr.getEncodedQuery());
if (rosterScheduleSpanGr.next()) {
var isInSchedule = this._isInSchedule(new GlideDateTime(startDateTime), new GlideDateTime(endDateTime), rosterScheduleSpanGr);
if (!isInSchedule)
return;
this._valid = false;
if (this.cmn_schedule_span.type == 'time_off')
this._displayMessage(gs.getMessage("Time off request for user overlaps with existing Coverage request"));
else if (this.cmn_schedule_span.type == 'time_off_in_approval')
this._displayMessage(gs.getMessage("Time off - In approval request for user overlaps with existing Coverage request"));
else if (rosterScheduleSpanGr.type == 'time_off_in_approval')
this._displayMessage(gs.getMessage("Coverage request for user overlaps with existing Time off - In approval request"));
else if (rosterScheduleSpanGr.type == 'time_off')
this._displayMessage(gs.getMessage("Coverage request for user overlaps with existing Time off request"));
}
},
/**
* @param startDateTime [GlideDateTime]: Start Date Time of the event
* @param endDateTime [GlideDateTime]: End Date Time of the event
* @param rosterScheduleSpanGr[GlideRecord]: Roster Schedule Span Record
*
* @return: isOverlapping[Boolean]
*/
_isInSchedule: function(startDateTime, endDateTime, rosterScheduleSpanGr) {
// Initialize a GlideSchedule and load UserSchedule and exclude current span(To avoid current span overlap)
// Usage: new GlidSchedule().load(String<scheduleSysID>, String<timeZone>, String<excludeSpanSysID>);
var glideSchedule = new GlideSchedule();
glideSchedule.load(rosterScheduleSpanGr.getValue('schedule') + '', rosterScheduleSpanGr.schedule.time_zone, this.cmn_schedule_span.sys_id + '');
if (glideSchedule.isValid()) {
// Check if the coverage is overlapping with any other span.
// Usage: new GlideSchedule().overlapsWith(GlideRecord<cmn_schedule_span>, String<timeZone>);
var overlapSchedules = glideSchedule.overlapsWith(this.cmn_schedule_span, rosterScheduleSpanGr.schedule.time_zone);
if (overlapSchedules.hasNext())
return true;
else {
// If there are no overlapping spans, check if start and end date times are in schedule
// Usage: new GlideSchedule().isInSchedule(GlideDateTime<startDateTime>, String<timeZone> /* Optional */);
return glideSchedule.isInSchedule(startDateTime) || glideSchedule.isInSchedule(endDateTime);
}
}
return false;
},
isValidExtraCoverage: function(rotaSysId) {
this._ensureRosterNoGroup();
this._validateNoOverlapRota(rotaSysId);
return this._valid;
},
_displayMessage: function(message) {
this._message = message;
if (this._showMessage)
gs.addErrorMessage(this._message);
},
// Remove any group value, for a "Roster Schedule Type" Schedule
_ensureRosterNoGroup: function() {
if (this.cmn_schedule_span.schedule.type + "" === "create_new_roster") {
if (JSUtil.notNil(this.cmn_schedule_span.group)) {
this._displayMessage(gs.getMessage("Group may not be specified for the 'create_new_roster' schedule type. Group removed."));
this.cmn_schedule_span.group = "";
}
}
},
// Ensure spans for a Rota Schedule do not overlap the spans in another Rota for the same Group
_validateNoOverlapRota: function(rotaSysId) {
if (this.cmn_schedule_span.schedule.type + "" === "roster") {
var groupID = this._getRotaGroupForSchedule(this.cmn_schedule_span.schedule) + "";
if (groupID) {
// test all other Rota schedules for this Schedule's Group
var rotaGR = new GlideRecord("cmn_rota");
rotaGR.initialize();
rotaGR.addQuery("group", groupID);
rotaGR.addActiveQuery();
if (rotaSysId)
rotaGR.addQuery('sys_id', rotaSysId);
rotaGR.query();
if (this._log.atLevel(global.GSLog.DEBUG))
this._log.debug("[_validateNoOverlapRota] encodedQuery: " + rotaGR.getEncodedQuery());
while (rotaGR.next() && this._valid) {
if (this._rotaScheduleOverlaps(rotaGR)) {
this._valid = false;
var msg = gs.getMessage("Span overlaps rotation schedule '{0}'", rotaGR.name);
this._displayMessage(msg);
}
}
}
}
},
_validateNoOverlapTimeOff: function() {
var spanType = this.cmn_schedule_span.type + "";
if (this._log.atLevel(global.GSLog.DEBUG))
this._log.debug("[_validateNoOverlapTimeOff] spanType: " + spanType);
if (spanType === "time_off" || spanType === "time_off_in_approval") {
var gr = new GlideRecord("roster_schedule_span");
gr.initialize();
gr.addQuery("schedule.document", "sys_user");
gr.addQuery("schedule.document_key", this.cmn_schedule_span.schedule.document_key + "");
gr.addQuery("group", this.cmn_schedule_span.group);
gr.addQuery("type", "IN", "time_off,time_off_in_approval");
if (this.cmn_schedule_span.sys_id)
gr.addQuery("sys_id", "!=", this.cmn_schedule_span.sys_id + "");
var startDateTime = this.cmn_schedule_span.start_date_time.getGlideObject().getValue() + "";
var endDateTime = this.cmn_schedule_span.end_date_time.getGlideObject().getValue() + "";
if (this._log.atLevel(global.GSLog.DEBUG))
this._log.debug("[_validateNoOverlapTimeOff] startDateTime: " + startDateTime + " endDateTime: " + endDateTime);
gr.addEncodedQuery(this._getDateLimitedEncQuery(gr.getEncodedQuery(), startDateTime, endDateTime));
gr.query();
if (this._log.atLevel(global.GSLog.DEBUG))
this._log.debug("[_validateNoOverlapTimeOff] encodedQuery: " + gr.getEncodedQuery());
while (gr.next()) {
if (this._rosterScheduleSpanOverlaps(gr)) {
this._valid = false;
this._displayMessage(gs.getMessage("Time off request for the user overlaps existing request."));
}
}
}
},
/**
* limitedBy is an encoded query itself
* startDateTime and endDateTime are strings in format: yyyyMMddThhmmssZ
* e.g. 20160802T000000Z
*/
_getDateLimitedEncQuery: function(limitedBy, startDateTime, endDateTime) {
if (this._log.atLevel(GSLog.DEBUG))
this._log.debug("[_getDateLimitedEncQuery] limitedBy: " + limitedBy + " startDateTime: " + startDateTime + " endDateTime: " + endDateTime);
if (!startDateTime || !endDateTime)
return limitedBy;
var NQ = "^NQ";
var startInEndIn = limitedBy + "^start_date_time>=" + startDateTime + "^end_date_time<=" + endDateTime;
var startOutEndIn = limitedBy + "^start_date_time<=" + startDateTime + "^end_date_time>=" + startDateTime + "^end_date_time<=" + endDateTime;
var startInEndOut = limitedBy + "^start_date_time>=" + startDateTime + "^start_date_time<=" + endDateTime + "^end_date_time>=" + endDateTime;
var startOutEndOut = limitedBy + "^start_date_time<=" + startDateTime + "^end_date_time>=" + endDateTime;
var repeatNotNull = limitedBy + "^repeat_type!=null" + "^repeat_until=00000000^ORrepeat_until=null^ORrepeat_until>=" + startDateTime.split("T")[0];
var encodedQuery = startInEndIn + NQ + startOutEndIn + NQ + startInEndOut + NQ + startOutEndOut + NQ + repeatNotNull;
if (this._log.atLevel(GSLog.DEBUG))
this._log.debug("[_getDateLimitedEncQuery] encodedQuery: " + encodedQuery);
return encodedQuery;
},
_getRotaGroupForSchedule: function(cmnSchedule) {
// get the group that this schedule belongs to
var rotaGroupGR = new GlideRecord("cmn_rota");
rotaGroupGR.initialize();
rotaGroupGR.addQuery("schedule", cmnSchedule);
rotaGroupGR.query();
if (!rotaGroupGR.next())
return undefined;
return rotaGroupGR.group;
},
_rotaScheduleOverlaps: function(cmnRotaGr) {
var scheduleID = cmnRotaGr.schedule + "";
var schedule = this._getSchedule();
schedule.load(scheduleID, null, this.cmn_schedule_span.sys_id + "");
var tz = this.cmn_schedule_span.schedule.time_zone + "";
var overlaps = schedule.overlapsWith(this.cmn_schedule_span, tz);
if (!overlaps.isEmpty())
return true;
return false;
},
_rosterScheduleSpanOverlaps: function(rosterScheduleSpanGr) {
var scheduleID = rosterScheduleSpanGr.schedule + "";
var schedule = this._getSchedule();
schedule.load(scheduleID, null, this.cmn_schedule_span.sys_id + "");
var tz = this.cmn_schedule_span.schedule.time_zone + "";
var overlaps = schedule.overlapsWith(this.cmn_schedule_span, tz);
if (!overlaps.isEmpty())
return true;
return false;
},
//getter to ease override in test
_getSchedule: function() {
var schedule = new GlideSchedule();
return schedule;
},
type: 'RotaScheduleEntryValidation'
};
Sys ID
74168ee49f2020008f88ed93ee4bcca4