CASE · 00 · SUMMARY

Case study

Two loaner builds

One business process, built twice on ServiceNow: first the way a System Administrator configures it, then the way a developer scripts it. Each build follows the blueprint of an exam I’m preparing for.

◂ Back to work
case · LNR / LTRTRAINING BUILD
platform
ServiceNow · Personal Developer Instance
builds
Infinity Loaner · Loaner Tracker
blueprints
CSA · CAD
role
Solo: design, build, test
year
2026
CASE · 01 · PROBLEM

The problem

TEMPORARY DEVICES, TRACKED

Employees sometimes need a temporary device: their laptop is in for repair, they are travelling, or they have just joined and their equipment has not arrived. The request has to be approved, a device has to be found and handed over, and someone has to notice when it is not returned.

I built that process twice. The first build uses the platform’s configuration tools, the way an administrator would. The second is a scoped application where the rules live in scripts, the way a developer would build it.

  • BUILD 01 · CSA

    Infinity Loaner Program

    A custom table, a catalog request form, an approval flow, a fulfilment board, a dashboard and access controls.

    Read build 1 ▸
  • BUILD 02 · CAD

    Loaner Tracker

    A scoped app with its own roles, scripted ACLs, client and server validation, GlideAjax and an outbound REST call.

    Read build 2 ▸
CASE · 02 · BUILD 1

Infinity Loaner Program

BUILD 01 · ADMIN · CSA BLUEPRINT · GLOBAL SCOPE

Data model

A Loaner Request table (u_loaner_request) that extends Task, with its own LNR numbering. Number is read-only on this table only, through a dictionary override, so Task and its other child tables are untouched.

Request form

A catalog record producer, Request a Loaner Device. A catalog client script limits the device models to the ones allowed for the request type: Standard gets the Infinity HHD, Executive gets the HHD Pro.

Approval

A Workflow Studio flow runs when a request is created: it asks for approval, then moves the request to Work in Progress, or to Closed Incomplete if it is rejected.

Fulfilment

A Visual Task Board with one lane per state; dragging a card updates the record. Notifications go out when a request is created and when it is assigned.

Reporting

An operations dashboard with totals, overdue loans, and requests by priority, device model, state and assignee.

Security

Requesters can read only the requests they opened. Only itil users can change the device model, and a Deny-Unless ACL locks a request once it is closed.

Device data

The device inventory comes in from a CSV through an import set and transform map. It coalesces on serial number, so a re-import updates existing devices instead of duplicating them.

CMDB

A device CI is related to the Email service it depends on, so the dependency view shows what a missing device affects.

▸REQUEST FORM · SERVICE PORTAL
The Request a Loaner Device form in the Service Portal, with request type Standard, device model Infinity HHD and a reason for the request
▸APPROVAL FLOW · WORKFLOW STUDIO
A Workflow Studio flow: trigger on Loaner Request created, ask for approval, then an if branch that updates the record either way
▸FULFILMENT · VISUAL TASK BOARD
A Visual Task Board with lanes Pending, Open, Work in Progress, Closed Complete, Closed Incomplete and Closed Skipped, holding loaner request cards with LNR numbers
▸REPORTING · OPERATIONS DASHBOARD
A ServiceNow dashboard for the Infinity Loaner programme: total requests and overdue counts, and charts of requests by priority, device model, state, return month and assignee
CASE · 03 · BUILD 2

Loaner Tracker

BUILD 02 · DEVELOPER · CAD BLUEPRINT · SCOPED APP

Scoped app

Its own scope and a Loaner Request table that extends Task, numbered LTR, with a Device reference to the CMDB, a loan reason and a return date.

Roles

A chain of three roles: admin contains user, which contains requester. I tested every rule with a user who holds only the user role.

Access controls

Requesters can create, read and edit; only admins can delete. A scripted write ACL allows the record’s owner or an app admin, and field ACLs open the description fields to requesters.

Validation

A UI policy makes the return date mandatory for travel loans. A client script rejects past dates as you type, and a before business rule enforces the same rule on the server.

GlideAjax

When a device is picked, the form calls a client-callable Script Include. It counts the device’s active loans with GlideAggregate and the form warns if the device is already out.

Automation

A flow, Prioritize travel loans, runs when a request is created with Travel as the loan reason and raises it to Priority 2 – High, running as the system user. The app also has a CSV import that coalesces on number, and an outbound REST message tested against a demo API.

▸THE FORM · THREE RULES AT ONCE
The Loaner Tracker form: loan reason Travel makes Return date mandatory, a blue message says the device already has one active loan, and a red message says the return date cannot be in the past
▸CLIENT · CLIENT SCRIPT · onChange ON DEVICE
function onChange(control, oldValue, newValue, isLoading, isTemplate) {
    g_form.hideFieldMsg('device', true);            // clear the last warning first
    if (isLoading || newValue === '') {
        return;
    }

    var ga = new GlideAjax('x_2103288_loaner_0.LoanerAjaxUtils');
    ga.addParam('sysparm_name', 'getActiveLoanCount');
    ga.addParam('sysparm_device_id', newValue);
    ga.addParam('sysparm_current_id', g_form.getUniqueValue());
    ga.getXMLAnswer(function(answer) {             // runs when the server replies
        if (parseInt(answer, 10) > 0) {
            g_form.showFieldMsg('device',
                'This device already has ' + answer + ' active loan(s).', 'info');
        }
    });
}
▸SERVER · SCRIPT INCLUDE · LoanerAjaxUtils · CLIENT-CALLABLE
var LoanerAjaxUtils = Class.create();
LoanerAjaxUtils.prototype = Object.extendsObject(global.AbstractAjaxProcessor, {

    getActiveLoanCount: function() {
        var deviceId  = this.getParameter('sysparm_device_id');
        var currentId = this.getParameter('sysparm_current_id');

        var ga = new GlideAggregate('x_2103288_loaner_0_loaner_request');
        ga.addQuery('device', deviceId);
        ga.addQuery('active', true);
        ga.addQuery('sys_id', '!=', currentId);   // an edited loan must not count itself
        ga.addAggregate('COUNT');
        ga.query();
        return ga.next() ? ga.getAggregate('COUNT') : '0';
    },

    type: 'LoanerAjaxUtils'
});
CASE · 04 · DEBUGGING

Three things that broke

SYMPTOM · CAUSE · FIX · LESSON

BUG 01 · BUILD 1FIXED
symptom

The renumber that said it worked

A background script renumbered 23 old requests and reported 23 updates, but every record still started with TASK.
cause
getNextObjNumberPadded() on the record took its number from the parent table’s counter, not the Loaner Request one.
fix
Named the counter explicitly with new NumberManager('u_loaner_request'), tested on one record with setLimit(1), then ran the rest.
lesson
Check the result, not the row count.
BUG 02 · BUILD 2FIXED
symptom

The date check you could walk around

A past return date was blocked on the form, but the same value saved without complaint when edited from the list.
cause
Client scripts only run on the form. List editing never loads them.
fix
A before business rule with the same check, so the server rejects the save whichever way it arrives.
lesson
Client checks are feedback; the rule itself belongs on the server.
BUG 03 · BUILD 2FIXED
symptom

Fields a requester could not edit

Requesters could write to their own requests, yet Description stayed read-only.
cause
The table extends Task, so it inherits Task’s field ACLs, and task.description requires itil.
fix
Field ACLs on the app’s own table for description and short description.
lesson
Extending a table inherits its security, not just its fields.
CASE · 05 · NEXT

What I’d do next

KNOWN GAPS

  • The closed-request ACL blocks edits but not deletes. A matching delete rule would close that gap.

  • State is not on the Infinity Loaner form, so a fulfiller has to use the board or the list to see it.

  • In Loaner Tracker the requester picks the device, and the form only warns when that device is tied to another open request. A real process would track each device’s own status (In stock, On loan, In repair), update it when a loan starts and ends, and let the fulfiller assign only devices that are in stock.

  • Most of Build 1 sits in the Default update set; only the last change was captured in a named one. Next time, a named update set from the first change.

© 2026 MAIS BILTOBUILT ON A PERSONAL DEVELOPER INSTANCE