CAB Cutoff Validation for Normal Changes
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
3 weeks ago
Hi Team,
I have a requirement related to CAB Date/Time (cab_date_time) on the Change Request table.
Scope
Applicable only for Normal Changes.
CAB meetings are conducted on:
Monday
Thursday
Business Requirement
A cutoff time exists for adding changes to the upcoming CAB meeting.
Cutoff time: 16:00 (4:00 PM) on the previous CAB-working day.
Examples:
Scenario 1 – Monday CAB
CAB meeting is on Monday.
Cutoff time is Sunday 16:00.
If a user tries to select a Monday CAB Date/Time after Sunday 16:00, the system should:
Show an error message:
"Time to include a change in CAB has passed, please follow CAB Cutoff exception procedure"
Clear the value selected in the cab_date_time field.
Scenario 2 – Thursday CAB
CAB meeting is on Thursday.
Cutoff time is Wednesday 16:00.
If a user tries to select a Thursday CAB Date/Time after Wednesday 16:00, the system should:
Show an error message:
"Time to include a change in CAB has passed, please follow CAB Cutoff exception procedure"
Clear the value selected in the cab_date_time field.
Exception
Users with the role:
change_manager
should be able to select any CAB Date/Time regardless of the cutoff, with no error displayed.
Configurability Requirement
The solution should use System Properties so that CAB schedules and cutoff times can be managed without code changes.
Suggested properties:
CAB Days
Example: Monday,Thursday
CAB Cutoff Time
Example: 16:00
Questions
What would be the recommended approach:
Client Script + GlideAjax?
onChange Client Script?
Business Rule validation?
Combination of client and server validation?
How would you design the solution using System Properties for:
CAB meeting days
CAB cutoff time
Has anyone implemented a similar CAB cutoff validation before?
Please provide the entire script.
Thanks in advance.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
3 weeks ago - last edited 3 weeks ago
Hi @Sirri,
First check what cab_date_time actually is on your instance. Out of the box the Change Request table only ships with cab_date, and it's a date field, not a date/time field, ServiceNow documents this and the related timezone quirks in KB0831541. If you're validating down to 16:00 on a specific day, you're either working against a custom datetime field someone already added, or you'll need to add one, because you can't run a "before 4pm" cutoff check against a value that carries no time component.
Once that's sorted, don't build this as a pure client script. onChange plus GlideAjax is fine for the user experience, showing the error and clearing the field the moment they pick an invalid slot, but it does nothing to stop a list edit, an import set, an integration, or someone hitting the field through the mobile app or REST API. Anything client-only is cosmetic. You need a before business rule on change_request, scoped to type equals normal, that fires when cab_date_time changes, and that one actually blocks the save. The client script is only there so the user isn't surprised by a server-side rejection after they hit save.
Keep the actual logic in one script include so the client script and the business rule call the same code instead of duplicating the cutoff math. That script include reads two system properties, say cab.days holding "Monday,Thursday" and cab.cutoff_time holding "16:00", splits the days property, walks forward from the current GlideDateTime to find the next matching weekday using getDayOfWeekLocalTime(), then walks back one CAB day and compares against the cutoff time. Do the role bypass server side with gs.hasRole('change_manager'), not client side with g_user.hasRole. Client-side role checks are trivially bypassed and this is a compliance control, not a UI nicety, so the server has to be the one enforcing the exception too.
Thank you,
Vikram Karety
Octigo Solutions INC
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
3 weeks ago
@Vikram Reddy ,
Thank you for your response & explanation. Actually there are two fields one is CAB Date type is date & another is CAB Date Time type is date & time from OOB. If you provide script for entire scenario it will helpful to me.
Thank you.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
3 weeks ago - last edited 3 weeks ago
Hey @Sirri,
That clears it up. Whether cab_date_time landed on your instance out of the box or was added by whoever configured CAB Workbench before you, it doesn't change the design: cab_date is date-only, so it can never carry a 4:00 PM cutoff. All the validation logic below runs against cab_date_time. Leave cab_date alone, it's the one CAB Workbench populates from the meeting record when agenda items refresh, and touching it isn't part of this requirement.
Here's the full build, in the same three-piece structure I described before: one script include holding the logic, a before business rule that actually blocks the save, and a client script that gives the user immediate feedback.
1. System Properties
- cab.days, string, value: Monday,Thursday
- cab.cutoff_time, string, value: 16:00
2. Script Include: CABCutoffValidator (client callable = true)
var CABCutoffValidator = Class.create();
CABCutoffValidator.prototype = Object.extendsObject(AbstractAjaxProcessor, {
// Called from the client script via GlideAjax
validateCutoff: function() {
var cabValue = this.getParameter('sysparm_cab_date_time');
return this.check(cabValue);
},
// Shared logic, also called directly (server side) from the before Business Rule
check: function(cabValue) {
if (gs.hasRole('change_manager')) {
return 'valid';
}
if (!cabValue) {
return 'valid';
}
var cabGdt = new GlideDateTime(cabValue);
var dayMap = {
Sunday: 7, Monday: 1, Tuesday: 2, Wednesday: 3,
Thursday: 4, Friday: 5, Saturday: 6
};
var configuredDays = gs.getProperty('cab.days', 'Monday,Thursday').split(',');
var validDayNumbers = [];
for (var i = 0; i < configuredDays.length; i++) {
var num = dayMap[configuredDays[i].trim()];
if (num) {
validDayNumbers.push(num);
}
}
if (validDayNumbers.indexOf(cabGdt.getDayOfWeekLocalTime()) === -1) {
return 'Selected date is not a configured CAB day, please choose a valid CAB Date/Time';
}
var cutoffParts = gs.getProperty('cab.cutoff_time', '16:00').split(':');
var cutoffHour = parseInt(cutoffParts[0], 10);
var cutoffMinute = parseInt(cutoffParts[1], 10);
// Cutoff day is the calendar day immediately before the selected CAB date
var cutoffDayGdt = new GlideDateTime(cabGdt);
cutoffDayGdt.addDaysLocalTime(-1);
var cutoffDateStr = cutoffDayGdt.getLocalDate().getByFormat('yyyy-MM-dd');
var nowGdt = new GlideDateTime();
var nowDateStr = nowGdt.getLocalDate().getByFormat('yyyy-MM-dd');
var pastCutoff = false;
if (nowDateStr > cutoffDateStr) {
pastCutoff = true;
} else if (nowDateStr === cutoffDateStr) {
var nowHour = parseInt(nowGdt.getLocalTime().getByFormat('HH'), 10);
var nowMinute = parseInt(nowGdt.getLocalTime().getByFormat('mm'), 10);
if (nowHour > cutoffHour || (nowHour === cutoffHour && nowMinute >= cutoffMinute)) {
pastCutoff = true;
}
}
if (pastCutoff) {
return 'Time to include a change in CAB has passed, please follow CAB Cutoff exception procedure';
}
return 'valid';
},
type: 'CABCutoffValidator'
});3. Business Rule, before, on change_request, insert and update
Filter condition: type is normal AND cab_date_time changes
(function executeRule(current, previous /*null when async*/) {
var result = new CABCutoffValidator().check(current.getValue('cab_date_time'));
if (result !== 'valid') {
gs.addErrorMessage(result);
current.setAbortAction(true);
}
})(current, previous);This is the piece that actually enforces the rule. It fires the same regardless of whether the record came from the form, a list edit, an import, or the REST API, and gs.hasRole runs inside check(), so change_manager bypass is enforced here too, not just in the client script.
4. Client Script, onChange, on cab_date_time
function onChange(control, oldValue, newValue, isLoading, isTemplate) {
if (isLoading || newValue === '') {
return;
}
// UX shortcut only, skips an unnecessary Ajax round trip for change managers.
// The role bypass that actually matters lives server side in the Business Rule.
if (g_user.hasRole('change_manager')) {
return;
}
var ga = new GlideAjax('CABCutoffValidator');
ga.addParam('sysparm_name', 'validateCutoff');
ga.addParam('sysparm_cab_date_time', newValue);
ga.getXMLAnswer(function(answer) {
if (answer && answer !== 'valid') {
g_form.showFieldMsg('cab_date_time', answer, 'error');
g_form.clearValue('cab_date_time');
} else {
g_form.hideFieldMsg('cab_date_time', true);
}
});
}One more thing worth calling out since you're on a datetime field now instead of the plain date field from the original question: cab_date_time stores UTC internally and getDayOfWeekLocalTime()/getLocalDate()/getLocalTime() all resolve against the session user's timezone, which is what you want for a 4:00 PM cutoff that means 4:00 PM to the person entering the change. Just make sure whoever tests this checks it from a couple of different user timezones, that's exactly the class of bug documented in KB0831541 for the plain cab_date field, and it's easy to reintroduce if someone later "simplifies" this logic to compare raw GlideDateTime values instead of the LocalTime variants.
Thank you,
Vikram Karety
Octigo Solutions INC
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
3 weeks ago
Hi @Sirr
As per my understanding, you are using the wrong use case here. The CAB date should never be set manually by a user; it should be populated automatically.
I have worked as a Change Manager and used the CAB Workbench. Once a CAB Workbench is created, it automatically generates based on the CAB creation conditions. If a change meets those criteria, it will be included in the CAB agenda and the CAB date will be updated automatically. Otherwise, there is no need to manually invoke or update anything.
This is an OOTB ServiceNow capability. You can make the CAB date field read-only so that it is always maintained by the system and gets updated whenever the CAB agenda is prepared.
Regards
Dr. Atul G. - Learn N Grow Together
ServiceNow Techno - Functional Trainer
LinkedIn: https://www.linkedin.com/in/dratulgrover
YouTube: https://www.youtube.com/@LearnNGrowTogetherwithAtulG
****************************************************************************************************************