プリザンター）サーバスクリプトで誕生日から年齢を自動計算する（改）

#はじめに

##その1
昨年のプリザンターAdvent Calendar 2020でサーバスクリプトの記事があり、「便利そうだな～」と思いつつ使う機会がありませんでした。
今回、サーバスクリプトを使う機会がありましたので、記事にします。
（思った通りに動かなかった＝未完成の部分もありますのであしからず）

##その2
https://pleasanter.org/manual/faq-automatically-calculate-age

上記の記事で、誕生日から年齢を自動計算できるのですが、下図のようにレコードを作成・更新したタイミングでしか計算されません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/925662/5c0448b2-bab0-bcaf-1db7-9c83941ef15a.png)
　　　　　　　　　　　　　　　　　　　↓↓↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/925662/5e69def1-a7c2-35ca-d799-85bcd0ed9d57.png)

作成・更新された後に誕生日を迎えても年齢は更新されませんが、誕生日を迎えたレコードを手動更新するのは現実的ではありません。
外部プログラムで毎日全レコードの年齢を確認、更新することも考えたのですが、サーバスクリプトを使ってリアルタイムで計算することにしました。

#方針
* 画面を表示する時にサーバスクリプトを実行して、正しい年齢を表示させる（DBの年齢は更新されなくてもOK）
* 年齢を一覧画面に表示させ、フィルタやソートをかけられるようにする（DBの年齢が更新されていなくても機能すること）

#画面表示
編集画面、一覧画面を表示する時に年齢を計算させるため、サーバスクリプトの実行条件に「画面表示の前」「行表示の前」を追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/925662/6f23f90e-c7a1-7d3e-2a99-66307891e567.png)
これで、画面表示する前に誕生日（生年月日）から年齢を計算するようになったため、画面に正しい年齢が表示されます。

#フィルタ
表示の前にサーバスクリプトを実行することで正しい年齢が表示されますが、DBの値が更新されていないとフィルタやソートは正しく機能しません。
そこで、フィルタに関しては、サーバスクリプトで「年齢のフィルタ」を「誕生日（生年月日）のフィルタ」に変換することにしました。

実行条件を「ビュー処理時」に設定して、以下のサーバスクリプトを設置します。

```javascript
// 年齢にフィルタがかかっているかチェック
if(view.Filters.NumA !== void 0){
	// 生年月日のフィルタ
	filter_DateA = '[\"';
	
	// 文字列「[\"30,40\"]」を「30」と「40」に変換する
	filter_NumA = JSON.parse(view.Filters.NumA)[0].split(",");
	
	// フィルタの開始年齢を年月日に変換する　！！何故かsplitで順番が逆転する！！
	if(filter_NumA[1].length > 0){
		start_date = new Date();
		start_date.setFullYear( start_date.getFullYear() - parseInt(filter_NumA[1]) - 1);
		start = start_date.getFullYear() + '/' + (start_date.getMonth() + 1) + '/' + start_date.getDate();
		start += ' ' + start_date.getHours() + ':00:00';
		filter_DateA += start;
	}
	
	filter_DateA += ',';
	
	// フィルタの終了年齢を年月日に変換する　！！何故かsplitで順番が逆転する！！
	if(filter_NumA[0].length > 0){
		end_date = new Date();
		end_date.setFullYear( end_date.getFullYear() - parseInt(filter_NumA[0]));
		end = end_date.getFullYear() + '/' + (end_date.getMonth() + 1) + '/' + end_date.getDate();
		end += ' ' + end_date.getHours() + ':00:00';
		filter_DateA += end;
	}
	
	filter_DateA += '\"]';

	// 年齢のフィルタを解除
	view.Filters.NumA = '';

	// 生年月日のフィルタをセット
	view.Filters.DateA = filter_DateA;
}
```

#ソート
ソートに関しては、「年齢のソートを無効化して、生年月日でソートするようメッセージを表示する」ことにしました。

##年齢のソート無効化
年齢フィルタを生年月日フィルタに変換するサーバスクリプトの末尾に、ソートを無効化するスクリプトを追記します。

```javascript
// 年齢のソートを解除
view.Sorters.NumA = 'release';
```

##生年月日でソートするようメッセージを表示
年齢でソートしようとした時に、生年月日でソートするようメッセージを表示します。
これはスクリプトで実現します。
出力先を「一覧」にして、以下のスクリプトを設置します。

```javascript
$p.events.on_grid_load = function () {
	// 一覧表示でタイトル「年齢」がクリックされた時の処理
	$('th[data-name="NumA"]').on('click', function(e) {
		alert("年齢は生年月日でソートしてください");
	});

	// ======== 表の子要素に変化が起こった時の処理 ========
	// 　・ソートで子要素が入れ替わる
	// 　・スクロールでtbodyが追加される
	var observer = new MutationObserver(function(){
		$('th[data-name="NumA"]').off('click'); // 2回呼び出されるのを防ぐ

		// 一覧表示でタイトル「年齢」がクリックされた時の処理
		$('th[data-name="NumA"]').on('click', function(e) {
			alert("年齢は生年月日でソートしてください");
		});
	});

	// 監視対象の要素オブジェクト
	const elem = document.getElementById('Grid');

	// 監視時のオプション
	const config = { 
		attributes: false, 
		childList: true, 
		characterData: false 
	};
		
	// 要素の変化監視をスタート
	observer.observe(elem, config);
}
```

以上で、レコードを更新しなくても正しい年齢を表示して、フィルタやソートもできるようになりました。

#思った通りに動かない部分が・・・
年齢でフィルタをかけた後、生年月日のフィルタを微調整しようとしたところ機能しませんでした。
サーバスクリプトで「年齢フィルタが設定されている ⇒ 生年月日フィルタに変換、年齢フィルタを解除」を実行しているのですが、その後に生年月日フィルタを変更しても「年齢フィルタが設定されている ⇒ 生年月日フィルタに変換、年齢フィルタを解除」が実行されていました。
要は```view.Filters.NumA = '';```を実行しても一時的に空になっただけで、次にサーバスクリプトが呼び出された時に```view.Filters.NumA```の値は復活しているということのようです。
年齢でフィルタをかけた後に生年月日のフィルタを微調整したければ、画面で年齢フィルタをクリアする必要がありました。
（```view.Filters```や```view.Sorters```の値の変更が維持されるようになると嬉しいです。）


#おわりに
従業員数百人規模の拠点でローテーション異動や昇進の管理をExcelで行っていたものをプリザンターに移行しようとしています。
年齢で抽出、並び替えしたいシーンがあるだろうと想定して、この記事のような機能を用意しました。
従業員の基本情報や異動・昇進履歴を格納・表示するところまで完成し、今は異動・昇進候補や補充要員数を提示する機能を作っています。（外部プログラムからAPIでプリザンターのデータを取得して、データ変換したものをExcel出力、みたいな感じです。）