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.
Rule 0 — Before any object (Application + Update Set + Studio)
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.
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