csm-patterns

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Customer Service Management for ServiceNow

ServiceNow 客户服务管理(CSM)

CSM enables organizations to deliver exceptional customer service through cases, accounts, and self-service.
CSM 支持企业通过案例、账户和自助服务为客户提供优质服务。

CSM Architecture

CSM 架构

Account (customer_account)
    ├── Contacts (customer_contact)
    ├── Contracts (ast_contract)
    │   └── Entitlements (service_entitlement)
    ├── Assets (alm_asset)
    └── Cases (sn_customerservice_case)
        ├── Case Tasks
        └── Communications
Account (customer_account)
    ├── Contacts (customer_contact)
    ├── Contracts (ast_contract)
    │   └── Entitlements (service_entitlement)
    ├── Assets (alm_asset)
    └── Cases (sn_customerservice_case)
        ├── Case Tasks
        └── Communications

Key Tables

核心数据表

TablePurpose
customer_account
Customer accounts
customer_contact
Account contacts
sn_customerservice_case
Customer cases
service_entitlement
Service entitlements
ast_contract
Service contracts
数据表用途
customer_account
客户账户
customer_contact
账户联系人
sn_customerservice_case
客户服务案例
service_entitlement
服务权限
ast_contract
服务合同

Customer Accounts (ES5)

客户账户(仅支持ES5)

Create Customer Account

创建客户账户

javascript
// Create customer account (ES5 ONLY!)
var account = new GlideRecord('customer_account');
account.initialize();

// Basic info
account.setValue('name', 'Acme Corporation');
account.setValue('account_code', 'ACME-001');
account.setValue('industry', 'Technology');

// Contact info
account.setValue('phone', '+1-555-123-4567');
account.setValue('email', 'info@acme.com');
account.setValue('website', 'https://www.acme.com');

// Address
account.setValue('street', '123 Main Street');
account.setValue('city', 'San Francisco');
account.setValue('state', 'CA');
account.setValue('zip', '94105');
account.setValue('country', 'US');

// Account details
account.setValue('account_type', 'customer');  // customer, partner, vendor
account.setValue('tier', 'gold');  // bronze, silver, gold, platinum

// Assignment
account.setValue('account_manager', accountManagerSysId);

account.insert();
javascript
// Create customer account (ES5 ONLY!)
var account = new GlideRecord('customer_account');
account.initialize();

// Basic info
account.setValue('name', 'Acme Corporation');
account.setValue('account_code', 'ACME-001');
account.setValue('industry', 'Technology');

// Contact info
account.setValue('phone', '+1-555-123-4567');
account.setValue('email', 'info@acme.com');
account.setValue('website', 'https://www.acme.com');

// Address
account.setValue('street', '123 Main Street');
account.setValue('city', 'San Francisco');
account.setValue('state', 'CA');
account.setValue('zip', '94105');
account.setValue('country', 'US');

// Account details
account.setValue('account_type', 'customer');  // customer, partner, vendor
account.setValue('tier', 'gold');  // bronze, silver, gold, platinum

// Assignment
account.setValue('account_manager', accountManagerSysId);

account.insert();

Create Contact

创建联系人

javascript
// Create contact for account (ES5 ONLY!)
var contact = new GlideRecord('customer_contact');
contact.initialize();

// Link to account
contact.setValue('account', accountSysId);

// Contact info
contact.setValue('name', 'John Smith');
contact.setValue('email', 'john.smith@acme.com');
contact.setValue('phone', '+1-555-123-4568');
contact.setValue('title', 'IT Manager');

// Contact type
contact.setValue('type', 'primary');  // primary, billing, technical
contact.setValue('active', true);

// Create user for portal access
var user = createUserFromContact(contact);
contact.setValue('user', user);

contact.insert();
javascript
// Create contact for account (ES5 ONLY!)
var contact = new GlideRecord('customer_contact');
contact.initialize();

// Link to account
contact.setValue('account', accountSysId);

// Contact info
contact.setValue('name', 'John Smith');
contact.setValue('email', 'john.smith@acme.com');
contact.setValue('phone', '+1-555-123-4568');
contact.setValue('title', 'IT Manager');

// Contact type
contact.setValue('type', 'primary');  // primary, billing, technical
contact.setValue('active', true);

// Create user for portal access
var user = createUserFromContact(contact);
contact.setValue('user', user);

contact.insert();

Customer Cases (ES5)

客户服务案例(仅支持ES5)

Create Customer Case

创建客户服务案例

javascript
// Create customer case (ES5 ONLY!)
var caseRecord = new GlideRecord('sn_customerservice_case');
caseRecord.initialize();

// Case info
caseRecord.setValue('short_description', 'Unable to access product features');
caseRecord.setValue('description', 'Customer reports error when trying to use premium features');

// Classification
caseRecord.setValue('category', 'product_issue');
caseRecord.setValue('subcategory', 'access_problem');
caseRecord.setValue('priority', 2);

// Customer
caseRecord.setValue('account', accountSysId);
caseRecord.setValue('contact', contactSysId);

// Product/Asset
caseRecord.setValue('product', productSysId);
caseRecord.setValue('asset', assetSysId);

// Assignment
caseRecord.setValue('assignment_group', getGroupSysId('Customer Support'));

// Channel
caseRecord.setValue('channel', 'email');  // email, phone, chat, web

caseRecord.insert();
javascript
// Create customer case (ES5 ONLY!)
var caseRecord = new GlideRecord('sn_customerservice_case');
caseRecord.initialize();

// Case info
caseRecord.setValue('short_description', 'Unable to access product features');
caseRecord.setValue('description', 'Customer reports error when trying to use premium features');

// Classification
caseRecord.setValue('category', 'product_issue');
caseRecord.setValue('subcategory', 'access_problem');
caseRecord.setValue('priority', 2);

// Customer
caseRecord.setValue('account', accountSysId);
caseRecord.setValue('contact', contactSysId);

// Product/Asset
caseRecord.setValue('product', productSysId);
caseRecord.setValue('asset', assetSysId);

// Assignment
caseRecord.setValue('assignment_group', getGroupSysId('Customer Support'));

// Channel
caseRecord.setValue('channel', 'email');  // email, phone, chat, web

caseRecord.insert();

Case Routing

案例路由

javascript
// Route case based on account and product (ES5 ONLY!)
// Business Rule: before, insert, sn_customerservice_case

(function executeRule(current, previous) {
    if (current.assignment_group) {
        return;  // Already assigned
    }

    var group = determineAssignmentGroup(current);
    if (group) {
        current.assignment_group = group;
    }
})(current, previous);

function determineAssignmentGroup(caseRecord) {
    // Check for premium support entitlement
    if (hasPremiumSupport(caseRecord.getValue('account'))) {
        return getGroupSysId('Premium Support');
    }

    // Route by product
    var product = caseRecord.product.getRefRecord();
    if (product.isValidRecord()) {
        var supportGroup = product.getValue('support_group');
        if (supportGroup) {
            return supportGroup;
        }
    }

    // Default
    return getGroupSysId('General Support');
}

function hasPremiumSupport(accountSysId) {
    var entitlement = new GlideRecord('service_entitlement');
    entitlement.addQuery('account', accountSysId);
    entitlement.addQuery('type', 'premium_support');
    entitlement.addQuery('start_date', '<=', new GlideDateTime());
    entitlement.addQuery('end_date', '>=', new GlideDateTime());
    entitlement.query();
    return entitlement.hasNext();
}
javascript
// Route case based on account and product (ES5 ONLY!)
// Business Rule: before, insert, sn_customerservice_case

(function executeRule(current, previous) {
    if (current.assignment_group) {
        return;  // Already assigned
    }

    var group = determineAssignmentGroup(current);
    if (group) {
        current.assignment_group = group;
    }
})(current, previous);

function determineAssignmentGroup(caseRecord) {
    // Check for premium support entitlement
    if (hasPremiumSupport(caseRecord.getValue('account'))) {
        return getGroupSysId('Premium Support');
    }

    // Route by product
    var product = caseRecord.product.getRefRecord();
    if (product.isValidRecord()) {
        var supportGroup = product.getValue('support_group');
        if (supportGroup) {
            return supportGroup;
        }
    }

    // Default
    return getGroupSysId('General Support');
}

function hasPremiumSupport(accountSysId) {
    var entitlement = new GlideRecord('service_entitlement');
    entitlement.addQuery('account', accountSysId);
    entitlement.addQuery('type', 'premium_support');
    entitlement.addQuery('start_date', '<=', new GlideDateTime());
    entitlement.addQuery('end_date', '>=', new GlideDateTime());
    entitlement.query();
    return entitlement.hasNext();
}

Entitlements (ES5)

服务权限(仅支持ES5)

Create Service Entitlement

创建服务权限

javascript
// Create entitlement (ES5 ONLY!)
var entitlement = new GlideRecord('service_entitlement');
entitlement.initialize();

entitlement.setValue('name', 'Premium Support - Acme Corp');
entitlement.setValue('account', accountSysId);
entitlement.setValue('contract', contractSysId);

// Entitlement type
entitlement.setValue('type', 'premium_support');

// Dates
entitlement.setValue('start_date', '2024-01-01');
entitlement.setValue('end_date', '2024-12-31');

// Limits
entitlement.setValue('total_cases', 100);
entitlement.setValue('used_cases', 0);
entitlement.setValue('remaining_cases', 100);

// SLA
entitlement.setValue('response_sla', '4 hours');
entitlement.setValue('resolution_sla', '24 hours');

entitlement.insert();
javascript
// Create entitlement (ES5 ONLY!)
var entitlement = new GlideRecord('service_entitlement');
entitlement.initialize();

entitlement.setValue('name', 'Premium Support - Acme Corp');
entitlement.setValue('account', accountSysId);
entitlement.setValue('contract', contractSysId);

// Entitlement type
entitlement.setValue('type', 'premium_support');

// Dates
entitlement.setValue('start_date', '2024-01-01');
entitlement.setValue('end_date', '2024-12-31');

// Limits
entitlement.setValue('total_cases', 100);
entitlement.setValue('used_cases', 0);
entitlement.setValue('remaining_cases', 100);

// SLA
entitlement.setValue('response_sla', '4 hours');
entitlement.setValue('resolution_sla', '24 hours');

entitlement.insert();

Check Entitlement

检查服务权限

javascript
// Check if customer is entitled to service (ES5 ONLY!)
function checkEntitlement(accountSysId, entitlementType) {
    var now = new GlideDateTime();

    var entitlement = new GlideRecord('service_entitlement');
    entitlement.addQuery('account', accountSysId);
    entitlement.addQuery('type', entitlementType);
    entitlement.addQuery('start_date', '<=', now);
    entitlement.addQuery('end_date', '>=', now);
    entitlement.query();

    if (entitlement.next()) {
        var remaining = parseInt(entitlement.getValue('remaining_cases'), 10);

        return {
            entitled: true,
            remaining: remaining,
            unlimited: remaining < 0,  // -1 = unlimited
            expiration: entitlement.getValue('end_date'),
            sla: {
                response: entitlement.getValue('response_sla'),
                resolution: entitlement.getValue('resolution_sla')
            }
        };
    }

    return {
        entitled: false,
        message: 'No active entitlement found'
    };
}
javascript
// Check if customer is entitled to service (ES5 ONLY!)
function checkEntitlement(accountSysId, entitlementType) {
    var now = new GlideDateTime();

    var entitlement = new GlideRecord('service_entitlement');
    entitlement.addQuery('account', accountSysId);
    entitlement.addQuery('type', entitlementType);
    entitlement.addQuery('start_date', '<=', now);
    entitlement.addQuery('end_date', '>=', now);
    entitlement.query();

    if (entitlement.next()) {
        var remaining = parseInt(entitlement.getValue('remaining_cases'), 10);

        return {
            entitled: true,
            remaining: remaining,
            unlimited: remaining < 0,  // -1 = unlimited
            expiration: entitlement.getValue('end_date'),
            sla: {
                response: entitlement.getValue('response_sla'),
                resolution: entitlement.getValue('resolution_sla')
            }
        };
    }

    return {
        entitled: false,
        message: 'No active entitlement found'
    };
}

Decrement Entitlement

扣减服务权限

javascript
// Use entitlement when case created (ES5 ONLY!)
// Business Rule: after, insert, sn_customerservice_case

(function executeRule(current, previous) {
    var accountSysId = current.getValue('account');
    if (!accountSysId) return;

    var entitlement = new GlideRecord('service_entitlement');
    entitlement.addQuery('account', accountSysId);
    entitlement.addQuery('type', 'support');
    entitlement.addQuery('start_date', '<=', new GlideDateTime());
    entitlement.addQuery('end_date', '>=', new GlideDateTime());
    entitlement.addQuery('remaining_cases', '>', 0);
    entitlement.orderBy('end_date');  // Use earliest expiring first
    entitlement.setLimit(1);
    entitlement.query();

    if (entitlement.next()) {
        var used = parseInt(entitlement.getValue('used_cases'), 10);
        var remaining = parseInt(entitlement.getValue('remaining_cases'), 10);

        entitlement.setValue('used_cases', used + 1);
        entitlement.setValue('remaining_cases', remaining - 1);
        entitlement.update();

        // Link case to entitlement
        current.u_entitlement = entitlement.getUniqueValue();
        current.update();

        // Alert if running low
        if (remaining - 1 <= 5) {
            gs.eventQueue('entitlement.low', entitlement, accountSysId, (remaining - 1).toString());
        }
    }
})(current, previous);
javascript
// Use entitlement when case created (ES5 ONLY!)
// Business Rule: after, insert, sn_customerservice_case

(function executeRule(current, previous) {
    var accountSysId = current.getValue('account');
    if (!accountSysId) return;

    var entitlement = new GlideRecord('service_entitlement');
    entitlement.addQuery('account', accountSysId);
    entitlement.addQuery('type', 'support');
    entitlement.addQuery('start_date', '<=', new GlideDateTime());
    entitlement.addQuery('end_date', '>=', new GlideDateTime());
    entitlement.addQuery('remaining_cases', '>', 0);
    entitlement.orderBy('end_date');  // Use earliest expiring first
    entitlement.setLimit(1);
    entitlement.query();

    if (entitlement.next()) {
        var used = parseInt(entitlement.getValue('used_cases'), 10);
        var remaining = parseInt(entitlement.getValue('remaining_cases'), 10);

        entitlement.setValue('used_cases', used + 1);
        entitlement.setValue('remaining_cases', remaining - 1);
        entitlement.update();

        // Link case to entitlement
        current.u_entitlement = entitlement.getUniqueValue();
        current.update();

        // Alert if running low
        if (remaining - 1 <= 5) {
            gs.eventQueue('entitlement.low', entitlement, accountSysId, (remaining - 1).toString());
        }
    }
})(current, previous);

Customer Portal (ES5)

客户门户(仅支持ES5)

Portal Case Submission

门户案例提交

javascript
// Widget Server Script for case submission (ES5 ONLY!)
(function() {
    // Handle case creation
    if (input && input.action === 'createCase') {
        var contactId = getContactForUser(gs.getUserID());
        if (!contactId) {
            data.error = 'No contact record found';
            return;
        }

        var contact = new GlideRecord('customer_contact');
        contact.get(contactId);

        // Create case
        var caseRecord = new GlideRecord('sn_customerservice_case');
        caseRecord.initialize();
        caseRecord.setValue('short_description', input.subject);
        caseRecord.setValue('description', input.description);
        caseRecord.setValue('contact', contactId);
        caseRecord.setValue('account', contact.getValue('account'));
        caseRecord.setValue('priority', input.priority || 3);
        caseRecord.setValue('channel', 'web');

        var caseSysId = caseRecord.insert();

        data.success = true;
        data.case_number = caseRecord.getValue('number');
        data.case_sys_id = caseSysId;
    }

    // Get user's cases
    if (!input || input.action === 'getCases') {
        var contactId = getContactForUser(gs.getUserID());
        data.cases = [];

        if (contactId) {
            var gr = new GlideRecord('sn_customerservice_case');
            gr.addQuery('contact', contactId);
            gr.orderByDesc('sys_created_on');
            gr.setLimit(20);
            gr.query();

            while (gr.next()) {
                data.cases.push({
                    sys_id: gr.getUniqueValue(),
                    number: gr.getValue('number'),
                    short_description: gr.getValue('short_description'),
                    state: gr.state.getDisplayValue(),
                    priority: gr.priority.getDisplayValue(),
                    opened_at: gr.getValue('opened_at')
                });
            }
        }
    }

    function getContactForUser(userId) {
        var contact = new GlideRecord('customer_contact');
        contact.addQuery('user', userId);
        contact.query();
        if (contact.next()) {
            return contact.getUniqueValue();
        }
        return null;
    }
})();
javascript
// Widget Server Script for case submission (ES5 ONLY!)
(function() {
    // Handle case creation
    if (input && input.action === 'createCase') {
        var contactId = getContactForUser(gs.getUserID());
        if (!contactId) {
            data.error = 'No contact record found';
            return;
        }

        var contact = new GlideRecord('customer_contact');
        contact.get(contactId);

        // Create case
        var caseRecord = new GlideRecord('sn_customerservice_case');
        caseRecord.initialize();
        caseRecord.setValue('short_description', input.subject);
        caseRecord.setValue('description', input.description);
        caseRecord.setValue('contact', contactId);
        caseRecord.setValue('account', contact.getValue('account'));
        caseRecord.setValue('priority', input.priority || 3);
        caseRecord.setValue('channel', 'web');

        var caseSysId = caseRecord.insert();

        data.success = true;
        data.case_number = caseRecord.getValue('number');
        data.case_sys_id = caseSysId;
    }

    // Get user's cases
    if (!input || input.action === 'getCases') {
        var contactId = getContactForUser(gs.getUserID());
        data.cases = [];

        if (contactId) {
            var gr = new GlideRecord('sn_customerservice_case');
            gr.addQuery('contact', contactId);
            gr.orderByDesc('sys_created_on');
            gr.setLimit(20);
            gr.query();

            while (gr.next()) {
                data.cases.push({
                    sys_id: gr.getUniqueValue(),
                    number: gr.getValue('number'),
                    short_description: gr.getValue('short_description'),
                    state: gr.state.getDisplayValue(),
                    priority: gr.priority.getDisplayValue(),
                    opened_at: gr.getValue('opened_at')
                });
            }
        }
    }

    function getContactForUser(userId) {
        var contact = new GlideRecord('customer_contact');
        contact.addQuery('user', userId);
        contact.query();
        if (contact.next()) {
            return contact.getUniqueValue();
        }
        return null;
    }
})();

MCP Tool Integration

MCP 工具集成

Available Tools

可用工具

ToolPurpose
snow_query_table
Query CSM tables
snow_find_artifact
Find CSM configurations
snow_execute_script_with_output
Test CSM scripts
snow_deploy
Deploy CSM widgets
工具用途
snow_query_table
查询CSM数据表
snow_find_artifact
查找CSM配置项
snow_execute_script_with_output
测试CSM脚本
snow_deploy
部署CSM组件

Example Workflow

示例工作流

javascript
// 1. Query customer cases
await snow_query_table({
    table: 'sn_customerservice_case',
    query: 'active=true^priority<=2',
    fields: 'number,short_description,account,contact,state'
});

// 2. Check entitlements
await snow_execute_script_with_output({
    script: `
        var result = checkEntitlement('account_sys_id', 'premium_support');
        gs.info(JSON.stringify(result));
    `
});

// 3. Find accounts with expiring contracts
await snow_query_table({
    table: 'ast_contract',
    query: 'endsBETWEENjavascript:gs.beginningOfToday()@javascript:gs.daysAgoEnd(-30)',
    fields: 'number,vendor,ends,account'
});
javascript
// 1. Query customer cases
await snow_query_table({
    table: 'sn_customerservice_case',
    query: 'active=true^priority<=2',
    fields: 'number,short_description,account,contact,state'
});

// 2. Check entitlements
await snow_execute_script_with_output({
    script: `
        var result = checkEntitlement('account_sys_id', 'premium_support');
        gs.info(JSON.stringify(result));
    `
});

// 3. Find accounts with expiring contracts
await snow_query_table({
    table: 'ast_contract',
    query: 'endsBETWEENjavascript:gs.beginningOfToday()@javascript:gs.daysAgoEnd(-30)',
    fields: 'number,vendor,ends,account'
});

Best Practices

最佳实践

  1. Account Hierarchy - Parent/child accounts
  2. Contact Roles - Clear contact types
  3. Entitlements - Track usage limits
  4. SLA Mapping - Account tier to SLA
  5. Portal Access - Secure customer data
  6. Case Routing - Smart assignment
  7. Communication - Audit trail
  8. ES5 Only - No modern JavaScript syntax
  1. 账户层级 - 建立父/子账户结构
  2. 联系人角色 - 明确联系人类型
  3. 服务权限 - 跟踪使用限额
  4. SLA映射 - 账户等级对应SLA
  5. 门户访问 - 保障客户数据安全
  6. 案例路由 - 智能分配案例
  7. 沟通记录 - 保留审计追踪
  8. 仅用ES5 - 不使用现代JavaScript语法