ServiceNow上に「資産番号管理」機能を実装する

はじめまして。
DeNAのIT資産管理システムをServiceNow上で実施している@wasa17です。
ServiceNowのITAM（ITAssetマネジメント）機能を使用したIT資産管理を実施する中で得た開発Tipsなど書いていきたいと思います。
まだ使い始めて日が浅いため至らぬ点多いと思いますが、お手柔らかにお願いします。

## 今回の記事が参考になりそうなシーン
* 条件分岐によりフィールド/レコード値を自動で設定したいとき
    * Aの値がnだったらBの値にxを入れる・・など。
* フォーム画面に機能の呼び出しボタンをつけたいとき
    * 手入力させたくないフィールドに自動入力をさせたい・・など。
* 新規レコード作成時のみスクリプト処理を実行したいとき

## 【今回のテーマ】ServiceNow上に資産番号の発行・管理機能を実装する
資産管理をする上で「資産番号」（そのモノを示すユニークなID）が必要になります。
会社から貸与されているPCを使っている場合、バーコードや英数字で「資産番号」が付いているのではないでしょうか。
ServiceNowには「レコードへの自動付番（オートナンバー）」機能がありますが、以下の通り要件に合わない箇所があったので機能を実装しました。
* テーブルごとに一意の番号設定となる
* それゆえ、条件指定で番号体型を分ける場合に細かい指定ができない
    * 今回は「資産の区分」（リースor購入など）で番号体型を分けたいので自動付番をそのまま使えませんでした

## 実現したい要件
* 資産の新規登録時に「資産の種別（購入品・リース品・モニタ・周辺機器等）」の選択値をもとに資産番号の体型を特定し、ルールに則った資産番号を発行できること。
* 資産番号は手入力でなく、「取得ボタン」から呼び出すことで正しい値を登録できること。（タイプミスを無くすこと。）
* 資産番号は管理用テーブルで管理し、担当者による番号体型の追加やメンテナンスを可能にすること。
* 番号は「前プリフィクス」-「番号（0埋め5桁）」-「後プリフィクス」の既存ルールにあわせること。
    * プリフィクスは番号体型によりあったり/なかったりします。

## 使用した環境
* ServiceNow バージョンSan Diego
* ITAMサブスクリプション

## 実装手順
### 1.カスタムテーブル（番号管理テーブル）の作成
まずは番号管理用のカスタムテーブルを作成します。
カスタムテーブルは作成できる数に限りがあります。（契約内容により数が異なります）
そのためカスタムテーブルを作成する際はなるべく汎用的に使えるように心がけています。
※今回作成したテーブルは資産番号のほか、ワークフローで使用する各種処理IDの発行や固定資産番号なども登録し、「ServiceNow上で管理する各種番号の汎用テーブル」として使用しています。
今回のカラムは以下のように設定しました。
| カラム | 内容 |
|:-|:-|
|区分  |番号の区分を表すタイトルです。購入資産・リース資産・○○処理など、端的に番号の内容を表す名称を日本語を入れます。 |
|前プリフィクス   |”hoge-”など|
|番号|  実際の数値 |
|後プリフィクス|”-hoge”など|
|会社|参考情報に会社情報を入れました。リース資産の場合はリース会社の情報を入れていますが、不要だったかなぁとも思っています。 |

### 2.資産登録画面のカスタマイズ
#### （1）資産種別を参照するフィールドの追加
* 「資産種別」を参照するフィールドを追加します。
    * ここに入っている値をキーに資産番号を取得します。
    * 番号管理テーブルの「区分」を表示するように設定しています
    * 資産番号の設定に必要な項目なので「必須」設定。
* 資産番号の手入力を禁止します（後述のボタンからのみ発行可能）
    * 手入力ミスを防止するための措置です。
    * また、後述するボタンの表示抑止との組み合わせで「資産登録後の資産番号変更」が起きないようにします。
        * 設定はUIポリシーでフィールドを「読み込み専用」にすればOKですね。
            * 設定するとフィールドがグレーアウトして入力が不可になります。
        * フィールドが読み込み専用でもスクリプトからの入力は可能です！
![資産登録画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2820675/2f5562b0-1748-4c23-6a62-179eaeb53a0b.png)

#### （2）「資産番号取得」ボタンを設置
* 次に「UIアクション」でフォームボタンを設置します
    * 「フォームボタン」をONにするとフォーム上部にボタンが出ます。
    * レコード作成時のみ表示させたいので「レコード更新時に表示」はOFFにしています。
    * クライアントからスクリプトを呼び出すため「クライアント」をONにします。
    * アクション名はスクリプト記載と合わせます。
![UIアクションの追加-資産番号取得](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2820675/106135e6-f709-9565-6afb-4a0ac1fed840.png)

#### （3）「資産番号取得」ボタンに番号呼出しスクリプトを追加
* スクリプトを以下のように設定しました。
* この処理は「番号を取得」するのみで「番号テーブルの更新（カウントアップ）」は行いません。
    * カウントアップはレコード登録時に実施します。
    * じゃあレコード登録時に裏で設定すれば良いのでは？
        ・・と思う方もいらっしゃるかもですが、「間違った番号が設定されていないか」を担当者に視認してもらうためにわざと目に見える形で番号を取得しています。
```JavaScript:資産番号取得
function getassetid() {
var gr = new GlideRecord('u_asset_number'); //番号管理テーブルを取得
var queryString = g_form.getValue('u_asset_type'); //フォーム「資産区分（u_asset_type）」の設定値を取得
gr.addQuery('sys_id',queryString); //フォーム値のsys_idをクエリ条件に設定
gr.query();	//クエリ実施
	while (gr.next()) {
    var assetid = gr.u_asset_number; //クエリ結果の「番号」カラムを取得
    var num = parseInt(assetid); //数値に変換
    var ex = num + 1; //資産管理番号に+1する
    var ez = ('00000' + ex ).slice(-5); //番号を5桁にする
    g_form.setValue('asset_tag', gr.u_prefix + ez); //前プリフィクス+5桁の番号+後プリフィクスをフォームの「資産番号」にセットする
    }
}
```
（参考）[GlideRecordクエリの書き方-GlideRecord Query Cheat Sheet](https://servicenowguru.com/scripting/gliderecord-query-cheat-sheet/ "GlideRecord Query Cheat Sheet")
（参考）[フォーム入力値の取得-The Fantastic g_form getReference Callback](https://servicenowguru.com/scripting/client-scripts-scripting/gform-getreference-callback/ "The Fantastic g_form getReference Callback")
#### （4）「資産番号クリア」ボタンを設置
資産番号フィールドは編集できないので間違って設定した場合のクリアボタンを付けます。
※資産区分を間違えて資産番号を取得した時にフィールドの値を消去するため。
* 資産作成後に資産番号を消されると厄介なので「レコード作成時に表示」をONに
* その他は資産番号取得と同じです。
![UIアクションの追加-資産番号クリア](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2820675/89c11ffe-3d54-8259-d0a4-1902d2c3884d.png)
* スクリプトはとっても単純
* フォームの値を''（空欄）にするだけ
```JavaScript:資産番号クリア
function clearassetid() {
    g_form.setValue('asset_tag', '');
    g_form.setValue('u_asset_no', '');
 }
```
### 5.「資産番号」が設定されていない場合はレコード登録できないようにする
これまでの手順でフォームの設定とボタンの設置が完了しました。ここから仕上げに入っていきます。
* 資産番号は「登録時にしか設定できない」ルールとしているので抜けがあると後から登録できません。
* 入力漏れを防止するためビジネスルールを追加します
    * 資産番号が入っていない状態で登録しようとすると→ 画面上にメッセージが出て登録できないようにしました。
![実行タイミング](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2820675/e5020a63-4415-f2c1-1f78-368b4f609211.png)
![アクション](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2820675/0de782b8-4ca8-7dd0-97e8-f7990cf4a787.png)

### 6.登録時に資産番号を再度取得して番号の重複を防ぐ→番号管理テーブルを更新する
* 誰かが同時に資産登録をしている可能性があるのでレコード登録時に改めて資産番号を取得→設定します。
* 同一スクリプト内で「番号取得→番号のカウントアップして番号テーブルを更新」することで最新値で番号管理テーブルの値を更新します。
* この処理はレコード追加時（送信ボタンを押した時）に裏で動けば良いので「クライアントスクリプト」で設定します。
    * タイプを「onSubmit」にすると送信時に動きます
    * 「新規登録」の際のみ番号のカウントアップを行いたいため、スクリプト内に`'if( g_form.isNewRecord() ){}'`を仕込みます。
    * これを忘れると「レコードを更新」しただけでカウントアップが裏で起こるので要注意です！
![クライアントスクリプト](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2820675/3c3149a5-4f7b-5fc6-441b-db5f040bb405.png)

```JavaScript:資産番号カウントアップ
function onSubmit() {
   //Type appropriate comment here, and begin script below
if( g_form.isNewRecord() ){ //新規レコード作成時のみスクリプトを実施するために設定
var gr = new GlideRecord('u_asset_number');
var queryString = g_form.getValue('u_asset_type');
	gr.addQuery('sys_id',queryString);
	gr.query();	
	while (gr.next()) {
    var assetid = gr.u_asset_number;
    var num = parseInt(assetid);
    var ex = num + 1; //資産管理番号に+1する
    var ez = ('00000' + ex ).slice(-5); //資産管理番号を5桁にする
    g_form.setValue('asset_tag', gr.u_prefix + ez); //改めて最新の番号を資産番号を登録レコードに付与。ここまでは資産番号取得ボタンと同じ動き。
	gr.u_asset_number = ex;　//+1した番号をテーブル書き戻し値として設定
    gr.update(); //テーブル更新
	}
  }
}
```
これで資産区分から資産番号を発行して資産を登録する流れが完成しました。
### （参考）フォーム入力以外での資産登録と番号の登録
PC資産を1つ1つ手で入力するのは大変です。
そのため、スプレッドシートと連携した「バルク登録」機能も別に作っています。
バルク登録の際も「資産区分」をシートのセルに記載しておき、番号管理テーブルから値を取得して登録するジョブを組んでいます。
スプレッドシートを使用した自動登録ジョブについては機会があればエントリにしてみます。
### まとめ
* ServiceNowには自動付番の便利機能があるが、細かい要件への対応が難しい
* カスタムテーブルとクライアントスクリプトを組み合わせればユーザー要件の番号管理ができる
* スクリプトはGlideRecordを使って記載すればかなりパターン化できる（現にこのスクリプトもテンプレ的に汎用してます）
* 次回はカスタムAPIの作成について記載予定です。