What this document is. It is the complete text of chapters 01–16, merged and re-ordered for a single sitting, not a summary. Nothing was cut except the setup rule that each chapter repeated verbatim — that now appears once, below, and applies to every chapter. Each chapter keeps its own quick-open link bar, and every cross-chapter link has been rewritten to jump inside this file instead of opening a new page.
How to work through it. Keep your instance on a second screen. Follow the timetable; it is built from the real length of each chapter. Use Collapse all in the bar above to navigate, then expand the one chapter you are working on. The Print / PDF button expands everything first, so the PDF contains the whole guide.
One-day review, not one-day build. 7 h 30 m is enough to read every chapter and build the core objects if you already know Studio. If this is your first scoped application, treat today as the read-through and tomorrow as the build — chapter 01 alone deserves its own session. The chapters are ordered by dependency: 02 needs 01's tables, 04 needs 02's ACLs, 08 tests what 01–04 built. Do not skip forward.
The one-day timetable
Starting at 08:30 with a short break and lunch, the day ends at 17:30. Shift the start time; keep the order and the proportions.
Chapter map
Seventeen cards, in build order. Each one tells you what you produce and how long it takes.
Rule 0 — before any object, in every chapter
Every source chapter opened with this same instruction block. It is stated here once and applies to all seventeen chapters below: before creating anything, make sure the application, the update set and the Studio context are correct. Getting this wrong means your work lands in the wrong scope or the wrong update set, and no amount of later cleverness fixes it cleanly.
Repeat these checks before creating or editing any application file. This section is also section 00 Update Set — create the capture set first.
- Application picker — top-right globe / application menu → select HR Leave (
x_786686_hr_leave). Never leave Global selected when creating scoped artifacts. - Update Set — All → System Update Sets → Local Update Sets → New:
After submit: open the record → click Make Current. Direct list: {BASE}/sys_update_set_list.doField Value Name HR Leave – BuildApplication HR Leave State In Progress Description Captures all HR Leave lab artifacts - Open Studio — {BASE}/$studio.do → confirm header shows HR Leave.
If artifacts land in Global scope, delete them and recreate under HR Leave. ACLs and events created globally will not behave correctly for this app.
Verify Update Set: Create a test Script Include in Studio → open HR Leave – Build → Related List Customer Updates shows the new file.
The chapters
Each section below is the full original chapter, with headings shifted one level down so the document has a single consistent outline.
01Development — build the application
Original chapter title: 01 — Development (Studio From Scratch) — Complete Studio create guide: Application, 4 tables, Script Includes, BR1–4, CS1–2, UI Policy/Action, Menu, Modules, Flow
What you build
Application, 4 tables (employee_v2, req_v2, bal_v2, audit_v2), 2 Script Includes, BR1–BR4, CS1–CS2, UI Policy, UI Action, menu, modules, approval Flow.
Proof it worked
A leave request can be created, days calculate automatically, and the state change writes an audit row.
Read it this way
Everything else in the day depends on these objects existing. If you only have time for one chapter, this is it.
Visual map — Ch.01 · Development — app from zero
App → 4 tables → SI → BR1–4 → CS → UI → menu → Flow
x_786686_hr_leave · employee/req/bal/audit_v2 · HRLX* utils
Development
Full visual path (all chapters): VISUAL_FROM_SCRATCH_00_16.html
Studio-only from scratch: This chapter is a complete create guide for all core development objects. No SDK, no Fluent, no CLI, no npx. Application scope x_786686_hr_leave · sys_id e5f5bc9493a303507520703efaba104d.
Also open: Properties + Direct Links · Schema · Studio Create All Objects
Use HRLXProLeaveUtilsV2, HRLXProSecurityUtilsV2, and u_* columns only — not legacy LeaveRequestUtil or duration_days.
How development objects relate
Application (HR Leave)
└── Tables: employee_v2 ←── bal_v2
↑ ↑
└── req_v2 ────┴── audit_v2
│
┌──────────────┼──────────────┐
▼ ▼ ▼
Script Includes Business Rules Client Scripts
LeaveUtilsV2 ←── BR1 days/approver
←── BR2/BR4 events
←── BR3 audit/balance
SecurityUtils ←── ACLs (ch.02)
LeaveUtils ←── CS2 GlideAjax
│
┌──────────────┼──────────────┐
▼ ▼ ▼
UI Policy/Action Menu/Modules Flow Approval
(sick / Submit) (navigator) (Ask For Approval)
Data flow: Employee (manager) → Leave Request → BR1 sets days + approver → Submit → BR2 event + Flow → Approve → BR3 audit/balance + BR4 notification event.
Part 1: Application Setup
Purpose
Creates the scoped HR Leave application container. All other OBJ artifacts belong to scope x_786686_hr_leave and app sys_id e5f5bc9493a303507520703efaba104d on the lab instance.
Navigation path
Direct URL
Prerequisites
- PDI or dev instance with admin + app_creator roles.
- Rule 0 Update Set can be created immediately after app exists.
Form fields
| Field | Value |
|---|---|
| Name | HR Leave |
| Scope name | x_786686_hr_leave |
| Version | 1.0.0 |
| Short description | HR Leave management v2 lab app |
| Package type | Custom application |
| Create application menu | Checked |
| Studio | Checked (default) |
Step-by-step create
- Open Studio.
- Click Create Application.
- Enter every field from the table. Scope must be
x_786686_hr_leave. - Click Create. Studio opens HR Leave workspace.
- Classic verify: Application record → Scope = x_786686_hr_leave.
- Proceed to Rule 0 Update Set → HR Leave – Build → Make Current.
Verify checklist
- Application picker lists HR Leave.
- Scope prefix on new tables is
x_786686_hr_leave_. - Application Menu stub exists (modules added in Modules).
Demo / test
Switch picker Global ↔ HR Leave. New Update Set shows Application = HR Leave.
Part 2: Table — HR Employee (employee_v2)
Purpose
Stores HR employee profiles linked to platform users. BR1 reads u_manager to populate leave request approver.
Navigation path
Direct URL
Prerequisites
- Part 1: Application Setup Application HR Leave.
- Rule 0 Update Set current.
Table form fields (create the table first)
| Field on New Table form | Suggested professional value |
|---|---|
| Label | HR Employee |
| Name | employee_v2 → full name becomes x_786686_hr_leave_employee_v2 |
| Extends table | (leave empty — do not extend Task) |
| Create module | true (or add later in Modules) |
| Create access controls | true (refine later in Administration) |
| Application | HR Leave |
Where: Studio → Create Application File → Table · Direct: {BASE}/sys_db_object.do
Column form fields (each column New)
On Columns related list → New: set Column label, Type, Max length, Reference. Element name = code below.
| Column | Column label | Type | Max length | Reference | Choice values | Display | Mandatory |
|---|---|---|---|---|---|---|---|
u_user | User | Reference | — | sys_user | — | No | Yes |
u_employee_id | Employee ID | String | 40 | — | — | No | Yes |
u_department | Department | String | 100 | — | — | No | No |
u_manager | Manager | Reference | — | sys_user | — | No | Yes |
u_join_date | Join Date | Date | — | — | — | No | No |
u_grade | Grade | String | 20 | — | — | No | No |
Form Layout — x_786686_hr_leave_employee_v2
- All → System Definition → Tables → open
x_786686_hr_leave_employee_v2(or Studio → Tables). - Related link Form Layout → New or edit default.
- Drag fields into sections: Profile (u_user, u_employee_id, u_department, u_grade, u_join_date), Management (u_manager).
- Save.
List Layout — x_786686_hr_leave_employee_v2
- Same table record → related link List Layout.
- Add columns (left to right): User, Employee ID, Department, Manager, Join Date, Grade.
- Save → open list URL and confirm columns appear.
Step-by-step create
- Studio → Create Application File → Table.
- Label:
HR Employee· Name:employee_v2→ full namex_786686_hr_leave_employee_v2. - Extends table: leave blank (standalone).
- Submit table record.
- For each column in the table above: Table → Columns related list → New:
- Element = column name · Column label = friendly name · Type = as listed · Max length · Reference · Application = HR Leave.
- Complete Form Layout and List Layout steps above.
Verify checklist
- List opens: employee_v2 list
- Dictionary:
{BASE}/sys_dictionary_list.do?sysparm_query=name=x_786686_hr_leave_employee_v2^elementSTARTSWITHu_ - Six u_* columns under HR Leave application.
Demo / test
- New record → User = employee1, Employee ID = E-1001, Manager = manager1.
- Save — no mandatory errors.
Part 3: Table — Leave Request (req_v2)
Purpose
Core leave request table. Business rules, Flow, catalog, portal, and REST all target this table.
Navigation path
Direct URL
Prerequisites
- Part 1: Application Setup Application.
- Part 2: Table employee_v2 (for u_employee_ref reference).
Table form fields (create the table first)
| Field on New Table form | Suggested professional value |
|---|---|
| Label | Leave Request |
| Name | req_v2 → x_786686_hr_leave_req_v2 |
| Extends table | (empty) |
| Create module | true |
| Application | HR Leave |
Where: Studio → Create Application File → Table · New table
Column form fields (each column New)
On Columns related list → New: set Column label, Type, Max length, Reference. Element name = code below.
| Column | Column label | Type | Max length | Reference | Choice values | Display | Mandatory |
|---|---|---|---|---|---|---|---|
u_number | Number | String | 40 | — | — | Yes (display) | No |
u_requested_for_user | Requested For | Reference | — | sys_user | — | No | Yes |
u_employee_ref | Employee | Reference | — | x_786686_hr_leave_employee_v2 | — | No | Yes |
u_leave_type | Leave Type | Choice | — | — | annual, sick, unpaid | No | Yes |
u_start_date | Start Date | Date | — | — | — | No | Yes |
u_end_date | End Date | Date | — | — | — | No | Yes |
u_days_count | Days | Integer | — | — | — | No | No (read-only on form) |
u_state | State | Choice | — | — | draft, submitted, approved, rejected, cancelled | No | Yes |
u_approver_user | Approver | Reference | — | sys_user | — | No | No (set by BR1) |
u_reason | Reason | String | 1000 | — | — | No | Conditional (UI Policy sick) |
Choice values — u_leave_type
| Value | Label |
|---|---|
| annual | Annual |
| sick | Sick |
| unpaid | Unpaid |
Choice values — u_state
| Value | Label |
|---|---|
| draft | Draft |
| submitted | Submitted |
| approved | Approved |
| rejected | Rejected |
| cancelled | Cancelled |
Form Layout — x_786686_hr_leave_req_v2
- All → System Definition → Tables → open
x_786686_hr_leave_req_v2(or Studio → Tables). - Related link Form Layout → New or edit default.
- Drag fields into sections: Request (u_number, u_requested_for_user, u_employee_ref, u_leave_type), Dates (u_start_date, u_end_date, u_days_count), Approval (u_approver_user, u_reason), Status (u_state).
- Save.
List Layout — x_786686_hr_leave_req_v2
- Same table record → related link List Layout.
- Add columns (left to right): Number, Requested For, Leave Type, Start Date, End Date, Days, State, Approver.
- Save → open list URL and confirm columns appear.
Step-by-step create
- Studio → Create Application File → Table.
- Label:
Leave Request· Name:req_v2→x_786686_hr_leave_req_v2. - Create all columns per field table. For Choice columns add values in Choice List related list.
- Table record → Display field =
u_number. - Dictionary: mark
u_days_countread-only on form (optional dictionary flag). - Complete Form Layout and List Layout above.
Verify checklist
- Ten u_* columns exist.
- Choice fields show five states and three leave types.
- Reference picker on u_employee_ref shows employee_v2 rows.
Demo / test
New req_v2 as admin: fill dates Mon–Fri, save — u_days_count fills after Business Rule BR1 BR1.
Part 4: Table — Leave Balance (bal_v2)
Purpose
Tracks per-employee leave entitlement and usage by year and leave type. BR3 recalculates on approval.
Navigation path
Direct URL
Prerequisites
- Part 2: Table employee_v2.
- Part 20: System Properties property for default entitled days (can seed manually first).
Table form fields (create the table first)
| Field on New Table form | Suggested professional value |
|---|---|
| Label | Leave Balance |
| Name | bal_v2 → x_786686_hr_leave_bal_v2 |
| Extends table | (empty) |
| Application | HR Leave |
Column form fields (each column New)
On Columns related list → New: set Column label, Type, Max length, Reference. Element name = code below.
| Column | Column label | Type | Max length | Reference | Choice values | Display | Mandatory |
|---|---|---|---|---|---|---|---|
u_employee_ref | Employee | Reference | — | x_786686_hr_leave_employee_v2 | — | No | Yes |
u_year | Year | Integer | — | — | — | No | Yes |
u_leave_type | Leave Type | Choice | — | — | annual, sick, unpaid | No | Yes |
u_entitled | Entitled | Integer | — | — | — | No | Yes |
u_used | Used | Integer | — | — | — | No | No |
u_remaining | Remaining | Integer | — | — | — | No | No |
Form Layout — x_786686_hr_leave_bal_v2
- All → System Definition → Tables → open
x_786686_hr_leave_bal_v2(or Studio → Tables). - Related link Form Layout → New or edit default.
- Drag fields into sections: Employee (u_employee_ref, u_year, u_leave_type), Balance (u_entitled, u_used, u_remaining).
- Save.
List Layout — x_786686_hr_leave_bal_v2
- Same table record → related link List Layout.
- Add columns (left to right): Employee, Year, Leave Type, Entitled, Used, Remaining.
- Save → open list URL and confirm columns appear.
Step-by-step create
- Studio → Table → Name
bal_v2. - Add six columns per table.
- Reuse u_leave_type choice values from req_v2.
- Form/List layouts per steps above.
Verify checklist
- bal_v2 list opens.
- Reference to employee_v2 works.
Demo / test
Seed: employee, year 2026, annual, entitled 20, used 0, remaining 20.
Part 5: Table — Leave Audit (audit_v2)
Purpose
Append-only audit trail for leave state changes. BR3 writes rows automatically.
Navigation path
Direct URL
Prerequisites
- Part 3: Table req_v2.
Table form fields (create the table first)
| Field on New Table form | Suggested professional value |
|---|---|
| Label | Leave Audit |
| Name | audit_v2 → x_786686_hr_leave_audit_v2 |
| Extends table | (empty) |
| Application | HR Leave |
Column form fields (each column New)
On Columns related list → New: set Column label, Type, Max length, Reference. Element name = code below.
| Column | Column label | Type | Max length | Reference | Choice values | Display | Mandatory |
|---|---|---|---|---|---|---|---|
u_leave_ref | Leave | Reference | — | x_786686_hr_leave_req_v2 | — | No | Yes |
u_old_state | Old State | String | 40 | — | — | No | No |
u_new_state | New State | String | 40 | — | — | No | Yes |
u_event_type | Event Type | String | 60 | — | — | No | No |
u_actor | Actor | Reference | — | sys_user | — | No | No |
Form Layout — x_786686_hr_leave_audit_v2
- All → System Definition → Tables → open
x_786686_hr_leave_audit_v2(or Studio → Tables). - Related link Form Layout → New or edit default.
- Drag fields into sections: Audit (u_leave_ref, u_event_type, u_old_state, u_new_state, u_actor).
- Save.
List Layout — x_786686_hr_leave_audit_v2
- Same table record → related link List Layout.
- Add columns (left to right): Leave Request, Old State, New State, Event Type, Actor, Created.
- Save → open list URL and confirm columns appear.
Step-by-step create
- Studio → Table →
audit_v2. - Add five columns.
- Layouts as above.
Verify checklist
- audit_v2 list opens.
- Reference to req_v2 works.
Demo / test
After Business Rule BR3 BR3: change req state → new audit row appears.
Part 6: Script Include — HRLXProLeaveUtilsV2
Purpose
Server-side utility for business-day calculation, approver resolution, audit writes, balance recalc, and GlideAjax for CS2.
Navigation path
Direct URL
Prerequisites
- Part 1: Application Setup.
- Tables Table HR Employee..05 exist.
Form fields
| Field | Value |
|---|---|
| Name | HRLXProLeaveUtilsV2 |
| API Name | x_786686_hr_leave.HRLXProLeaveUtilsV2 |
| Client callable | Checked |
| Active | Checked |
| Accessible from | All application scopes |
| Script | Paste block below |
Extends AbstractAjaxProcessor for ajaxGetBusinessDays.
Step-by-step create
- Studio → Script Include → New.
- Fill form fields table.
- Paste full script below into Script field.
- Save — fix any syntax errors.
Full script (paste-ready)
/**
* Script Include: HRLXProLeaveUtilsV2
* Scope: x_786686_hr_leave
* Application: Custom · Scope: x_786686_hr_leave · sys_id: 83d5298c932b8f107520703efaba1072
* Client callable: true (extends AbstractAjaxProcessor)
* Purpose: Leave calculations + helpers (business days, approver, balance, audit).
*
* Create in ServiceNow:
* Studio > Create Application File > Script Include
* Name: HRLXProLeaveUtilsV2
* Client callable: checked
*/
var HRLXProLeaveUtilsV2 = Class.create();
HRLXProLeaveUtilsV2.prototype = Object.extendsObject(global.AbstractAjaxProcessor, {
/**
* Count business days (Mon-Fri) inclusive between two date strings.
* @param {string} startDate - 'yyyy-MM-dd'
* @param {string} endDate - 'yyyy-MM-dd'
* @return {number}
*/
calculateBusinessDays: function (startDate, endDate) {
if (!startDate || !endDate) return 0;
var start = new GlideDateTime(startDate + ' 00:00:00');
var end = new GlideDateTime(endDate + ' 00:00:00');
if (end.before(start)) return 0;
var days = 0;
var cursor = new GlideDateTime(start);
while (!cursor.after(end)) {
var dow = cursor.getDayOfWeekLocalTime(); // 1=Mon ... 7=Sun
if (dow >= 1 && dow <= 5) days++;
cursor.addDaysLocalTime(1);
}
return days;
},
/**
* Resolve the approver (employee's manager) and set it on the record.
* @param {GlideRecord} currentRecord - leave request record
*/
setApproverFromEmployee: function (currentRecord) {
var empId = currentRecord.getValue('u_employee_ref');
if (!empId) return;
var emp = new GlideRecord('x_786686_hr_leave_employee_v2');
if (emp.get(empId) && emp.u_manager) {
currentRecord.u_approver_user = emp.getValue('u_manager');
}
},
/**
* Write an audit row when the state changes.
*/
auditStateChange: function (leaveSysId, oldState, newState, eventType) {
var a = new GlideRecord('x_786686_hr_leave_audit_v2');
a.initialize();
a.u_leave_ref = leaveSysId;
a.u_old_state = oldState || '';
a.u_new_state = newState || '';
a.u_event_type = eventType || 'state_change';
a.u_actor = gs.getUserID();
a.insert();
},
/**
* Recalculate remaining balance for an employee/year.
*/
recalculateBalance: function (employeeSysId, year) {
var used = this._sumApprovedDays(employeeSysId, year);
var bal = new GlideRecord('x_786686_hr_leave_bal_v2');
bal.addQuery('u_employee_ref', employeeSysId);
bal.addQuery('u_year', year);
bal.query();
if (bal.next()) {
bal.u_used = used;
bal.u_remaining = parseInt(bal.getValue('u_entitled') || 0, 10) - used;
bal.update();
}
},
_sumApprovedDays: function (employeeSysId, year) {
var total = 0;
var gr = new GlideAggregate('x_786686_hr_leave_req_v2');
gr.addQuery('u_employee_ref', employeeSysId);
gr.addQuery('u_state', 'approved');
gr.addQuery('u_start_date', '>=', year + '-01-01');
gr.addQuery('u_start_date', '<=', year + '-12-31');
gr.addAggregate('SUM', 'u_days_count');
gr.query();
if (gr.next()) total = parseInt(gr.getAggregate('SUM', 'u_days_count') || 0, 10);
return total;
},
/**
* GlideAjax entry point for client-side day calculation.
*/
ajaxGetBusinessDays: function () {
return this.calculateBusinessDays(
this.getParameter('sysparm_start'),
this.getParameter('sysparm_end')
);
},
type: 'HRLXProLeaveUtilsV2'
});
Verify checklist
- Active record under HR Leave.
- No compile error banner.
- Background test returns 5 for Mon–Fri range.
Demo / test
var util = new x_786686_hr_leave.HRLXProLeaveUtilsV2();
gs.info('Days: ' + util.calculateBusinessDays('2026-09-01', '2026-09-05'));Part 7: Script Include — HRLXProSecurityUtilsV2
Purpose
Centralized read/write authorization logic consumed by table ACLs on req_v2.
Navigation path
Direct URL
Prerequisites
- Part 6: Script Include (same app scope).
Form fields
| Field | Value |
|---|---|
| Name | HRLXProSecurityUtilsV2 |
| Client callable | Unchecked |
| Active | Checked |
Step-by-step create
- Studio → Script Include → New.
- Name = HRLXProSecurityUtilsV2, Client callable = false.
- Paste script below. Save.
Full script (paste-ready)
/**
* Script Include: HRLXProSecurityUtilsV2
* Scope: x_786686_hr_leave
* Application: Custom · Scope: x_786686_hr_leave · sys_id: 83d5298c932b8f107520703efaba1072
* Client callable: false
* Purpose: Centralized security checks used by ACLs.
*
* Used by ACL scripts, e.g.:
* answer = new x_786686_hr_leave.HRLXProSecurityUtilsV2().canReadLeave(current);
*/
var HRLXProSecurityUtilsV2 = Class.create();
HRLXProSecurityUtilsV2.prototype = {
initialize: function () {
this.userId = gs.getUserID();
},
/** HR admin can do anything. */
isHrAdmin: function () {
return gs.hasRole('x_786686_hr_leave.hr_admin');
},
/** Can the current user READ this leave request? */
canReadLeave: function (current) {
if (this.isHrAdmin()) return true;
// owner
if (current.getValue('u_requested_for_user') === this.userId) return true;
// approver / manager
if (current.getValue('u_approver_user') === this.userId) return true;
// manager of the employee
return this._isManagerOfEmployee(current.getValue('u_employee_ref'));
},
/** Can the current user WRITE this leave request? */
canWriteLeave: function (current) {
if (this.isHrAdmin()) return true;
var state = current.getValue('u_state');
// owner can edit only while draft/submitted
if (current.getValue('u_requested_for_user') === this.userId) {
return (state === 'draft' || state === 'submitted');
}
// approver can act when submitted
if (current.getValue('u_approver_user') === this.userId) {
return (state === 'submitted');
}
return false;
},
_isManagerOfEmployee: function (employeeSysId) {
if (!employeeSysId) return false;
var emp = new GlideRecord('x_786686_hr_leave_employee_v2');
if (emp.get(employeeSysId)) {
return emp.getValue('u_manager') === this.userId;
}
return false;
},
type: 'HRLXProSecurityUtilsV2'
};
Verify checklist
- Record active under HR Leave.
- Used by ACLs ACL scripts.
Demo / test
Background: instantiate and call canReadLeave on a sample req_v2 GlideRecord.
Part 8: Business Rule BR1 — Calculate Days
Purpose
Calculates u_days_count and sets u_approver_user from employee manager before save.
Navigation path
Direct URL
Prerequisites
- Part 3: Table req_v2.
- Part 6: Script Include HRLXProLeaveUtilsV2.
Form fields
| Field | Value |
|---|---|
| Name | HRLX BR1 Calculate Days |
| Table | x_786686_hr_leave_req_v2 |
| Advanced | Checked |
| When | before |
| Insert | Checked |
| Update | Checked |
| Delete | Unchecked |
| Order | 100 |
| Active | Checked |
Step-by-step create
- Studio → Business Rule → New.
- Fill every field from table. Check Advanced.
- Paste script below into Script field.
- Save. Confirm Active = true.
Full script (paste-ready)
/**
* Business Rule: BR1 - Calculate Days + Approver
* Application: Custom · Scope: x_786686_hr_leave · sys_id: 83d5298c932b8f107520703efaba1072
* Table: x_786686_hr_leave_req_v2
* When: before
* Insert: true Update: true
* Order: 100
*
* Calculates u_days_count and resolves the approver before saving.
*/
(function executeRule(current, previous /*null when inserting*/) {
var util = new x_786686_hr_leave.HRLXProLeaveUtilsV2();
if (current.u_start_date.changes() || current.u_end_date.changes() || current.isNewRecord()) {
current.u_days_count = util.calculateBusinessDays(
current.getValue('u_start_date'),
current.getValue('u_end_date')
);
}
if (current.u_employee_ref.changes() || current.isNewRecord()) {
util.setApproverFromEmployee(current);
}
})(current, previous);
Verify checklist
- BR order: 100, 200, 250, 300.
- All Active on req_v2 under HR Leave.
Demo / test
See section demo after BR is active (days calc, events, audit, balance).
Part 9: Business Rule BR2 — On Submit Event
Purpose
Queues submitted event when u_state becomes submitted (notifications + Flow trigger).
Navigation path
Direct URL
Prerequisites
- Part 3: Table req_v2.
- Part 6: Script Include HRLXProLeaveUtilsV2.
Form fields
| Field | Value |
|---|---|
| Name | HRLX BR2 On Submit Event |
| Table | x_786686_hr_leave_req_v2 |
| Advanced | Checked |
| When | after |
| Insert | Checked |
| Update | Checked |
| Delete | Unchecked |
| Order | 200 |
| Active | Checked |
Step-by-step create
- Studio → Business Rule → New.
- Fill every field from table. Check Advanced.
- Paste script below into Script field.
- Save. Confirm Active = true.
Full script (paste-ready)
/**
* Business Rule: BR2 - On Submit Fire Event
* Application: Custom · Scope: x_786686_hr_leave · sys_id: 83d5298c932b8f107520703efaba1072
* Table: x_786686_hr_leave_req_v2
* When: after
* Update: true Insert: true
* Order: 200
*
* Fires an event when state becomes 'submitted' (drives notification / flow).
*/
(function executeRule(current, previous) {
if (current.u_state.changesTo('submitted')) {
gs.eventQueue(
'x_786686_hr_leave.leave_v2.submitted',
current,
current.getValue('u_approver_user'),
current.getValue('u_number')
);
}
})(current, previous);
Verify checklist
- BR order: 100, 200, 250, 300.
- All Active on req_v2 under HR Leave.
Demo / test
See section demo after BR is active (days calc, events, audit, balance).
Part 10: Business Rule BR3 — Audit State Change
Purpose
Writes audit_v2 row on state change; recalculates balance on approval.
Navigation path
Direct URL
Prerequisites
- Part 3: Table req_v2.
- Part 6: Script Include HRLXProLeaveUtilsV2.
Form fields
| Field | Value |
|---|---|
| Name | HRLX BR3 Audit State Change |
| Table | x_786686_hr_leave_req_v2 |
| Advanced | Checked |
| When | after |
| Insert | Unchecked |
| Update | Checked |
| Delete | Unchecked |
| Order | 300 |
| Active | Checked |
Step-by-step create
- Studio → Business Rule → New.
- Fill every field from table. Check Advanced.
- Paste script below into Script field.
- Save. Confirm Active = true.
Full script (paste-ready)
/**
* Business Rule: BR3 - Audit State Change + Balance
* Application: Custom · Scope: x_786686_hr_leave · sys_id: 83d5298c932b8f107520703efaba1072
* Table: x_786686_hr_leave_req_v2
* When: after
* Update: true
* Order: 300
*
* Writes an audit row on any state change and recalculates balance on approval.
*/
(function executeRule(current, previous) {
if (!current.u_state.changes()) return;
var util = new x_786686_hr_leave.HRLXProLeaveUtilsV2();
util.auditStateChange(
current.getUniqueValue(),
previous ? previous.getValue('u_state') : '',
current.getValue('u_state'),
'state_change'
);
if (current.u_state.changesTo('approved') && current.u_employee_ref) {
var year = new GlideDateTime(current.getValue('u_start_date')).getYearLocalTime();
util.recalculateBalance(current.getValue('u_employee_ref'), year);
}
})(current, previous);
Verify checklist
- BR order: 100, 200, 250, 300.
- All Active on req_v2 under HR Leave.
Demo / test
See section demo after BR is active (days calc, events, audit, balance).
Part 11: Business Rule BR4 — Approve / Reject Event
Purpose
Queues approved/rejected events for notifications.
Navigation path
Direct URL
Prerequisites
- Part 3: Table req_v2.
- Part 6: Script Include HRLXProLeaveUtilsV2.
Form fields
| Field | Value |
|---|---|
| Name | HRLX BR4 Approve Reject Event |
| Table | x_786686_hr_leave_req_v2 |
| Advanced | Checked |
| When | after |
| Insert | Unchecked |
| Update | Checked |
| Delete | Unchecked |
| Order | 250 |
| Active | Checked |
Step-by-step create
- Studio → Business Rule → New.
- Fill every field from table. Check Advanced.
- Paste script below into Script field.
- Save. Confirm Active = true.
Full script (paste-ready)
/**
* Business Rule: BR4 - Fire Approved / Rejected Events
* Application: Custom · Scope: x_786686_hr_leave · sys_id: 83d5298c932b8f107520703efaba1072
* Table: x_786686_hr_leave_req_v2
* When: after
* Update: true
* Insert: false
* Order: 250
*
* Drives notifications for approved and rejected leave requests.
* Requires events registered in Event Registry (see events.md).
*/
(function executeRule(current, previous) {
if (current.u_state.changesTo('approved')) {
gs.eventQueue(
'x_786686_hr_leave.leave_v2.approved',
current,
current.getValue('u_requested_for_user'),
current.getValue('u_number')
);
}
if (current.u_state.changesTo('rejected')) {
gs.eventQueue(
'x_786686_hr_leave.leave_v2.rejected',
current,
current.getValue('u_requested_for_user'),
current.getValue('u_number')
);
}
})(current, previous);
Verify checklist
- BR order: 100, 200, 250, 300.
- All Active on req_v2 under HR Leave.
Demo / test
See section demo after BR is active (days calc, events, audit, balance).
Part 12: Client Script CS1 — Validate Dates
Purpose
Client-side validation: end date cannot precede start date on req_v2 form.
Navigation path
Direct URL
Prerequisites
- Part 3: Table req_v2 table/form.
Form fields
| Field | Value |
|---|---|
| Name | HRLX CS1 Validate Dates |
| Table | x_786686_hr_leave_req_v2 |
| Type | onChange |
| Field name | u_end_date |
| UI Type | Desktop + All checked |
| Active | Checked |
Step-by-step create
- Studio → Client Script → New.
- Fill fields. Type = onChange, Field = u_end_date.
- Paste script. Save.
Full script (paste-ready)
/**
* Client Script: CS1 - Validate Dates
* Application: Custom · Scope: x_786686_hr_leave · sys_id: 83d5298c932b8f107520703efaba1072
* Table: x_786686_hr_leave_req_v2
* Type: onChange
* Field: u_end_date
*
* Shows an error if end date is before start date.
*/
function onChange(control, oldValue, newValue, isLoading, isTemplate) {
if (isLoading || newValue === '') return;
var start = g_form.getValue('u_start_date');
var end = g_form.getValue('u_end_date');
if (!start || !end) return;
if (end < start) {
g_form.showFieldMsg('u_end_date', getMessage('End date cannot be before start date'), 'error');
g_form.clearValue('u_end_date');
} else {
g_form.hideFieldMsg('u_end_date', true);
}
}
Verify checklist
- Client script list shows Active on req_v2.
Demo / test
Start 2026-09-10, End 2026-09-01 → error, end cleared.
Part 13: Client Script CS2 — Calc Days (GlideAjax)
Purpose
Calls HRLXProLeaveUtilsV2.ajaxGetBusinessDays to preview u_days_count before save.
Navigation path
Direct URL
Prerequisites
- Part 6: Script Include client-callable Script Include.
- Part 12: Client Script CS1 can share u_end_date onChange.
Form fields
| Field | Value |
|---|---|
| Name | HRLX CS2 Calc Days GlideAjax |
| Table | x_786686_hr_leave_req_v2 |
| Type | onChange |
| Field name | u_end_date (duplicate CS for u_start_date if desired) |
| UI Type | Desktop + All |
| Active | Checked |
Step-by-step create
- Create Client Script after Script Include HRLXProLeaveUtilsV2.
- onChange on u_end_date — same field as CS1 (both execute).
- Paste script. Save.
Full script (paste-ready)
/**
* Client Script: CS2 - Calculate Days (GlideAjax)
* Application: Custom · Scope: x_786686_hr_leave · sys_id: 83d5298c932b8f107520703efaba1072
* Table: x_786686_hr_leave_req_v2
* Type: onChange
* Field: u_end_date
*
* Calls HRLXProLeaveUtilsV2.ajaxGetBusinessDays asynchronously to fill u_days_count.
*/
function onChange(control, oldValue, newValue, isLoading, isTemplate) {
if (isLoading || newValue === '') return;
var start = g_form.getValue('u_start_date');
var end = g_form.getValue('u_end_date');
if (!start || !end || end < start) return;
var ga = new GlideAjax('x_786686_hr_leave.HRLXProLeaveUtilsV2');
ga.addParam('sysparm_name', 'ajaxGetBusinessDays');
ga.addParam('sysparm_start', start);
ga.addParam('sysparm_end', end);
ga.getXMLAnswer(function (answer) {
if (answer !== null && answer !== '') {
g_form.setValue('u_days_count', answer);
}
});
}
Verify checklist
- Browser dev tools → Network → GlideAjax to HRLXProLeaveUtilsV2.
Demo / test
Valid Mon–Fri range → u_days_count populates before save.
Part 14: UI Policy — Sick Requires Reason
Purpose
When leave type is sick, makes u_reason mandatory on the req_v2 form.
Navigation path
Direct URL
Prerequisites
- Part 3: Table req_v2 with u_leave_type and u_reason.
Form fields
| Field | Value |
|---|---|
| Table | x_786686_hr_leave_req_v2 |
| Short description | Sick leave requires reason |
| Application | HR Leave |
| Active | Checked |
| Reverse if false | Checked |
| On load | Checked |
| Condition | u_leave_type is sick |
UI Policy Action (related list New)
| Field | Value |
|---|---|
| Field | u_reason |
| Mandatory | true |
| Visible | true |
Step-by-step create
- Application = HR Leave.
- All → System UI → UI Policies → New.
- Fill policy fields. Condition builder: Leave Type is sick.
- Submit policy.
- Related list UI Policy Actions → New → u_reason mandatory true.
- Save action. Verify policy Active.
Verify checklist
- Leave type Sick → red asterisk on Reason.
- Annual → Reason not mandatory.
Demo / test
Select Sick, leave Reason empty, try Save → client mandatory warning.
Part 15: UI Action — Submit
Purpose
Form button that moves draft leave requests to submitted state (triggers BR2 and Flow).
Navigation path
Direct URL
Prerequisites
- Part 3: Table req_v2.
- Part 9: Business Rule BR2 BR2 for event.
Form fields
| Field | Value |
|---|---|
| Name | Submit |
| Table | x_786686_hr_leave_req_v2 |
| Action name | submit_leave |
| Form button | Checked |
| Form style | Primary (optional) |
| Order | 100 |
| Condition | current.u_state == 'draft' |
| Client | Unchecked (server script) |
| Active | Checked |
Step-by-step create
- Studio → UI Action → New on req_v2.
- Fill fields. Uncheck Client checkbox.
- Paste server script below in Script field.
- Save. Open draft req_v2 → Submit button visible.
Full script (paste-ready)
current.u_state = 'submitted';
current.update();
action.setRedirectURL(current);
Verify checklist
- Button only on draft records.
- After click state = submitted.
Demo / test
Draft → Submit → BR2 event + Flow Flow Leave Approval starts.
Part 24: Application Menu
Purpose
Top-level Application Navigator entry for HR Leave modules.
Navigation path
Direct URL
Prerequisites
- Part 1: Application Setup with Create application menu checked.
Form fields
| Field | Value |
|---|---|
| Title | HR Leave |
| Application | HR Leave |
| Order | 100 |
| Roles | x_786686_hr_leave.employee (minimum) |
Step-by-step create
- If missing: Studio → Application Menu → New → fill fields.
- Save. Confirm appears in Application Navigator.
Verify checklist
- Navigator shows HR Leave heading.
Demo / test
employee1 login → sees HR Leave menu after modules Modules.
Part 25: Modules
Purpose
Four list modules linking to req_v2, employee_v2, bal_v2, audit_v2 tables.
Navigation path
Direct URL
Prerequisites
- Part 24: Application Menu menu. Tables Table HR Employee..05.
Form fields
| Title | Type | Table | Order | Roles |
|---|---|---|---|---|
| Leave Requests | List | x_786686_hr_leave_req_v2 | 10 | employee, manager, hr_admin |
| Employees | List | x_786686_hr_leave_employee_v2 | 20 | hr_admin |
| Balances | List | x_786686_hr_leave_bal_v2 | 30 | hr_admin |
| Audit Trail | List | x_786686_hr_leave_audit_v2 | 40 | hr_admin |
Step-by-step create
- Module → New for each row. Application Menu = HR Leave.
- Type = List → Table = as listed. Order as table.
- Roles: restrict Employees/Balances/Audit to hr_admin; Requests to all three roles.
Verify checklist
- Four modules under HR Leave menu.
- URLs x_786686_hr_leave_*_list.do open.
Demo / test
employee1 → only Leave Requests visible.
Part 27: Flow Designer — Leave Approval
Purpose
When leave state becomes submitted, sends approval to u_approver_user; on approve/reject updates u_state.
Navigation path
Direct URL
Prerequisites
- Part 3: Table req_v2 with u_state, u_approver_user.
- Part 8: Business Rule BR1 BR1 fills approver.
- manager1 user for approval tests.
Form fields — Create Flow wizard
| Field | Value |
|---|---|
| Name | HRLX Leave Approval |
| Application | HR Leave |
| Description | Approval flow for submitted leave requests |
Trigger — Record Updated
| Field | Value |
|---|---|
| Trigger type | Record Updated |
| Table | x_786686_hr_leave_req_v2 |
| Condition | State [u_state] is submitted AND State changes |
| Run flow | Once per record (recommended) |
Action 1 — Ask For Approval
| Input | Value |
|---|---|
| Record | Trigger → Leave Request Record (data pill) |
| Table | x_786686_hr_leave_req_v2 |
| Approval field | u_approver_user |
| Approval reason | Leave approval required |
| Rules → Approve | Anyone approves (required — do not leave empty) |
If Approved → Update Record
| Record | Trigger record pill |
| Field map | u_state = approved |
Else → Update Record
| Field map | u_state = rejected |
Step-by-step create
- Flow Designer → Create Flow → Name HRLX Leave Approval, Application HR Leave.
- Add trigger → Record Updated → table req_v2 → condition u_state is submitted + changes.
- + → Ask for Approval → fill Action 1 table → Rules: Anyone approves → Done.
- + → Flow Logic → If → condition "approval was Approved".
- Yes branch → Update Record → u_state = approved.
- Else branch → Update Record → u_state = rejected.
- Save → fix any red errors (missing approval rule is common).
- Test → pick sample submitted req_v2 → Run test.
- Publish → Publish new version → wait for Published status.
Verify checklist
- Submitted req creates sysapproval_approver for manager.
- Approve → u_state approved → BR3 audit + BR4 event.
- Reject → u_state rejected.
Demo / test
- employee1 submits leave (UI Action or catalog).
- manager1 → My Approvals → Approve.
- Verify state, audit_v2, bal_v2 updated.
Optional — CS1 on u_start_date
Duplicate CS1 logic on u_start_date onChange (same script body) so validation runs when the user changes start date after end date is set.
| Field | Value |
|---|---|
| Type | onChange |
| Field | u_start_date |
| UI Type | All |
| Active | true |
Development verify / demo checklist
Complete these checks after creating all objects in this chapter (Application Setup through UI Action Submit, Application Menu, Modules, Flow Leave Approval).
- Update Set — HR Leave – Build is current; Customer Updates shows new artifacts.
- Tables — Four _v2 lists open; dictionary shows u_* columns only (Schema).
- Script Includes — HRLXProLeaveUtilsV2 client-callable; Background script:
new x_786686_hr_leave.HRLXProLeaveUtilsV2().calculateBusinessDays('2026-06-01','2026-06-05')→ 5. - Business Rules — New req_v2 sets u_days_count and u_approver_user (BR1); submit fires event (BR2); approve writes audit + balance (BR3/BR4).
- Client Scripts — End date before start shows error (CS1); valid range fills u_days_count via GlideAjax (CS2). Optional: duplicate CS2 on
u_start_dateonChange. - UI Policy — Sick → u_reason mandatory.
- UI Action Submit — Draft → submitted visible only on draft.
- Menu + Modules — HR Leave navigator shows four modules.
- Flow — Submit → manager approval → approved/rejected.
Extended properties catalog: PROFESSIONAL_HR_CREATE_PROPERTIES_AND_LINKS.html · Master OBJ index: STUDIO_CREATE_ALL_OBJECTS_01_16.html
01-optDevelopment Options — every field and alternative
Original chapter title: 01 — Development Options — كل الحقول والبدائل (HR Leave) — ServiceNow Australia · Custom app · Arabic + English readable · scripts copy/paste ready
What you build
Nothing new. This is the decision reference behind chapter 01: for each object, which field options exist and which one to pick.
Proof it worked
For any field in chapter 01 you can state what the other choices were and why yours is right.
Read it this way
Read it beside chapter 01, not after. It converts "I followed the steps" into "I understand the steps".
Visual map — Ch.01b · Dev options
Field/option decisions for ch.01
Decision register
Development
Full visual path (all chapters): VISUAL_FROM_SCRATCH_00_16.html
Decision register
Development
Full visual path (all chapters): VISUAL_FROM_SCRATCH_00_16.html
01 — Development Options — كل الحقول والبدائل (HR Leave)
Platform target: ServiceNow Australia (current family, Sep 2026) · next EA: Brazil
App: Custom · Scope x_786686_hr_leave · sys_id e5f5bc9493a303507520703efaba104d
URLs: ../SERVICENOW_DIRECT_URLS_CATALOG.html · 1-week: ../SERVICENOW_ONE_WEEK_STUDY_DEPLOY_AUSTRALIA.htmlCompanion to 01_DEVELOPMENT.md — use when fields, menus, or features differ on your instance.
Pattern hub: 00_TUTORIAL_OPTIONS_REFERENCE.md
App type: Custom · Scope: x_786686_hr_leave · sys_id: e5f5bc9493a303507520703efaba104d · Date created: 2026-09-09 20:20:55
1) Scope / Application — options
Option A — Studio (recommended)
Navigate: /now/studio → Create Application
| Field | Required | Value | Notes |
|---|---|---|---|
| Name | Yes | HR Leave | Display name |
| App type | Yes | Custom | Scoped custom application |
| Scope name | Yes | x_786686_hr_leave | Auto-generated prefix; cannot change |
| Date created | — | 2026-09-09 20:20:55 | From your instance |
| sys_id | — | e5f5bc9493a303507520703efaba104d | sys_app record |
| Version | No | 1.0.0 | Semantic versioning |
| Short description | No | Leave management scoped app | |
| Logo | No | optional |
Option B — From sys_app list (admin)
Navigate: sys_app.list → New (admin only; prefer Studio)
Option C — Personal Developer Instance template
Some PDIs ship HR samples — still create your scope to practice Update Sets.
After create — Application record fields
Navigate: All > System Applications > Applications → HR Leave — Open direct
| Field / related | Recommended | Options you forget |
|---|---|---|
| Trackable | true | Required for scoped Update Sets |
| Can read in public | false | Internal HR app |
| Application Access | Restrict to roles | employee, manager, hr_admin |
| Runtime Access | Same as design | |
| Menu | Auto from modules | Classic UI app menu |
| Source control | Optional Git | Studio → Source Control |
Common errors
| Error | Fix |
|---|---|
| Tables created in Global | Switch app picker → HR Leave → recreate |
| Cannot delete scope | Expected — scope is permanent |
2) Tables — options
Option A — Studio → Table
| Field | Required | Example req_v2 |
|---|---|---|
| Label | Yes | Leave Request |
| Plural | No | Leave Requests |
| Name | Yes | x_786686_hr_leave_req_v2 |
| Extends | No | blank or task (if you want task features) |
| Create module | Yes | true — adds app menu link |
| Auto-number | Optional | Prefix LRX |
Option B — Extend task (alternative design)
| Pros | Cons |
|---|---|
| SLA, assignment OOTB | Heavier; mixes with ITSM patterns |
| Familiar to ITSM devs | More ACL overlap |
Our app: standalone tables (no extend) — simpler for learning.
Option C — Import from XML / Studio
Files: SCHEMA_AND_DATA.html, STUDIO_CREATE_ALL_OBJECTS_01_16.html, 11 Studio practice
Field (Dictionary) — all options to know
Navigate: field → Configure Dictionary
| Option | When to use |
|---|---|
| Mandatory | Always required |
| Read only | Calculated (u_days_count) |
| Default value | draft for state |
| Reference qual | Active users only |
| Choice | u_leave_type, u_state |
| Max length | Strings |
| Display | Reference display field |
Instance note
- Dictionary Entries vs Table Columns — same data, two UIs.
- Scope on dictionary must match table scope.
3) Script Includes — options
Option A — Studio → Script Include
| Field | Value |
|---|---|
| Name | HRLXProLeaveUtilsV2 |
| API Name | same |
| Client callable | false (server) / true (GlideAjax) |
| Accessible from | All application scopes / This scope only |
| Active | true |
Files: src/server/script_includes/*.js
Option B — Global Script Include (avoid)
Only for shared platform utilities — keep HR logic in scope.
Client callable variant
| Field | Value |
|---|---|
| Client callable | true |
| Use with | GlideAjax from Client Script |
4) Business Rules — options
Navigate: Studio → Business Rule OR sys_script.list
| Field | Required | BR1 example |
|---|---|---|
| Name | Yes | HRLX Calculate Days and Approver |
| Table | Yes | x_786686_hr_leave_req_v2 |
| Active | Yes | true |
| Advanced | Often true | when using current/previous |
| When | Yes | before / after / async |
| Insert | true/false per BR | |
| Update | true | |
| Delete | usually false | |
| Filter conditions | Recommended | u_state changes, etc. |
| Order | lower runs first (100, 200…) | |
| Run as | Optional | system user for elevated ops |
When to use which
| When | Use |
|---|---|
| before | Calculate fields, validate, block save |
| after | Events, audit, related records |
| async | Long work — avoid blocking save |
Option B — Flow instead of BR
Use Flow for approvals; BR for short table logic.
Option C — Data Policy
Mandatory/read-only without script — all channels.
Files: src/server/business_rules/BR*.js
5) Client Scripts — options
| Field | CS1 example |
|---|---|
| Name | HRLX Validate Dates |
| Type | onChange / onSubmit / onLoad |
| Table | x_786686_hr_leave_req_v2 |
| Field | u_start_date (onChange) |
| UI Type | 10 desktop / mobile / both |
| Active | true |
Type guide
| Type | When |
|---|---|
| onChange | Instant validation |
| onSubmit | Last check before save |
| onLoad | Default values, hide fields |
Option B — UI Policy (no code)
See §7 — prefer for show/hide/mandatory.
Option C — Declarative (UXF)
Newer forms — check if workspace uses UI Policy equivalents.
6) UI Policies — options
Navigate: sys_ui_policy.list
| Field | Example |
|---|---|
| Table | x_786686_hr_leave_req_v2 |
| Conditions | u_leave_type is sick |
| Reverse if false | true (usually) |
| Run scripts | false (prefer actions) |
| On load | true |
UI Policy Actions
| Action | Effect |
|---|---|
| Visible | show/hide field |
| Mandatory | require field |
| Read only | lock field |
File: src/client/ui_policies/UIP1_sick_requires_reason.md
7) Flow — options
Full steps: src/flow/approval_flow.md
Option A — Flow Designer (recommended)
| Setting | Value |
|---|---|
| Trigger | Record Updated (not Created only) |
| Table | x_786686_hr_leave_req_v2 |
| Condition | u_state is submitted |
| Action | Ask For Approval |
| Rules | Anyone approves (mandatory) |
| End | Publish (not Save only) |
Option B — Legacy Workflow (avoid for new)
Still on old instances — migrate to Flow.
Option C — Approval Policy / CAB
ITSM pattern — overkill for single manager leave.
Common errors
| Error | Fix |
|---|---|
| Rules missing on action 1 | Rules → Anyone approves |
| Flow never starts | Trigger = Updated + state submitted |
| Double approval | Trigger condition too broad |
8) Integration — options
Full: 12_INTEGRATION_FULL_GUIDE.md
Inbound all fields: src/server/rest/SCRIPTED_REST_INBOUND_SETUP.md
Outbound all fields: src/server/rest/OUTBOUND_REST_MESSAGE_SETUP.md
| Path | Tool | When |
|---|---|---|
| A | REST API Explorer + Table API | sys_ids known |
| B | REST API Explorer + Scripted REST | employee_id from HRIS |
| C | Import Set | Bulk employees/balances |
| D | IntegrationHub | Orchestration + spokes |
| E | MID | On-prem ERP |
| Outbound | REST Message → POST Push Decision → Send | ERP push (not Explorer) |
Never skip: Explorer Send (inbound) before Postman; REST Message Send (outbound) before Flow.
9) Application Menu / Modules — options
Option A — Studio → Module
| Field | Example |
|---|---|
| Title | Leave Requests |
| Link type | List of Records |
| Table | x_786686_hr_leave_req_v2 |
| Order | 100, 200… |
| Roles | hr_admin, manager |
| Condition | optional filter query |
Option B — sys_app_module list
Classic admin — same fields.
Option C — Workspace only (no classic module)
Modern agents — UI Builder workspace + excluded classic menu.
10) Notifications & Events — options
| Option | Navigate |
|---|---|
| Event registry | sysevent.list |
| Notification | sysevent_email_action.list |
| Email script | Template + $mail_script |
| Flow notification | Flow Designer action |
Files: 06_NOTIFICATIONS_EVENTS_JOBS.md, src/server/events/events.md
Cross-cutting — deployment options
| Option | Use |
|---|---|
| Update Set | Instance → instance (classic) |
| XML export | Single artifact |
| Source Control / Git | Team dev |
| CI/CD pipeline | Enterprise |
Admin detail: 02_ADMINISTRATION.md §8
HR Leave — master dev checklist
- [ ] App scope x_786686_hr_leave active in picker
- [ ] 4 tables + choices from schema/*.csv
- [ ] 3 Script Includes active
- [ ] BR1/2/3 order and filters correct
- [ ] Client scripts + UI policies on form
- [ ] Flow published with Anyone approves
- [ ] Integration: Explorer 201 on Table or Scripted REST
- [ ] Modules visible to correct roles
- [ ] Update Set complete
02Administration — security and configuration
Original chapter title: 02 — Administration (Studio From Scratch) — Roles, Groups, Users, ACLs, all 9 system properties, Update Set promotion
What you build
4 scoped roles, groups, 4 lab users, ACLs on req_v2, 9 system properties, and the first update set promotion.
Proof it worked
employee1 sees only their own requests; manager1 sees the team; hradmin sees everything.
Read it this way
Data isolation is the single most tested thing in any HR app review. Get it right before building UI on top.
Visual map — Ch.02 · Administration — security
Roles → Groups → Users → ACLs → 9 properties → Update Set
employee · manager · hr_admin · integration
Administration
Full visual path (all chapters): VISUAL_FROM_SCRATCH_00_16.html
Studio-only from scratch: Security and configuration in ServiceNow UI. Requires 01 Development tables and Script Includes first.
Also open: Properties + Direct Links · Schema · Studio Create All Objects · Previous: 01 Development
How administration objects relate
01 Development (tables, Script Includes, BR)
│
▼
Part 1 Roles (4 scoped roles)
│
▼
Part 2 Groups (grant roles to lab users)
│
▼
Part 3 Users (employee1, manager1, hradmin, integration)
│
├──────────────────┐
▼ ▼
Part 4 ACLs Part 5 System Properties (9 config keys)
(read/write/create (annual_days, notify, ERP endpoint…)
on req_v2 via SI)
│
▼
Part 6 Update Set promotion (export to target)
Security flow: User → Group → Role → ACL script (HRLXProSecurityUtilsV2) → req_v2 access. Properties drive accrual job, notifications, portal page id.
Part 1: Roles
Purpose
Defines four scoped roles for employee self-service, manager approval, HR admin, and integration REST access.
Navigation path
Direct URL
Prerequisites
- Part 1: Application Setup (01 Development).
- Rule 0 Update Set current.
Form fields
| Field | Suggested value |
|---|---|
| Name | x_786686_hr_leave.employee |
| Description | Submit and view own leave |
| Assignable by | hr_admin |
Repeat for three more roles:
| Name | Description | Assignable by |
|---|---|---|
x_786686_hr_leave.employee | Submit and view own leave | hr_admin |
x_786686_hr_leave.manager | Approve team leave | hr_admin |
x_786686_hr_leave.hr_admin | Full HR Leave admin | admin |
x_786686_hr_leave.integration | REST / integration user | admin |
Step-by-step create
- For each row: Studio → Role → New.
- Name = scoped role name. Description as table. Application = HR Leave.
- Submit. Repeat four times.
Verify checklist
- Four roles filtered by x_786686_hr_leave.
Demo / test
Assign employee role via group in Part 2 → user inherits role after login.
Part 2: Groups
Purpose
Assignment groups that grant scoped roles to lab users.
Navigation path
Direct URL
Prerequisites
- Part 1: Roles exist.
Form fields
| Field | Suggested value |
|---|---|
| Name | HRLX Employees |
| Group Roles | x_786686_hr_leave.employee |
| Members | employee1 (after Part 3) |
| Group name | Group Roles | Members |
|---|---|---|
| HRLX Employees | x_786686_hr_leave.employee | employee1 |
| HRLX Managers | x_786686_hr_leave.manager, employee | manager1 |
| HRLX HR Admins | x_786686_hr_leave.hr_admin | hradmin |
| HRLX Integration | x_786686_hr_leave.integration, rest_service | hrlx.integration |
Step-by-step create
- Groups → New → Name from table.
- Related list Group Roles → Edit → add scoped roles.
- Related list Users → add members.
- Repeat for all four groups.
Verify checklist
- User record → Roles related list shows inherited group roles.
Demo / test
Impersonate employee1 → roles include employee.
Part 3: Users (lab matrix)
Purpose
Four lab users for E2E testing.
Navigation path
Direct URL
Prerequisites
- Part 2: Groups ready.
Form fields
| Field | Suggested value |
|---|---|
| User ID | employee1 |
| First name / Last name | Lab Employee |
| employee1@lab.local | |
| Active | true |
| Group | HRLX Employees |
| User ID | Name | Group | |
|---|---|---|---|
| employee1 | Lab Employee | employee1@lab.local | HRLX Employees |
| manager1 | Lab Manager | manager1@lab.local | HRLX Managers |
| hradmin | Lab HR Admin | hradmin@lab.local | HRLX HR Admins |
| hrlx.integration | Integration Bot | hrlx.integration@lab.local | HRLX Integration |
Step-by-step create
- Users → New for each row. Set password per instance policy.
- Active = true. Email required for notifications (chapter 06).
- Add to group via Groups related list.
- Seed employee_v2: employee1 user, manager = manager1, Employee ID E-1001.
Verify checklist
- Impersonate each user successfully.
- employee_v2 links employee1 → manager1.
Demo / test
employee1 creates req_v2 → u_approver_user = manager1 after save (BR1).
Part 4: ACLs
Purpose
Record and field ACLs enforce who can read, write, and create leave requests using HRLXProSecurityUtilsV2.
Navigation path
Direct URL
Prerequisites
- Part 7: HRLXProSecurityUtilsV2 (01).
- Part 1: Roles.
- Elevate security_admin.
Form fields
ACL 1 — read
| Field | Suggested value |
|---|---|
| Type | record |
| Operation | read |
| Name | x_786686_hr_leave_req_v2 |
| Advanced | Checked |
| Active | true |
| Application | HR Leave |
| Script | answer = new x_786686_hr_leave.HRLXProSecurityUtilsV2().canReadLeave(current); |
ACL 2 — write
| Field | Suggested value |
|---|---|
| Operation | write |
| Script | answer = new x_786686_hr_leave.HRLXProSecurityUtilsV2().canWriteLeave(current); |
ACL 3 — create
| Field | Suggested value |
|---|---|
| Operation | create |
| Requires role | x_786686_hr_leave.employee |
| Script | answer = gs.hasRole('x_786686_hr_leave.employee') || gs.hasRole('x_786686_hr_leave.hr_admin') || gs.hasRole('x_786686_hr_leave.integration'); |
ACL 4 — field write u_approver_user
| Field | Suggested value |
|---|---|
| Name | x_786686_hr_leave_req_v2.u_approver_user |
| Operation | write |
| Script | answer = gs.hasRole('x_786686_hr_leave.hr_admin'); |
Step-by-step create
- Elevate security_admin. Application = HR Leave.
- ACL → New for each table above. Active = true.
- Paste scripts. Save all four.
Verify checklist
- employee1 sees own requests only.
- manager1 sees direct reports.
- hradmin sees all.
- employee1 direct URL to another user's record → denied.
Demo / test
Impersonate employee1 → open coworker sys_id → Access denied.
Part 5: System Properties
Purpose
Scoped configuration — create all 9 properties from the professional catalog.
Navigation path
Direct URL
Prerequisites
Form fields — create each row
| Label | Name | Type | Value | Description |
|---|---|---|---|---|
| Annual Days | x_786686_hr_leave.config.annual_days | integer | 20 | Default annual entitlement |
| Sick Days | x_786686_hr_leave.config.sick_days | integer | 10 | Default sick entitlement |
| Notify Enabled | x_786686_hr_leave.config.notify_enabled | true|false | true | Master email switch |
| Min Notice Days | x_786686_hr_leave.config.min_notice_days | integer | 3 | Min days before start |
| Max Days Per Request | x_786686_hr_leave.config.max_days_per_request | integer | 30 | Max days per request |
| ERP Endpoint | x_786686_hr_leave.config.erp_endpoint | string | https://webhook.site/… | Outbound ERP URL |
| Integration User | x_786686_hr_leave.config.integration_user | string | hrlx.integration | Inbound API user id |
| Number Prefix | x_786686_hr_leave.config.number_prefix | string | HRLX | u_number prefix |
| Portal Page ID | x_786686_hr_leave.config.portal_page_id | string | hrlx_home | Service Portal page id |
On every property form: Application = HR Leave · Ignore cache = false · Private = false.
Step-by-step create
- Application picker = HR Leave.
- Open New property.
- Fill Label, Name, Type, Value, Description from one row → Submit.
- Repeat for all 9 rows.
Verify checklist
- Background:
gs.info(gs.getProperty('x_786686_hr_leave.config.annual_days'));→ 20. - Nine properties visible in scoped list.
Demo / test
Run accrual job (chapter 06) → new balances use annual_days.
Part 6: Update Set Promotion
Purpose
Complete and export HR Leave – Build for target instance promotion.
Navigation path
Direct URL
Prerequisites
- All Parts 1–5 captured in HR Leave – Build.
- Rule 0 Update Set was current during creates.
Form fields
| Field | Suggested value |
|---|---|
| Name | HR Leave – Build |
| State | Complete (when ready to export) |
| Application | HR Leave |
Step-by-step create
- Complete local set — HR Leave – Build → State = Complete.
- Export — Retrieved Update Sets → Import from source OR XML export.
- Target instance — Import → Preview conflicts → Commit.
- Post-import — Recreate users/groups if not in set; verify 9 properties; run accrual job once.
- Smoke test — employee1 submit → manager1 approve → balance updated.
Never promote Global artifacts. Confirm Application = HR Leave on every captured file before export.
Verify checklist
- Customer Updates lists roles, ACLs, properties.
- No Global-scope rows in set.
Demo / test
Import to second PDI → smoke test E2E leave cycle.
Administration verify checklist
Complete after Parts 1–6 (Roles through Update Set promotion).
- Four scoped roles exist under HR Leave.
- Four groups with correct role assignments and lab users.
- Four lab users impersonate successfully; employee_v2 seeded.
- Four ACLs active; employee isolation verified.
- Nine system properties readable via gs.getProperty.
- Update Set Complete and ready for export.
03Service Catalog — the request intake
Original chapter title: 03 — Service Catalog (Studio From Scratch) — Category HR Leave, Record Producer Submit Leave Request, variables, producer script, publish
What you build
HR Leave category, the Submit Leave Request record producer, its variables, the producer script, and publishing.
Proof it worked
A non-technical user can submit leave from the catalog and a req_v2 record appears correctly populated.
Read it this way
This is the front door most employees will actually use. The form is the product.
Visual map — Ch.03 · Service Catalog — intake
Category → Record Producer → variables → publish
Submit Leave Request → req_v2
Development
Full visual path (all chapters): VISUAL_FROM_SCRATCH_00_16.html
Studio-only from scratch: Service Catalog intake for leave requests. Requires req_v2 table and employee role from chapters 01–02.
Also open: Properties + Direct Links · Schema · Studio Create All Objects · Previous: 02 Administration
How catalog objects relate
Part 1 Category (HR Leave)
│
▼
Part 2 Record Producer → Part 3 Variables
│
▼
Part 4 Producer script (maps variables → req_v2)
│
▼
Part 5 Publish → Employee Center / Portal catalog
│
▼
BR1 days + approver → BR2 event → Flow approval
Flow: employee1 opens catalog → submits → req_v2 inserted in submitted state → same lifecycle as UI Action Submit.
Part 1: Catalog Category — HR Leave
Purpose
Groups the Submit Leave Request record producer under a dedicated HR Leave catalog category.
Navigation path
Direct URL
Prerequisites
- Part 1: Application Setup (01 Development).
- Rule 0 Update Set current.
Form fields
| Field | Value |
|---|---|
| Title | HR Leave |
| Description | Leave requests and balances |
| Active | true |
| Order | 100 |
| Application | HR Leave |
Step-by-step create
- Service Catalog → Categories → New.
- Fill Title, Description, Order from table. Active = true.
- Submit. Note category sys_id for record producer.
Verify checklist
- Category HR Leave appears in category list.
Demo / test
Open Employee Center → catalog browse → HR Leave category visible (after producer published).
Part 2: Record Producer — Submit Leave Request
Purpose
Service Catalog item that inserts submitted req_v2 from catalog variables.
Navigation path
Direct URL
Prerequisites
- Part 3: Table — Leave Request (01).
- Part 1: Roles employee role (02).
- Part 1: Catalog Category.
Form fields
| Field | Value |
|---|---|
| Name | Submit Leave Request |
| Table | x_786686_hr_leave_req_v2 |
| Category | HR Leave |
| Roles | x_786686_hr_leave.employee |
| Active | true |
| Application | HR Leave |
Step-by-step create
- Record Producers → New. Fill header fields from table.
- Category = HR Leave. Table = req_v2. Roles = employee.
- Save. Continue to Part 3 for variables and Part 4 for script.
Verify checklist
- Producer record saved under HR Leave application.
Demo / test
Open producer form → Table field shows x_786686_hr_leave_req_v2.
Part 3: Catalog Variables
Purpose
Four question variables mapped by the producer script to req_v2 u_* fields.
Navigation path
Direct URL
Prerequisites
- Part 2: Record Producer created.
Form fields — each variable New
| Name | Label | Type | Question | Mandatory |
|---|---|---|---|---|
| leave_type | Leave Type | Choice | Leave Type | Yes |
| start_date | Start Date | Date | Start Date | Yes |
| end_date | End Date | Date | End Date | Yes |
| reason | Reason | Multi Line Text | Reason | No |
Choice values for leave_type: annual, sick, personal (match req_v2 u_leave_type choices).
Step-by-step create
- Open Submit Leave Request producer → Variables related list → New.
- For each row: Name must match producer.* references in script. Set Label, Type, Mandatory.
- Order: leave_type, start_date, end_date, reason.
Verify checklist
- Four variables on producer; names exactly leave_type, start_date, end_date, reason.
Demo / test
Try It → variable form renders all four questions.
Part 4: Producer Script
Purpose
Server-side script maps catalog variables to req_v2 fields and sets state submitted. Source: src/catalog/RP_submit_leave.js.
Navigation path
Direct URL
Prerequisites
- Part 3: Catalog Variables.
- Part 8: BR1 calculates days on insert.
Step-by-step create
- Open producer → Script tab.
- Paste full script below.
- Save.
Full script (paste-ready)
/**
* Record Producer Script: Submit Leave Request
* Application: Custom · Scope: x_786686_hr_leave · sys_id: 83d5298c932b8f107520703efaba1072
* Target table: x_786686_hr_leave_req_v2
*
* Maps producer variables to record fields. Runs server-side when submitted.
* Variables expected: leave_type, start_date, end_date, reason
*/
(function () {
current.u_requested_for_user = gs.getUserID();
current.u_leave_type = producer.leave_type;
current.u_start_date = producer.start_date;
current.u_end_date = producer.end_date;
current.u_reason = producer.reason;
current.u_state = 'submitted';
// Link to employee profile of the current user (if exists)
var emp = new GlideRecord('x_786686_hr_leave_employee_v2');
emp.addQuery('u_user', gs.getUserID());
emp.query();
if (emp.next()) {
current.u_employee_ref = emp.getUniqueValue();
}
// BR1 will calculate days and set the approver on insert.
})();
Verify checklist
- Script saves without syntax errors.
Demo / test
Background: no errors on save. Catalog submit tested in Part 5.
Part 5: Publish and Test
Purpose
Publish catalog item and verify end-to-end submit from Employee Center.
Navigation path
Direct URL
Prerequisites
- Parts 1–4 complete.
- employee1 user with employee role and employee_v2 seeded.
Step-by-step create
- Record Producer → Related Lists → Catalogs → add to HR Leave category.
- Service Catalog → open producer → Publish.
- Login as employee1 → Employee Center → HR Leave → Submit Leave Request.
- Fill variables → Submit.
- Verify req_v2 created in submitted state; u_days_count and u_approver_user set by BR1.
- Verify Flow starts for manager1 approval.
Verify checklist
- Catalog item visible to employee role.
- Submit creates req_v2 in submitted state.
- sysapproval_approver row for manager1.
Demo / test
employee1 submits → req_v2 row, BR1 sets days + approver, Flow starts.
Service Catalog verify checklist
Complete after Parts 1–5.
- HR Leave category active.
- Record Producer scoped to HR Leave with four variables.
- Producer script from src/catalog/RP_submit_leave.js pasted.
- Published and visible in Employee Center.
- E2E submit creates submitted req_v2.
04Service Portal — self-service widgets
Original chapter title: 04 — Service Portal (Studio From Scratch) — Widgets hrlx_my_leaves + hrlx_balance, page hrlx_home, roles, portal URL
What you build
Widgets hrlx_my_leaves and hrlx_balance (server + client script), page hrlx_home, and role restrictions.
Proof it worked
Logged in as employee1 the portal page shows that user's balance and only that user's requests.
Read it this way
Widgets are where server script, client script and ACLs meet. Bugs here teach you more than any single chapter.
Visual map — Ch.04 · Service Portal — self-service
Widgets hrlx_my_leaves + hrlx_balance → page hrlx_home
Portal suffix hrlx
Development
Full visual path (all chapters): VISUAL_FROM_SCRATCH_00_16.html
Studio-only from scratch: Portal widgets use u_* fields on req_v2 and bal_v2. No external build tools.
Also open: Properties + Direct Links · Schema · Studio Create All Objects · Previous: 03 Service Catalog
How portal objects relate
Portal (url_suffix hrlx)
└── Page hrlx_home
├── Part 1 Widget hrlx_my_leaves (req_v2 list + cancel)
└── Part 2 Widget hrlx_balance (bal_v2 cards)
Property x_786686_hr_leave.config.portal_page_id = hrlx_home
Part 1: Widget — hrlx_my_leaves
Purpose
Lists current user's leave requests with cancel action for draft/submitted rows.
Navigation path
Direct URL
Prerequisites
- Part 3: Table — Leave Request (01).
- Part 1: Roles employee (02).
Form fields
| Field | Value |
|---|---|
| ID | hrlx_my_leaves |
| Name | HRLX My Leaves |
| Client controller | HrlxMyLeavesCtrl |
| Application | HR Leave |
Step-by-step create
- SP → Widgets → New → ID hrlx_my_leaves.
- Paste HTML template, CSS, Client script, Server script from sections below.
- Save.
Full script — hrlx_my_leaves HTML template
<div class="hrlx-my-leaves">
<h3>My Leave Requests</h3>
<table class="table table-striped" ng-if="data.leaves.length">
<thead>
<tr>
<th>Number</th>
<th>Type</th>
<th>Start</th>
<th>End</th>
<th>Days</th>
<th>State</th>
<th></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="item in data.leaves">
<td>{{item.number}}</td>
<td>{{item.type}}</td>
<td>{{item.start}}</td>
<td>{{item.end}}</td>
<td>{{item.days}}</td>
<td><span class="label label-default">{{item.state}}</span></td>
<td>
<button class="btn btn-xs btn-danger"
ng-if="item.state === 'submitted' || item.state === 'draft'"
ng-click="c.cancelLeave(item)">Cancel</button>
</td>
</tr>
</tbody>
</table>
<p ng-if="!data.leaves.length" class="text-muted">No leave requests yet.</p>
</div>
Full script — hrlx_my_leaves CSS
.hrlx-my-leaves {
padding: 16px;
h3 {
margin-top: 0;
margin-bottom: 16px;
font-weight: 600;
}
.label {
text-transform: capitalize;
}
}
Full script — hrlx_my_leaves Client script
/**
* Widget: hrlx_my_leaves — Client Controller
* Application: Custom · Scope: x_786686_hr_leave · sys_id: 83d5298c932b8f107520703efaba1072
*/
function HrlxMyLeavesCtrl() {
var c = this;
c.cancelLeave = function (item) {
if (!confirm('Cancel leave ' + item.number + '?')) return;
c.data.action = 'cancel';
c.data.sys_id = item.sys_id;
c.server.update().then(function () {
c.data.action = '';
c.data.sys_id = '';
});
};
}
Full script — hrlx_my_leaves Server script
/**
* Widget: hrlx_my_leaves — Server Script
* Application: Custom · Scope: x_786686_hr_leave · sys_id: 83d5298c932b8f107520703efaba1072
* Shows the current user's leave requests (secure).
*/
(function () {
data.leaves = [];
var gr = new GlideRecordSecure('x_786686_hr_leave_req_v2');
gr.addQuery('u_requested_for_user', gs.getUserID());
gr.orderByDesc('sys_created_on');
gr.setLimit(50);
gr.query();
while (gr.next()) {
data.leaves.push({
sys_id: gr.getUniqueValue(),
number: gr.getValue('u_number'),
type: gr.getDisplayValue('u_leave_type'),
start: gr.getValue('u_start_date'),
end: gr.getValue('u_end_date'),
days: gr.getValue('u_days_count'),
state: gr.getValue('u_state')
});
}
// Cancel action from client
if (input && input.action === 'cancel' && input.sys_id) {
var c = new GlideRecord('x_786686_hr_leave_req_v2');
if (c.get(input.sys_id) && c.getValue('u_requested_for_user') === gs.getUserID()) {
c.u_state = 'cancelled';
c.update();
}
}
})();
Verify checklist
- Widget saves. Preview shows table structure.
Demo / test
After Part 3 page built: employee1 sees own requests.
Part 2: Widget — hrlx_balance
Purpose
Read-only balance cards for current year from bal_v2.
Navigation path
Direct URL
Prerequisites
- Part 4: Table — Leave Balance (01).
- Part 1: Widget hrlx_my_leaves (same portal context).
Form fields
| Field | Value |
|---|---|
| ID | hrlx_balance |
| Name | HRLX Balance |
| Client controller | HrlxBalanceCtrl |
| Application | HR Leave |
Step-by-step create
- SP → Widgets → New → ID hrlx_balance.
- Paste HTML, CSS, Client, Server from below.
- Save.
Full script — hrlx_balance HTML template
<div class="hrlx-balance">
<h3>Leave Balance {{data.year}}</h3>
<div class="hrlx-cards" ng-if="data.balances.length">
<div class="hrlx-card" ng-repeat="b in data.balances">
<div class="hrlx-card-type">{{b.type}}</div>
<div class="hrlx-card-remaining">{{b.remaining}}</div>
<div class="hrlx-card-meta">used {{b.used}} / {{b.entitled}}</div>
</div>
</div>
<p ng-if="!data.balances.length" class="text-muted">No balance records found.</p>
</div>
Full script — hrlx_balance CSS
.hrlx-balance {
padding: 16px;
h3 { margin-top: 0; font-weight: 600; }
.hrlx-cards {
display: flex;
flex-wrap: wrap;
gap: 12px;
}
.hrlx-card {
flex: 1 1 120px;
border: 1px solid #e0e0e0;
border-radius: 8px;
padding: 12px;
text-align: center;
}
.hrlx-card-type { text-transform: capitalize; color: #707070; }
.hrlx-card-remaining { font-size: 28px; font-weight: 700; }
.hrlx-card-meta { font-size: 12px; color: #909090; }
}
Full script — hrlx_balance Client script
/**
* Widget: hrlx_balance — Client Controller
* Application: Custom · Scope: x_786686_hr_leave · sys_id: 83d5298c932b8f107520703efaba1072
*/
function HrlxBalanceCtrl() {
var c = this;
// No client actions needed; data is read-only.
}
Full script — hrlx_balance Server script
/**
* Widget: hrlx_balance — Server Script
* Application: Custom · Scope: x_786686_hr_leave · sys_id: 83d5298c932b8f107520703efaba1072
* Shows the current user's remaining leave balance for the current year.
*/
(function () {
data.balances = [];
data.year = new GlideDateTime().getYearLocalTime();
// find employee record of current user
var emp = new GlideRecord('x_786686_hr_leave_employee_v2');
emp.addQuery('u_user', gs.getUserID());
emp.query();
if (!emp.next()) return;
var bal = new GlideRecordSecure('x_786686_hr_leave_bal_v2');
bal.addQuery('u_employee_ref', emp.getUniqueValue());
bal.addQuery('u_year', data.year);
bal.query();
while (bal.next()) {
data.balances.push({
type: bal.getDisplayValue('u_leave_type'),
entitled: parseInt(bal.getValue('u_entitled') || 0, 10),
used: parseInt(bal.getValue('u_used') || 0, 10),
remaining: parseInt(bal.getValue('u_remaining') || 0, 10)
});
}
})();
Verify checklist
- Cards show entitled/used/remaining when bal_v2 seeded.
Demo / test
employee1 with balance row → remaining displays correctly.
Part 3: Portal Page — hrlx_home
Purpose
HR Leave home page with balance widget on top and my leaves below.
Navigation path
Direct URL
Prerequisites
Form fields — Portal (sp_portal)
| Field | Value |
|---|---|
| Title | HR Leave Portal |
| URL suffix | hrlx |
| Application | HR Leave |
Page (sp_page)
| Field | Value |
|---|---|
| ID | hrlx_home |
| Title | HR Leave Home |
| Application | HR Leave |
| Roles | x_786686_hr_leave.employee |
Step-by-step create
- Portals → New OR configure dedicated portal url_suffix
hrlx. - Pages → New → ID hrlx_home → drag hrlx_balance (top), hrlx_my_leaves (below) on 12-column layout.
- Page security → role employee.
- Set property
x_786686_hr_leave.config.portal_page_id= hrlx_home (02 Administration Part 5). - Save. Open {BASE}/hrlx?id=hrlx_home as employee1.
Verify checklist
- employee1 sees own requests and balance.
- Cancel button works on draft/submitted.
Demo / test
Login employee1 → hrlx?id=hrlx_home → requests + balance visible.
Service Portal verify checklist
Complete after Parts 1–3.
- Two widgets scoped to HR Leave.
- Page hrlx_home published with role restriction.
- Portal URL opens for employee1.
05Workspace / UI Builder — the fulfiller experience
Original chapter title: 05 — Workspace / UI Builder (Studio From Scratch) — UI Builder experience HR Leave Workspace, pages, data resource on req_v2
What you build
The HR Leave Workspace experience, three pages (My Requests, Team Approvals, Admin), a data resource on req_v2, and role-bound lists.
Proof it worked
manager1 opens the workspace and sees a Team Approvals list containing only their reports' requests.
Read it this way
Agents and managers do not live in the portal. This is the half of the app that decides whether staff adopt it.
Visual map — Ch.05 · Workspace — fulfiller UI
My Requests · Team Approvals · Admin pages
UI Builder experience published
Development
Full visual path (all chapters): VISUAL_FROM_SCRATCH_00_16.html
Studio-only from scratch: Now Experience workspace with list pages on req_v2. Studio UI only — no npx or external build.
Also open: Properties + Direct Links · Schema · Studio Create All Objects · Previous: 04 Service Portal
How workspace objects relate
Part 1 Experience (HR Leave Workspace) ├── Part 2 Pages (My Requests, Team Approvals, Admin) ├── Part 3 Data resource → req_v2 table broker └── Part 4 List component + role bindings
Part 1: Create Experience — HR Leave Workspace
Purpose
Now Experience workspace container scoped to HR Leave application.
Navigation path
Direct URL
Prerequisites
- Part 3: Table — Leave Request (01).
- Roles from 02 Administration.
Form fields
| Field | Value |
|---|---|
| Name | HR Leave Workspace |
| Application | HR Leave |
| Experience type | Workspace / Custom |
Step-by-step create
- UI Builder → Create experience → fill fields → Create.
- Confirm Application = HR Leave in header.
Verify checklist
- Experience opens in UI Builder.
Demo / test
Preview URL loads (empty until pages added).
Part 2: Pages — My Requests, Team Approvals, Admin
Purpose
Three routed pages for employee, manager, and HR admin personas.
Navigation path
Prerequisites
Form fields
| Label | Route | Roles |
|---|---|---|
| My Requests | my-requests | employee, manager, hr_admin |
| Team Approvals | team-approvals | manager, hr_admin |
| Admin | admin | hr_admin |
Step-by-step create
- Add page → Name My Requests → Route my-requests.
- Repeat for Team Approvals and Admin pages.
- Add navigation menu items linking each page (Part 4).
Verify checklist
- Three pages listed in experience.
Demo / test
Preview each route — page shell loads.
Part 3: Data Resource — req_v2
Purpose
Table data broker feeding list components with req_v2 records.
Navigation path
Prerequisites
Form fields — My Requests broker
| Field | Value |
|---|---|
| Label | My Leave Requests |
| Table | x_786686_hr_leave_req_v2 |
| Condition | u_requested_for_user=javascript:gs.getUserID() |
Team Approvals broker
| Condition | u_state=submitted AND u_approver_user=current user |
|---|
Step-by-step create
- Data Resources → Create → Table → req_v2.
- Set conditions per page persona.
- Columns: Number, Leave Type, Start, End, Days, State.
Verify checklist
- Data resource preview returns rows for test user.
Demo / test
Preview broker as employee1 → only own requests.
Part 4: Bind List + Role Restrictions
Purpose
Attach list components to data resources and publish workspace with role access.
Navigation path
Prerequisites
Step-by-step create
- My Requests page → drag List → bind My Leave Requests data resource.
- Team Approvals page → List → team broker filter.
- Admin page → Lists for employee_v2 and bal_v2 — hr_admin only.
- Navigation: menu items with role restrictions per page.
- Publish workspace → assign roles employee, manager, hr_admin.
- Impersonate each test user → open workspace URL.
Verify checklist
- employee sees only own requests on My Requests.
- manager sees submitted items awaiting approval.
- hr_admin sees admin lists.
Demo / test
Impersonate each role → list data matches ACL expectations.
Workspace verify checklist
Complete after Parts 1–4.
- Experience published under HR Leave.
- Three pages with correct role gates.
- req_v2 list filtered per persona.
06Notifications, Events & Jobs — the asynchronous layer
Original chapter title: 06 — Notifications, Events & Jobs (Studio From Scratch) — 3 events, 3 email notifications, scheduled accrual job with full script
What you build
3 registered events, 3 email notifications driven by them, and the annual accrual scheduled job with its full script.
Proof it worked
Submitting a request queues the event, the notification renders with the right values, and the accrual job updates balances.
Read it this way
Events decouple business rules from communication. This pattern reappears in every ServiceNow application you will ever build.
Visual map — Ch.06 · Events · mail · jobs
3 events → 3 emails · annual accrual job
leave_v2.submitted / approved / rejected
Administration
Full visual path (all chapters): VISUAL_FROM_SCRATCH_00_16.html
Studio-only from scratch: Events are queued by BR2 and BR4 from 01 Development. Notifications and accrual job complete the async layer.
Also open: Properties + Direct Links · Schema · Studio Create All Objects · Previous: 05 Workspace
How events, notifications, and jobs relate
BR2 submit → Part 1 Event leave_v2.submitted → Part 2 Notification to approver BR4 approve/reject → events approved/rejected → Part 2 Notifications to requester Part 3 Scheduled Job → creates bal_v2 rows from property annual_days
Part 1: Event Registry
Purpose
Registers three scoped events queued by BR2/BR4 for notifications and monitoring.
Navigation path
Direct URL
Prerequisites
- Part 3: Table req_v2 table.
Form fields
| Event name | Table | Active |
|---|---|---|
x_786686_hr_leave.leave_v2.submitted | x_786686_hr_leave_req_v2 | true |
x_786686_hr_leave.leave_v2.approved | x_786686_hr_leave_req_v2 | true |
x_786686_hr_leave.leave_v2.rejected | x_786686_hr_leave_req_v2 | true |
Step-by-step create
- Registry → New (three times).
- Event name exactly as table. Table = Leave Request. Application = HR Leave. Active.
Verify checklist
- Submit req → System Logs → Events shows leave_v2.submitted.
Demo / test
Event parm1 = approver sys_id, parm2 = u_number.
Part 2: Notifications
Purpose
Three email notifications: submitted (to approver), approved/rejected (to requester).
Navigation path
Direct URL
Prerequisites
- Part 1: Events events.
- Part 9: Business Rule BR2 BR2, Part 11: Business Rule BR4 BR4.
- Users have email addresses.
Form fields
Notification 1 — Submitted
| Field | Value |
|---|---|
| Name | HRLX Leave Submitted |
| Table | x_786686_hr_leave_req_v2 |
| Active | true |
| When to send | Event is fired |
| Event name | x_786686_hr_leave.leave_v2.submitted |
| Users/Groups in fields | u_approver_user |
| Subject | Leave ${u_number} needs your approval |
Notification 2 — Approved
| Event | leave_v2.approved |
| Users/Groups in fields | u_requested_for_user |
| Subject | Your leave ${u_number} was approved |
Notification 3 — Rejected
| Event | leave_v2.rejected |
| Users/Groups in fields | u_requested_for_user |
| Subject | Your leave ${u_number} was rejected |
Step-by-step create
- Application = HR Leave. Notifications → New for each.
- Tab When to send: Event is fired + event name.
- Tab Who will receive: Users/Groups in fields → pick u_approver_user or u_requested_for_user.
- Tab What it will contain: Subject + Message HTML with ${u_number}, ${u_start_date}, ${URI_REF}.
- Submit each notification. Active = true.
Verify checklist
- Three active notifications on Leave Request table.
- Email log entry after submit/approve.
Demo / test
Submit as employee1 → manager1 receives email or sys_email row.
Part 3: Scheduled Job — Annual Accrual
Purpose
Creates missing bal_v2 rows for current year for every employee using property annual_days.
Navigation path
Direct URL
Prerequisites
- Part 2: Table employee_v2, Part 4: Table bal_v2, Part 5: System Properties property.
Form fields
| Field | Value |
|---|---|
| Name | HRLX Annual Accrual |
| Application | HR Leave |
| Active | Checked |
| Run | Daily 02:00 or Yearly Jan 1 |
| Run as | System (default) |
Step-by-step create
- Scheduled Script Execution → New.
- Fill fields. Paste script below.
- Save. Click Execute Now to test.
Full script (paste-ready)
/**
* Application: Custom · Scope: x_786686_hr_leave · sys_id: 83d5298c932b8f107520703efaba1072
* Scheduled Job: Annual Leave Accrual
* Run: yearly (e.g. Jan 1)
*
* Creates a fresh balance row per employee for the new year if missing.
* Default entitlement is read from a property (fallback 21 annual days).
*/
(function annualAccrual() {
var year = new GlideDateTime().getYearLocalTime();
var annualEntitlement = parseInt(gs.getProperty('x_786686_hr_leave.config.annual_days', '21'), 10);
var emp = new GlideRecord('x_786686_hr_leave_employee_v2');
emp.query();
var created = 0;
while (emp.next()) {
// skip if a balance for this year/type already exists
var existing = new GlideRecord('x_786686_hr_leave_bal_v2');
existing.addQuery('u_employee_ref', emp.getUniqueValue());
existing.addQuery('u_year', year);
existing.addQuery('u_leave_type', 'annual');
existing.query();
if (existing.hasNext()) continue;
var bal = new GlideRecord('x_786686_hr_leave_bal_v2');
bal.initialize();
bal.u_employee_ref = emp.getUniqueValue();
bal.u_year = year;
bal.u_leave_type = 'annual';
bal.u_entitled = annualEntitlement;
bal.u_used = 0;
bal.u_remaining = annualEntitlement;
bal.insert();
created++;
}
gs.info('[HRLX] Annual accrual ' + year + ' created ' + created + ' balance rows.');
})();
Verify checklist
- Execute Now creates bal_v2 per employee without duplicate year row.
- System log: [HRLX] Annual accrual message.
Demo / test
New employee → run job → u_entitled=20, u_remaining=20.
Notifications & Jobs verify checklist
Complete after Parts 1–3.
- Three events registered on req_v2.
- Three email notifications active.
- Accrual job creates balance rows without duplicates.
07Reports & Dashboards — making the data visible
Original chapter title: 07 — Reports & Dashboards (Studio From Scratch) — Report Leave Requests by State + dashboard widget — every wizard property
What you build
The Leave Requests by State report through the wizard, plus a dashboard and widget.
Proof it worked
The report runs, respects ACLs for a non-admin viewer, and appears on the dashboard.
Read it this way
Short chapter, high stakeholder value. A working dashboard is what gets the project a second phase.
Visual map — Ch.07 · Reports & dashboards
Leave by State report → dashboard widget
Scoped report + dashboard
Administration
Full visual path (all chapters): VISUAL_FROM_SCRATCH_00_16.html
Studio-only from scratch: Build reports in the Reports module wizard — no external tooling.
Also open: Properties + Direct Links · Schema · Studio Create All Objects · Previous: 06 Notifications
How reports and dashboards relate
Part 1 Report groups req_v2 by u_state → Part 2 Dashboard embeds report as widget for hr_admin/manager.
Part 1: Report Wizard — Leave Requests by State
Purpose
Visual report grouping leave requests by state.
Navigation path
Direct URL
Prerequisites
- Part 3: Table — Leave Request with seeded mixed states.
Form fields — Report wizard
| Wizard step | Selection |
|---|---|
| 1 Source type | Table |
| 2 Table | Leave Request [x_786686_hr_leave_req_v2] |
| 3 Report type | Bar chart or Pie chart |
| 4 Configure | Group by: State (u_state), Aggregate: Count |
| 5 Title | Leave Requests by State |
| Application | HR Leave |
Step-by-step create
- Reports → Create New → Table → req_v2.
- Choose Bar or Pie → Group by u_state → Count.
- Title: Leave Requests by State. Application HR Leave.
- Run report → verify segments for draft/submitted/approved.
Verify checklist
- Report renders with correct counts.
Demo / test
Seed 2 draft, 3 submitted, 1 approved → chart reflects counts.
Part 2: Dashboard + Widget
Purpose
HR Leave dashboard with embedded report widget.
Navigation path
Direct URL
Prerequisites
Form fields
| Field | Value |
|---|---|
| Name | HR Leave Dashboard |
| Widget type | Report |
| Report | Leave Requests by State |
| Roles | hr_admin, manager |
Step-by-step create
- Dashboards → New → Name HR Leave Dashboard.
- Add widget → Report → select report from Part 1.
- Share with roles hr_admin, manager. Save dashboard.
Verify checklist
- Dashboard widget displays report.
Demo / test
manager1 opens dashboard → chart visible.
Reports verify checklist
- Report scoped to HR Leave.
- Dashboard shared with manager/hr_admin.
08ATF Testing — automated proof it still works
Original chapter title: 08 — ATF Testing (Studio From Scratch) — Suite HRLX Leave Core + 3 tests with concrete steps
What you build
Test suite HRLX Leave Core plus three tests: BR1 business days (server), CS2 form days (client), ACL employee isolation (server).
Proof it worked
All three tests pass, and deliberately breaking BR1 makes exactly one test fail.
Read it this way
The ACL isolation test is the one that protects you during upgrades. Write it once, trust it forever.
Visual map — Ch.08 · ATF — proof
Suite HRLX Leave Core · BR1 · ACL · CS2
Automated evidence
Development
Full visual path (all chapters): VISUAL_FROM_SCRATCH_00_16.html
Studio-only from scratch: ATF runs in-instance after core app from chapters 01–02 exists.
Also open: Properties + Direct Links · Schema · Studio Create All Objects · Previous: 07 Reports
How ATF objects relate
Part 1 Suite groups Parts 2–4 individual tests (server BR1, client CS2, ACL isolation).
Part 1: Test Suite — HRLX Leave Core
Purpose
Container suite for all HR Leave automated tests.
Navigation path
Direct URL
Prerequisites
- Part 1: Application Setup. ATF plugin active.
Form fields
| Field | Value |
|---|---|
| Name | HRLX Leave Core |
| Application | HR Leave |
| Active | true |
Step-by-step create
- Test Suite → New → fill fields.
- After Parts 2–4: Related list Tests → Add all three tests.
- Run Suite → all green.
Verify checklist
- Suite lists three tests.
Demo / test
Run HRLX Leave Core suite after E2E seed → 3/3 pass.
Part 2: Test — BR1 Business Days (Server)
Purpose
Validates BR1 sets u_days_count = 5 for Mon–Fri range.
Navigation path
Prerequisites
- Part 8: BR1 active.
Form fields
| Field | Value |
|---|---|
| Name | HRLX BR1 Days Calculation |
| Application | HR Leave |
Step-by-step create
- ATF → Tests → New → Name: HRLX BR1 Days Calculation.
- Step 1: Create a Record → Table req_v2 → u_start_date=2026-06-01, u_end_date=2026-06-05, u_state=draft.
- Step 2: Run Server Side Script → assertion below.
Full script (assertion step)
(function(outputs, steps, stepResult, assertEqual) {
var gr = new GlideRecord('x_786686_hr_leave_req_v2');
gr.get(steps('Create a Record').record_id);
assertEqual({ name: 'days', expected: '5', actual: gr.getValue('u_days_count') });
})(outputs, steps, stepResult, assertEqual);
Verify checklist
- Test passes on clean instance.
Demo / test
Run test alone → green.
Part 3: Test — CS2 Form Days (Client)
Purpose
Client test: changing dates on form fills u_days_count via GlideAjax.
Navigation path
Prerequisites
- Part 13: CS2 active.
Form fields
| Field | Value |
|---|---|
| Name | HRLX CS2 Form Days |
Step-by-step create
- New client test: HRLX CS2 Form Days.
- Step: Open/New req_v2 form.
- Step: Set Field Values u_start_date, u_end_date.
- Step: Assert Field Value u_days_count equals 5.
Verify checklist
- Client test passes.
Demo / test
Run test → u_days_count populated.
Part 4: Test — ACL Employee Isolation (Server)
Purpose
Impersonate employee1 → query returns only own req_v2 records.
Navigation path
Prerequisites
- Part 4: ACLs (02).
Form fields
| Field | Value |
|---|---|
| Name | HRLX ACL Employee Isolation |
Step-by-step create
- Test: HRLX ACL Employee Isolation.
- Step: Impersonate user employee1.
- Step: Record Query req_v2 → assert count matches only own records.
Verify checklist
- Isolation test passes.
Demo / test
Add to suite Part 1 → run full suite.
ATF verify checklist
- Three tests in HRLX Leave Core suite.
- Suite runs without failure.
09Modules & Integration — navigation and the wider platform
Original chapter title: 09 — Modules & Integration (Studio From Scratch) — Application Menu, Modules, Flow — full create; cross-module study abbreviated
What you build
Application menu, modules, the Leave Approval flow, and the map into the per-module encyclopedias.
Proof it worked
Every table has a reachable module, and the approval flow completes end to end.
Read it this way
Also your index into ITSM / CSM / HRSD / FSM / SPM / GRC study material when you move beyond this app.
Visual map — Ch.09 · Modules & Flow
Application menu + Ask For Approval flow
Navigator modules · exclusive flow
Development
Full visual path (all chapters): VISUAL_FROM_SCRATCH_00_16.html
Studio-only from scratch: Full create steps for navigator and approval flow. Cross-module study is optional reading.
Also open: Properties + Direct Links · Schema · Studio Create All Objects · Previous: 08 ATF Testing
How menu, modules, and flow relate
Part 1 Application Menu → Part 2 Modules (4 list links) Submit (UI Action / Catalog) → Part 3 Flow → Approval → BR3/BR4
Part 1: Application Menu
Purpose
Top-level Application Navigator entry for HR Leave modules.
Navigation path
Direct URL
Prerequisites
- Part 1: Application Setup with Create application menu checked.
Form fields
| Field | Value |
|---|---|
| Title | HR Leave |
| Application | HR Leave |
| Order | 100 |
| Roles | x_786686_hr_leave.employee (minimum) |
Step-by-step create
- If missing: Studio → Application Menu → New → fill fields.
- Save. Confirm appears in Application Navigator.
Verify checklist
- Navigator shows HR Leave heading.
Demo / test
employee1 login → sees HR Leave menu after modules Modules.
Part 2: Modules
Purpose
Four list modules linking to req_v2, employee_v2, bal_v2, audit_v2 tables.
Navigation path
Direct URL
Prerequisites
- Part 1: Application Menu menu. Tables Table HR Employee..05.
Form fields
| Title | Type | Table | Order | Roles |
|---|---|---|---|---|
| Leave Requests | List | x_786686_hr_leave_req_v2 | 10 | employee, manager, hr_admin |
| Employees | List | x_786686_hr_leave_employee_v2 | 20 | hr_admin |
| Balances | List | x_786686_hr_leave_bal_v2 | 30 | hr_admin |
| Audit Trail | List | x_786686_hr_leave_audit_v2 | 40 | hr_admin |
Step-by-step create
- Module → New for each row. Application Menu = HR Leave.
- Type = List → Table = as listed. Order as table.
- Roles: restrict Employees/Balances/Audit to hr_admin; Requests to all three roles.
Verify checklist
- Four modules under HR Leave menu.
- URLs x_786686_hr_leave_*_list.do open.
Demo / test
employee1 → only Leave Requests visible.
Part 3: Flow Designer — Leave Approval
Purpose
When leave state becomes submitted, sends approval to u_approver_user; on approve/reject updates u_state.
Navigation path
Direct URL
Prerequisites
- Part 3: Table req_v2 with u_state, u_approver_user.
- Part 8: Business Rule BR1 BR1 fills approver.
- manager1 user for approval tests.
Form fields — Create Flow wizard
| Field | Value |
|---|---|
| Name | HRLX Leave Approval |
| Application | HR Leave |
| Description | Approval flow for submitted leave requests |
Trigger — Record Updated
| Field | Value |
|---|---|
| Trigger type | Record Updated |
| Table | x_786686_hr_leave_req_v2 |
| Condition | State [u_state] is submitted AND State changes |
| Run flow | Once per record (recommended) |
Action 1 — Ask For Approval
| Input | Value |
|---|---|
| Record | Trigger → Leave Request Record (data pill) |
| Table | x_786686_hr_leave_req_v2 |
| Approval field | u_approver_user |
| Approval reason | Leave approval required |
| Rules → Approve | Anyone approves (required — do not leave empty) |
If Approved → Update Record
| Record | Trigger record pill |
| Field map | u_state = approved |
Else → Update Record
| Field map | u_state = rejected |
Step-by-step create
- Flow Designer → Create Flow → Name HRLX Leave Approval, Application HR Leave.
- Add trigger → Record Updated → table req_v2 → condition u_state is submitted + changes.
- + → Ask for Approval → fill Action 1 table → Rules: Anyone approves → Done.
- + → Flow Logic → If → condition "approval was Approved".
- Yes branch → Update Record → u_state = approved.
- Else branch → Update Record → u_state = rejected.
- Save → fix any red errors (missing approval rule is common).
- Test → pick sample submitted req_v2 → Run test.
- Publish → Publish new version → wait for Published status.
Verify checklist
- Submitted req creates sysapproval_approver for manager.
- Approve → u_state approved → BR3 audit + BR4 event.
- Reject → u_state rejected.
Demo / test
- employee1 submits leave (UI Action or catalog).
- manager1 → My Approvals → Approve.
- Verify state, audit_v2, bal_v2 updated.
Part 4: Professional Module Encyclopedias
Purpose
After HR Leave menu/flow works, use the professional implementer guides (same Part structure as 01 Development) for ITSM, CMDB, ITOM, ITAM, HRSD, CSM, FSM, SPM, GRC, SecOps, DevOps, App Engine — with direct URLs, properties, and navigator module publish steps.
Navigation path
Direct URL
Prerequisites
- Parts 1–3 complete for core HR Leave.
Related guides
- 00 Modules Index — hub + study order for all 12 modules
- ITSM · CMDB · ITOM · ITAM
- HRSD · CSM · FSM · SPM
- GRC · SecOps · DevOps · App Engine
- 12 Integration — Scripted REST after menu/flow works
Verify checklist
- Core app E2E works without optional modules.
- Modules Index opens and links resolve.
Demo / test
employee1 → HR Leave menu → submit → manager approves. Then open HRSD Part 13 for HR Leave cross-link.
Modules verify checklist
- HR Leave menu visible.
- Four modules with correct roles.
- Flow published and approving.
10Implementation & Config — the go-live gate
Original chapter title: 10 — Implementation & Config (Studio From Scratch) — E2E UAT checklist, seed data, update set complete/export — professional go-live
What you build
Seed data, the end-to-end demo checklist, update set export, and the UAT matrix.
Proof it worked
A clean instance can be brought to a working demo using only the exported update set and the seed steps.
Read it this way
This is the chapter that separates a lab exercise from something you can hand to another engineer.
Visual map — Ch.10 · Implementation — go-live
Seed → E2E → Update Set → UAT matrix
Cutover on TARGET instance
Implementation
Full visual path (all chapters): VISUAL_FROM_SCRATCH_00_16.html
Studio-only from scratch: Go-live checklist after creating objects via Studio chapters 01–09 (and optional 03–08).
Also open: Properties + Direct Links · Schema · Studio Create All Objects · Previous: 09 Modules
How implementation artifacts relate
Seed data → E2E demo → Update Set export → UAT sign-off matrix.
Part 1: Seed Data
Purpose
Lab employee, manager link, and opening balance for E2E demos.
Navigation path
Prerequisites
- Chapters 01–02 complete (tables, users, properties).
Step-by-step create
- Lookup sys_ids for employee1 and manager1 users.
- Run background script below as admin, Application = HR Leave.
- Verify employee_v2 and bal_v2 rows.
Full script (paste-ready)
(function () {
var empUser = 'PASTE_EMPLOYEE1_SYS_ID';
var mgrUser = 'PASTE_MANAGER1_SYS_ID';
var emp = new GlideRecord('x_786686_hr_leave_employee_v2');
emp.initialize();
emp.u_user = empUser;
emp.u_employee_id = 'E-1001';
emp.u_department = 'People Ops';
emp.u_manager = mgrUser;
emp.u_join_date = '2024-01-15';
emp.u_grade = 'G5';
var empId = emp.insert();
var bal = new GlideRecord('x_786686_hr_leave_bal_v2');
bal.initialize();
bal.u_employee_ref = empId;
bal.u_year = 2026;
bal.u_leave_type = 'annual';
bal.u_entitled = parseInt(gs.getProperty('x_786686_hr_leave.config.annual_days', 20), 10);
bal.u_used = 0;
bal.u_remaining = bal.u_entitled;
bal.insert();
gs.info('Seeded employee=' + empId);
})();
Verify checklist
- employee_v2 links employee1 → manager1.
- bal_v2 year 2026 with entitled from property.
Demo / test
employee1 creates req_v2 → BR1 sets approver manager1.
Part 2: E2E Demo Checklist
Purpose
Prove full leave lifecycle from draft/submit through approval, audit, balance, notifications, portal, REST.
Prerequisites
Verify checklist
Complete after Application Setup through Flow (extended: Portal, REST).
- Update Set current — HR Leave – Build (Rule 0).
- Seed employee — Part 2: employee_v2, Part 3: Users.
- Seed balance — Part 4: bal_v2.
- Create request — Part 3: req_v2 OR Part 2: Record Producer.
- Submit — Part 15: UI Action, Part 9: BR2.
- Flow approval — Part 3: Flow.
- Audit — Part 10: BR3.
- Balance — BR3 updates bal_v2.
- Notification — Part 2: Notifications.
- Security — Part 4: ACLs.
- Portal — Part 3: hrlx_home.
- REST — Part 4: REST Explorer.
Demo / test
Walk checklist top-to-bottom as three personas.
Part 3: Update Set Export
Purpose
Complete and export HR Leave – Build for target instance promotion.
Navigation path
Direct URL
Form fields
| Field | Value |
|---|---|
| Name | HR Leave – Build |
| State | Complete |
| Application | HR Leave |
Step-by-step create
- HR Leave – Build → State = Complete.
- Export via Retrieved Update Sets or XML per process.
- Target: Import → Preview → Commit.
- Post-import: users/groups if missing; verify 9 properties; run accrual job once.
- Smoke test: employee1 submit → manager1 approve → balance updated.
Never promote Global artifacts. Confirm Application = HR Leave on every captured file.
Verify checklist
- Customer Updates lists all scoped artifacts.
Demo / test
Import to second PDI → smoke test.
Part 4: UAT Matrix
Purpose
Sign-off matrix for lab personas and expected outcomes.
Form fields — test matrix
| Persona | Test | Expected |
|---|---|---|
| employee1 | Create + submit leave | State submitted, approver = manager1 |
| manager1 | Approve in My Approvals | State approved, notification sent |
| hradmin | View all modules | Employees, Balances, Audit visible |
| employee1 | Open coworker req URL | ACL denied |
Seed values: SCHEMA_AND_DATA.html
Verify checklist
- All four UAT rows pass.
Demo / test
Record pass/fail per row before go-live.
Implementation verify checklist
- Seed data present.
- E2E checklist green.
- Update Set exported.
- UAT matrix signed.
11Studio Practice Companion — the daily workflow
Original chapter title: 11 — Studio Practice Companion — Day-to-day Studio workflow — points to chapters 01–12. No external CLI.
What you build
Nothing new. The day-to-day Studio routine and the object-type → chapter lookup table.
Proof it worked
Given any object type ("I need a new client script"), you can name the chapter and the exact Studio path in under ten seconds.
Read it this way
Use it as the index you keep open while working, not as a chapter you read once.
Visual map — Ch.11 · Studio daily practice
Picker · update set · one artefact family
Habit loop
Development
Full visual path (all chapters): VISUAL_FROM_SCRATCH_00_16.html
Studio-only from scratch: Studio practice companion only. Build the core app via 01 Development and Studio Create All first.
Also open: Properties + Direct Links · Schema · Studio Create All Objects · Previous: 10 Implementation
How chapters map to Studio work
Part 1 daily workflow → Part 2 object-type table (chapters 01–12) → Part 3 scripts portal for paste-ready code.
Part 1: Daily Workflow in Studio
Purpose
Repeatable steps every time you create or edit HR Leave artifacts.
Navigation path
Step-by-step create
- Set Application picker to HR Leave (
x_786686_hr_leave). - Set Local Update Set current (HR Leave – Build).
- Open Studio → select HR Leave.
- Create Application File → choose type (Table, Business Rule, Client Script, Script Include, …).
- Fill every form field from the matching chapter Part section.
- Paste script from embedded
<pre>blocks or Scripts portal. - Save → open list/form URL → run Verify + Demo for that Part.
Verify checklist
- Customer Updates shows new artifact in HR Leave – Build.
Demo / test
One object per session: create → verify → demo before moving on.
Part 2: Object Type → Chapter Map (01–12)
Purpose
Quick lookup: which chapter documents each ServiceNow object type.
| Object | Studio / Navigation | Chapter · Part |
|---|---|---|
| Application / Tables / SI / BR / CS / UI | Studio → Create Application File | 01 Parts 1–15 |
| Roles / Groups / Users / ACLs / Properties | Security / User Administration | 02 Parts 1–6 |
| Record Producer / Category | Service Catalog | 03 Parts 1–5 |
| Portal widgets / Page | Service Portal | 04 Parts 1–3 |
| UI Builder workspace | Now Experience → UI Builder | 05 Parts 1–4 |
| Events / Notifications / Jobs | System Policy / System Definition | 06 Parts 1–3 |
| Reports / Dashboard | Reports / Dashboards | 07 Parts 1–2 |
| ATF | Automated Test Framework | 08 Parts 1–4 |
| Menu / Modules / Flow | Application Menus / Flow Designer | 09 Parts 1–3 |
| Seed / UAT / Update Set | Background scripts / Update Sets | 10 Parts 1–4 |
| Scripted REST | System Web Services | 12 Parts 1–4 |
Verify checklist
- Every row links to a chapter with Part anchors (not OBJ).
Part 3: Scripts Portal
Purpose
Browse paste-ready scripts mirrored from HR_Leave_App/src/ on disk.
Navigation path
Direct URL
Step-by-step create
- Open Scripts portal → find script by name (e.g. RP_submit_leave.js, annual_accrual.js).
- Copy from HTML pre block OR open chapter Part Full script section.
- Paste into Studio form Script field while Application = HR Leave.
Verify checklist
- Script saves without syntax errors in Studio.
Demo / test
Practice companion verify checklist
- Workflow habit: picker + Update Set before every create.
- Object table bookmarked.
- Scripts portal used for paste source.
12Integration Full Guide — inbound and outbound
Original chapter title: 12 — Integration Full Guide (Studio From Scratch) — Scripted REST + Integration Script Include — REST Explorer test
What you build
Script Include HRLXProIntegrationUtilsV2, a Scripted REST API definition, the POST /leave resource script, and the REST Explorer test.
Proof it worked
A POST from REST Explorer creates a leave request, and an invalid payload returns a clean error instead of a stack trace.
Read it this way
Error handling, not the happy path, is what makes an integration production-grade. Read the resource script twice.
Visual map — Ch.12 · Integration — REST API
Scripted REST POST /leave · Explorer
hrlx_api_v2 · integration user
Development
Full visual path (all chapters): VISUAL_FROM_SCRATCH_00_16.html
Studio-only from scratch: Scripted REST + Integration Script Include — create in ServiceNow UI only. Test with REST API Explorer.
Also open: Properties + Direct Links · Schema · Studio Create All Objects · Previous: 11 Studio Practice
How integration objects relate
Part 1 HRLXProIntegrationUtilsV2 (SI)
└── Part 2 REST API definition (hrlx_api_v2)
└── Part 3 POST /leave resource script
└── Part 4 REST Explorer test (201 + sys_id)
Part 1: Script Include — HRLXProIntegrationUtilsV2
Purpose
Inbound create + outbound ERP push helpers for REST integration.
Navigation path
Direct URL
Prerequisites
- Part 1: Roles integration role.
- Part 3: req_v2.
Form fields
| Field | Value |
|---|---|
| Name | HRLXProIntegrationUtilsV2 |
| Client callable | false |
| Active | true |
| Application | HR Leave |
Step-by-step create
- Studio → Script Include → New → fill fields → paste script → Save.
Full script — HRLXProIntegrationUtilsV2
/**
* Script Include: HRLXProIntegrationUtilsV2
* Scope: x_786686_hr_leave
* Application: Custom · Scope: x_786686_hr_leave · sys_id: 83d5298c932b8f107520703efaba1072
* Client callable: false
* Purpose: Inbound create + Outbound push for leave integration.
*/
var HRLXProIntegrationUtilsV2 = Class.create();
HRLXProIntegrationUtilsV2.prototype = {
initialize: function () {},
/**
* Inbound: create a leave request from a JSON payload (string).
* @return {string} sys_id of created record
*/
inboundCreateLeave: function (jsonString) {
var data = {};
try { data = JSON.parse(jsonString); } catch (e) { return ''; }
var gr = new GlideRecord('x_786686_hr_leave_req_v2');
gr.initialize();
gr.u_employee_ref = this._findEmployeeByExternalId(data.employee_id);
gr.u_requested_for_user = this._findUserByEmployee(data.employee_id);
gr.u_leave_type = data.leave_type || 'annual';
gr.u_start_date = data.start_date || '';
gr.u_end_date = data.end_date || '';
gr.u_reason = data.reason || '';
gr.u_state = 'submitted';
return gr.insert();
},
/**
* Outbound: push an approved leave decision to ERP/Payroll.
* Endpoint + auth come from system properties (no secrets in code).
*/
outboundPushDecision: function (leaveSysId) {
var gr = new GlideRecord('x_786686_hr_leave_req_v2');
if (!gr.get(leaveSysId)) return false;
var endpoint = gs.getProperty('x_786686_hr_leave.config.erp_endpoint');
if (!endpoint) {
gs.error('[HRLX] Missing property x_786686_hr_leave.config.erp_endpoint');
return false;
}
try {
var r = new sn_ws.RESTMessageV2();
r.setEndpoint(endpoint);
r.setHttpMethod('POST');
r.setRequestHeader('Content-Type', 'application/json');
r.setRequestBody(JSON.stringify({
number: gr.getValue('u_number'),
employee: gr.getDisplayValue('u_employee_ref'),
type: gr.getValue('u_leave_type'),
days: gr.getValue('u_days_count'),
state: gr.getValue('u_state')
}));
var resp = r.execute();
var code = resp.getStatusCode();
gs.info('[HRLX] Outbound status ' + code);
return (code >= 200 && code < 300);
} catch (ex) {
gs.error('[HRLX] Outbound error: ' + ex.message);
return false;
}
},
_findEmployeeByExternalId: function (employeeId) {
var e = new GlideRecord('x_786686_hr_leave_employee_v2');
e.addQuery('u_employee_id', employeeId);
e.query();
return e.next() ? e.getUniqueValue() : '';
},
_findUserByEmployee: function (employeeId) {
var e = new GlideRecord('x_786686_hr_leave_employee_v2');
e.addQuery('u_employee_id', employeeId);
e.query();
return e.next() ? e.getValue('u_user') : '';
},
type: 'HRLXProIntegrationUtilsV2'
};
Verify checklist
- Script Include active under HR Leave.
Demo / test
Background: new x_786686_hr_leave.HRLXProIntegrationUtilsV2() instantiates.
Part 2: Scripted REST API Definition
Purpose
Defines scoped API hrlx_api_v2 with authentication and default ACLs.
Navigation path
Direct URL
Prerequisites
Form fields — Scripted REST API (sys_ws_definition)
| Field | Value |
|---|---|
| Name | HRLX Leave API |
| API ID | hrlx_api_v2 |
| Application | HR Leave (NOT Global) |
| API namespace | x_786686_hr_leave (read-only) |
| Requires authentication | true |
| Default ACLs | ACL execute requiring x_786686_hr_leave.integration |
Step-by-step create
- Scripted REST APIs → New → fill API fields.
- Security → Default ACLs → execute requires integration role.
- Submit API record before adding resource in Part 3.
Verify checklist
- API ID hrlx_api_v2 under HR Leave (not Global).
Demo / test
API appears in REST Explorer namespace list.
Part 3: Resource Script — POST /leave
Purpose
POST /api/x_786686_hr_leave/hrlx_api_v2/leave creates submitted req_v2 from JSON.
Navigation path
Prerequisites
- Part 2: REST API Definition.
- employee_v2 with u_employee_id = EMP001.
Form fields — Resource POST /leave
| Field | Value |
|---|---|
| Name | Create Leave |
| HTTP method | POST |
| Relative path | /leave |
| Active | true |
Step-by-step create
- Related Resources → New → POST /leave → paste resource script → Submit.
Full script — Resource (POST /leave)
/**
* Application: Custom · Scope: x_786686_hr_leave · sys_id: 83d5298c932b8f107520703efaba1072
* Scripted REST API Resource: Create Leave (Inbound)
* API ID: hrlx_api_v2
* Resource: POST /leave
* Full path: /api/x_786686_hr_leave/hrlx_api_v2/leave
*
* TEST FIRST (no Postman until this works):
* All > System Web Services > REST API Explorer ($restapi.do)
* Namespace: x_786686_hr_leave | API: hrlx_api_v2 | Resource: leave | POST
* Request body: { "employee_id":"EMP001", "leave_type":"annual", ... }
* Click Send → expect 201 → Copy as cURL
*
* Simpler alternative (Table API, no script):
* Explorer → namespace now → Table API → x_786686_hr_leave_req_v2 → POST
* See: src/server/rest/rest_api_explorer.md
*
* Create API record:
* All > System Web Services > Scripted REST APIs > New + Resource POST /leave
*/
(function process(request, response) {
var data = request.body && request.body.data ? request.body.data : {};
// Validate
if (!data.employee_id) {
response.setStatus(400);
return response.setBody({ error: 'employee_id is required' });
}
if (!data.start_date || !data.end_date) {
response.setStatus(400);
return response.setBody({ error: 'start_date and end_date are required' });
}
// Create via integration utility
var sysId = new x_786686_hr_leave.HRLXProIntegrationUtilsV2().inboundCreateLeave(JSON.stringify(data));
if (!sysId) {
response.setStatus(422);
return response.setBody({ error: 'Could not create leave request (employee not found?)' });
}
response.setStatus(201);
response.setBody({ sys_id: sysId, message: 'Leave request created' });
})(request, response);
Verify checklist
- Resource active; path /leave relative to API.
Demo / test
Test in Part 4 REST Explorer.
Part 4: REST Explorer Test
Purpose
Validate inbound POST returns 201 and creates req_v2 as integration user.
Navigation path
Direct URL
/api/x_786686_hr_leave/hrlx_api_v2/leavePrerequisites
- Parts 1–3 complete.
- User hrlx.integration with integration + rest_service roles.
Step-by-step create
- Application = HR Leave.
- Studio → Script Include → HRLXProIntegrationUtilsV2 → paste script → Save.
- System Security → ACL → New: Type scripted_rest_api, Operation execute, Requires role integration → Save.
- Scripted REST APIs → New → fill API fields → Security tab → Default ACLs → add ACL → Submit.
- Related Resources → New → POST /leave → paste resource script → Submit.
- REST API Explorer → Namespace x_786686_hr_leave → API hrlx_api_v2 → Resource leave → POST.
- Body: {"employee_id":"EMP001","leave_type":"annual","start_date":"2026-06-01","end_date":"2026-06-05","reason":"Vacation"}
- Run as hrlx.integration (Impersonate) → Send → expect 201 + sys_id.
Verify checklist
- 201 response with sys_id.
- req_v2 row exists in submitted state.
- 403 without integration role; 422 if EMP001 missing.
Demo / test
Demo / test
Explorer Send → copy cURL → verify record in req_v2 list.
Integration verify checklist
- SI + API + resource scoped to HR Leave.
- POST returns 201.
- req_v2 row in submitted state.
- 403 without integration role.
132-Hour Master Summary — the revision pass
Original chapter title: 13 — 2-Hour Master Summary (Studio Revision) — Revision checklist linking chapter Parts — Studio only
What you build
Nothing. A checklist that links back to every chapter Part, plus the end-to-end demo script and the known gaps.
Proof it worked
You can walk the full demo from memory, using the checklist only to confirm.
Read it this way
Best used twice: once now as a map, and again at the end of the day as the exit exam.
Visual map — Ch.13 · 2-hour summary
OBJ-01→32 revision with openable proof
Reviewer checklist
Implementation
Full visual path (all chapters): VISUAL_FROM_SCRATCH_00_16.html
Studio-only from scratch: 2-hour revision checklist — confirm every Part exists before demo or interview.
Also open: Properties + Direct Links · Schema · Studio Create All Objects · Previous: 12 Integration
Revision map
Part 1 quick checklist → Part 2 E2E demo → Part 3 optional gaps.
Part 1: Quick Checklist (Chapter Parts)
Purpose
Fast pass/fail review of all create objects linked by chapter Part anchors.
Verify checklist
| Area | Object | Chapter · Part |
|---|---|---|
| 00 | Update Set HR Leave – Build | 01 Rule 0 |
| 01 | Application HR Leave | 01 Part 1 |
| 02–05 | Tables employee/req/bal/audit _v2 | 01 Parts 2–5 |
| 06–07 | Script Includes | 01 Parts 6–7 |
| 08–11 | BR1–BR4 | 01 Parts 8–11 |
| 12–15 | CS, UI Policy, UI Action, Menu, Flow | 01 Parts 12–15+ |
| 16–20 | Roles, Groups, Users, ACLs, Properties | 02 Parts 1–5 |
| 21–23 | Events, Notifications, Job | 06 Parts 1–3 |
| 24–27 | Menu, Modules, Flow | 09 Parts 1–3 |
| 26 | Record Producer | 03 Part 2 |
| 28 | Portal widgets + page | 04 Parts 1–3 |
| 29 | UI Builder workspace | 05 Parts 1–4 |
| 30 | Report + Dashboard | 07 Parts 1–2 |
| 31 | ATF suite | 08 Parts 1–4 |
| 32 | Scripted REST | 12 Parts 1–4 |
Part 2: E2E Demo
Purpose
Single scripted walkthrough proving leave lifecycle.
Complete after core Parts through Flow (extended: Portal, REST).
- Update Set current — HR Leave – Build (Rule 0).
- Seed employee — Part 2: employee_v2, Part 3: Users.
- Seed balance — Part 4: bal_v2.
- Create request — Part 3: req_v2 OR Part 2: Record Producer.
- Submit — Part 15: UI Action, Part 9: BR2.
- Flow approval — Part 3: Flow.
- Audit — Part 10: BR3.
- Balance — BR3 updates bal_v2.
- Notification — Part 2: Notifications.
- Security — Part 4: ACLs.
- Portal — Part 3: hrlx_home.
- REST — Part 4: REST Explorer.
Demo / test
Timebox: 30 minutes for full E2E as three personas.
Part 3: Gaps (Optional)
Purpose
Items often skipped in a 2-hour sprint — schedule if time allows.
- 05 Workspace — Now Experience lists
- 07 Reports — dashboard for managers
- 08 ATF — regression suite
- 12 REST — inbound POST /leave
- 14 AI Agents — platform AI (post core app)
Verify checklist
- Core path (01, 02, 03, 06, 09, 10) complete before optional gaps.
Demo / test
Summary verify checklist
- All Part links resolve.
- E2E demo passes.
- Gaps documented if deferred.
14AI in ServiceNow + Agents — the platform AI chapter
Original chapter title: 14 — AI in ServiceNow + Agents — Platform AI features after core Studio app (01–12) exists.
What you build
Plugin activation and roles, Now Assist skills, AI Agent Studio, BYO LLM, AI Search + GenAI, Predictive Intelligence, Virtual Agent + NLU, Document Intelligence, Workflow Studio AI actions, the HR Leave AI example, governance, cost, labs and a deployment checklist.
Proof it worked
One AI capability is active on your instance and produces a correct result on a real HR Leave record.
Read it this way
The longest chapter by far. For deeper agentic build steps, RAG, MCP and the SDK, continue into the AI full-demo master guide linked at the end.
Visual map — Ch.14 · AI — after core works
Now Assist / VA / Agent Studio / Control Tower
Optional layer
Development
Full visual path (all chapters): VISUAL_FROM_SCRATCH_00_16.html
Studio-only for core app: Create chapters 01 through 12 first. AI configuration uses ServiceNow platform UI (Now Assist, AI Agent Studio, Virtual Agent).
Also open: Properties + Direct Links · Schema · Studio Create All Objects · Previous: 13 Summary
Prerequisite: All tables, BR, Flow, and ACLs must exist on the instance before enabling AI features.
How AI features relate to HR Leave
Core app (ch.01–12) — tables, BR, Flow, ACLs
│
▼
Platform AI (this chapter) — Now Assist, Agents, VA, Search
│
▼
Optional: HR Leave skills/topics referencing req_v2 / bal_v2 data
AI layers sit on top of the scoped app — they do not replace Studio create steps in chapters 01–12.
Table of Contents
- What You Will Build
- Direct Navigation URLs
- Part 0 — Quick Map & Prerequisites
- Part 1 — Plugin Activation & Roles
- Part 2 — Now Assist Studio (Skills)
- Part 3 — AI Agent Studio
- Part 4 — BYO LLM (Claude / GPT)
- Part 5 — AI Search + GenAI Search
- Part 6 — Predictive Intelligence
- Part 7 — Virtual Agent + NLU (Full Topics & Flows)
- Part 8 — Document Intelligence (IDP)
- Part 9 — Workflow Studio AI Actions
- Part 10 — Now Assist for Developers
- Part 11 — End-to-End HR Leave AI Example
- Part 12 — AI Governance & Safety
- Part 13 — Performance & Cost Optimization
- Part 14 — Hands-On Labs (5 Sessions)
- Part 15 — Best Practices
- Part 16 — Production Deployment Checklist
- Appendices
🎯 What You Will Build — ماذا ستبني
By the end of this guide you will have deployed a complete AI layer on the HR Leave scoped app:
- ✅ 6 Now Assist Skills (summarize, classify, suggest dates, history summary, sentiment, email draft)
- ✅ 1 AI Agent — HR Leave Assistant with 6 tools wired to
HRLXAIAgentTools - ✅ 1 AI Search profile —
hrlx_kb_profilewith GenAI answers + masked sensitive fields - ✅ 2 Predictive Intelligence solutions — approval prediction + duration regression
- ✅ 1 NLU model —
hrlx_leave_nluwith 8 intents and 200+ utterances - ✅ 8 Virtual Agent topics — balance, request, cancel, policy, status, handoff, greeting, fallback
- ✅ 1 IDP template — medical certificate extraction wired to sick-leave flow
- ✅ 3 Flow Designer AI Actions — summarize history, classify type, draft manager email
- ✅ Governance — AI policies, PII redaction, audit to
x_786686_hr_leave_audit_v2 - ✅ Monitoring dashboard — skill latency, agent containment, PI drift, search zero-results
Style in this doc: English instruction line → Arabic explanation on its own line where helpful. All code in fenced blocks.
📍 Direct Navigation URLs — روابط التنقل المباشرة
Replace {BASE} with your instance URL (e.g. https://dev12345.service-now.com).
Now Assist & AI Agents
| Tool | Navigation Path | Direct URL | Purpose |
|---|---|---|---|
| Now Assist Admin | All > Now Assist > Now Assist Admin | {BASE}/now/assist/admin | Skill setup wizard |
| Now Assist Studio | App Picker > Now Assist Studio | {BASE}/now/assist/studio | Create/edit skills |
| Now Assist Skills | Now Assist Admin > Skills | {BASE}/sys_one_extend_skill_list.do | Skill records |
| Prompt Templates | (Skill) Templates related list | {BASE}/sys_one_extend_prompt_template_list.do | Versioned prompts |
| Skill Audit Log | Now Assist Admin > Audit | {BASE}/sys_one_extend_audit_list.do | Execution telemetry |
| AI Agent Studio | All > AI Agent Studio | {BASE}/now/aia/studio | Build agents + tools |
| AI Agents | AI Agent Studio > Agents | {BASE}/sn_aia_agent_list.do | Agent definitions |
| AI Tools | AI Agent Studio > Tools | {BASE}/sn_aia_tool_list.do | Callable tools |
| Agent Executions | AI Agent Studio > Executions | {BASE}/sn_aia_execution_list.do | Run history |
| LLM Provider Config | Now Assist Admin > LLM Providers | {BASE}/sys_one_extend_provider_config_list.do | BYO LLM setup |
| AI Policy Center | All > AI Governance > AI Policy Center | {BASE}/now/ai/policy | Policies & rules |
AI Search
| Tool | Navigation Path | Direct URL | Purpose |
|---|---|---|---|
| AI Search Admin | All > AI Search > Admin | {BASE}/now/ais/admin | Search admin home |
| Search Sources | AI Search Admin > Sources | {BASE}/ais_search_source_list.do | Index sources |
| Search Profiles | AI Search Admin > Profiles | {BASE}/ais_search_profile_list.do | Profile config |
| Search Applications | AI Search Admin > Applications | {BASE}/ais_search_application_list.do | Portal/Workspace UI |
| Query Log | AI Search Admin > Analytics | {BASE}/sys_aisearch_query_log_list.do | Zero-result analysis |
| GenAI Search Log | AI Search Admin > GenAI Log | {BASE}/sys_aisearch_genai_log_list.do | RAG answers |
Predictive Intelligence
| Tool | Navigation Path | Direct URL | Purpose |
|---|---|---|---|
| Solution Definitions | All > Predictive Intelligence > Solution Definitions | {BASE}/ml_solution_definition_list.do | Define ML problem |
| Solutions | All > Predictive Intelligence > Solutions | {BASE}/ml_solution_list.do | Trained models |
| Solution Monitor | All > Predictive Intelligence > Solution Monitor | {BASE}/ml_solution_monitor_list.do | Drift & accuracy |
| Training Dataset | (Solution) Training Dataset | {BASE}/ml_capability_definition_base_list.do | Feature mapping |
Virtual Agent & NLU
| Tool | Navigation Path | Direct URL | Purpose |
|---|---|---|---|
| Virtual Agent Designer | All > Virtual Agent > Designer | {BASE}/now/virtual-agent/designer | Topic builder |
| Topics | Virtual Agent > Topics | {BASE}/sys_cs_topic_list.do | Topic records |
| Conversations | Virtual Agent > Conversations | {BASE}/sys_cs_conversation_list.do | Chat logs |
| NLU Workbench | All > NLU > Workbench | {BASE}/now/nlu/workbench | Intent/entity models |
| NLU Models | NLU > Models | {BASE}/nlu_model_list.do | Model list |
| NLU Intents | (Model) Intents | {BASE}/nlu_intent_list.do | Intent definitions |
| Employee Center | Direct | {BASE}/esc | Test VA in prod-like UI |
| Service Portal | Direct | {BASE}/sp?id=chat | Portal chat widget |
Document Intelligence
| Tool | Navigation Path | Direct URL | Purpose |
|---|---|---|---|
| IDP Admin | All > Document Intelligence | {BASE}/now/idp/admin | IDP home |
| Templates | IDP Admin > Templates | {BASE}/sn_idp_template_list.do | Document templates |
| Extraction Models | IDP Admin > Models | {BASE}/sn_idp_model_list.do | Trained extractors |
| Extraction Results | IDP Admin > Results | {BASE}/sn_idp_extraction_result_list.do | Run output |
HR Leave App Tables (Grounding & Tools)
| Table | Direct URL | Key Fields for AI |
|---|---|---|
| Leave Request | {BASE}/x_786686_hr_leave_req_v2_list.do | u_leave_type, u_state, u_reason, u_days_count |
| Employee | {BASE}/x_786686_hr_leave_employee_v2_list.do | u_employee_id, user reference |
| Balance | {BASE}/x_786686_hr_leave_bal_v2_list.do | u_leave_type, u_remaining_days |
| Audit | {BASE}/x_786686_hr_leave_audit_v2_list.do | u_action, u_payload |
| Knowledge (policy) | {BASE}/kb_knowledge_list.do?sysparm_query=topic=HR Leave | text, short_description |
Development & Deployment
| Tool | Direct URL |
|---|---|
| Studio | {BASE}/$studio.do |
| Script Includes | {BASE}/sys_script_include_list.do?sysparm_query=sys_scope.scope=x_786686_hr_leave |
| Business Rules | {BASE}/sys_script_list.do?sysparm_query=sys_scope.scope=x_786686_hr_leave |
| Flow Designer | {BASE}/now/flow-designer |
| Update Sets | {BASE}/sys_update_set_list.do |
| Background Scripts | {BASE}/sys.scripts.do |
| System Properties | {BASE}/sys_properties_list.do?sysparm_query=nameSTARTSWITHx_786686_hr_leave |
| Plugins | {BASE}/v_plugin_list.do |
Part 0 — Quick Map & Prerequisites
0.1 Decision matrix — which AI tool when?
| User need | Best tool | Why | Section |
|---|---|---|---|
| One-click summary on form | Now Assist Skill | Deterministic, cheap, fast | §2 |
| “How many days left?” in chat | Virtual Agent topic OR AI Agent tool | VA = predictable; Agent = flexible | §3, §7 |
| Open-ended policy Q&A | AI Search GenAI + Agent | RAG with citations | §3, §5 |
| Predict if manager approves in 24h | Predictive Intelligence | Classical ML on history | §6 |
| Extract fields from medical PDF | Document Intelligence | Structured extraction | §8 |
| Multi-step: check balance → request → confirm | AI Agent Studio | Plans + tools + memory | §3 |
| Classify free-text leave reason | Now Assist Skill OR PI | Skill = LLM; PI = ML on features | §2, §6 |
| Code a Business Rule faster | Now Assist for Developers | In-Studio codegen | §10 |
ابدأ بـ Skill للمهام أحادية الخطوة. انتقل إلى Agent عندما يحتاج المستخدم محادثة متعددة الخطوات مع أدوات.
0.2 Prerequisites checklist
Before starting any AI work:
[ ] HR Leave v2 tables exist and have ≥ 50 test leave requests
[ ] Roles assigned: x_786686_hr_leave.employee, .manager, .hr_admin
[ ] Update Set created: "HR Leave AI v1.0"
[ ] Application picker set to HR Leave (x_786686_hr_leave) in Studio
[ ] At least 5 KB articles tagged topic=HR Leave (for AI Search)
[ ] Integration user NOT used for AI testing — use real employee personas
[ ] Instance has outbound HTTPS to LLM APIs (if BYO) — check ACL network rules
[ ] Admin access to activate plugins (requires admin role)
0.3 Verify app scope (Background Script)
var app = new GlideRecord('sys_app');
app.addQuery('scope', 'x_786686_hr_leave');
app.query();
if (app.next()) {
gs.info('App: ' + app.name + ' | sys_id: ' + app.sys_id);
} else {
gs.error('HR Leave app not found — create via 01_DEVELOPMENT.md first');
}
var tables = ['x_786686_hr_leave_req_v2', 'x_786686_hr_leave_bal_v2',
'x_786686_hr_leave_employee_v2', 'x_786686_hr_leave_audit_v2'];
tables.forEach(function (t) {
var gr = new GlideRecord(t);
gs.info(t + ' exists: ' + gr.isValid());
});
Part 1 — Plugin Activation & Roles
Step 1.1: Activate required plugins
Navigation: All > System Definition > Plugins — Open direct
Direct URL: {BASE}/v_plugin_list.do
| # | Plugin name (search) | Plugin ID (typical) | Required for |
|---|---|---|---|
| 1 | Now Assist for HRSD | com.snc.now_assist.hrsd | HR skills pack |
| 2 | Now Assist Platform | com.snc.now_assist.platform | Core GenAI |
| 3 | AI Agents | com.snc.ai_agents | Agent Studio |
| 4 | AI Search | com.snc.ai_search | Modern search + RAG |
| 5 | Predictive Intelligence | com.glide.platform_ml | ML solutions |
| 6 | Document Intelligence | com.snc.document_intelligence | IDP |
| 7 | Virtual Agent | com.glide.cs.chatbot | Chatbot topics |
| 8 | NLU Workbench | com.snc.nlu_workbench | Intent models |
| 9 | Now Assist for Creator | com.snc.now_assist.creator | Dev codegen (optional) |
Detailed activation steps:
1. Open {BASE}/v_plugin_list.do
2. Filter: Name contains "Now Assist" OR "AI Search" OR "Virtual Agent"
3. For each plugin in the table above:
a. Click the plugin name
b. Click "Activate" (or "Install" on some instances)
c. UNCHECK "Load demo data" in production-like sub-prod
d. Wait for activation (may take 2–10 minutes)
e. Note any dependent plugins auto-activated
4. After all activations: logout and login (refresh session roles)
5. Open Now Assist Setup wizard: {BASE}/now/assist/admin
6. Accept terms → select data region → Enable HRSD skill pack
7. Verify: All > Now Assist > Now Assist Admin shows "Configured"
Step 1.2: Assign AI admin roles
Navigation: All > User Administration > Users — Open direct
Direct URL: {BASE}/sys_user_list.do
| Role | Assign to | Purpose |
|---|---|---|
sn_nowassist_admin |
AI lead developer | Skills, providers |
sn_aia.admin |
AI lead developer | Agent Studio |
ai_search_admin |
Search admin | AI Search profiles |
ml_admin |
Data/ML owner | Train PI solutions |
nlu_admin |
Chatbot owner | NLU models |
sn_idp.admin |
Doc processing owner | IDP templates |
x_786686_hr_leave.hr_admin |
HR team | Use AI on records |
x_786686_hr_leave.manager |
Managers | Summarize UI Action |
Step 1.3: Create system properties (AI config)
Navigation: All > System Properties — Open direct
Direct URL: {BASE}/sys_properties_list.do
Create these properties in scope x_786686_hr_leave:
| Name | Type | Default | Description |
|---|---|---|---|
x_786686_hr_leave.ai.enabled |
true/false | true | Master kill switch |
x_786686_hr_leave.llm.daily_budget_usd |
string | 10 | Daily spend cap |
x_786686_hr_leave.llm.timeout_ms |
string | 15000 | REST timeout |
x_786686_hr_leave.llm.anthropic.api_key |
password2 | (empty) | Claude key |
x_786686_hr_leave.llm.anthropic.model |
string | claude-sonnet-4-5 | Model id |
x_786686_hr_leave.llm.openai.api_key |
password2 | (empty) | OpenAI key |
x_786686_hr_leave.llm.openai.model |
string | gpt-4o-mini | Model id |
x_786686_hr_leave.ai_search.profile |
string | hrlx_kb_profile | Default profile |
x_786686_hr_leave.nlu.model |
string | hrlx_leave_nlu | NLU model name |
x_786686_hr_leave.idp.template |
string | hrlx_medical_cert | IDP template |
x_786686_hr_leave.va.bot_name |
string | HR Leave Bot | Display name |
Verification script:
var props = ['ai.enabled', 'llm.daily_budget_usd', 'ai_search.profile'];
props.forEach(function (p) {
var v = gs.getProperty('x_786686_hr_leave.' + p, 'NOT SET');
gs.info('x_786686_hr_leave.' + p + ' = ' + v);
});
Part 2 — Now Assist Studio (Skills)
2.1 Architecture overview
| Object | Table | Purpose |
|---|---|---|
| Skill | sys_one_extend_skill | One GenAI capability |
| Prompt template | sys_one_extend_prompt_template | Versioned prompt text |
| Activation | sys_one_extend_activation | Where skill is enabled |
| Audit | sys_one_extend_audit | Every execution logged |
Step 2.1: Open Now Assist Studio
1. App picker (top-left) → select "HR Leave" scope
2. Click "Skills" in left nav
3. Click "New" → "Create Skill"
4. Confirm Application = x_786686_hr_leave on every record
Step 2.2: Skill 1 — hrlx_summarize_reason
| Field | Value |
|---|---|
| Name | hrlx_summarize_reason |
| Label | Summarize Leave Reason |
| Type | Summarization |
| Application | HR Leave |
| LLM | Now LLM Service (change to BYO in §4) |
| Max tokens | 120 |
| Temperature | 0.2 |
| PII redaction | ON |
Prompt template (SYSTEM + USER):
SYSTEM:
You are an HR assistant for a multinational company. Summarize the employee's
leave reason in ONE sentence, maximum 25 words, neutral professional tone.
Do NOT include medical diagnoses, medication names, or sensitive health details.
If reason is empty, return exactly: "No reason provided."
Respond in the same language as the reason text (English or Arabic).
USER:
Employee: {{employee_name}}
Leave type: {{u_leave_type}}
Start: {{u_start_date}} | End: {{u_end_date}} | Days: {{u_days_count}}
Reason:
"""
{{u_reason}}
"""
Variable mapping:
| Variable | Source field on x_786686_hr_leave_req_v2 |
|---|---|
| employee_name | u_requested_for_user (display) |
| u_leave_type | u_leave_type |
| u_start_date | u_start_date |
| u_end_date | u_end_date |
| u_days_count | u_days_count |
| u_reason | u_reason |
Grounding config:
1. Skill > Grounding tab > Enable
2. Table: x_786686_hr_leave_req_v2
3. Key: sys_id
4. Map each {{variable}} to column above
5. Save
Test in Playground (5 samples):
| # | u_reason input | Expected output quality |
|---|---|---|
| 1 | “Family wedding in Dubai, need 4 days travel” | Mentions wedding/travel, no PII |
| 2 | “Flu — doctor advised rest” | Generic “medical rest”, no diagnosis |
| 3 | (empty) | “No reason provided.” |
| 4 | Arabic: “إجازة أمومة” | Arabic one-liner |
| 5 | 500-word essay | ≤ 25 words |
Playground steps:
1. Skills > hrlx_summarize_reason > Test
2. Paste sys_id of test request OR fill variables manually
3. Run → check latency (target ≤ 2s)
4. If bad output → edit prompt → Save as version 1.1
5. Repeat until 90%+ acceptable on 20 samples
6. Status → Active
Step 2.3: Skill 2 — hrlx_classify_leave_type
| Field | Value |
|---|---|
| Name | hrlx_classify_leave_type |
| Type | Classification |
| Output validation | Regex: ^(annual\|sick\|parental\|unpaid)$ |
SYSTEM:
Classify the leave request text into EXACTLY ONE label:
annual, sick, parental, unpaid
Return ONLY the label, lowercase, no punctuation or explanation.
USER:
Leave type already selected (may be wrong): {{u_leave_type}}
Employee reason: {{u_reason}}
Additional notes: {{u_notes}}
Wire to Business Rule (before insert/update when u_leave_type empty):
(function executeRule(current, previous) {
if (!gs.getProperty('x_786686_hr_leave.ai.enabled', 'true') === 'true') return;
if (current.u_leave_type) return;
if (!current.u_reason) return;
try {
var ext = new sn_one_extend.OneExtendUtil();
var res = ext.execute({
executionRequests: [{
capabilityId: 'hrlx_classify_leave_type',
payload: {
u_leave_type: String(current.u_leave_type),
u_reason: String(current.u_reason),
u_notes: String(current.u_notes || '')
}
}]
});
var label = (res.capabilities[0] || {}).response;
if (/^(annual|sick|parental|unpaid)$/.test(label))
current.u_leave_type = label;
} catch (e) {
gs.warn('hrlx_classify_leave_type failed: ' + e);
}
})(current, previous);
Step 2.4: Skill 3 — hrlx_suggest_dates
| Field | Value |
|---|---|
| Name | hrlx_suggest_dates |
| Type | Generic / Structured output |
| Output schema | JSON Schema (see §12) |
SYSTEM:
Suggest adjusted leave dates avoiding public holidays.
Return ONLY valid JSON:
{"start":"YYYY-MM-DD","end":"YYYY-MM-DD","notes":"..."}
No markdown fences.
USER:
Country: {{u_country}}
Requested start: {{u_start_date}}
Requested end: {{u_end_date}}
Leave type: {{u_leave_type}}
Public holidays next 90 days:
{{holidays_json}}
Pre-step Script Include to build holidays_json:
var HRLXHolidayUtil = Class.create();
HRLXHolidayUtil.prototype = {
initialize: function() {},
getHolidaysJson: function(country, days) {
// Replace with your holiday table or IntegrationHub spoke
var out = [];
var gr = new GlideRecord('sn_hr_core_federal_holiday'); // if HRSD installed
if (gr.isValid()) {
gr.addQuery('country', country);
gr.addQuery('date', '>=', new GlideDate());
gr.setLimit(days || 90);
gr.query();
while (gr.next()) {
out.push({ date: String(gr.date), name: String(gr.name) });
}
}
return JSON.stringify(out);
},
type: 'HRLXHolidayUtil'
};
Step 2.5: Skill 4 — hrlx_summarize_history
Summarizes last N leave requests for a manager email.
SYSTEM:
Summarize this employee's leave history for a manager briefing.
Max 150 words. Bullet format. Include totals by type. Neutral tone.
USER:
Employee: {{employee_name}}
Lookback days: {{lookback_days}}
History records:
{{history_text}}
Step 2.6: Skill 5 — hrlx_detect_sentiment
SYSTEM:
Classify employee leave reason sentiment: positive, neutral, frustrated, urgent.
Return ONLY the label.
USER:
Reason: {{u_reason}}
Use output to route frustrated → human HR in Flow Designer.
Step 2.7: Skill 6 — hrlx_draft_manager_email
SYSTEM:
Draft a short email from HR system to employee about their leave request.
Professional, empathetic. Max 120 words. Include request number and dates.
Do not invent approval status — use {{u_state}} exactly.
USER:
Employee name: {{employee_name}}
Request: {{u_number}}
Type: {{u_leave_type}}
Dates: {{u_start_date}} to {{u_end_date}}
State: {{u_state}}
Manager note: {{manager_comment}}
Step 2.8: Activate skills per channel
URL: {BASE}/sys_one_extend_activation_list.do
For each skill create activation:
| Skill | Channels enabled |
|---|---|
| hrlx_summarize_reason | Workspace, UI Action, Flow Designer |
| hrlx_classify_leave_type | Business Rule (server), Flow Designer |
| hrlx_suggest_dates | Service Portal widget |
| hrlx_summarize_history | Flow Designer |
| hrlx_detect_sentiment | Flow Designer |
| hrlx_draft_manager_email | Workspace |
Step 2.9: UI Action — Summarize on form
URL: {BASE}/sys_ui_action_list.do
| Field | Value |
|---|---|
| Name | Summarize reason (AI) |
| Table | x_786686_hr_leave_req_v2 |
| Condition | current.u_state != 'cancelled' && gs.hasRole('x_786686_hr_leave.manager') |
| Form button | true |
| Client | false (server-side) |
(function executeAction(current) {
var ext = new sn_one_extend.OneExtendUtil();
var res = ext.execute({
executionRequests: [{
capabilityId: 'hrlx_summarize_reason',
payload: { record: current.getUniqueValue() }
}]
});
var summary = (res.capabilities[0] || {}).response || 'No summary';
gs.addInfoMessage('AI Summary: ' + summary);
})(current);
Part 3 — AI Agent Studio
3.1 What AI Agent Studio provides
AI Agent Studio builds agentic assistants: an LLM that plans steps, selects tools, executes them, observes results, and responds — across multiple conversation turns.
| Table | Purpose |
|---|---|
| sn_aia_agent | Agent definition + system prompt |
| sn_aia_tool | Callable action (Script Include, Flow, AI Search) |
| sn_aia_team | Multi-agent routing |
| sn_aia_trigger | Chat, email, record, schedule triggers |
| sn_aia_execution | One run (tokens, latency, cost) |
| sn_aia_execution_step | Each tool invocation |
| sn_aia_audit | Compliance-grade immutable log |
Direct URL: {BASE}/now/aia/studio
Step 3.1: Create Script Include HRLXAIAgentTools
URL: {BASE}/sys_script_include_list.do
Application: HR Leave
| Field | Value |
|---|---|
| Name | HRLXAIAgentTools |
| Client callable | false |
| Accessible from | This application scope only |
var HRLXAIAgentTools = Class.create();
HRLXAIAgentTools.prototype = {
initialize: function() {
this.SCOPE = 'x_786686_hr_leave';
this.T_EMP = 'x_786686_hr_leave_employee_v2';
this.T_REQ = 'x_786686_hr_leave_req_v2';
this.T_BAL = 'x_786686_hr_leave_bal_v2';
this.T_AUD = 'x_786686_hr_leave_audit_v2';
},
_getEmployeeByUser: function(userId) {
var gr = new GlideRecord(this.T_EMP);
gr.addQuery('user', userId);
gr.setLimit(1);
gr.query();
return gr.next() ? gr : null;
},
getBalance: function(userId) {
var emp = this._getEmployeeByUser(userId);
if (!emp) return { error: 'employee_not_found' };
var out = { annual: 0, sick: 0, parental: 0, unpaid: 0 };
var bal = new GlideRecord(this.T_BAL);
bal.addQuery('u_employee_ref', emp.getUniqueValue());
bal.query();
while (bal.next()) {
var t = String(bal.u_leave_type);
if (out.hasOwnProperty(t)) out[t] = Number(bal.u_remaining_days) || 0;
}
return out;
},
requestLeave: function(payload) {
try {
if (gs.getProperty(this.SCOPE + '.ai.enabled', 'true') !== 'true')
return { ok: false, error: 'ai_disabled' };
var emp = this._getEmployeeByUser(payload.user_id);
if (!emp) return { ok: false, error: 'employee_not_found' };
var start = new GlideDateTime(payload.start_date);
var end = new GlideDateTime(payload.end_date);
if (start.compareTo(end) > 0)
return { ok: false, error: 'start_after_end' };
var bal = this.getBalance(payload.user_id);
var days = Math.ceil((end.getNumericValue() - start.getNumericValue()) / 86400000) + 1;
if ((bal[payload.type] || 0) < days)
return { ok: false, error: 'insufficient_balance', needed: days, available: bal[payload.type] || 0 };
var req = new GlideRecord(this.T_REQ);
req.initialize();
req.u_requested_for_user = payload.user_id;
req.u_employee_ref = emp.getUniqueValue();
req.u_leave_type = payload.type;
req.u_start_date = payload.start_date;
req.u_end_date = payload.end_date;
req.u_reason = payload.reason || '';
req.u_state = 'submitted';
req.u_source = 'ai_agent';
var sysId = req.insert();
this._audit('ai_request_leave', sysId, payload);
return { ok: true, request_id: sysId, number: req.getValue('u_number') };
} catch (e) {
gs.error('HRLXAIAgentTools.requestLeave: ' + e);
return { ok: false, error: 'exception' };
}
},
checkPolicy: function(question) {
var profile = gs.getProperty(this.SCOPE + '.ai_search.profile', 'hrlx_kb_profile');
try {
var sn = new sn_ais.AISearch();
var results = sn.search(profile, question, { limit: 5, genai: true });
return {
answer: results.genai_answer || '',
citations: results.citations || []
};
} catch (e) {
return { answer: '', citations: [], error: String(e) };
}
},
escalate: function(userId, summary) {
var emp = this._getEmployeeByUser(userId);
if (!emp) return { ok: false, error: 'employee_not_found' };
var hr = new GlideRecord('sn_hr_core_case');
if (!hr.isValid()) {
// Fallback: create audit + notify
this._audit('ai_escalate_no_hrsd', userId, { summary: summary });
return { ok: true, case_id: null, note: 'HRSD not installed — audit only' };
}
hr.initialize();
hr.opened_for = userId;
hr.short_description = 'AI escalation: ' + (summary || '').substring(0, 80);
hr.description = summary;
hr.subject_person = userId;
var sysId = hr.insert();
this._audit('ai_escalate', sysId, { summary: summary });
return { ok: true, case_id: sysId };
},
getMyRequests: function(userId, days) {
var emp = this._getEmployeeByUser(userId);
if (!emp) return [];
var since = new GlideDateTime();
since.addDaysLocalTime(-1 * (days || 90));
var gr = new GlideRecord(this.T_REQ);
gr.addQuery('u_employee_ref', emp.getUniqueValue());
gr.addQuery('sys_created_on', '>=', since);
gr.orderByDesc('sys_created_on');
gr.query();
var out = [];
while (gr.next()) {
out.push({
id: gr.getUniqueValue(),
number: String(gr.u_number),
type: String(gr.u_leave_type),
start: String(gr.u_start_date),
end: String(gr.u_end_date),
state: String(gr.u_state)
});
}
return out;
},
cancelRequest: function(userId, requestId) {
var gr = new GlideRecord(this.T_REQ);
if (!gr.get(requestId)) return { ok: false, error: 'not_found' };
if (gr.u_requested_for_user != userId) return { ok: false, error: 'not_owner' };
if (gr.u_state != 'submitted') return { ok: false, error: 'not_cancellable' };
gr.u_state = 'cancelled';
gr.update();
this._audit('ai_cancel_request', requestId, { user: userId });
return { ok: true };
},
_audit: function(action, recordId, payload) {
var a = new GlideRecord(this.T_AUD);
a.initialize();
a.u_action = action;
a.u_record = recordId;
a.u_payload = JSON.stringify(payload || {});
a.u_actor = gs.getUserID();
a.u_source = 'ai_agent';
a.insert();
},
type: 'HRLXAIAgentTools'
};
Step 3.2: Define tools in AI Agent Studio
URL: {BASE}/sn_aia_tool_list.do
Tool 1 — GetMyBalance
| Field | Value |
|---|---|
| Name | GetMyBalance |
| Description | Returns current user’s leave balance by type (annual, sick, parental, unpaid) |
| Implementation | Script Include |
| Script Include | HRLXAIAgentTools |
| Method | getBalance |
| Input | user_id (string) — auto-map from session |
| Output | JSON object with balances |
Tool 2 — RequestLeave
| Input field | Type | Required |
|---|---|---|
| user_id | string | yes |
| type | string (enum) | yes |
| start_date | date (YYYY-MM-DD) | yes |
| end_date | date (YYYY-MM-DD) | yes |
| reason | string | no |
Confirmation gate: Enable “Require user confirmation” for this tool in Agent Studio.
Tool 3 — CheckPolicy
| Field | Value |
|---|---|
| Implementation | AI Search Profile OR Script |
| Profile | hrlx_kb_profile |
| Input | question (string) |
Tool 4 — GetMyRequests
| Method | getMyRequests |
| Input | user_id, days (default 90) |
Tool 5 — CancelRequest
| Method | cancelRequest |
| Input | user_id, request_id |
Tool 6 — EscalateToHR
| Method | escalate |
| Input | user_id, summary |
Step 3.3: Create agent “HR Leave Assistant”
URL: {BASE}/sn_aia_agent_list.do
| Field | Value |
|---|---|
| Name | HR Leave Assistant |
| Application | x_786686_hr_leave |
| Model | now-llm-pro-v1 |
| LLM Provider | Now LLM Service |
| Status | Draft → Active |
System prompt:
You are "HR Leave Assistant" for employees of this company.
CAPABILITIES (use tools):
- GetMyBalance: show remaining leave days
- RequestLeave: submit a new request (ALWAYS confirm dates/type first)
- CheckPolicy: answer policy questions from official KB (cite sources)
- GetMyRequests: list recent requests
- CancelRequest: cancel a submitted request owned by the user
- EscalateToHR: create HR case when user is frustrated or issue is complex
RULES:
1. Identity comes from session — never ask for employee ID number.
2. NEVER reveal another employee's data.
3. Out of scope → "I can only help with leave. Contact HR at hr@company.com."
4. Match user language (English or Arabic).
5. Before RequestLeave: summarize dates, type, days count, ask "Shall I submit?"
6. If tool returns error insufficient_balance, explain clearly with numbers.
7. For medical/sick policy, remind user a certificate may be required.
STYLE: concise, friendly, professional. Use bullet lists for balances.
Agent JSON export reference:
{
"name": "HR Leave Assistant",
"scope": "x_786686_hr_leave",
"model": "now-llm-pro-v1",
"tools": ["GetMyBalance", "RequestLeave", "CheckPolicy", "GetMyRequests", "CancelRequest", "EscalateToHR"],
"knowledge_sources": [{ "type": "ai_search_profile", "name": "hrlx_kb_profile" }],
"triggers": [
{ "type": "chat", "channel": "employee_center" },
{ "type": "chat", "channel": "service_portal" }
],
"memory": { "session": true, "persistent": { "enabled": true, "max_turns": 8 } },
"guardrails": {
"pii_redaction": true,
"escalation_keywords": ["urgent", "complaint", "discrimination", "harassment", "lawyer"]
},
"audit": { "store_inputs": true, "store_outputs": true, "retention_days": 365 }
}
Step 3.4: Enable agent on Employee Center
1. Open {BASE}/now/assist/admin
2. Channels > Employee Center
3. Enable "AI Agents"
4. Add agent: HR Leave Assistant
5. Position: HR Leave category in chat launcher
6. Save
7. Test: {BASE}/esc → open chat → "How many annual days do I have?"
Step 3.5: Test agent (10 test utterances)
| # | User says | Expected tool | Pass criteria |
|---|---|---|---|
| 1 | “my balance” | GetMyBalance | Shows 4 types |
| 2 | “request 3 days annual next Mon-Wed” | RequestLeave (after confirm) | Creates req |
| 3 | “can I carry over leave?” | CheckPolicy | Cites KB |
| 4 | “show my last requests” | GetMyRequests | Lists records |
| 5 | “cancel HRLX0001234” | CancelRequest | State=cancelled |
| 6 | “what is parental leave?” | CheckPolicy | Policy answer |
| 7 | Arabic: “كم يوم إجازة عندي؟” | GetMyBalance | Arabic response |
| 8 | “I want 100 days annual” | RequestLeave | insufficient_balance error |
| 9 | “this is discrimination!” | EscalateToHR | Case created |
| 10 | “what’s the weather?” | none | Out of scope message |
Part 4 — BYO LLM (Claude / GPT)
Step 4.1: When to use BYO LLM
| Scenario | Recommendation |
|---|---|
| Default Now Assist skills | Now LLM Service |
| Regulated data residency | Azure OpenAI in your region |
| Existing Anthropic/OpenAI contract | BYO |
| Cost optimization at scale | gpt-4o-mini for classify, Claude for complex |
Step 4.2: Configure Anthropic provider
URL: {BASE}/sys_one_extend_provider_config_list.do
1. New > Provider type: Anthropic (or Generic REST)
2. Name: hrlx_anthropic
3. API endpoint: https://api.anthropic.com/v1/messages
4. Credential: link to Generic API Key credential (x-api-key)
5. Default model: claude-sonnet-4-5
6. Test connection
7. Per skill: LLM Provider = hrlx_anthropic
Step 4.3: REST Message — Claude_API
URL: {BASE}/sys_rest_message_list.do
| Header | Value |
|---|---|
| x-api-key | ${x_786686_hr_leave.llm.anthropic.api_key} |
| anthropic-version | 2023-06-01 |
| content-type | application/json |
Body template:
{
"model": "${model}",
"max_tokens": ${max_tokens},
"system": "${system}",
"messages": [{ "role": "user", "content": "${user_message}" }]
}
Step 4.4: Script Include — HRLXAIProvider (fallback chain)
var HRLXAIProvider = Class.create();
HRLXAIProvider.prototype = {
initialize: function() {
this.SCOPE = 'x_786686_hr_leave';
this.TIMEOUT_MS = parseInt(gs.getProperty(this.SCOPE + '.llm.timeout_ms', '15000'), 10);
},
_prop: function(name, def) { return gs.getProperty(this.SCOPE + '.' + name, def); },
_budgetCheck: function(provider) {
var budget = parseFloat(this._prop('llm.daily_budget_usd', '10'));
var key = 'llm.spent.' + provider + '.' + new GlideDateTime().getDate().getDisplayValue();
return parseFloat(gs.getProperty(this.SCOPE + '.' + key, '0')) < budget;
},
askClaude: function(prompt, model, maxTokens, system) {
if (!this._budgetCheck('anthropic')) return { error: 'budget_exceeded' };
try {
var rm = new sn_ws.RESTMessageV2('Claude_API', 'post');
rm.setHttpTimeout(this.TIMEOUT_MS);
rm.setStringParameterNoEscape('model', model || this._prop('llm.anthropic.model', 'claude-sonnet-4-5'));
rm.setStringParameterNoEscape('max_tokens', String(maxTokens || 400));
rm.setStringParameterNoEscape('system', system || 'You are a helpful HR assistant.');
rm.setStringParameterNoEscape('user_message', prompt);
var resp = rm.execute();
if (resp.getStatusCode() >= 400) return { error: 'http_' + resp.getStatusCode() };
var json = JSON.parse(resp.getBody());
var text = '';
(json.content || []).forEach(function(c) { if (c.type === 'text') text += c.text; });
return { text: text, usage: json.usage || {} };
} catch (e) { return { error: 'exception', message: String(e) }; }
},
askWithFallback: function(prompt, options) {
options = options || {};
var chain = options.chain || ['anthropic', 'openai', 'now_llm'];
for (var i = 0; i < chain.length; i++) {
var res = (chain[i] === 'anthropic') ? this.askClaude(prompt, options.model, options.max_tokens, options.system) : null;
if (res && !res.error && res.text) return res;
}
return { error: 'all_providers_failed' };
},
type: 'HRLXAIProvider'
};
Step 4.5: HRLXRagService (custom RAG)
var HRLXRagService = Class.create();
HRLXRagService.prototype = {
initialize: function() {
this.PROVIDER = new HRLXAIProvider();
this.PROFILE = gs.getProperty('x_786686_hr_leave.ai_search.profile', 'hrlx_kb_profile');
},
answer: function(question) {
var sn = new sn_ais.AISearch();
var res = sn.search(this.PROFILE, question, { limit: 5, genai: true, min_similarity: 0.65 });
if (res.genai_answer) return { text: res.genai_answer, citations: res.citations || [] };
return { text: 'I could not find an answer in HR Leave policies.', citations: [] };
},
type: 'HRLXRagService'
};
Part 5 — AI Search + GenAI Search
5.1 AI Search vs Zing
| Feature | Zing | AI Search |
|---|---|---|
| Vector search | No | Yes (RaptorDB) |
| GenAI answers | No | Yes |
| Re-ranking | Basic | ML re-ranker |
| Multi-source profiles | Limited | Native |
Step 5.1: Create search source — hrlx_kb
URL: {BASE}/ais_search_source_list.do
1. New Search Source
2. Name: hrlx_kb
3. Table: kb_knowledge
4. Condition: topic=HR Leave^workflow_state=published^active=true
5. Indexed fields:
- title (boost 2.0)
- short_description (boost 1.5)
- text (boost 1.0)
- meta (boost 0.5)
6. Index transform script (PII mask):
(function transform(record, indexed) {
indexed.title = record.short_description + '';
var body = (record.text || '') + '';
indexed.body = body.replace(/\b(SSN|PASSPORT|MRN|diagnosis)\b[: ]?\S+/gi, '[REDACTED]');
indexed.u_kb_number = record.number + '';
return indexed;
})(record, indexed);
7. Save → Full Index (wait for completion)
8. Verify: AI Search Admin > Index Status = Green
Step 5.2: Create source — hrlx_policy_attachments
| Field | Value |
|---|---|
| Name | hrlx_policy_attachments |
| Table | kb_knowledge (attachment text extraction) |
| Condition | Same as hrlx_kb |
| Enable | Attachment indexing |
Step 5.3: Create profile — hrlx_kb_profile
URL: {BASE}/ais_search_profile_list.do
| Setting | Value |
|---|---|
| Name | hrlx_kb_profile |
| Sources | hrlx_kb, hrlx_policy_attachments |
| GenAI Search | ON |
| LLM | Now LLM Service |
| Max chunks | 5 |
| Min similarity | 0.65 |
| Chunk size | 800 |
| Chunk overlap | 100 |
| Refusal message | “I cannot find the answer in HR Leave policies. Contact HR.” |
| Citation format | KB number + title |
Step 5.4: Create search application (Portal)
URL: {BASE}/ais_search_application_list.do
1. New Application: hrlx_portal_search
2. Profile: hrlx_kb_profile
3. UI: Service Portal
4. Placeholder: "Ask about leave policy, sick days, carry-over..."
5. Enable GenAI answer panel + citations
6. Link to page: {BASE}/sp?id=hrlx_policy_search
Widget HTML (Service Portal):
<div class="hrlx-aisearch">
<sp-search
profile="hrlx_kb_profile"
placeholder="Ask about leave policy"
show-genai-answer="true"
show-citations="true">
</sp-search>
</div>
Step 5.5: Boost rules & synonyms
| Type | Example |
|---|---|
| Synonym | “PTO” = “annual leave” = “vacation” |
| Synonym | “sick” = “medical” = “illness” |
| Boost | u_leave_type field match × 1.3 |
| Filter facet | country, leave_type |
URL for synonyms: {BASE}/ais_dictionary_term_list.do
Step 5.6: Search analytics dashboard
Build PA indicators from:
| Table | Metric |
|---|---|
| sys_aisearch_query_log | Queries/day, zero-result rate |
| sys_aisearch_click_log | Click-through rate |
| sys_aisearch_genai_log | GenAI answer rate, citation count |
Target: zero-result rate < 5% after 30 days.
Part 6 — Predictive Intelligence
6.1 Solution types for HR Leave
| Type | HR Leave use case | Output field |
|---|---|---|
| Classification | Approved within 24h? | u_predicted_approval_24h |
| Regression | Predict duration days | u_predicted_duration |
| Similarity | Find similar past requests | (UI Action link) |
| Anomaly | Unusual sick-leave pattern | u_anomaly_score |
Step 6.1: Add ML output fields to req_v2
URL: {BASE}/sys_dictionary_list.do?sysparm_query=name=x_786686_hr_leave_req_v2
| Column | Type | Label |
|---|---|---|
| u_predicted_approval_24h | string (40) | Predicted 24h Approval |
| u_predicted_confidence_24h | decimal | Prediction Confidence |
| u_predicted_duration | decimal | Predicted Duration |
| u_anomaly_score | decimal | Anomaly Score |
| u_approved_within_24h | string | Actual 24h Approval (training label) |
Step 6.2: Backfill training label (one-time script)
Run after you have historical approvals:
var gr = new GlideRecord('x_786686_hr_leave_req_v2');
gr.addQuery('u_state', 'IN', 'approved,rejected');
gr.query();
while (gr.next()) {
var created = new GlideDateTime(gr.sys_created_on);
var updated = new GlideDateTime(gr.sys_updated_on);
var hours = (updated.getNumericValue() - created.getNumericValue()) / 3600000;
var approved = gr.u_state == 'approved' && hours <= 24;
gr.u_approved_within_24h = approved ? 'yes' : 'no';
gr.setWorkflow(false);
gr.update();
}
gs.info('Backfill complete');
Minimum training rows: ≥ 1,000 historical requests (≥ 100 per class for classification).
Step 6.3: Create classification solution — hrlx_predict_approval_24h
URL: {BASE}/ml_solution_definition_list.do
1. New Solution Definition
2. Name: hrlx_predict_approval_24h
3. Table: x_786686_hr_leave_req_v2
4. Solution type: Classification
5. Input fields:
- u_leave_type
- u_days_count
- u_department (from employee)
- u_employee_tenure_days (calculated field or BR)
- day_of_week (derived from u_start_date)
- u_has_reason (boolean: u_reason length > 5)
6. Output field: u_approved_within_24h
7. Prediction fields: u_predicted_approval_24h, u_predicted_confidence_24h
8. Training window: last 18 months
9. Exclusion: u_state=draft
10. Click Train → wait (15–60 min)
11. Review accuracy report: target ≥ 0.80 accuracy, F1 ≥ 0.65 minority class
12. Activate solution
Step 6.4: Business Rule — predict on insert
(function executeRule(current, previous) {
if (current.u_predicted_approval_24h) return;
try {
var ml = new sn_ml.SolutionStore().get('hrlx_predict_approval_24h');
if (!ml || !ml.isActive()) return;
var startDt = new GlideDateTime(current.u_start_date);
var input = {
u_leave_type: String(current.u_leave_type),
u_days_count: parseInt(current.u_days_count || '0', 10),
u_department: String(current.u_department || ''),
u_employee_tenure_days: parseInt(current.u_employee_tenure_days || '0', 10),
day_of_week: startDt.getDayOfWeekUTC(),
u_has_reason: (current.u_reason && current.u_reason.length > 5) ? 1 : 0
};
var pred = ml.predict([input]);
if (pred && pred[0]) {
current.u_predicted_approval_24h = pred[0].predicted_value;
current.u_predicted_confidence_24h = pred[0].confidence;
}
} catch (e) {
gs.warn('PI predict failed: ' + e);
}
})(current, previous);
| BR setting | Value |
|---|---|
| When | before insert |
| Table | x_786686_hr_leave_req_v2 |
| Order | 500 |
Step 6.5: Regression solution — hrlx_predict_duration
Same wizard, type = Regression, output = u_days_count (actual), prediction = u_predicted_duration.
Step 6.6: Solution Monitor
URL: {BASE}/ml_solution_monitor_list.do
| KPI | Threshold | Action if breached |
|---|---|---|
| Accuracy | ≥ 0.80 | Re-train |
| Drift score | < 0.20 | Investigate feature changes |
| RMSE (regression) | ≤ 1.5 days | Re-train |
| Prediction volume | track | Capacity planning |
Schedule monthly re-train via Scheduled Script Execution.
Part 7 — Virtual Agent + NLU (Full Topics & Flows)
7.1 Virtual Agent vs AI Agent — coexistence
| Aspect | Virtual Agent (VA) | AI Agent (AIA) |
|---|---|---|
| Logic | Deterministic topic blocks | LLM-planned |
| Cost | Low | Per-token |
| Best for | Slot-filling, forms | Open Q&A |
| Handoff | VA → AIA via “Transfer to AI Agent” block | AIA → VA via tool |
Recommended pattern: VA handles structured flows; fallback / policy Q&A routes to AI Agent.
Step 7.1: Create NLU model — hrlx_leave_nlu
| Field | Value |
|---|---|
| Model name | hrlx_leave_nlu |
| Language | en (add ar as second model if needed) |
| Domain | HR Leave |
| Application | x_786686_hr_leave |
Step 7.2: Intents (8 total)
| Intent | Description |
|---|---|
| greeting | Hello, hi, start |
| check_balance | Remaining leave days |
| request_leave | Book time off |
| check_policy | Policy questions |
| cancel_leave | Withdraw request |
| check_status | Status of existing request |
| speak_to_human | Live agent handoff |
| fallback | Out of scope |
Step 7.3: Entities
| Entity | Type | Values / Notes |
|---|---|---|
| leave_type | List | annual, sick, parental, unpaid |
| date_range | SYS.DATE | “next Monday”, “June 5 to 10” |
| days_count | Number | “3 days”, “two weeks” |
| request_number | Regex | HRLX\d+ |
| leave_type_ar | List | سنوية, مرضية, أمومة |
Step 7.4: Utterances — check_balance (30 examples)
how many leave days do I have
what is my annual leave balance
show my remaining vacation days
how much PTO do I have left
check my sick leave balance
remaining parental leave
how many days off can I take
what's my leave balance
do I have any unpaid leave left
show balance for annual leave
how many annual days remaining
tell me my vacation balance
what leave do I have available
check my time off balance
how much annual leave is left
show me sick days remaining
balance inquiry for leave
how many days of leave remain
what is my current leave entitlement
remaining days for sick leave
how much leave have I got left
annual balance please
sick leave days left
parental leave balance check
unpaid leave remaining
how many holidays can I still take
leave days available
my remaining leave
check PTO balance
how many days can I still book off
Step 7.5: Utterances — request_leave (35 examples)
I want to take leave next Monday
request 3 days annual leave
book sick leave for tomorrow
I need time off from June 5 to June 10
apply for parental leave starting next month
request vacation days
take 2 days off next week
I want to book annual leave
schedule sick leave for Friday
need unpaid leave for 5 days
book me 3 days annual starting Monday
I would like to request leave
can I take leave next Tuesday and Wednesday
request time off for a wedding
apply for 10 days annual leave in July
I need sick leave for 2 days
book parental leave from September 1
request leave for family emergency
take annual leave Dec 20 to Dec 31
I want 1 week off
schedule 4 days sick leave
request unpaid leave next month
book leave type annual for 3 days
I need to take Friday off
request half day sick leave
apply for leave starting {date_range}
book {days_count} days of {leave_type} leave
I want {leave_type} leave from {date_range}
need {days_count} days off
request leave please
take time off
book my vacation
I want to go on leave
schedule my days off
can I have next week off
Step 7.6: Utterances — check_policy (25 examples)
what is the maternity leave policy
can I carry over unused annual leave
how many sick days am I allowed per year
what is the parental leave entitlement
do I need a medical certificate for sick leave
what is the notice period for annual leave
can managers reject my leave request
what happens to unused leave at year end
is unpaid leave available
what is the maximum consecutive leave days
how does sick leave accrue
what is the leave policy for new joiners
can I take leave during probation
what documents are required for sick leave
explain the annual leave policy
how far in advance must I request leave
what is the carry forward limit
are public holidays deducted from leave
what is bereavement leave policy
can I split parental leave
what is the leave approval process
how is leave calculated for part time staff
what is the sick leave waiting period
tell me about leave encashment
what are the rules for emergency leave
Step 7.7: Utterances — cancel_leave (20 examples)
cancel my leave request
withdraw my leave application
I want to cancel HRLX0001234
cancel my upcoming vacation
withdraw the leave I submitted
delete my leave request
cancel submitted leave
I no longer need those days off
please cancel my time off request
revoke my leave request
cancel annual leave for next week
withdraw sick leave request
cancel request number {request_number}
I want to undo my leave request
cancel my pending leave
stop my leave application
cancel the leave I booked
withdraw my days off
cancel leave starting Monday
I need to cancel my vacation request
Step 7.8: Utterances — check_status (20 examples)
what is the status of my leave request
has my leave been approved
track my leave application
where is my request HRLX0001234
is my leave approved yet
check approval status
status of my vacation request
has manager approved my leave
what happened to my leave request
show status of pending leave
is my sick leave approved
check my submitted leave
approval status please
where is my annual leave request
has HR approved my leave
track request {request_number}
what state is my leave in
is my leave still pending
did my leave get rejected
update on my leave request
Step 7.9: Train and publish NLU
1. NLU Workbench > hrlx_leave_nlu
2. Add all utterances above (minimum 15 per intent; we have 20–35)
3. Annotate entities in utterances (highlight dates, types)
4. Click Train → wait for model metrics
5. Target: Intent accuracy ≥ 0.92 on test set
6. Publish model version 1.0
7. Set property x_786686_hr_leave.nlu.model = hrlx_leave_nlu
Step 7.10: Topic catalog (8 topics)
URL: {BASE}/now/virtual-agent/designer
| Topic | Trigger | NLU intent |
|---|---|---|
| HRLX Greeting | greeting | greeting |
| HRLX Check Balance | check_balance | check_balance |
| HRLX Request Leave | request_leave | request_leave |
| HRLX Check Policy | check_policy | check_policy OR transfer to AI Agent |
| HRLX Cancel Leave | cancel_leave | cancel_leave |
| HRLX Check Status | check_status | check_status |
| HRLX Live Agent | speak_to_human | speak_to_human |
| HRLX Fallback | no match | fallback |
TOPIC FLOW 1 — HRLX Check Balance (block-by-block)
Topic: HRLX Check Balance
Channel: Employee Center, Service Portal
NLU: check_balance (confidence ≥ 0.7)
BLOCK 1 — Script Action "Fetch Balance"
Script:
(function execute() {
var userId = vaSystem.getUserId();
var tools = new x_786686_hr_leave.HRLXAIAgentTools();
var bal = tools.getBalance(userId);
if (bal.error) {
vaInputs.has_error = true;
vaInputs.error_msg = 'Employee profile not found. Contact HR.';
return;
}
vaInputs.has_error = false;
vaInputs.balance_text =
'Annual: ' + bal.annual + ' days\n' +
'Sick: ' + bal.sick + ' days\n' +
'Parental: ' + bal.parental + ' days\n' +
'Unpaid: ' + bal.unpaid + ' days';
})();
BLOCK 2 — Decision
Condition: has_error == true
True → Bot Message: "${error_msg}" → End
False → continue
BLOCK 3 — Bot Message
Text: "Here is your current leave balance:\n${balance_text}"
BLOCK 4 — Quick Reply
Question: "Would you like to request leave?"
Options: Yes | No
BLOCK 5 — Branch
Yes → Link to Topic "HRLX Request Leave"
No → Bot Message "Have a great day!" → End
TOPIC FLOW 2 — HRLX Request Leave (slot filling)
Topic: HRLX Request Leave
NLU: request_leave
BLOCK 1 — Collect leave_type (if not in NLU entities)
Prompt: "Which type of leave? (annual / sick / parental / unpaid)"
Slot: leave_type
Validation: must be in [annual, sick, parental, unpaid]
Max retries: 2 → on fail go to Fallback
BLOCK 2 — Collect start_date
Prompt: "What is the start date? (YYYY-MM-DD or 'next Monday')"
Slot: start_date
Validation: date >= today
BLOCK 3 — Collect end_date
Prompt: "What is the end date?"
Slot: end_date
Validation: end_date >= start_date
BLOCK 4 — Collect reason (optional)
Prompt: "Reason for leave? (optional — press Skip)"
Slot: reason
Required: false
BLOCK 5 — Script Action "Calculate Days Preview"
(function execute() {
var start = new GlideDateTime(vaVars.start_date);
var end = new GlideDateTime(vaVars.end_date);
var days = Math.ceil((end.getNumericValue() - start.getNumericValue()) / 86400000) + 1;
vaInputs.preview_days = days;
var tools = new x_786686_hr_leave.HRLXAIAgentTools();
var bal = tools.getBalance(vaSystem.getUserId());
vaInputs.enough = (bal[vaVars.leave_type] || 0) >= days;
vaInputs.available = bal[vaVars.leave_type] || 0;
})();
BLOCK 6 — Decision insufficient balance
Condition: enough == false
True → Message: "You need ${preview_days} days but only have ${available}. Choose shorter dates."
→ Go to BLOCK 2 (re-collect dates)
False → continue
BLOCK 7 — Confirmation
Message: "Please confirm:\nType: ${leave_type}\nFrom: ${start_date} To: ${end_date}\nDays: ${preview_days}\nReason: ${reason}"
Quick Reply: Confirm | Cancel
BLOCK 8 — Branch Cancel → End polite
BLOCK 9 — Script Action "Submit Request"
(function execute() {
var tools = new x_786686_hr_leave.HRLXAIAgentTools();
var res = tools.requestLeave({
user_id: vaSystem.getUserId(),
type: vaVars.leave_type,
start_date: vaVars.start_date,
end_date: vaVars.end_date,
reason: vaVars.reason || ''
});
vaInputs.ok = !!res.ok;
vaInputs.req_number = res.number || '';
vaInputs.err = res.error || '';
})();
BLOCK 10 — Decision ok
True → "Your leave request ${req_number} has been submitted for approval."
False → "Sorry, submission failed: ${err}. Try again or contact HR."
→ End
TOPIC FLOW 3 — HRLX Check Policy
BLOCK 1 — Decision: use AI Agent for open questions
If utterance length > 50 chars OR contains "?"
→ Transfer to AI Agent "HR Leave Assistant" with context
Else
→ Script Action CheckPolicy with vaInputs.last_utterance
BLOCK 2 — Display answer + citations
BLOCK 3 — "Was this helpful?" Yes/No → log to analytics
TOPIC FLOW 4 — HRLX Cancel Leave
BLOCK 1 — Collect request_number (or pick from list)
Script Action: getMyRequests → build choice list
BLOCK 2 — Confirm cancellation
BLOCK 3 — Script Action cancelRequest
BLOCK 4 — Success/fail message
TOPIC FLOW 5 — HRLX Live Agent Handoff
BLOCK 1 — Collect issue summary (optional)
BLOCK 2 — Live Agent Queue
Queue: HR Leave Support
Pre-chat fields: user_id, last_intent, last_5_messages
BLOCK 3 — Fallback if no agent available
Script Action escalate()
Message: "An HR partner will contact you. Case reference logged."
Step 7.11: Enable VA on channels
1. Virtual Agent > Administration > Channels
2. Employee Center: ON, Bot name from x_786686_hr_leave.va.bot_name
3. Service Portal: add Virtual Agent widget to sp_page
4. Topic recommendations: enable HR Leave topics
5. Test: {BASE}/esc
Step 7.12: VA analytics KPIs
| KPI | Target | Source |
|---|---|---|
| Containment rate | ≥ 70% | sys_cs_conversation |
| Avg turns | ≤ 6 | sys_cs_message count |
| Intent recognition | ≥ 90% | nlu_inference_result |
| Handoff rate | ≤ 15% | live agent transfers |
Part 8 — Document Intelligence (IDP)
8.1 Use case — medical certificate for sick leave
When u_leave_type = sick and u_days_count > 2, require medical certificate attachment. IDP extracts fields automatically.
| Extracted field | Maps to column |
|---|---|
| patient_name | (validate vs employee) |
| certificate_date | u_cert_date |
| days_recommended | u_cert_days |
| doctor_name | u_cert_doctor |
| clinic_name | u_cert_clinic |
| condition_summary | u_cert_summary (masked in UI) |
Step 8.1: Create IDP template — hrlx_medical_cert
URL: {BASE}/sn_idp_template_list.do
1. New Template
2. Name: hrlx_medical_cert
3. Document type: Medical Certificate
4. Upload 8–12 sample PDFs (anonymized):
- 3 hospital letterheads
- 3 clinic formats
- 2 Arabic certificates
- 2 poor-scan quality (for robustness)
5. For each sample, annotate bounding boxes:
- patient_name
- certificate_date
- doctor_name
- days_recommended
- clinic_name
- condition_summary
6. Save annotations
Step 8.2: Train extraction model
URL: {BASE}/sn_idp_model_list.do
1. New Model → Template: hrlx_medical_cert
2. Train/Test split: 80/20
3. Click Train (30–90 minutes)
4. Review per-field F1 scores (target ≥ 0.90 each)
5. If condition_summary F1 low: add more samples or mark optional
6. Activate model v1.0
7. Set property x_786686_hr_leave.idp.template = hrlx_medical_cert
Step 8.3: Flow — run IDP on attachment
Flow: HRLX IDP Medical Certificate
Trigger: Attachment added to x_786686_hr_leave_req_v2
Condition: u_leave_type=sick^u_days_count>2
Step 1 — Get attachment sys_id
Step 2 — Action: Document Intelligence Extract
Template: hrlx_medical_cert
Input: attachment_sys_id
Step 3 — Script (update request):
(function(inputs, outputs) {
var fields = inputs.extraction_result.fields || {};
var req = new GlideRecord('x_786686_hr_leave_req_v2');
if (!req.get(inputs.request_sys_id)) return;
req.u_cert_date = fields.certificate_date || '';
req.u_cert_days = fields.days_recommended || '';
req.u_cert_doctor = fields.doctor_name || '';
req.u_cert_clinic = fields.clinic_name || '';
// Mask sensitive summary for display
req.u_cert_summary = (fields.condition_summary || '').substring(0, 200);
req.update();
var aud = new GlideRecord('x_786686_hr_leave_audit_v2');
aud.initialize();
aud.u_action = 'idp_extract_cert';
aud.u_record = req.getUniqueValue();
aud.u_payload = JSON.stringify(fields);
aud.u_actor = gs.getUserID();
aud.u_source = 'idp';
aud.insert();
})(inputs, outputs);
Step 4 — Decision: days_recommended < u_days_count → notify HR for review
Step 5 — Email employee if extraction failed (manual upload retry)
Step 8.4: Standalone IDP test
1. IDP Admin > Test Extraction
2. Upload new PDF not in training set
3. Verify all fields extracted
4. Latency target: ≤ 8 seconds per document
Step 8.5: IDP metrics dashboard
| Metric | Target |
|---|---|
| Field-level F1 | ≥ 0.90 |
| Document success rate | ≥ 0.95 |
| Avg latency | ≤ 8s |
| Manual correction rate | ≤ 10% |
Part 9 — Workflow Studio AI Actions
Step 9.1: Built-in actions to reuse
| Platform action | HR Leave adaptation |
|---|---|
| Summarize Record | Summarize u_reason on approval |
| Categorize Text | Classify leave type from reason |
| Generate Email Response | Manager approval/rejection email |
| Sentiment Analysis | Route frustrated requests |
| Extract Entities | Parse dates from free-text reason |
Step 9.2: Custom action — HRLX Summarize Leave History
URL: {BASE}/now/flow-designer → Custom Actions
1. New Custom Action: HRLX Summarize Leave History
2. Application: HR Leave
3. Inputs:
- employee_ref (Reference: x_786686_hr_leave_employee_v2)
- lookback_days (Integer, default 365)
4. Outputs:
- summary_text (String)
5. Steps:
a. Look Up Records: x_786686_hr_leave_req_v2
Query: u_employee_ref = employee_ref AND sys_created_on >= lookback
b. Transform Data: build history_text lines
c. Now Assist Skill: hrlx_summarize_history
d. Return summary_text
6. Guardrails: PII redaction ON, max_tokens 250
Step 9.3: Flow — weekly manager digest
Trigger: Scheduled weekly Monday 08:00
For Each: managers with pending approvals
Action: HRLX Summarize Leave History (direct reports, 90 days)
Action: Send Email with summary + link to workspace
Step 9.4: Flow — auto-classify on submit
Trigger: Record created x_786686_hr_leave_req_v2, u_state=submitted
Action: Now Assist Skill hrlx_classify_leave_type
Decision: if output != u_leave_type → set u_ai_suggested_type + notify HR
Action: PI predict approval
Decision: if confidence < 0.6 → route to senior manager
Part 10 — Now Assist for Developers
Step 10.1: Generate Script Include from prompt
In Studio ({BASE}/$studio.do):
Prompt:
"Create Script Include HRLXLeaveValidation in scope x_786686_hr_leave.
Methods: validateDates(start, end), validateBalance(userId, type, days).
Use GlideRecord on x_786686_hr_leave_bal_v2. Return {ok, error} objects.
No client callable. Include null checks."
Review → Tab accept → add audit comment → ATF test.
Step 10.2: Inline shortcuts
| Action | Keys |
|---|---|
| Accept suggestion | Tab |
| Partial accept | Ctrl+Right |
| Multiline panel | Ctrl+Enter |
| Reject | Esc |
Step 10.3: Generate ATF from Script Include
1. Open HRLXAIAgentTools in Studio
2. Now Assist > Generate Tests
3. Select: getBalance, requestLeave, cancelRequest
4. Save tests under sys_atf_test scoped to HR Leave
5. Run Test Runner: {BASE}/sys_atf_test_runner.do
Step 10.4: Code review checklist for AI-generated code
[ ] No hard-coded sys_ids
[ ] Scoped table names only (x_786686_hr_leave_*)
[ ] ACL-safe (respects current user)
[ ] No gs.print in production code
[ ] Error handling with gs.error/gs.warn
[ ] Human reviewer name in header comment
Part 11 — End-to-End HR Leave AI Example
Scenario: Employee submits sick leave via chat with certificate
Actors: Employee (ESS), HR Leave Assistant (AIA), VA topics, IDP, PI, Flow
Step 1 — Employee opens {BASE}/esc
Step 2 — "I need sick leave Thu-Fri" → VA Topic Request Leave OR AIA
Step 3 — Slot fill: type=sick, dates confirmed
Step 4 — AIA calls RequestLeave → req HRLX001234 created
Step 5 — Employee uploads medical PDF on form
Step 6 — Flow HRLX IDP Medical Certificate runs → fields extracted
Step 7 — BR runs PI → u_predicted_approval_24h = yes (0.87)
Step 8 — Manager opens record → UI Action Summarize reason (AI)
Step 9 — Manager approves → Flow sends hrlx_draft_manager_email (optional)
Step 10 — Audit rows in x_786686_hr_leave_audit_v2 for ai_request_leave, idp_extract_cert
Verification queries
// All AI-touched requests last 7 days
var gr = new GlideRecord('x_786686_hr_leave_req_v2');
gr.addQuery('u_source', 'ai_agent');
gr.addQuery('sys_created_on', '>=', gs.daysAgoStart(7));
gr.query();
gs.info('AI agent requests: ' + gr.getRowCount());
var aud = new GlideRecord('x_786686_hr_leave_audit_v2');
aud.addQuery('u_source', 'IN', 'ai_agent,idp');
aud.addQuery('sys_created_on', '>=', gs.daysAgoStart(7));
aud.query();
gs.info('AI audit rows: ' + aud.getRowCount());
Part 12 — AI Governance & Safety
Step 12.1: Create AI policy — no_raw_medical_text
URL: {BASE}/now/ai/policy
{
"policy": "hrlx_no_raw_medical_text",
"scope": "x_786686_hr_leave",
"rules": [
{
"id": "redact_diagnosis",
"applies_to": ["input", "output"],
"regex": "(?i)(diagnosis|condition|illness|medication):\\s*[^\\n]+",
"action": "redact",
"replacement": "[REDACTED-MEDICAL]"
},
{
"id": "block_external_emails",
"applies_to": ["output"],
"regex": "\\b[A-Z0-9._%+-]+@(?!company\\.com)[A-Z0-9.-]+\\.[A-Z]{2,}\\b",
"action": "block"
}
]
}
Step 12.2: Human-in-the-loop matrix
| AI action | Human checkpoint |
|---|---|
| RequestLeave | User confirms in chat |
| Auto-approve (never) | Manager always approves |
| PI low confidence | Route to senior manager |
| IDP mismatch days | HR manual review |
| Generated email | Manager edits before send |
Step 12.3: Compliance notes
| Regulation | Requirement |
|---|---|
| GDPR | Right to erasure on sn_aia_audit / sys_one_extend_audit |
| EU AI Act | HR decisions = high-risk → transparency + human oversight |
| HIPAA | Encrypt certificate attachments, restrict ACL |
| PDPL (GCC) | Data residency → pick LLM region |
Step 12.4: Audit retention
Configure 365-day retention on sn_aia_audit and sys_one_extend_audit. Domain audit x_786686_hr_leave_audit_v2 retained per HR policy (typically 7 years).
Part 13 — Performance & Cost Optimization
Step 13.1: Response cache table
Create table x_786686_hr_leave_ai_cache (hash, skill, response, expires_at).
var HRLXAICache = Class.create();
HRLXAICache.prototype = {
initialize: function() { this.TABLE = 'x_786686_hr_leave_ai_cache'; },
_hash: function(s) { return new GlideDigest().getSHA256Base64(s); },
get: function(skill, input) {
var gr = new GlideRecord(this.TABLE);
gr.addQuery('u_hash', this._hash(skill + '|' + input));
gr.addQuery('u_expires_at', '>', new GlideDateTime());
gr.setLimit(1);
gr.query();
return gr.next() ? String(gr.u_response) : null;
},
put: function(skill, input, response, ttlMin) {
var gr = new GlideRecord(this.TABLE);
gr.initialize();
gr.u_hash = this._hash(skill + '|' + input);
gr.u_skill = skill;
gr.u_response = response;
var exp = new GlideDateTime();
exp.addSeconds(60 * (ttlMin || 60));
gr.u_expires_at = exp;
gr.insert();
},
type: 'HRLXAICache'
};
Use for hrlx_classify_leave_type (deterministic input).
Step 13.2: Model right-sizing
| Task | Model |
|---|---|
| classify, sentiment | gpt-4o-mini / Now LLM Generic |
| summarize | Now LLM Generic |
| agent multi-step | now-llm-pro-v1 / claude-sonnet-4-5 |
| code gen | Now Assist for Creator |
Step 13.3: Monitoring dashboard widgets
| Widget | Source | Alert |
|---|---|---|
| Skill calls/day | sys_one_extend_audit | > 2× baseline |
| p95 latency | sys_one_extend_audit | > 3s |
| Daily LLM spend | system properties | > budget |
| Agent containment | sn_aia_execution | < 60% |
| PI drift | ml_solution_monitor | > 0.2 |
Part 14 — Hands-On Labs (5 Sessions)
Lab 1 — Now Assist Skill (45 min)
Goal: Deploy hrlx_summarize_reason with UI Action.
| Time | Step |
|------|------|
| 0–5 | Verify prerequisites (§0.2) |
| 5–15 | Create skill in Now Assist Studio (§2.2) |
| 15–25 | Test 5 samples in Playground |
| 25–35 | Create UI Action on req_v2 form |
| 35–40 | Impersonate manager → click Summarize |
| 40–45 | Check sys_one_extend_audit for log row |
Pass: Summary appears ≤ 2s, no medical detail leaked.
URL: {BASE}/now/assist/studio
Lab 2 — AI Search + GenAI (45 min)
Goal: Ask “Can I carry over annual leave?” and get cited answer.
| Time | Step |
|------|------|
| 0–10 | Create 3 KB articles (topic=HR Leave) if missing |
| 10–25 | Create source hrlx_kb + profile hrlx_kb_profile (§5) |
| 25–35 | Full index → wait green |
| 35–40 | Test in AI Search Admin preview |
| 40–45 | Add sp-search widget to portal page |
Pass: GenAI answer cites KB number.
URL: {BASE}/now/ais/admin
Lab 3 — Virtual Agent topic (60 min)
Goal: Complete balance → request leave conversation.
| Time | Step |
|------|------|
| 0–15 | Import NLU utterances for check_balance + request_leave |
| 15–25 | Train + publish hrlx_leave_nlu |
| 25–45 | Build topics per §7 block diagrams |
| 45–55 | Enable on {BASE}/esc |
| 55–60 | Run conversation test script (10 utterances) |
Pass: Request created with u_source=virtual_agent (add field if needed).
URL: {BASE}/now/virtual-agent/designer
Lab 4 — AI Agent + tools (60 min)
Goal: HR Leave Assistant submits leave after confirmation.
| Time | Step |
|------|------|
| 0–15 | Deploy HRLXAIAgentTools Script Include |
| 15–30 | Create 6 tools in Agent Studio |
| 30–45 | Create agent + system prompt |
| 45–55 | Enable on Employee Center |
| 55–60 | Test: "book 2 days annual next week" → confirm → verify req |
Pass: Record in req_v2, audit row written.
URL: {BASE}/now/aia/studio
Lab 5 — IDP + PI (90 min)
Goal: Sick leave + certificate extraction + approval prediction.
| Time | Step |
|------|------|
| 0–20 | Upload samples + annotate hrlx_medical_cert |
| 20–50 | Train IDP model (parallel: backfill PI labels) |
| 50–65 | Create Flow for attachment trigger |
| 65–80 | Train hrlx_predict_approval_24h |
| 80–90 | Submit sick leave with PDF → verify cert fields + prediction |
Pass: u_cert_date populated, u_predicted_approval_24h set.
URL: {BASE}/now/idp/admin , {BASE}/ml_solution_definition_list.do
Part 15 — Best Practices
15.1 Design
✅ One skill = one job (summarize OR classify, not both)
✅ Ground skills to specific tables with field-level mapping
✅ VA for happy-path slots; AIA for long-tail questions
✅ Always show citations for policy answers
✅ Confirm destructive tool calls (RequestLeave, CancelRequest)
✅ Log every AI action to x_786686_hr_leave_audit_v2
❌ Do not send cross-employee data to LLM
❌ Do not auto-approve leave based on PI alone
❌ Do not index u_reason without PII masking
❌ Do not skip human review on AI-generated code
15.2 Prompt engineering
✅ Separate SYSTEM (rules) from USER (data)
✅ Specify output format explicitly (JSON, single label, max words)
✅ Include refusal behavior ("If unsure, say...")
✅ Test in Arabic and English if bilingual workforce
✅ Version prompts; never edit production without version bump
15.3 Operations
✅ Weekly: review zero-result AI Search queries → add KB
✅ Monthly: review skill latency + cost dashboards
✅ Quarterly: re-train PI + NLU with new utterances
✅ Per release: run 5 labs as regression suite
✅ Maintain eval set (50 questions + expected answers) per skill
15.4 Security
✅ Mask medical text in prompts sent to external LLMs
✅ Use instance ACLs — tools run as authenticated user
✅ Budget caps on BYO LLM properties
✅ Disable x_786686_hr_leave.ai.enabled kill switch tested quarterly
Part 16 — Production Deployment Checklist
Phase A — Pre-deployment (sub-prod complete)
[ ] All 5 labs passed on sub-prod
[ ] Update Set "HR Leave AI v1.0" complete + peer reviewed
[ ] ATF tests green for HRLXAIAgentTools
[ ] Eval set ≥ 90% pass rate per skill
[ ] NLU intent accuracy ≥ 0.92
[ ] IDP field F1 ≥ 0.90
[ ] PI accuracy ≥ 0.80
[ ] AI Search zero-result rate < 10%
[ ] Agent containment ≥ 70% in UAT
[ ] Security review: PII redaction verified
[ ] Cost model approved (projected RU/month)
[ ] Rollback plan documented (disable ai.enabled property)
Phase B — Deployment day
[ ] Clone Update Set to production via DevOps/CD pipeline
[ ] Set production LLM API keys (password2 properties)
[ ] Activate plugins (if not already) — maintenance window
[ ] Now Assist Admin → enable skills for production scope
[ ] AI Search → full re-index on production
[ ] NLU model published to production instance
[ ] IDP model activated
[ ] PI solutions activated (monitor only first 24h)
[ ] Enable VA on Employee Center (feature flag gradual)
[ ] Enable AI Agent on Employee Center (pilot group first)
[ ] Verify kill switch: x_786686_hr_leave.ai.enabled = true
[ ] Smoke test: 10 utterances from §3.5 + §7
[ ] Monitor logs 2 hours: syslog, sn_aia_execution, sys_one_extend_audit
Phase C — Post-deployment (week 1)
[ ] Day 1: review all sn_aia_audit for errors
[ ] Day 2: review AI Search zero-result queries
[ ] Day 3: review VA containment + handoff rate
[ ] Day 5: review LLM spend vs budget
[ ] Day 7: stakeholder sign-off meeting
[ ] Document known issues + FAQ for HR helpdesk
[ ] Schedule monthly PI re-train
[ ] Schedule quarterly prompt review
Rollback procedure
1. Set x_786686_hr_leave.ai.enabled = false (immediate stop)
2. Disable AI Agent channel in Now Assist Admin
3. Disable VA topics (set inactive)
4. Deactivate PI solutions (optional — predictions stop)
5. Revert Update Set if Script Include defects found
6. Communicate to HR: "AI assistant temporarily offline"
Appendices
Appendix A — Key tables reference
| Area | Tables |
|---|---|
| Skills | sys_one_extend_skill, sys_one_extend_prompt_template, sys_one_extend_audit |
| Agents | sn_aia_agent, sn_aia_tool, sn_aia_execution, sn_aia_execution_step, sn_aia_audit |
| AI Search | ais_search_source, ais_search_profile, sys_aisearch_query_log, sys_aisearch_genai_log |
| PI | ml_solution_definition, ml_solution, ml_solution_monitor |
| NLU | nlu_model, nlu_intent, nlu_entity, nlu_utterance |
| VA | sys_cs_topic, sys_cs_topic_block, sys_cs_conversation, sys_cs_message |
| IDP | sn_idp_template, sn_idp_model, sn_idp_extraction_result |
| HR Leave | x_786686_hr_leave_req_v2, _bal_v2, _employee_v2, _audit_v2 |
Appendix B — Script Includes
| Script Include | Purpose |
|---|---|
| HRLXAIAgentTools | Agent/VA tools |
| HRLXAIProvider | BYO LLM + fallback |
| HRLXRagService | RAG answers |
| HRLXAICache | Response cache |
| HRLXHolidayUtil | Holiday JSON for date skill |
Appendix C — REST Messages
| Name | Endpoint |
|---|---|
| Claude_API | https://api.anthropic.com/v1/messages |
| OpenAI_API | https://api.openai.com/v1/chat/completions |
Appendix D — Roles
| Role | Use |
|---|---|
| sn_nowassist_admin | Skills |
| sn_aia.admin | Agents |
| ml_admin | Predictive Intelligence |
| ai_search_admin | AI Search |
| nlu_admin | NLU |
| sn_idp.admin | IDP |
| x_786686_hr_leave.manager | UI Actions |
| x_786686_hr_leave.hr_admin | Full HR AI access |
Appendix E — Plugin quick search terms
Now Assist, AI Agents, AI Search, Predictive Intelligence,
Document Intelligence, Virtual Agent, NLU Workbench, Now Assist for Creator
Appendix F — Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Skill returns empty | Missing grounding sys_id | Pass record in payload |
| Agent can’t call tool | ACL on Script Include | Check scope + roles |
| AI Search no results | Index not built | Full index in admin |
| NLU always fallback | Model not published | Publish + bind to topic |
| IDP low F1 | Too few samples | Add 5+ annotated PDFs |
| PI predict null | Solution inactive | Activate + check field map |
| Budget exceeded | Daily cap hit | Raise budget or wait |
| VA handoff fails | Queue empty | Configure HR queue hours |
Appendix G — Related documentation
| Doc | Topic |
|---|---|
| 01_DEVELOPMENT.md | Tables, BRs, Script Includes |
| 03_SERVICE_CATALOG.md | Record producer + catalog VA |
| 07_REPORTS_DASHBOARDS_PA.md | AI metrics dashboards |
| 08_ATF_TESTING.md | Test HRLXAIAgentTools |
| External codegen | |
| 12_INTEGRATION_FULL_GUIDE.md | REST + Table API |
End of guide. Start at Part 0 → pick your track (Skill, Agent, VA, Search, PI, IDP) → run matching Lab → complete Deployment Checklist.
للأسئلة: ابدأ من Quick Map → نفّذ Lab المناسب → راجع Troubleshooting (Appendix F).
15Scripts — copy into Studio
Original chapter title: 15 — Scripts: Copy Into Studio Only — Paste-ready script mapping — link to chapter Parts, do not duplicate huge scripts here
What you build
Nothing new. The paste-ready mapping from every script file to the chapter Part and Studio form it belongs in.
Proof it worked
You can locate the source file for any script in the app and know exactly which form it is pasted into.
Read it this way
Keep the scripts portal open beside Studio. Copying from the wrong place is the most common build error.
Visual map — Ch.15 · Scripts library
Paste from scripts/ portal into Studio
One source of truth
Development
Full visual path (all chapters): VISUAL_FROM_SCRATCH_00_16.html
Studio-only from scratch: Use AI assistants or local editors to draft code, then paste into matching ServiceNow Studio forms while Application = HR Leave.
Also open: Properties + Direct Links · Schema · Studio Create All Objects · Previous: 14 AI Agents
How script files map to chapters
Part 1 paste workflow → Part 2 mapping table (file → chapter Part). Full scripts live in linked Parts — not duplicated here.
Part 1: Paste Into Studio Instructions
Purpose
Standard workflow for copying scripts from disk or Scripts portal into Studio forms.
Navigation path
Step-by-step create
- Application picker = HR Leave (
x_786686_hr_leave). - Update Set HR Leave – Build = current.
- Open the chapter Part for the object (form fields + navigation path).
- Open matching file under
HR_Leave_App/src/OR Scripts portal. - Copy entire script — include header comment block.
- Paste into Studio Script field (Business Rule, Script Include, Record Producer, Widget server, etc.).
- Save → run that Part Verify + Demo checklist.
Do not paste into Global-scoped records. Confirm Application = HR Leave on the form before save.
Verify checklist
- No syntax errors on save.
- Customer Updates captures the script change.
Demo / test
One script at a time: paste → save → verify behavior before next file.
Part 2: Script File → Chapter Part Mapping
Purpose
Quick index: which source file maps to which chapter Part (links — not full script bodies).
| Source file (src/) | Object | Chapter · Part (Full script) |
|---|---|---|
server/script_includes/HRLXProLeaveUtilsV2.js | Script Include | 01 Part 6 |
server/script_includes/HRLXProSecurityUtilsV2.js | Script Include | 01 Part 7 |
server/business_rules/BR1_calculate_days.js | Business Rule | 01 Part 8 |
server/business_rules/BR2_on_submit_event.js | Business Rule | 01 Part 9 |
server/business_rules/BR3_audit_state_change.js | Business Rule | 01 Part 10 |
server/business_rules/BR4_on_approve_reject_event.js | Business Rule | 01 Part 11 |
client/client_scripts/CS1_validate_dates.js | Client Script | 01 Part 12 |
client/client_scripts/CS2_calc_days_glideajax.js | Client Script | 01 Part 13 |
catalog/RP_submit_leave.js | Record Producer | 03 Part 4 |
server/scheduled_jobs/annual_accrual.js | Scheduled Job | 06 Part 3 |
portal/widgets/hrlx_my_leaves/*.js | Widget client/server | 04 Part 1 |
portal/widgets/hrlx_balance/*.js | Widget client/server | 04 Part 2 |
server/script_includes/HRLXProIntegrationUtilsV2.js | Script Include | 12 Part 1 |
server/rest/inbound_scripted_rest_leave.js | REST resource | 12 Part 3 |
Browse all: Scripts portal · Inventory: SCRIPTS_INVENTORY.html
Verify checklist
- Every row opens a Part with Full script block.
Demo / test
Pick one row → paste → run linked Part demo.
Scripts chapter verify checklist
- Paste workflow followed.
- Mapping table covers BR, SI, catalog, job, portal, REST.
16Master Path — the ordered OBJ-01 → OBJ-32 build
Original chapter title: 16 — Studio From Scratch Master Path — Ordered create path 01→15 with Part anchors, master links, E2E gate
What you build
Nothing new. The single ordered sequence of all 32 objects across every chapter, in dependency order.
Proof it worked
You can rebuild the entire application on a clean instance following only this ordering.
Read it this way
Finish the day here. It is the chapter you will come back to whenever you rebuild from scratch.
Visual map — Ch.16 · Master path 01→15
Ordered build · E2E gate · joiner spine
Follow table order only
Implementation
Full visual path (all chapters): VISUAL_FROM_SCRATCH_00_16.html
Studio-only from scratch: Ordered master path linking chapters 01–15. Each chapter is self-contained; this page is the index.
Also open: Properties + Direct Links · Schema · Studio Create All Objects · Previous: 15 Scripts
Master path overview
Complete walkthrough with every script embedded: Studio Create All Objects
Every form property + suggested values: Properties + Links
Part 1: Ordered Path Table (01→15)
Purpose
Recommended build order with Part anchors — follow sequentially on a fresh PDI.
Verify checklist
| Order | Chapter | Parts | Start anchor |
|---|---|---|---|
| 1 | 01 Development | 15+ | Part 1 Application |
| 2 | 02 Administration | 6 | Part 1 Roles |
| 3 | 03 Service Catalog | 5 | Part 1 Category |
| 4 | 06 Notifications | 3 | Part 1 Events |
| 5 | 09 Modules | 4 | Part 1 Menu |
| 6 | 04 Portal | 3 | Part 1 Widget |
| 7 | 05 Workspace | 4 | Part 1 Experience |
| 8 | 07 Reports | 2 | Part 1 Report |
| 9 | 08 ATF | 4 | Part 1 Suite |
| 10 | 12 Integration | 4 | Part 1 SI |
| 11 | 10 Implementation | 4 | Part 1 Seed |
| 12 | 13 Summary | 3 | Part 1 Checklist |
| 13 | 11 Practice | 3 | Part 1 Workflow |
| 14 | 15 Scripts | 2 | Part 1 Paste |
| 15 | 14 AI (optional) | — | After core E2E gate |
Part 2: Master Links
Purpose
One-click entry points for the three master reference pages.
- Studio Create All Objects 01–16 — every Part with Full script
- Professional Properties + Direct Links — all 9 properties + URLs
- Schema and Data — u_* columns and seed values
- Scripts portal — mirror of src/ on disk
- Portal index — all chapters 01–16
Verify checklist
- All five links open from this page.
Part 3: E2E Gate
Purpose
Do not proceed to optional chapters (14 AI, advanced gaps) until this gate passes.
Prerequisites
- Ordered path rows 1–11 complete (through 10 Implementation seed + export).
Verify checklist
Run 10 Part 2 E2E Checklist — all items green.
- Update Set current — HR Leave – Build (Rule 0).
- Seed employee — Part 2: employee_v2, Part 3: Users.
- Seed balance — Part 4: bal_v2.
- Create request — Part 3: req_v2 OR Part 2: Record Producer.
- Submit — Part 15: UI Action, Part 9: BR2.
- Flow approval — Part 3: Flow.
- Audit — Part 10: BR3.
- Balance — BR3 updates bal_v2.
- Notification — Part 2: Notifications.
- Security — Part 4: ACLs.
- Portal — Part 3: hrlx_home.
- REST — Part 4: REST Explorer.
Demo / test
employee1 submit → manager1 approve → balance + audit + notification → portal home → optional REST 201.
Master path verify checklist
- Followed ordered path table through Implementation.
- Master links bookmarked.
- E2E gate passed.
Screens pack — what to capture as you go
The chapters describe Studio forms in words. Capture these eighteen screenshots on your own instance as you build; together they become an enablement deck that proves the application exists and works. Save them to assets/shots-one-day/.
| # | Filename | Capture at | Must show |
|---|---|---|---|
| 01 | 01-application.png | Chapter 01 Part 1 | The application record with scope x_786686_hr_leave |
| 02 | 02-update-set.png | Rule 0 | Your current update set, selected and in progress |
| 03 | 03-table-req.png | Chapter 01 Part 3 | The req_v2 table with its full column list |
| 04 | 04-script-include.png | Chapter 01 Part 6 | HRLXProLeaveUtilsV2 open, script visible |
| 05 | 05-business-rule.png | Chapter 01 Part 8 | BR1 with its when/condition settings |
| 06 | 06-form-calculated.png | Chapter 01 verify | A leave request form where days calculated automatically |
| 07 | 07-roles-acls.png | Chapter 02 Parts 1 & 4 | The four scoped roles and the ACL list on req_v2 |
| 08 | 08-isolation-proof.png | Chapter 02 verify | employee1's list showing only their own records |
| 09 | 09-record-producer.png | Chapter 03 Part 2 | The catalog item as an employee sees it |
| 10 | 10-portal-home.png | Chapter 04 Part 3 | hrlx_home with both widgets rendering real data |
| 11 | 11-workspace.png | Chapter 05 Part 2 | Team Approvals list as manager1 |
| 12 | 12-notification.png | Chapter 06 Part 2 | A rendered notification preview with real field values |
| 13 | 13-accrual-job.png | Chapter 06 Part 3 | The scheduled job record plus its log output |
| 14 | 14-report-dashboard.png | Chapter 07 | Leave Requests by State on the dashboard |
| 15 | 15-atf-results.png | Chapter 08 | All three tests passing in the suite result |
| 16 | 16-flow-run.png | Chapter 09 Part 3 | A completed approval flow execution |
| 17 | 17-rest-explorer.png | Chapter 12 Part 4 | A successful POST /leave with the response body |
| 18 | 18-update-set-export.png | Chapter 10 Part 3 | The completed update set ready to export |
Insert a captured screenshot anywhere in this document with:
<figure class="shot">
<img src="assets/shots-one-day/10-portal-home.png" alt="Portal home with both HR Leave widgets" style="display:block;width:100%;height:auto" />
<figcaption class="shot-cap"><strong>Portal home.</strong> employee1 sees own balance and own requests only.</figcaption>
</figure>
Blur user names, email addresses and your instance hostname before sharing any of these outside your team.
Exit checklist — you reviewed the day successfully if…
| # | Check | Chapter |
|---|---|---|
| 1 | A leave request can be created, days calculate automatically, and the state change writes an audit row. | 01 |
| 2 | For any field in chapter 01 you can state what the other choices were and why yours is right. | 01-opt |
| 3 | employee1 sees only their own requests; manager1 sees the team; hradmin sees everything. | 02 |
| 4 | A non-technical user can submit leave from the catalog and a req_v2 record appears correctly populated. | 03 |
| 5 | Logged in as employee1 the portal page shows that user's balance and only that user's requests. | 04 |
| 6 | manager1 opens the workspace and sees a Team Approvals list containing only their reports' requests. | 05 |
| 7 | Submitting a request queues the event, the notification renders with the right values, and the accrual job updates balances. | 06 |
| 8 | The report runs, respects ACLs for a non-admin viewer, and appears on the dashboard. | 07 |
| 9 | All three tests pass, and deliberately breaking BR1 makes exactly one test fail. | 08 |
| 10 | Every table has a reachable module, and the approval flow completes end to end. | 09 |
| 11 | A clean instance can be brought to a working demo using only the exported update set and the seed steps. | 10 |
| 12 | Given any object type ("I need a new client script"), you can name the chapter and the exact Studio path in under ten seconds. | 11 |
| 13 | A POST from REST Explorer creates a leave request, and an invalid payload returns a clean error instead of a stack trace. | 12 |
| 14 | You can walk the full demo from memory, using the checklist only to confirm. | 13 |
| 15 | One AI capability is active on your instance and produces a correct result on a real HR Leave record. | 14 |
| 16 | You can locate the source file for any script in the app and know exactly which form it is pasted into. | 15 |
| 17 | You can rebuild the entire application on a clean instance following only this ordering. | 16 |
| 18 | Your update set contains every object you built today and exports without errors. | 10 |
| 19 | You can walk the end-to-end demo — submit, approve, notify, report — without opening this document. | 13 |
| 20 | You wrote down the gaps you could not finish, with the chapter and Part number for each. | 13 |
What to do tomorrow. If today was the read-through, rebuild from chapter 16's ordered OBJ-01 → OBJ-32 path on a clean instance — that is the fastest way to turn reading into competence. If you finished the build, go deeper on platform AI: chapter 14 covers the features, and the AI full-demo master guide takes you through AI Agent Studio, agentic workflows, AI Control Tower, RAG, MCP, Python + LLM and the ServiceNow SDK in another 2–3 hours.