csm-patterns
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseCustomer 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
└── CommunicationsAccount (customer_account)
├── Contacts (customer_contact)
├── Contracts (ast_contract)
│ └── Entitlements (service_entitlement)
├── Assets (alm_asset)
└── Cases (sn_customerservice_case)
├── Case Tasks
└── CommunicationsKey Tables
核心数据表
| Table | Purpose |
|---|---|
| Customer accounts |
| Account contacts |
| Customer cases |
| Service entitlements |
| Service contracts |
| 数据表 | 用途 |
|---|---|
| 客户账户 |
| 账户联系人 |
| 客户服务案例 |
| 服务权限 |
| 服务合同 |
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
可用工具
| Tool | Purpose |
|---|---|
| Query CSM tables |
| Find CSM configurations |
| Test CSM scripts |
| Deploy CSM widgets |
| 工具 | 用途 |
|---|---|
| 查询CSM数据表 |
| 查找CSM配置项 |
| 测试CSM脚本 |
| 部署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
最佳实践
- Account Hierarchy - Parent/child accounts
- Contact Roles - Clear contact types
- Entitlements - Track usage limits
- SLA Mapping - Account tier to SLA
- Portal Access - Secure customer data
- Case Routing - Smart assignment
- Communication - Audit trail
- ES5 Only - No modern JavaScript syntax
- 账户层级 - 建立父/子账户结构
- 联系人角色 - 明确联系人类型
- 服务权限 - 跟踪使用限额
- SLA映射 - 账户等级对应SLA
- 门户访问 - 保障客户数据安全
- 案例路由 - 智能分配案例
- 沟通记录 - 保留审计追踪
- 仅用ES5 - 不使用现代JavaScript语法