勤怠アラートを自動化し、通知コストを半減させた件について
こんにちは。エンジニアのふるやです🙃
今回は 勤怠アラートを自動化し、通知コストを半減させた件 について、
綴らせていただきます。
経緯
以上により、
打刻エラーが発生したら自動でChatworkでタスク化してくれるツール
(以後「スクリプト」と呼称します)を開発しました!
今回、自動化をがんばってくれるツールたち
管理用スプレッドシートの各シート情報
事前準備
・タスク化するChatworkアカウント
(以後「タスク化ユーザー」と呼称します)を作成する
・タスク化ユーザーのコンタクトにスタッフ全員を追加する
・タスク化ユーザーのAPIトークンをスクリプトに設定する
※トークン発行方法はChatwork公式のヘルプをご参照ください
・Settingsシートに
「通知開始・終了日、Logsシートのデータ保持日数」を設定する
・Googleアカウント(以後「実行ユーザー」と呼称します)を作成する
・打刻エラーのメールを受信するよう勤怠システムで設定する
・勤怠ツールに登録するスタッフ名と
Chatworkアカウントのスタッフ名は完全一致するようにする
・Googleカレンダーに「会社の休日」「会社の就業日」の
マイカレンダーを作成し、管理部に設定を依頼する
※毎年度必要
処理の流れ
1. タスク化ユーザーのChatworkのコンタクト情報を取得し、
Membersシートを最新化する
2. SettingsシートからLogsシートのデータ保持日数を参照し、
それより前のログは削除する。また、削除した行数だけ行追加する
3. タスク化ユーザーのコンタクトのアカウント情報を取得し、
Membersシートを参照し登録されていないものは登録する
4. 実行ユーザーのGmailにて、
全スレッドから指定条件(指定文字列・未読・受信日など)で絞りこみ、
該当のスレッドを抽出する
5. スレッドのメールから打刻エラーがあるユーザーを抽出する
6. Membersシートに設定されているChatworkRoomにて、
上記4で抽出したユーザーにタスク化する
7. タスク化に成功した、あるいはタスク化が無効の場合は
スレッドを既読にする(ここまでを全スレッド分実行する)
8. 完了
備考
設定について
実用する非エンジニアが変更できるようSettingsシートにまとめています。
通知スクリプト実行時に getRange() で取得しています。
指定日・時間のみ実行する方法
スクリプトは営業日のみ実行します。条件は以下です。
自動実行設定について
1. スクリプトエディタを開き、左メニューのトリガーを押下する。
2. 以下の内容のトリガーを追加する
※実行する関数は各々のスクリプトに合わせて修正してください
※運用前にあらかじめ手動実行し、スクリプトからGoogleカレンダーやGmailなどへのアクセス権限を許可しておく
スタッフ就職時の対応
Membersシートの「タスク化するかどうか」チェックボックスのチェックを入れる
スタッフ退職時の対応
Membersシートの「タスク化するかどうか」チェックボックスのチェックを外す
よく使用した便利なGASメモ
スクリプトをロックし、多重実行を回避する
var lock = LockService.getScriptLock();
スクリプトのロック状況を確認し、後続の処理を実行する
// スクリプトのロックを試みる
if (lock.tryLock(1000)) // 引数はタイムアウトするまでのミリ秒数
{
// 処理を実行する
exec();
// ロックを解除する
lock.releaseLock();
}
else
{
// 実行できなかった旨のポップアップを表示する
Browser.msgBox("現在、別の処理が実行中の為、実行できませんでした。\\n\\n時間をおいて、再度実行して下さい。");
}
Gmailの受信トレイを指定した検索文字列で検索し、スレッドを取得する
// 検索文字列 ※未読のもの、該当文字列を含む、該当文字列を含まないなど、指定できます
var search_string = "is:unread 勤怠アラートが発生しています -{休暇申請}";
// 指定した検索文字列のスレッドを取得する ※500件まで取得
var threads = GmailApp.search(search_string, 0, 500);
スレッドの内容を確認、タスク化、既読処理を実行する
// スレッドの数だけ処理を実行する
for (var i = 0; i < threads.length; i++)
{
// スレッドを一件ずつ取得する
var msgs = GmailApp.getMessagesForThread(threads[i]);
// メッセージの数だけ処理を実行する
for (var j = 0; j < msgs.length; j++)
{
// メッセージの本文を取得する
var tmpBody = msgs[j].getBody();
// スクレイピング的に文字列を取得、使用する処理
hoge(tmpBody);
// チャットワークAPIでタスク化
...
}
// 既読にする
threads[i].markRead();
}
シートから、日本時間の形式で時間を取得する
var endDate = Utilities.formatDate(sheet.getRange('A1').getValue(), 'Asia/Tokyo', 'yyyy/M/d');
指定範囲の最終入力行を取得する
var lastRowNumRangeAA = sheet.getRange('A:A').getValues().filter(String).length;
指定範囲の値を取得し配列化する
var values = sheet.getRange('A1:A1000').getValues();
values = Array.prototype.concat.apply([],values);
チャットワークAPIでタスク化する
// APIトークン
var CHATWORK_API_TOKEN = "hogehoge";
// 期限 ※今回は一律で現在日時にする
var limit = Math.floor( (new Date()).getTime() / 1000 ) ;
// 期限タイプ(none, date, time)
var limit_type = "time";
// HTTPメソッド
var method = "post";
// タスク化したいChatworkのroom_id
var room_id = 11111111;
// 本文
var body = encodeURI("メールを確認して下さい。\n※期限 : " + endDate + "まで※");
// 送信先
var to_ids = user_ids;
// URLを組み立てる
var url = "https://api.chatwork.com/v2/rooms/"+room_id+"/tasks?body="+body+"&limit="+limit+"&limit_type="+limit_type+"&to_ids="+to_ids;
// APIを実行する
execChatworkApi(method, url);
// API実行関数は関数化、汎用化する
function execChatworkApi(method, url)
{
var _url = url;
var _method = method;
var params = {
headers : {"X-ChatWorkToken" : CHATWORK_API_TOKEN},
method : _method
};
var strResponse = UrlFetchApp.fetch(_url, params);
var json = JSON.parse(strResponse.getContentText());
return json;
}
今後の課題
窓に追加されている前提のシステム
タスク追加対象ユーザーがMembersシート指定のRoomに存在しない
場合に、Chatwork API 実行時エラーになる
起動時間5分を超えるとスクリプトが途中で停止する
規模が大きい場合は処理を分割して実行できるような仕組みを考えるべき
以上、エンジニアのふるや(@h_furuya_)が綴らせていただきました。