← Script library

Business Rules

Async Notification with Complex Conditions

Send notifications asynchronously based on complex business logic, preventing delays in record save operations.

JavaScript
(function executeRule(current, previous /*null when async*/) {

  // Async Business Rules run in background after the transaction completes
  // Use for notifications, integrations, and non-critical updates
  // Note: 'previous' is null in async business rules

  try {
    // Configuration
    var notificationEnabled = gs.getProperty('custom.notifications.enabled', 'true') === 'true';

    if (!notificationEnabled) {
      gs.info('Notifications disabled via system property');
      return;
    }

    // Determine notification scenarios
    var shouldNotify = false;
    var notificationType = '';
    var recipients = [];
    var additionalInfo = {};

    // Scenario 1: High-priority incident created
    if (current.isNewRecord() && current.priority.toString() === '1') {
      shouldNotify = true;
      notificationType = 'high_priority_created';

      // Notify assignment group manager
      if (current.assignment_group) {
        var groupManager = getGroupManager(current.assignment_group.toString());
        if (groupManager) {
          recipients.push(groupManager);
        }
      }

      // Notify incident manager role
      recipients = recipients.concat(getUsersWithRole('incident_manager'));

      additionalInfo.reason = 'High-priority incident requires immediate attention';
    }

    // Scenario 2: Incident reassigned multiple times (potential issue)
    if (!current.isNewRecord()) {
      var reassignmentCount = getReassignmentCount(current.sys_id.toString());

      if (reassignmentCount >= 3) {
        shouldNotify = true;
        notificationType = 'excessive_reassignments';

        // Notify service delivery manager
        recipients = recipients.concat(getUsersWithRole('service_delivery_manager'));

        additionalInfo.reason = 'Incident reassigned ' + reassignmentCount + ' times';
        additionalInfo.reassignmentCount = reassignmentCount;
      }
    }

    // Scenario 3: SLA breach imminent (within 30 minutes)
    if (current.sla_due) {
      var slaDue = new GlideDateTime(current.sla_due);
      var now = new GlideDateTime();
      var timeUntilDue = GlideDateTime.subtract(now, slaDue);
      var minutesUntilDue = timeUntilDue.getNumericValue() / (1000 * 60);

      if (minutesUntilDue > 0 && minutesUntilDue <= 30) {
        shouldNotify = true;
        notificationType = 'sla_breach_imminent';

        // Notify assigned user and their manager
        if (current.assigned_to) {
          recipients.push(current.assigned_to.toString());

          var assigneeManager = current.assigned_to.manager.toString();
          if (assigneeManager) {
            recipients.push(assigneeManager);
          }
        }

        additionalInfo.reason = 'SLA breach in ' + minutesUntilDue.toFixed(0) + ' minutes';
        additionalInfo.minutesUntilDue = minutesUntilDue.toFixed(0);
      }
    }

    // Scenario 4: VIP caller incident
    if (current.caller_id && current.caller_id.vip.toString() === 'true') {
      shouldNotify = true;
      notificationType = 'vip_incident';

      // Notify VIP support team
      recipients = recipients.concat(getUsersInGroup('VIP Support'));

      // Notify caller's account manager if exists
      if (current.caller_id.u_account_manager) {
        recipients.push(current.caller_id.u_account_manager.toString());
      }

      additionalInfo.reason = 'VIP caller requires priority attention';
    }

    // Scenario 5: Incident age threshold exceeded
    if (current.opened_at) {
      var opened = new GlideDateTime(current.opened_at);
      var now = new GlideDateTime();
      var age = GlideDateTime.subtract(opened, now);
      var ageHours = Math.abs(age.getNumericValue()) / (1000 * 60 * 60);

      // Notify if incident open > 48 hours
      if (ageHours > 48 && current.state.toString() !== '6') {  // Not resolved
        shouldNotify = true;
        notificationType = 'aging_incident';

        // Notify assignment group and manager
        if (current.assignment_group) {
          recipients = recipients.concat(getGroupMembers(current.assignment_group.toString()));
        }

        additionalInfo.reason = 'Incident open for ' + ageHours.toFixed(0) + ' hours';
        additionalInfo.ageHours = ageHours.toFixed(0);
      }
    }

    // Send notifications if needed
    if (shouldNotify && recipients.length > 0) {
      // Remove duplicates
      recipients = removeDuplicates(recipients);

      // Send notification to each recipient
      recipients.forEach(function(recipientId) {
        sendNotification(recipientId, current, notificationType, additionalInfo);
      });

      gs.info('Sent ' + notificationType + ' notifications for incident ' + 
              current.number + ' to ' + recipients.length + ' recipients');
    }

  } catch (e) {
    gs.error('Async notification BR error for incident ' + current.number + ': ' + e.message);
  }

  // Helper functions
  function getGroupManager(groupId) {
    var grGroup = new GlideRecord('sys_user_group');
    if (grGroup.get(groupId) && grGroup.manager) {
      return grGroup.manager.toString();
    }
    return null;
  }

  function getUsersWithRole(roleName) {
    var users = [];
    var grUserRole = new GlideRecord('sys_user_has_role');
    grUserRole.addQuery('role.name', roleName);
    grUserRole.addQuery('user.active', 'true');
    grUserRole.query();

    while (grUserRole.next()) {
      users.push(grUserRole.user.toString());
    }
    return users;
  }

  function getUsersInGroup(groupName) {
    var users = [];
    var grGroup = new GlideRecord('sys_user_group');
    grGroup.addQuery('name', groupName);
    grGroup.query();

    if (grGroup.next()) {
      var grMember = new GlideRecord('sys_user_grmember');
      grMember.addQuery('group', grGroup.sys_id);
      grMember.query();

      while (grMember.next()) {
        users.push(grMember.user.toString());
      }
    }
    return users;
  }

  function getGroupMembers(groupId) {
    var users = [];
    var grMember = new GlideRecord('sys_user_grmember');
    grMember.addQuery('group', groupId);
    grMember.query();

    while (grMember.next()) {
      users.push(grMember.user.toString());
    }
    return users;
  }

  function getReassignmentCount(incidentId) {
    var ga = new GlideAggregate('sys_audit');
    ga.addQuery('tablename', 'incident');
    ga.addQuery('documentkey', incidentId);
    ga.addQuery('fieldname', 'assignment_group');
    ga.addAggregate('COUNT');
    ga.query();

    if (ga.next()) {
      return parseInt(ga.getAggregate('COUNT'));
    }
    return 0;
  }

  function removeDuplicates(arr) {
    return arr.filter(function(item, index) {
      return arr.indexOf(item) === index;
    });
  }

  function sendNotification(userId, incident, type, info) {
    // Create notification using gs.eventQueue or direct email
    gs.eventQueue('custom.incident.notification', incident, userId, type, 
                  JSON.stringify(info));

    // Alternative: Send email directly
    /*
    var grUser = new GlideRecord('sys_user');
    if (grUser.get(userId) && grUser.email) {
      var email = new GlideSysEmail();
      email.setSubject('Incident Alert: ' + incident.number);
      email.setBody('Alert Type: ' + type + '\nReason: ' + info.reason);
      email.addAddress(grUser.email.toString());
      email.send();
    }
    */
  }

})(current, previous);

How to use it

1. Create an async Business Rule on incident table 2. Check "Insert" and "Update" checkboxes 3. Customize notification scenarios for your requirements 4. Create corresponding notification records or email templates 5. Test with various incident scenarios 6. Monitor system logs for notification delivery 7. Set up system property for enabling/disabling notifications 8. Consider notification throttling to prevent spam

Adapt the table names, fields, and conditions to your instance. Test the behavior in a development environment before using it in production.