Google Apps Script で勤怠フォームを作って出欠確認を自動化する

ぐるなびでは、勤怠時間の管理はIDカードを使用しております。本記事は、イレギュラーが起きた際の連絡手段をシステム化させた方法・背景について記載しております。

f:id:yoshitsugu_gnavi:20160609160501p:plain

はじめまして。ぐるなびエンジニアの吉次洋毅と申します。
2014年に新卒で入社して、現在3年目の24歳。ぐるなびのポイントを用いた決済系のスマホアプリやAPI、その他店舗向けのWEBページ等の開発を担当しています。

気になったことはやってみるタイプなので、趣味はカメラ・ギター・スノーボードなどまとまりのないラインナップとなっております。最近ハマっているのは会社から歩いて家まで帰ること。帰り道にはラーメン屋がたくさんあるのでラーメン食べ歩きがマイブームになりつつあります。

私の記事では、おもに新人エンジニア向けに技術の捉え方や社内における仕事への取り組み方、ハッカソン等の社外イベントなどを題材にしていこうと考えています。

初回となる今回は、私が2年目の4月に行った勤怠連絡方法を変え、出欠確認を自動化した経緯を振り返ってみようと思います。

1. 勤怠フォーム作成の背景

まずはフォームを作ろうと思った背景から。

1.1 勤怠メールのコピペが業務になった新卒1年目

勤怠フォームが適用される2015年の4月まで、ぐるなびの企画・開発部門ではメールで勤怠連絡をしていました。出社したら社員からのメールをチェックし、スプレッドシートに勤怠状況を転記するのが新卒1年目の業務だったのです。

  1. メールから氏名、対象日、勤怠ステータスを拾う
  2. 名字しか書かない人もいるので社員検索をかけて部署から個人を特定
  3. スプレッドシートへ転記

くわえて、転記作業を非効率にさせる要因がいくつかありました。

  • フォーマットがバラバラ→自動化しにくい
  • メールの送り先がバラバラ→複数のMLをチェックする必要がある
  • 情報の粒度がバラバラ→担当者がある程度汲み取る必要がでてくる
  • 範囲が広すぎる(企画・開発部門合わせて400名近い人数が対象)→作業量の単調増加

1.2 フォームを使ったシステム化決定、要件を整理

運用コストに問題意識をもっていたこともあり、組織変更のタイミングで勤怠連絡の効率化を提案しました。当時は2年目でしたが提案が通り、勤怠連絡にフォームを使う方針で進めることに。 まずは要件を整理する作業から始めました。

  • プライバシー(メールアドレス、休暇理由など)の保護と業務調整等の必要な連絡の両立
  • 利用者の負担にならないこと(複数回連絡させない)
  • 出欠の状態は広い範囲で把握できること

自分たちの業務を効率化するためのウラ要件も合わせて定義します。

  • 本来の要件を満たしつつも、手運用が一切発生しない仕組みにすること

2. 勤怠確認自動化のシステム構成

以上の要件を満たす仕組みを考え、構築しました。
f:id:yoshitsugu_gnavi:20160609160501p:plain

構成要素一覧

No. 構成要素 概要
1 勤怠フォーム 社員が勤怠連絡をする際に利用するフォーム
2 回答記録シート フォームを送信した際に自動的に生成される回答が記録されるシート
3 スクリプト 回答記録シートから勤怠確認シートに転記し、メールを送信する記述をしたGoogle Apps Script によるプログラム
4 勤怠確認シート 広い範囲への公開に値する最低限の情報のみが記載されるシート
5 メール 勤怠のステータスに加えて、休暇理由・業務調整等、チームメンバーのみに伝えたいことが含まれるメール

3. スクリプトで勤怠確認シートを自動生成

3.1 メインメソッド紹介

スクリプトでは3つの大きな処理をおこないます。

  1. 回答データの取得
  2. 公開用スプレッドシートへの転記
  3. チームMLへのメール送信

時限式(1分間隔)で実行されるスクリプトのメインメソッドはこちら。

function main(){  
  var ss_answer_org = SpreadsheetApp.getActive();    //回答シート取得  
  var sheet_answer = ss_answer_org.getSheetByName( 'answer' );  
  var last_row = sheet_answer.getLastRow(); //最終行番号を取得  
  
  for( var index = 2; index <= last_row; index++ ){  
    autoInform( sheet_answer, 2 );  // 2行目のデータを処理  
    sheet_answer.deleteRow( 2 );     // 2行目のデータを削除  
  }  
}  

メインメソッドでやることは大きく分けて3つ。

  1. 対象のシートを取得して、処理対象行数を確定
  2. スプレッドシート転記・メール送信を行う別メソッド呼び出し
  3. 処理を終えた行の削除

このうち2と3は1で取得した処理対象行数分ループを回します。

3.2 回答データを取得する

まずは勤怠回答を取得する準備を行います。

[Tips1]スプレッドシートの取得 - getActive, openById

スプレッドシート(Excelで言うブックの単位)を取得するにはgetActiveやopenByIdメソッドを使用します。フォームに対応するスプレッドシートはgetActiveで取得可能なようです。もちろんopenByIdで取得することも可能。

var ss_copy_to = SpreadsheetApp.openById('[転記先のスプレッドシートID]');  

スプレッドシートIDは対象のURLに含まれる文字列です。ブラウザでスプレッドシートを開くと、アドレスバーに下記のようなURLが表示されるかと思いますが、d/に続く「XXXXXXXXXXXXXXXX」がスプレッドシートIDとなります。

https://docs.google.com/spreadsheets/d/XXXXXXXXXXXXXXXX/edit#gid=999999  

[Tips2]シートの取得 - getSheetByName

スプレッドシートに含まれる1枚のシートを取得するにはgetSheetByNameを使用。SpreadsheetAppクラスのオブジェクトが持つ関数で名前を指定してシートオブジェクトを取得できます。

[Tips3]行の削除 - deleteRow

役目を終えたデータは消す方針で作成していたので、処理したデータは削除しています。ここではループの回数分、常に2行目を処理・削除しています。 一見、不自然に見えますね。行を消した時のスプレッドシートの挙動を見てみましょう。

初期状態

1 名前 ステータス
2 鈴木 お休み
3 田中 遅刻

deleteRow(2)実行後

1 名前 ステータス
2 田中 遅刻

このように、行を削除するとデータが上に詰められます。そのためヘッダ行(1行目)は無視して常に2行目を処理・削除対象としていればOKということになります。処理→削除のかたまりでループを回していますが対象行を全て処理した後にdeleteRowsメソッドで一度に消すことも可能です。今回は複数行に対する操作の途中でエラーが発生した場合に、正常に処理されたデータが残らないように1行ずつ処理・削除をしています。

3.3 データ取得やスプレッドシートを設定する

スプレッドシートへの転記やメール送信前に、データ取得やスプレッドシートの設定、メール送信先の設定など必要な準備をします。

/******セルの値取得******/  
var time_stamp = sheet_answer.getRange( target_row, 1 ).getValue();  
var depart = sheet_answer.getRange( target_row, 2 ).getValue();  
var name = sheet_answer.getRange( target_row, 3 ).getValue();  
var date_from = sheet_answer.getRange( target_row, 4 ).getValue();  
var date_to = sheet_answer.getRange( target_row, 5 ).getValue();  
var status = sheet_answer.getRange( target_row, 6 ).getValue();  
...以下略。  
/******セルの値取得******/  
  
  
/******初期設定******/  
// 転記先のスプレッドシート  
var ss_copy_to = SpreadsheetApp.openById('[転記先のスプレッドシートID]');  
  
// コピー先シート  
var sheet_copy_to = ss_copy_to.getSheetByName('[転記先のシート名]');  
  
...以下略。  
/******初期設定******/  
  

[Tips4]セルの値取得 - getRange, getValue

このときセルの値を取得するためにはgetRangeとgetValueメソッドを使用します。

var time_stamp = sheet_answer.getRange( target_row, 1 ).getValue();  

getRangeはセルの位置を指定するメソッドで、第一引数に行番号、第二引数に列番号を与えます。
行番号・列番号は0ではなく1から開始することに注意しましょう。

3.4 公開用スプレッドシートに転記する

いよいよ広範囲に公開されるスプレッドシートへの転記です。コピー先のシートオブジェクトやフォームで入力された値を受け取って、別のシートに転記します。

function addToPublicSheet( sheet_copy_to, depart, name, date, status, remarks, time_stamp ){  
  // コピー先の最終行+1取得  
  var to_row = sheet_copy_to.getLastRow()+1;  
  
  // 公開用シートへのコピー  
  var today_function = '=if(TODAY()=D' + to_row + ',"Today","")'; //本日判定関数  
  var holiday_function = '=if(OR(WEEKDAY(D' + to_row + ', 2)=6,WEEKDAY(D' + to_row + ', 2)=7), "Holiday", "")';   // 休日(土日)判定関数  
  sheet_copy_to.getRange(to_row, 1).setValue(today_function);  
  sheet_copy_to.getRange(to_row, 2).setValue(holiday_function);  
  sheet_copy_to.getRange(to_row, 3).setValue(time_stamp.toString());  // タイムスタンプを設定  
  sheet_copy_to.getRange(to_row, 4).setValue(date.toString());//日付をコピー  
  sheet_copy_to.getRange(to_row, 5).setValue(depart.toString());//所属をコピー  
  sheet_copy_to.getRange(to_row, 6).setValue(name.toString());//氏名をコピー  
  sheet_copy_to.getRange(to_row, 7).setValue(status.toString());//状況をコピー  
  sheet_copy_to.getRange(to_row, 8).setValue(remarks.toString());//備考をコピー  
}  

[Tips5]セルへの値入力 - getRange, setValue

セルへの値入力は値取得と似たような要領で実装できます。getRangeでセルを指定するのは同様で、setValueを用いて書き込みます。ここでgetValueで取得した値を書き込む場合は注意。getValueではオブジェクト型が返却されるため、純粋な文字列として扱う場合はtoStringで変換してやります。

[Tips6]便利な関数 - 本日判定関数,土日判定関数

公開用スプレッドシートにはフォームで入力された値以外に2つの関数を書き込んでいます。
1つめは本日判定関数です。
「今日の勤怠」を確認したい場合に、対象データのみを抽出できる関数です。この例では判定用の列の値が「Today」になっている行を抽出すればOK。

=if(TODAY()=[対象日が入ったセル],"Today","")  

もう1つは土日判定関数。 金曜日と翌週月曜日を有給にするといったように、土日をまたいだ期間を指定してフォームを入力した場合に有効な関数です。期間を指定してフォームを入力すると一日ごとに行が生成されるようにしていますが、土日の行は不要なのでフィルタリングして非表示にすることが可能です。この場合は判定用の列が「Holiday」以外のものを抽出すればOKです。

=if(OR(WEEKDAY([対象日が入ったセル])=6,WEEKDAY([対象日が入ったセル])=7), "Holiday", "")  

4. 運用のはなし

仕組みを作るのは良しとして、正しく使ってもらわなければ意味がありません。

4.1 利用者向けの運用ルールの策定

そこでユーザガイドを部門に展開しました。 ガイド作成時に意識したポイントは以下。

  1. それぞれの入力項目や選択肢の意味を明確に定義すること
  2. スプレッドシートとメール転送の全体的な仕組みが理解できること
  3. 入力ミス、修正時、宛先エラー時の対応方法が分かること
  4. 入力ミスを防ぐために準備しておくこと(チームMLの辞書登録)

特に力を入れたのが、みんなが便利に使えるように多くのTipsを用意したことです。ホーム画面へのショートカット作成や宛先メールアドレスの辞書登録、スマホからも利用できるようにするなど作業時の負担を減らせるようにしています。

4.2 手運用の削減

企画・開発両部門で400名近いメンバーを管理していたため、以前は多い時だと20分程度運用作業が発生していました。現在は勤怠連絡範囲の見直しとシステム化により、手運用が0になりました。

5. まとめ

5.1 Google Apps Script を有効活用する

改めて自分で作ったスクリプトを見直すと、直したい箇所がたくさん発見できるのですが、 細かいことをあまり気にせず業務効率化のために手軽にササッと書けるのがGoogle Apps Script の良いところだと思います。Google Apps™を導入している企業も多いと思いますので、是非活用することをおすすめします。

5.2 組織に合った方法を考えてみる

勤怠の連絡方法は様々なものが考えられますが、会社の規模や組織体系、利用しているツールによって適している方法は異なります(少人数への連絡で事足りるならチャットなど)。弊社の企画・開発部門の場合、まだ人数がさほど多くなかった頃の方法が組織の規模が大きくなってからも残り続けていたため、運用上の問題が出はじめていました。
勤怠に限らずその時々で最適な方法を考えましょう。

5.3 効率化という観点から組織・業務を見直す

新卒で配属されると、私が担当した勤怠確認のように、直接事業の本質に影響しないように見える業務を任されることがあるかもしれません。 ただそうした仕事の積み重ねの中で、まずは自分の仕事を効率化するという観点から組織や業務について冷静に見てみるのも良いのではないでしょうか? 効率化を提案して形にできれば、自分が楽になるだけでなく、周りの人も幸せになるかもしれません。

2016/09/20 16:18 追記:冒頭に弊社の勤怠システムと今記事のシステムの目的について追記しました。

お借りした素材:

Google Developers

Icon made by Freepik from www.flaticon.com

Icons made by Freepik from www.flaticon.com is licensed by CC 3.0 BY

Google Apps は、Google Inc. の登録商標です。

お知らせ
ぐるなびでは一緒に働く仲間を募集しています。



吉次洋毅

1991年生まれ・山口県出身。 学生時代は高専で情報電子工学を専攻し、現在はぐるなびのWEBエンジニアとしてポイント決済や社内運用システムなどを担当。
新しいサービスを考えることが好きで、社内懸賞や社外のハッカソン・アイデアソンなどに積極的に参加している。いま気になるキーワードはIoT・人工知能・UX。