勤怠アラートを自動化し、通知コストを半減させた件について
見出し画像

勤怠アラートを自動化し、通知コストを半減させた件について

こんにちは。エンジニアのふるやです🙃
今回は 勤怠アラートを自動化し、通知コストを半減させた件 をご紹介させていただきます。

経緯

勤怠打刻漏れ等、打刻エラーの有無について、
 スタッフ自身がマイページで確認する必要がある
 ※メール通知はあるが、見落とす可能性が高い
全スタッフの打刻エラー修正を依頼する管理部のコストが大きい

以上により、
打刻エラーが発生したら自動でChatworkでタスク化してくれるツール
(以後「スクリプト」と呼称します)を開発しました!

※導入後の打刻修正申請までフロー
 打刻エラーがあるスタッフへその旨でタスク化する
 → スタッフが気付く
 → 打刻エラー修正申請する
 → タスクを完了する

今回、自動化をがんばってくれるツールたち

Googleスプレッドシート
Googleカレンダー
GoogleAppScript (GAS)
Chatwork

管理用スプレッドシートの各シート情報

・Members : Chatworkアカウント一覧、
       タスク化するChatworkRoom、タスク化するかどうか
  ChatworkRoomはRoomsシートを参照し、プルダウンで選択可能
・Rooms : RoomID、Room名
・Settings : 通知開始・終了日、Logsシートのデータ保持日数
・Logs : 通知対象者名、勤怠実績日、打刻エラーの理由、タスク化した日

事前準備

タスク化する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() で取得しています。

getRange() は処理が遅いのであまり使わないほうが良いそう
(GASでは、プロパティが推奨されている?
 これも非エンジニアには分かりにくいと思ったので使っていません)

指定日・時間のみ実行する方法
スクリプトは営業日のみ実行します。条件は以下です。

Settingsシートの通知開始・終了日時かどうか
土日ではないか
Googleカレンダーの「会社の休日」「会社の就業日」「日本の休日」
 を元に判断した「営業日」かどうか

自動実行設定について
1. スクリプトエディタを開き、左メニューのトリガーを押下する。

スクリーンショット 2021-08-11 19.17.27

2. 以下の内容のトリガーを追加する
 ※実行する関数は各々のスクリプトに合わせて修正してください

スクリーンショット 2021-08-11 19.13.02

※運用前にあらかじめ手動実行し、スクリプトから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_)が綴らせて頂きました。

スキを頂きありがとうございます!是非SNSでシェア頂けますと幸いです!
ソーシャルゲームアプリ開発・運営を行う株式会社ダンクハーツのメンバーが、自身が持つ技術/スキル/知識を発信していきます📝 ダンクハーツに集う“原動力たち”を知って頂ける機会となれば幸いです✨ https://www.dank-hearts.co.jp/recruit/