mcpField
B
business-rules

business-rules

github.com/happy-technologies-llc开发4142 次安装2 天前更新
代码仓库

关于 business-rules

面向开发 ServiceNow 服务端自动化的开发者,介绍如何验证数据、更新记录并触发工作流。内容涵盖业务规则时机、Glide API、性能、测试,以及推送前检查远程版本的本地编辑流程。

主要功能

  1. 01业务规则时机与触发选项
  2. 02current 和 previous 记录处理
  3. 03条件字段与脚本配置
  4. 04Glide API 与性能实践
  5. 05测试、日志和安全脚本更新

适用场景

  1. 01保存前验证记录
  2. 02变更后创建关联记录
  3. 03异步执行耗时任务
  4. 04安全测试和更新业务规则

MCP 相关

介绍如何通过 ServiceNow MCP 工具创建、查询、测试和更新业务规则,包括 SN-Create-Record、SN-Query-Table 和 SN-Update-Record。

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/happy-technologies-llc/happy-platform-skills --skill business-rules

安装后在 Claude Code 中输入/business-rules确认已加载。

详细介绍

业务规则开发

概述

业务规则是在显示、插入、更新、删除或查询记录时执行的服务器端脚本。它们是 ServiceNow 自动化的支柱。

  • 解决什么问题? 自动化记录级逻辑、确保数据完整性,并根据记录变更触发工作流
  • 谁应该使用此技能? 构建自定义自动化逻辑的 ServiceNow 开发者
  • 预期成果: 结构良好、性能出色且遵循 ServiceNow 最佳实践的业务规则

前置条件

  • 角色: admin 或作用域应用开发者角色
  • 知识: JavaScript 基础、GlideRecord API 基础
  • 访问权限: sys_script 表、业务规则的目标表
  • 相关技能: admin/script-execution、admin/update-set-management

何时使用业务规则

时机矩阵
时机触发条件用例current/previous
before插入/更新/删除验证数据、设置字段值、中止操作两者均可用
after插入/更新/删除创建相关记录、发送通知、外部集成两者均可用
async插入/更新/删除长时间运行的操作、外部 API 调用仅 current
display查询/显示计算运行时值、填充 scratchpad仅 current
决策指南

在以下情况使用 BEFORE:

  • 根据其他字段设置默认值
  • 保存前验证数据
  • 提交前修改字段值
  • 使用 current.setAbortAction(true) 中止无效操作

在以下情况使用 AFTER:

  • 创建子记录/相关记录
  • 发送通知(记录提交后)
  • 更新其他表
  • 触发工作流

在以下情况使用 ASYNC:

  • 发起外部 REST/SOAP 调用
  • 处理大型数据集
  • 失败时无需阻塞用户的操作
  • 长时间运行的计算

在以下情况使用 DISPLAY:

  • 仅为表单显示计算值
  • 为客户端脚本填充 g_scratchpad
  • 仅在运行时使用的字段值(不存储)

操作步骤

阶段 1:创建业务规则
步骤 1.1:查询表结构

首先,了解目标表的结构。

使用 MCP:

Tool: SN-Get-Table-Schema
Parameters:
  table_name: incident
步骤 1.2:创建基本业务规则

使用 MCP:

Tool: SN-Create-Record
Parameters:
  table_name: sys_script
  data:
    name: Set Priority Based on Impact and Urgency
    collection: incident
    active: true
    when: before
    order: 100
    filter_condition: impactCHANGES^ORurgencyCHANGES
    script: |
      (function executeRule(current, previous /*null when async*/) {

        // Calculate priority from impact and urgency matrix
        var impact = parseInt(current.impact);
        var urgency = parseInt(current.urgency);

        // Priority matrix: 1=Critical, 2=High, 3=Moderate, 4=Low, 5=Planning
        var matrix = {
          '1-1': 1, '1-2': 2, '1-3': 3,
          '2-1': 2, '2-2': 3, '2-3': 4,
          '3-1': 3, '3-2': 4, '3-3': 5
        };

        var key = impact + '-' + urgency;
        var newPriority = matrix[key] || 4;

        if (current.priority != newPriority) {
          current.priority = newPriority;
          gs.info('Priority calculated: ' + newPriority + ' for ' + current.number);
        }

      })(current, previous);
步骤 1.3:创建 After 业务规则

使用 MCP:

Tool: SN-Create-Record
Parameters:
  table_name: sys_script
  data:
    name: Create Related Task on P1 Incident
    collection: incident
    active: true
    when: after
    order: 200
    filter_condition: priority=1^stateVALCHANGES1
    script: |
      (function executeRule(current, previous /*null when async*/) {

        // Only on insert or when becoming P1
        if (current.operation() == 'insert' ||
            (previous && previous.priority != 1)) {

          var task = new GlideRecord('sc_task');
          task.initialize();
          task.short_description = 'P1 Response: ' + current.short_description;
          task.description = 'Critical incident requires immediate response.\n\nIncident: ' + current.number;
          task.assignment_group = current.assignment_group;
          task.assigned_to = current.assigned_to;
          task.priority = 1;
          task.parent = current.sys_id;
          var taskId = task.insert();

          gs.info('Created P1 response task: ' + taskId + ' for ' + current.number);
        }

      })(current, previous);
步骤 1.4:创建 Async 业务规则

使用 MCP:

Tool: SN-Create-Record
Parameters:
  table_name: sys_script
  data:
    name: Notify External System on Incident Create
    collection: incident
    active: true
    when: async
    order: 500
    action_insert: true
    script: |
      (function executeRule(current, previous /*null when async*/) {

        try {
          var request = new sn_ws.RESTMessageV2('External Notification', 'POST');
          request.setStringParameterNoEscape('incident_number', current.number.toString());
          request.setStringParameterNoEscape('short_description', current.short_description.toString());
          request.setStringParameterNoEscape('priority', current.priority.toString());

          var response = request.execute();
          var httpStatus = response.getStatusCode();

          if (httpStatus == 200 || httpStatus == 201) {
            gs.info('External notification sent for: ' + current.number);
          } else {
            gs.error('External notification failed: ' + httpStatus + ' - ' + response.getBody());
          }
        } catch (e) {
          gs.error('External notification error: ' + e.message);
        }

      })(current, previous);
阶段 2:使用 current 和 previous 对象
步骤 2.1:了解 current 与 previous 的区别

current 对象表示正在处理的记录。previous 对象包含当前事务发生前的字段值。

主要区别:

方面currentprevious
可用性所有业务规则仅 before/after(async 中为 null)
可修改是(before 规则)否(只读)
插入操作有值null
值新值/已修改的值变更前的原始值
步骤 2.2:检测字段变更

使用 .changes() 方法(推荐):

Tool: SN-Create-Record
Parameters:
  table_name: sys_script
  data:
    name: Log State Changes
    collection: incident
    active: true
    when: after
    order: 100
    script: |
      (function executeRule(current, previous /*null when async*/) {

        // EFFICIENT: Exit early if field hasn't changed
        if (!current.state.changes()) {
          return;  // No work to do
        }

        // Get old and new values
        var oldState = previous ? previous.state.getDisplayValue() : '(new)';
        var newState = current.state.getDisplayValue();

        gs.info('Incident ' + current.number + ' state changed: ' + oldState + ' -> ' + newState);

        // Add work note
        current.work_notes = 'State changed from ' + oldState + ' to ' + newState;

      })(current, previous);

手动检测变更(不适合使用 .changes() 时):

// For complex comparisons or calculated changes
if (previous && current.priority != previous.priority) {
  // Priority changed
}

// For reference fields, compare sys_id
if (previous && current.assigned_to.toString() != previous.assigned_to.toString()) {
  // Assignment changed
}

// For multiple fields
var fieldsToCheck = ['state', 'priority', 'assigned_to'];
var changedFields = [];
for (var i = 0; i < fieldsToCheck.length; i++) {
  var field = fieldsToCheck[i];
  if (current[field].changes()) {
    changedFields.push(field);
  }
}
if (changedFields.length > 0) {
  gs.info('Changed fields: ' + changedFields.join(', '));
}
步骤 2.3:检查操作类型
// Determine what triggered the business rule
var operation = current.operation();

switch (operation) {
  case 'insert':
    // Record is being created
    gs.info('New record: ' + current.getTableName());
    break;
  case 'update':
    // Record is being updated
    gs.info('Update to: ' + current.getUniqueValue());
    break;
  case 'delete':
    // Record is being deleted
    gs.info('Deleting: ' + current.getUniqueValue());
    break;
}
阶段 3:条件字段与脚本条件
步骤 3.1:条件字段(简单条件的首选方式)

条件字段使用编码查询,并在脚本运行前进行评估。这样效率更高,因为:

  • 在数据库层进行评估
  • 条件不满足时,脚本不会执行
  • 不产生 JavaScript 开销

条件字段的最佳实践:

# Only run on active P1 incidents
active=true^priority=1

# Only when state changes to Resolved
stateVALCHANGES6

# Only when assigned_to changes
assigned_toCHANGES

# Multiple conditions (AND)
active=true^priority=1^stateVALCHANGES6

# Only on insert (no previous value for state)
stateISEMPTYfalse^ORstateISEMPTY

常见条件运算符:

运算符含义示例
CHANGES字段值已更改stateCHANGES
VALCHANGES更改为特定值stateVALCHANGES6
CHANGESFROM从特定值更改stateCHANGESFROM1
=等于priority=1
!=不等于state!=7
ISEMPTY字段为空assigned_toISEMPTY
ISNOTEMPTY字段有值assigned_toISNOTEMPTY
步骤 3.2:脚本条件(用于复杂逻辑)

仅当条件字段无法表达所需逻辑时,才使用脚本条件。

使用 MCP:

Tool: SN-Create-Record
Parameters:
  table_name: sys_script
  data:
    name: Complex Condition Example
    collection: incident
    active: true
    when: before
    order: 100
    condition: |
      // Script condition - returns true/false
      // Available: current, previous

      // Check if escalating (priority going from higher number to lower)
      if (current.priority.changes()) {
        var oldPri = previous ? parseInt(previous.priority) : 5;
        var newPri = parseInt(current.priority);
        return newPri < oldPri;  // True if escalating
      }
      return false;
    script: |
      (function executeRule(current, previous /*null when async*/) {

        // This only runs if condition returned true
        current.work_notes = 'Incident escalated from P' + previous.priority + ' to P' + current.priority;
        gs.info('Escalation detected: ' + current.number);

      })(current, previous);

何时使用脚本条件:

  • 复杂的日期计算
  • 跨表验证
  • 根据用户角色动态生成的条件
  • 涉及多个字段的计算
  • 需要 GlideRecord 查询的条件
阶段 4:常见 Glide API 方法
步骤 4.1:GlideRecord 基础知识
// Query records
var gr = new GlideRecord('incident');
gr.addQuery('active', true);
gr.addQuery('priority', 1);
gr.orderByDesc('sys_created_on');
gr.setLimit(10);
gr.query();

while (gr.next()) {
  gs.info('Found: ' + gr.number + ' - ' + gr.short_description);
}

// Get single record by sys_id
var incident = new GlideRecord('incident');
if (incident.get('sys_id_value')) {
  gs.info('Found: ' + incident.number);
}

// Get by field value
var user = new GlideRecord('sys_user');
if (user.get('user_name', 'admin')) {
  gs.info('Admin sys_id: ' + user.sys_id);
}

// Insert new record
var newTask = new GlideRecord('task');
newTask.initialize();
newTask.short_description = 'New task from business rule';
newTask.assignment_group = current.assignment_group;
var sysId = newTask.insert();

// Update record
var toUpdate = new GlideRecord('incident');
if (toUpdate.get('sys_id_value')) {
  toUpdate.work_notes = 'Updated via business rule';
  toUpdate.update();
}

// Delete record (use with caution)
var toDelete = new GlideRecord('task');
if (toDelete.get('sys_id_value')) {
  toDelete.deleteRecord();
}
步骤 4.2:GlideSystem(gs)方法
// Logging
gs.info('Information message');
gs.warn('Warning message');
gs.error('Error message');
gs.debug('Debug message');  // Requires debug enabled

// User context
var userId = gs.getUserID();
var userName = gs.getUserName();
var userDisplayName = gs.getUserDisplayName();

// Check roles
if (gs.hasRole('admin')) {
  // Admin-only logic
}
if (gs.hasRole('itil') || gs.hasRole('catalog_admin')) {
  // ITIL or catalog admin logic
}

// Date/Time
var now = gs.now();                    // Current date/time string
var nowDT = gs.nowDateTime();          // GlideDateTime
var today = gs.beginningOfToday();     // Start of today
var daysAgo = gs.daysAgo(7);           // 7 days ago

// Properties
var propValue = gs.getProperty('my.property.name', 'default');
gs.setProperty('my.property.name', 'new_value');

// Generate GUID
var guid = gs.generateGUID();

// Include script include
gs.include('MyScriptInclude');
var util = new MyScriptInclude();

// Nil check (empty string, null, undefined)
if (gs.nil(current.assigned_to)) {
  // Field is empty
}

// Event queue (trigger events)
gs.eventQueue('incident.created', current, current.number, current.priority);
步骤 4.3:GlideDateTime 操作
// Current time
var now = new GlideDateTime();

// Create from string
var dt = new GlideDateTime('2026-02-06 10:30:00');

// Add/subtract time
var future = new GlideDateTime();
future.addDays(5);
future.addHours(2);
future.addMinutes(30);

// Compare dates
var dt1 = new GlideDateTime();
var dt2 = new GlideDateTime(current.sys_created_on);
if (dt1.after(dt2)) {
  gs.info('dt1 is after dt2');
}

// Calculate duration
var duration = GlideDateTime.subtract(dt2, dt1);  // Returns GlideDuration
var seconds = duration.getNumericValue() / 1000;
var displayValue = duration.getDisplayValue();  // "2 Days 3 Hours"

// Business time calculations
var schedule = new GlideSchedule('sys_id_of_schedule');
var dur = schedule.duration(dt1, dt2);

// Format output
var formatted = now.getDisplayValue();  // User's timezone
var internal = now.getValue();          // Internal format (UTC)
var date = now.getLocalDate().getValue();  // Date only
步骤 4.4:使用 GlideAggregate 进行计数和求和
// Count records
var ga = new GlideAggregate('incident');
ga.addQuery('active', true);
ga.addAggregate('COUNT');
ga.query();
if (ga.next()) {
  var count = ga.getAggregate('COUNT');
  gs.info('Active incidents: ' + count);
}

// Count by group
var ga = new GlideAggregate('incident');
ga.addQuery('active', true);
ga.addAggregate('COUNT');
ga.groupBy('priority');
ga.orderByAggregate('COUNT', false);  // Descending
ga.query();
while (ga.next()) {
  var priority = ga.priority.getDisplayValue();
  var count = ga.getAggregate('COUNT');
  gs.info(priority + ': ' + count + ' incidents');
}

// Sum values
var ga = new GlideAggregate('sc_task');
ga.addQuery('active', true);
ga.addAggregate('SUM', 'time_worked');
ga.query();
if (ga.next()) {
  var totalTime = ga.getAggregate('SUM', 'time_worked');
}

// Average
ga.addAggregate('AVG', 'priority');

// Min/Max
ga.addAggregate('MIN', 'sys_created_on');
ga.addAggregate('MAX', 'sys_created_on');
阶段 5:错误处理和日志记录
步骤 5.1:全面的错误处理模式

使用 MCP:

Tool: SN-Create-Record
Parameters:
  table_name: sys_script
  data:
    name: Robust Error Handling Example
    collection: incident
    active: true
    when: after
    order: 100
    script: |
      (function executeRule(current, previous /*null when async*/) {

        var BR_NAME = 'Robust Error Handling Example';

        try {
          // Validate inputs
          if (gs.nil(current.sys_id)) {
            throw new Error('Current record has no sys_id');
          }

          // Main logic
          var relatedCI = new GlideRecord('cmdb_ci');
          if (!relatedCI.get(current.cmdb_ci)) {
            gs.warn('[' + BR_NAME + '] No CI found for incident: ' + current.number);
            return;  // Exit gracefully
          }

          // Perform operations
          relatedCI.operational_status = 2;  // Under maintenance
          relatedCI.update();

          gs.info('[' + BR_NAME + '] Updated CI: ' + relatedCI.name);

        } catch (e) {
          // Log error with context
          gs.error('[' + BR_NAME + '] Error: ' + e.message);
          gs.error('[' + BR_NAME + '] Incident: ' + current.number);
          gs.error('[' + BR_NAME + '] Stack: ' + e.stack);

          // Optionally create incident for business rule error
          // createErrorIncident(BR_NAME, e, current);
        }

      })(current, previous);
步骤 5.2:提供用户反馈并中止操作
// Before business rule - prevent invalid save
(function executeRule(current, previous) {

  var errors = [];

  // Validation 1: Required field
  if (current.priority == 1 && gs.nil(current.assigned_to)) {
    errors.push('P1 incidents must have an assigned user');
  }

  // Validation 2: Business logic
  if (current.state == 6 && gs.nil(current.resolution_notes)) {
    errors.push('Resolution notes are required when resolving');
  }

  // Validation 3: Cross-field validation
  if (current.impact == 1 && current.urgency == 1 && current.priority != 1) {
    errors.push('Impact 1 + Urgency 1 must equal Priority 1');
  }

  // Abort if errors
  if (errors.length > 0) {
    var errorMsg = errors.join('\n');
    gs.addErrorMessage(errorMsg);
    current.setAbortAction(true);
    gs.info('[Validation BR] Aborted save: ' + errorMsg);
  }

})(current, previous);
阶段 6:性能优化
步骤 6.1:使用 .changes() 提升效率

错误——总是执行完整脚本:

// Inefficient - script runs on every update
(function executeRule(current, previous) {
  if (previous && current.state != previous.state) {
    // State changed - do work
  }
})(current, previous);

正确——使用条件字段 + .changes():

Filter Condition: stateCHANGES

Script:
(function executeRule(current, previous) {
  // Script only runs when state changes
  // Additional validation with .changes() for safety
  if (current.state.changes()) {
    // Do work
  }
})(current, previous);
步骤 6.2:尽量减少 GlideRecord 查询

错误——在循环中查询:

var incidents = new GlideRecord('incident');
incidents.query();
while (incidents.next()) {
  // BAD: Query for each incident
  var user = new GlideRecord('sys_user');
  user.get(incidents.assigned_to);
  // ...
}

正确——使用点遍历或批量查询:

// Option 1: Dot-walking (single query)
var incidents = new GlideRecord('incident');
incidents.query();
while (incidents.next()) {
  var userName = incidents.assigned_to.name;  // Dot-walk
  var userEmail = incidents.assigned_to.email;
}

// Option 2: Batch query with lookup map
var userIds = [];
var incidents = new GlideRecord('incident');
incidents.query();
while (incidents.next()) {
  if (!gs.nil(incidents.assigned_to)) {
    userIds.push(incidents.assigned_to.toString());
  }
}

var userMap = {};
if (userIds.length > 0) {
  var users = new GlideRecord('sys_user');
  users.addQuery('sys_id', 'IN', userIds.join(','));
  users.query();
  while (users.next()) {
    userMap[users.sys_id.toString()] = {
      name: users.name.toString(),
      email: users.email.toString()
    };
  }
}
步骤 6.3:顺序和 Active 标志最佳实践
实践建议
顺序验证使用 100-199,字段赋值使用 200-399,外部/异步操作使用 400+
Active开发期间设为 false,测试通过后启用
条件使用筛选条件字段,而非脚本条件
继承谨慎设置“Inherits”,通常保持未选中
步骤 6.4:避免递归更新

危险——可能导致无限循环:

// After business rule on incident
(function executeRule(current, previous) {
  current.work_notes = 'Updated at ' + gs.now();
  current.update();  // DANGER: Triggers business rules again!
})(current, previous);

安全——将 workflow 设为 false,或使用 before 规则:

// Option 1: Disable workflow on update
var gr = new GlideRecord('incident');
if (gr.get(current.sys_id)) {
  gr.setWorkflow(false);  // Skip business rules
  gr.autoSysFields(false);  // Skip sys field updates
  gr.work_notes = 'Updated at ' + gs.now();
  gr.update();
}

// Option 2: Use before rule instead (preferred)
// Before business rule - modifies current directly
(function executeRule(current, previous) {
  current.work_notes = 'Updated at ' + gs.now();
  // No .update() needed - current is saved automatically
})(current, previous);
阶段 7:测试 Business Rule
步骤 7.1:查询现有 Business Rule

使用 MCP:

Tool: SN-Query-Table
Parameters:
  table_name: sys_script
  query: collection=incident^active=true
  fields: name,when,order,filter_condition,active
  limit: 50
步骤 7.2:使用 Background Script 测试

使用 MCP:

Tool: SN-Execute-Background-Script
Parameters:
  script: |
    // Test business rule by simulating record operation
    var testIncident = new GlideRecord('incident');
    testIncident.initialize();
    testIncident.short_description = 'Test incident for BR testing';
    testIncident.caller_id = gs.getUserID();
    testIncident.impact = 1;
    testIncident.urgency = 1;

    // Insert will trigger before/after insert rules
    var sysId = testIncident.insert();
    gs.info('Created test incident: ' + sysId);

    // Verify priority was calculated
    testIncident.get(sysId);
    gs.info('Priority after BR: ' + testIncident.priority.getDisplayValue());

    // Clean up (optional)
    // testIncident.deleteRecord();
  description: Test priority calculation business rule
步骤 7.3:检查系统日志

使用 MCP:

Tool: SN-Query-Table
Parameters:
  table_name: syslog
  query: messageLIKEbusiness rule^ORmessageLIKE[BR]^sys_created_on>javascript:gs.minutesAgo(10)
  fields: message,level,sys_created_on,source
  limit: 50
步骤 7.4:明确的本地开发周期

拉取当前脚本和新鲜度元数据:

Tool: SN-Get-Record
Parameters:
  table_name: sys_script
  sys_id: [business_rule_sys_id]
  fields: sys_id,script,sys_updated_on,sys_mod_count
  instance: dev

在本地保存 script 字段,并在 IDE 中编辑。推送前立即再次执行 SN-Get-Record,并比较 sys_updated_on、sys_mod_count 和原始脚本。如果远程记录已更改,请在本地合并,而不是覆盖它。否则,只推送审核过的脚本字段:

Tool: SN-Update-Record
Parameters:
  table_name: sys_script
  sys_id: [business_rule_sys_id]
  data:
    script: [reviewed_local_script]
  instance: dev

工具使用摘要

操作MCP 工具用途
创建 BRSN-Create-Record (sys_script)创建新的业务规则
更新 BRSN-Update-Record修改现有业务规则
查询 BRSN-Query-Table查找表中的业务规则
测试 BRSN-Execute-Background-Script模拟记录操作
调试SN-Query-Table (syslog)检查执行日志
本地开发SN-Get-Record + SN-Update-Record明确拉取、检查新鲜度、编辑并推送
获取 SchemaSN-Get-Table-Schema了解表结构

最佳实践

代码质量
  • 使用 IIFE 模式: 将所有脚本封装在 (function executeRule(current, previous) {...})(current, previous); 中
  • 变量命名清晰: 使用 existingTask 等描述性名称,而不是 gr2
  • 为复杂逻辑添加注释: 解释原因,而不是说明做了什么
  • 避免使用魔法数字: 对于 state=6 之类的值,使用常量或添加注释
性能
  • 使用 .changes() 方法: 如果字段没有变化,始终提前退出
  • 优先使用条件字段: 数据库级筛选比脚本筛选更快
  • 限制查询次数: 使用 setLimit(),避免在循环中查询
  • 谨慎设置顺序: 数值越小,越先运行。将验证逻辑放在前面(100-199)
可靠性
  • 始终处理错误: 使用 try-catch 包裹代码,尤其是外部调用
  • 记录有意义的消息: 包含记录标识符和字段值
  • 使用多个角色进行测试: 确保 ACL 不会破坏业务规则
  • 避免递归: 更新同一张表时使用 setWorkflow(false)
可维护性
  • 每条规则只做一件事: 将复杂规则拆分为专注于单一功能的组件
  • 使用 Script Includes: 将可复用逻辑移至脚本包含项
  • 记录依赖关系: 注明此规则与哪些其他规则或脚本交互
  • 版本控制: 使用 SN-Get-Record 拉取,将基础修订版本保存在 Git 中,检查内容是否为最新,然后使用 SN-Update-Record 推送

故障排查

业务规则未触发

症状: 记录已保存,但业务规则脚本未执行 原因:

  • Active = false
  • 筛选条件不匹配
  • 顺序与中止操作冲突
  • ACL 阻止访问字段 解决方案:
Tool: SN-Query-Table
Parameters:
  table_name: sys_script
  query: collection=incident^name=*priority*
  fields: name,active,when,filter_condition,order
无限循环 / 堆栈溢出

症状: 错误“Maximum call stack size exceeded”或超时 原因: 业务规则在同一张表上调用 .update(),从而触发自身 解决方案:

// Use setWorkflow(false) to prevent recursion
var gr = new GlideRecord('incident');
gr.get(current.sys_id);
gr.setWorkflow(false);
gr.update();
previous 为 Null

症状: 访问 previous.fieldname 时出错 原因: 在插入操作或异步规则中,previous 为 null 解决方案:

// Always check for null
if (previous && current.state != previous.state) {
  // Safe to access previous
}

// Or use operation check
if (current.operation() != 'insert' && previous) {
  // previous is available
}
未检测到更改

症状: 字段已更改,但 .changes() 返回 false 原因: 字段被设为相同的值,或在查询后进行了更改 解决方案:

// Log actual values for debugging
gs.info('Current state: ' + current.state + ', Previous: ' + (previous ? previous.state : 'null'));
gs.info('Changes: ' + current.state.changes());

// Use condition field for reliable change detection
// Filter: stateCHANGES

示例

示例 1:根据类别自动分配
(function executeRule(current, previous) {

  // Exit early if category hasn't changed
  if (!current.category.changes()) {
    return;
  }

  // Assignment mapping
  var categoryAssignment = {
    'network': 'Network Team',
    'hardware': 'Desktop Support',
    'software': 'Application Support',
    'database': 'DBA Team'
  };

  var category = current.category.toString();
  var groupName = categoryAssignment[category];

  if (groupName) {
    var group = new GlideRecord('sys_user_group');
    if (group.get('name', groupName)) {
      current.assignment_group = group.sys_id;
      gs.info('Auto-assigned to ' + groupName + ' based on category');
    }
  }

})(current, previous);
示例 2:SLA 升级警告
(function executeRule(current, previous) {

  // Check SLA breach approaching (80% of response time)
  var sla = new GlideRecord('task_sla');
  sla.addQuery('task', current.sys_id);
  sla.addQuery('stage', 'in_progress');
  sla.query();

  while (sla.next()) {
    var percentComplete = sla.percentage.floatValue();

    if (percentComplete >= 80 && percentComplete < 100) {
      // Approaching breach - escalate
      if (current.priority > 1) {
        current.priority = parseInt(current.priority) - 1;
        current.work_notes = 'Auto-escalated: SLA at ' + Math.round(percentComplete) + '%';
        gs.info('SLA escalation for ' + current.number + ' - ' + percentComplete + '%');
      }
    }
  }

})(current, previous);
示例 3:敏感字段更改的审计跟踪
(function executeRule(current, previous) {

  var sensitiveFields = ['priority', 'assignment_group', 'assigned_to', 'state'];
  var changes = [];

  for (var i = 0; i < sensitiveFields.length; i++) {
    var field = sensitiveFields[i];
    if (current[field].changes()) {
      changes.push({
        field: field,
        from: previous ? previous[field].getDisplayValue() : '(empty)',
        to: current[field].getDisplayValue()
      });
    }
  }

  if (changes.length > 0) {
    var auditNote = 'Field Changes:\n';
    for (var j = 0; j < changes.length; j++) {
      var c = changes[j];
      auditNote += '- ' + c.field + ': "' + c.from + '" -> "' + c.to + '"\n';
    }
    auditNote += '\nChanged by: ' + gs.getUserDisplayName();

    current.work_notes = auditNote;
    gs.info('Audit trail recorded for ' + current.number);
  }

})(current, previous);

相关技能

  • admin/script-execution - 后台脚本模式与执行
  • admin/update-set-management - 跟踪业务规则更改
  • development/script-includes - 可复用的服务端库
  • catalog/ui-policies - 客户端字段控制(与 BR 配合使用)
  • security/acl-management - 影响 BR 执行的访问控制

参考资料

评论

支持 Markdown