关于 csm-patterns
csm-patterns 是用于实现 ServiceNow 客户服务管理模式的技能,涵盖客户账户、联系人、客户案例、服务权益、路由和门户提交,并提供 ES5 示例。
主要功能
- 01创建客户账户和联系人
- 02创建并路由客户案例
- 03管理服务权益
- 04通过客户门户提交案例
- 05查询 CSM 表并测试脚本
适用场景
- 01设置账户和联系人记录
- 02按账户和产品路由案例
- 03处理案例前检查服务资格
- 04构建门户案例提交流程
MCP 相关
文档介绍了用于查询 CSM 表、查找或部署 CSM 配置,以及测试 CSM 脚本的 MCP 工具。
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ serac-labs/ serac --skill csm-patterns安装后在 Claude Code 中输入/csm-patterns确认已加载。
详细介绍
ServiceNow 客户服务管理
CSM 通过案件、账户和自助服务,帮助组织提供卓越的客户服务。
CSM 架构
Account (customer_account)
├── Contacts (customer_contact)
├── Contracts (ast_contract)
│ └── Entitlements (service_entitlement)
├── Assets (alm_asset)
└── Cases (sn_customerservice_case)
├── Case Tasks
└── Communications
主要表
| 表 | 用途 |
|---|---|
customer_account | 客户账户 |
customer_contact | 账户联系人 |
sn_customerservice_case | 客户案件 |
service_entitlement | 服务权益 |
ast_contract | 服务合同 |
客户账户(ES5)
创建客户账户
// 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", "[email protected]")
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 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", "[email protected]")
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()
客户案件(ES5)
创建客户案件
// 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()
案件路由
// 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()
}
服务权益(ES5)
创建服务权益
// 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 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",
}
}
扣减服务权益
// 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)
客户门户(ES5)
门户案件提交
// 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 工具集成
可用工具
| 工具 | 用途 |
|---|---|
snow_query_table | 查询 CSM 表 |
snow_artifact_manage | 查找 CSM 配置(action: "find")并部署 CSM 小组件(action: "create") |
snow_execute_script | 测试 CSM 脚本 |
工作流示例
// 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({
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",
})
最佳实践
- 账户层级 - 父子账户
- 联系人角色 - 明确联系人类型
- 服务权益 - 跟踪使用限额
- SLA 映射 - 将账户级别映射到 SLA
- 门户访问 - 保护客户数据
- 案件路由 - 智能分配
- 沟通 - 审计跟踪
- 仅限 ES5 - 不使用现代 JavaScript 语法
评论
支持 Markdown