field-service について
ServiceNow の現場サービスを管理する管理者や開発者向けのスキルです。作業オーダー、技術者リソース、ディスパッチ、モバイルでの更新を ES5 の例と MCP ツールの手順で扱います。
主な機能
- 01作業オーダーとタスクの作成
- 02技術者リソースとスキルの管理
- 03技術者の空き状況の確認
- 04作業オーダーの予定設定と自動割り当て
- 05ステータス更新と作業時間の記録
活用シーン
- 01現場修理依頼の登録
- 02スキルとテリトリーによる技術者検索
- 03作業への技術者と時間枠の割り当て
- 04現場状況と作業時間の記録
MCP 対応
フィールドサービスのテーブル検索、スクリプトのテスト、設定の検索に使う MCP ツールを列挙し、未完了の作業オーダーを検索する例も示します。
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ serac-labs/ serac --skill field-serviceインストール後、Claude Code で/field-serviceを入力して読み込まれたか確認します。
詳細
ServiceNow のフィールドサービス管理
フィールドサービス管理(FSM)は、作業指示、技術者の派遣、モバイルでの現場業務を管理します。
FSM のアーキテクチャ
Work Order (wm_order)
├── Work Order Tasks (wm_task)
│ ├── Time Entries
│ └── Parts Used
├── Asset/CI
└── Location
Dispatch
├── Scheduling
└── Route Optimization
主要なテーブル
| テーブル | 用途 |
|---|---|
wm_order | 作業指示 |
wm_task | 作業指示タスク |
wm_resource | フィールド技術者 |
wm_schedule_entry | スケジュール項目 |
wm_territory | サービス提供地域 |
作業指示(ES5)
作業指示の作成
// Create work order (ES5 ONLY!)
var workOrder = new GlideRecord("wm_order")
workOrder.initialize()
// Basic info
workOrder.setValue("short_description", "HVAC repair - Building A")
workOrder.setValue("description", "AC unit not cooling properly")
workOrder.setValue("priority", 2)
// Classification
workOrder.setValue("work_order_type", "repair")
workOrder.setValue("category", "hvac")
// Location
workOrder.setValue("location", locationSysId)
workOrder.setValue("cmdb_ci", hvacUnitCISysId)
// Customer/Contact
workOrder.setValue("account", customerAccountSysId)
workOrder.setValue("contact", contactSysId)
// Scheduling
var scheduledStart = new GlideDateTime()
scheduledStart.addDaysLocalTime(1)
workOrder.setValue("scheduled_start", scheduledStart)
// Assignment
workOrder.setValue("assignment_group", fieldServiceGroupSysId)
// SLA
workOrder.setValue("sla", slaDefinitionSysId)
workOrder.insert()
作業指示タスク
// Create work order tasks (ES5 ONLY!)
function createWorkOrderTasks(workOrderSysId, tasks) {
var createdTasks = []
for (var i = 0; i < tasks.length; i++) {
var task = new GlideRecord("wm_task")
task.initialize()
task.setValue("work_order", workOrderSysId)
task.setValue("short_description", tasks[i].description)
task.setValue("order", (i + 1) * 100)
// Estimated duration
task.setValue("estimated_duration", tasks[i].duration)
// Skills required
if (tasks[i].skills) {
task.setValue("skills", tasks[i].skills)
}
// Parts needed
if (tasks[i].parts) {
task.setValue("u_parts_required", tasks[i].parts)
}
var taskSysId = task.insert()
createdTasks.push({
sys_id: taskSysId,
number: task.getValue("number"),
})
}
return createdTasks
}
// Example
createWorkOrderTasks(workOrderSysId, [
{ description: "Diagnose AC unit", duration: "01:00:00", skills: "hvac_certified" },
{ description: "Replace compressor", duration: "02:00:00", parts: "COMP-AC-001" },
{ description: "Test and verify", duration: "00:30:00" },
])
技術者管理(ES5)
リソースプロファイルの作成
// Create field technician profile (ES5 ONLY!)
var resource = new GlideRecord("wm_resource")
resource.initialize()
// Link to user
resource.setValue("user", userSysId)
// Skills
resource.setValue("skills", "hvac_certified,electrical,plumbing")
// Territory
resource.setValue("territory", territorySysId)
// Availability
resource.setValue("work_schedule", scheduleId)
// Vehicle/Equipment
resource.setValue("vehicle", vehicleCISysId)
// Active
resource.setValue("active", true)
resource.insert()
技術者の稼働状況の確認
// Get available technicians for time slot (ES5 ONLY!)
function getAvailableTechnicians(scheduledStart, scheduledEnd, requiredSkills, territory) {
var available = []
// Get all active technicians in territory
var resource = new GlideRecord("wm_resource")
resource.addQuery("active", true)
if (territory) {
resource.addQuery("territory", territory)
}
resource.query()
while (resource.next()) {
// Check skills
if (requiredSkills && !hasRequiredSkills(resource, requiredSkills)) {
continue
}
// Check availability
if (!isAvailable(resource, scheduledStart, scheduledEnd)) {
continue
}
var user = resource.user.getRefRecord()
available.push({
resource_sys_id: resource.getUniqueValue(),
user_sys_id: user.getUniqueValue(),
name: user.getDisplayValue(),
skills: resource.getValue("skills"),
territory: resource.territory.getDisplayValue(),
})
}
return available
}
function hasRequiredSkills(resource, requiredSkills) {
var techSkills = resource.getValue("skills").split(",")
var required = requiredSkills.split(",")
for (var i = 0; i < required.length; i++) {
if (techSkills.indexOf(required[i].trim()) === -1) {
return false
}
}
return true
}
function isAvailable(resource, start, end) {
// Check for conflicting assignments
var assignment = new GlideRecord("wm_schedule_entry")
assignment.addQuery("resource", resource.getUniqueValue())
assignment.addQuery("start", "<", end)
assignment.addQuery("end", ">", start)
assignment.query()
return !assignment.hasNext()
}
派遣とスケジュール管理(ES5)
作業指示の割り当て
// Dispatch work order to technician (ES5 ONLY!)
function dispatchWorkOrder(workOrderSysId, resourceSysId, scheduledStart, scheduledEnd) {
// Create schedule entry
var schedule = new GlideRecord("wm_schedule_entry")
schedule.initialize()
schedule.setValue("work_order", workOrderSysId)
schedule.setValue("resource", resourceSysId)
schedule.setValue("start", scheduledStart)
schedule.setValue("end", scheduledEnd)
schedule.setValue("state", "scheduled")
schedule.insert()
// Update work order
var wo = new GlideRecord("wm_order")
if (wo.get(workOrderSysId)) {
wo.setValue("assigned_to", getResourceUser(resourceSysId))
wo.setValue("scheduled_start", scheduledStart)
wo.setValue("scheduled_end", scheduledEnd)
wo.setValue("state", "assigned")
wo.update()
}
// Notify technician
gs.eventQueue("wm.work_order.assigned", wo, resourceSysId, "")
return schedule.getUniqueValue()
}
自動派遣
// Auto-dispatch to best available technician (ES5 ONLY!)
function autoDispatch(workOrderSysId) {
var wo = new GlideRecord("wm_order")
if (!wo.get(workOrderSysId)) {
return { success: false, message: "Work order not found" }
}
// Get requirements
var scheduledStart = new GlideDateTime(wo.getValue("scheduled_start"))
var estimatedDuration = wo.getValue("estimated_duration") || "02:00:00"
var scheduledEnd = new GlideDateTime(scheduledStart)
var durationParts = estimatedDuration.split(":")
scheduledEnd.addSeconds(
parseInt(durationParts[0], 10) * 3600 + parseInt(durationParts[1], 10) * 60 + parseInt(durationParts[2], 10),
)
var requiredSkills = wo.getValue("u_required_skills")
var location = wo.location.getRefRecord()
var territory = location.getValue("u_territory")
// Find available technicians
var available = getAvailableTechnicians(scheduledStart, scheduledEnd, requiredSkills, territory)
if (available.length === 0) {
return { success: false, message: "No available technicians" }
}
// Select best match (first available, could add routing optimization)
var bestMatch = available[0]
// Dispatch
var scheduleId = dispatchWorkOrder(workOrderSysId, bestMatch.resource_sys_id, scheduledStart, scheduledEnd)
return {
success: true,
technician: bestMatch.name,
schedule_id: scheduleId,
}
}
モバイルフィールドサービス(ES5)
作業指示のステータス更新(モバイル)
// Update from mobile app (ES5 ONLY!)
function updateWorkOrderFromMobile(workOrderSysId, statusUpdate) {
var wo = new GlideRecord("wm_order")
if (!wo.get(workOrderSysId)) {
return { success: false, message: "Work order not found" }
}
// Update state
if (statusUpdate.state) {
wo.setValue("state", statusUpdate.state)
if (statusUpdate.state === "work_in_progress") {
wo.setValue("actual_start", new GlideDateTime())
} else if (statusUpdate.state === "closed_complete") {
wo.setValue("actual_end", new GlideDateTime())
}
}
// Add work notes
if (statusUpdate.notes) {
wo.work_notes = statusUpdate.notes
}
// Update location (GPS)
if (statusUpdate.latitude && statusUpdate.longitude) {
wo.setValue("u_technician_latitude", statusUpdate.latitude)
wo.setValue("u_technician_longitude", statusUpdate.longitude)
}
wo.update()
return { success: true }
}
作業時間の記録
// Record technician time (ES5 ONLY!)
function recordTimeEntry(workOrderSysId, timeData) {
var entry = new GlideRecord("time_card")
entry.initialize()
entry.setValue("task", workOrderSysId)
entry.setValue("user", gs.getUserID())
entry.setValue("type", timeData.type) // work, travel, break
entry.setValue("start_time", timeData.startTime)
entry.setValue("end_time", timeData.endTime)
// Calculate duration
var start = new GlideDateTime(timeData.startTime)
var end = new GlideDateTime(timeData.endTime)
var duration = GlideDateTime.subtract(start, end)
entry.setValue("duration", duration)
// Notes
entry.setValue("comments", timeData.notes)
return entry.insert()
}
MCP ツールの統合
利用可能なツール
| ツール | 用途 |
|---|---|
snow_query_table | FSM テーブルのクエリ |
snow_execute_script | FSM スクリプトのテスト |
snow_artifact_manage | 構成の検索(action='find') |
ワークフローの例
// 1. Query open work orders
await snow_query_table({
table: "wm_order",
query: "state!=closed_complete^state!=cancelled",
fields: "number,short_description,location,scheduled_start,assigned_to",
})
// 2. Find available technicians
await snow_execute_script({
script: `
var available = getAvailableTechnicians(
new GlideDateTime(),
new GlideDateTime().addHours(2),
'hvac_certified',
null
);
gs.info(JSON.stringify(available));
`,
})
// 3. Get technician schedule
await snow_query_table({
table: "wm_schedule_entry",
query: "resource.user=technician_user_id^startONToday",
fields: "work_order,start,end,state",
})
ベストプラクティス
- スキルのマッチング - 技術者のスキルを要件に合わせます
- テリトリーの計画 - サービス提供地域を最適化します
- ルートの最適化 - 移動時間を最小限に抑えます
- モバイルファースト - 現場での利用を前提に設計します
- リアルタイム更新 - GPS とステータスを追跡します
- 部品管理 - 在庫を追跡します
- 時間の追跡 - 正確な作業時間を記録します
- ES5 のみ - 最新の JavaScript 構文は使用しません
コメント
Markdown 対応