GASからIEYASU APIを利用する

今回はクラウドの勤怠管理システムであるIEYASUのAPIをGASから利用してみたいと思います。この記事を書いている時点のIEYASU APIのバージョンは、1.0.6です。

今回作ったシステム概要

今回はGASのHTML画面からIEYASUの勤怠の打刻をするシステムになります。
とても単純なシステムですが、応用するなら、打刻時に社員へアンケートをとって、社員のメンタルヘルスを管理したり、日報を書いてもらうなどの活用をすることができます。
日報をなかなか書いてくれない社員がいたとしても、勤怠とセットにすることで、やらざる得なくなります。ぜひ今回の勤怠の打刻の仕組みを活用して、社内により良い仕組みを導入してみてください。

打刻画面

「出勤」ボタンを押すと、ieyasu側に送信されて出勤の打刻が打てます。また、出勤中の場合は、「退勤」ボタンに変わり、押すと退勤打刻が打てるというシステムです。

また、名前のところには、Googleにログインしているユーザーの名前が表示され、状態欄には、今自分が出勤中なのか退勤中なのかを示してくれます。

IEYASUのAPIを有効にする

まずはIEYASU側でAPIを利用できるようにしましょう。
IEYASUの管理者権限のアカウントでログインし、「システム管理」画面へ移動してください。

IEYASUのヘッダーメニュー

システム管理画面の左側のメニューの「システム設定」へ移動してください。

システム設定画面にある「編集」ボタンを押し、「API KEYの設定」欄からSecret Keyを発行してください。Secret Keyは、後で使用しますので、コピーしておいてください。

IEYASUのAPIの説明は、下記のページに書いてありますので、ここを参考にしてプログラムを作っていきましょう。
https://ieyasu.co/docs/api.html

トークンを取得する

IEYASUのAPIから各種操作を行うには、まず初めに認証用のトークンを取得する必要があります。
APIを利用する場合、POSTかGETの方法でGASからAPIへ情報を送ります。
(GETはURLに情報を渡す方法で、POSTはHTTPヘッダーに情報を渡す方法です)

ここで認証用トークンを取得するAPIの説明を見てみましょう。

IEYASU APIの認証用トークンを取得する説明箇所

まずGETと書かれているので、このAPIはGETで叩く(アクセスする)ことがわかります。
次に、curlと書かれています。カールと読みますが、PHPの場合はcURL関数というのがあり、それでアクセスすれば良いのですが、GASの場合はUrlFetchAppクラスのfetch関数というものがありますので、それを使ってアクセスします。

-H 'Authorization: Basic MGE3M2Y2NDA3MTIxZTRlMzNkMDFhOTgw' -H 'Content-Type:application/json'

と書かれていますが、「-H」はHTTPヘッダーを意味します。ヘッダーの頭文字です。
Basicの後に書かれているアルファベットと数字の羅列が、先程取得した Secret Key になります。
では実際にGASでトークンを取得するプログラムを書いてみましょう。

  let headers =
    {
      "Authorization" : 'Basic '+ieyasu_api_key,
      "Content-Type" : 'application/json'
    };

    let options =
    {
      "method" : "get",
      "headers": headers
    };

    let response = UrlFetchApp.fetch('https://ieyasu.co/api/'+compay_name+'/v1/authentication/token', options);
    let json = JSON.parse(response.getContentText());

ieyasu_api_keyの変数には、 Secret Key を格納してください。
また、compay_nameの変数には、企業IDが入ります。企業IDは、ieyasuにログインする時に使っているURLの「https://ieyasu.co/xxxxx」のxxxxx部分になりますので、確認してみてください。

これでieyasuのAPIのトークンを取得することができます。ではAPIを叩いた後にどのようなレスポンスがあるのか見ていきましょう。
先程のAPIの説明箇所に戻ってください。Response samplesと書かれた部分があります。
「200」と「default」と書かれたボタンが並んでいます。
200は、成功した場合のレスポンス 例 です。defaultは、失敗した場合のレスポンス例になります。

レスポンスはJSONという形式でデータが返ってきます。上記のプログラムでは、response変数にJSONデータが格納されます。
JSONデータのままではデータが出せませんので、配列に変換します。JSON.parseを使うと配列に変換できます。

打刻する

続いて打刻をするAPIをGASから叩いてみましょう!

打刻登録のAPI

今度は、GETではなく、POSTであることがわかります。
GETはURLからデータを渡すと書きましたが、先程のトークン取得のAPIではURLから何もデータを渡していませんでした。GETでデータを渡すときはURLの最後に?a=bのようにハテナを付けてデータを渡します。
POSTの場合は、HTTPヘッダーからデータを渡しますので、どんなデータがサーバーに渡っているのか、利用者は簡単に見ることができません。(GETの場合はURLを見ればわかります)

-H 'Authorization: Token Em94Vf961af8gSB2QtTVmGgn' -H 'Content-Type:application/json' -d '{"user_id":23,"stamp_type":1,"latitude": 34.6431, "longitude":131.9972,"address":"東京都港区北青山3-5-6"}'

APIを叩くときは認証があります。先程のトークン取得では、 Secret Key が認証キーとなっていました。今回はトークンが認証キーになります。
Authorization: Tokenの後に記載されている文字列がトークン情報になります。
また今回は、-dという箇所があります。-dはデータを表しています。-d以降にJSON形式でデータを渡しています。

ieyasuの場合、位置情報の座標と、住所も打刻と一緒に渡すことができます。今回のプログラムでは省略させていただきます。

  let headers =
   {
     "Authorization" : 'Token '+token,
     "Content-Type" : 'application/json'
   };

   var data = {
    "user_id":ieyasu_id,
    "stamp_type":stamp_type
   }

   let options =
   {
     "method" : "post",
     "payload": JSON.stringify(data),
     "headers": headers
   };
   let response = UrlFetchApp.fetch('https://ieyasu.co/api/'+compay_name+'/v1/stamp_logs', options);
   let json = JSON.parse(response.getContentText());

上記が、GASのソースになります。token変数には最初に取得したトークン情報を格納してください。
ieyasu_id変数には、ieyasuで登録した社員のIDが入ります。
stamp_type変数には、 打刻区分が入ります。(打刻区分 1. 出勤, 2. 退勤, 7. 休憩開始, 8. 休憩終了)
出勤する場合は1を渡し、退勤する場合は2を渡します。

ログイン

今回のアプリでは、Googleアカウントにログインしないとアクセスできません。
Googleのアカウントと、ieyasuのIDを紐付けることで、ログインした社員の打刻が行えるようにします。両者の紐付けはスプレッドシートで行います。

今回はA列にieyasuのID、B列に名前、C列にGoogleアカウントで使用しているメールアドレス、D列にトークン、E列にトークンの有効期限を入れます。
D列とE列はプログラムから挿入しますので、空白で構いません。

  let user = Session.getActiveUser();
  let useremail = user.getEmail();

ログインしているユーザー情報は、Sessionクラスの getActiveUser 関数を利用します。
取得できるのは、ユーザーの名前(アルファベット)、メールアドレス、ユーザーIDの3つです。
上記ではメールアドレスを取得しています。

function get_user(useremail){
  let spreadsheet = SpreadsheetApp.openById('スプレッドシートのID');
  let sheet = spreadsheet.getSheetByName('シート名');
  let textFinder = sheet.createTextFinder(useremail);
  let cells = textFinder.findNext();
  if(cells){
    let NumRow = cells.getRow();
    const ieyasu_id = sheet.getRange("A"+NumRow).getValue();
    const user_name = sheet.getRange("B"+NumRow).getValue();
    return {ieyasu_id,user_name,NumRow};
  }else{
    return '';
  }
}

上記の関数は、スプレッドシートからieyasuのIDと社員の名前、行番号を取得するものです。
行番号を返したのは、後ほどトークンと有効期限をD列、E列に格納するためです。
関数の引数には先程取得したGoogleアカウントのメールアドレスを渡しています。

トークンを格納する

トークンは毎回新しいものを取得してもいいのですが、APIとの通信回数を減らすため、有効な期間は使い回すようにします。

function check_token(NumRow){
  let spreadsheet = SpreadsheetApp.openById('スプレッドシートのID');
  let sheet = spreadsheet.getSheetByName('シート名');
  let token = sheet.getRange("D"+NumRow).getValue();
  let expired_at = sheet.getRange("E"+NumRow).getValue();
  if((token == '') || (expired_at == '')){
    //トークンが空の場合は新規取得
    let {new_token,unixTime} = get_token();
    sheet.getRange("D"+NumRow).setValue(new_token);
    sheet.getRange("E"+NumRow).setValue(unixTime);
    return new_token;
  }else{
    //トークンの有効期限を確認
    let now = Date.parse(new Date())/1000;
    if((expired_at - now) > 3600){
      //有効期限まで1時間以上ある
      return token;
    }else{
      //有効期限まで1時間未満であるため、新規取得
      let {new_token,unixTime} = get_token();
      sheet.getRange("D"+NumRow).setValue(new_token);
      sheet.getRange("E"+NumRow).setValue(unixTime);
      return new_token;
    }
  }
}

check_token関数を作りました。引数には先程取得したスプレッドシートの行番号を渡します。
スプレッドシート内にトークンと有効期限がない場合は、新しいトークンを取得するget_token関数を実行します。
トークンと有効期限をスプレッドシートから取得できた場合は、トークンの有効期限を調べます。
ieyasuの場合、トークンの有効期限は24時間あるようです。念の為、トークンの有効期限まで1時間以上ある場合のみ利用することにしました。
日付の比較の際は、UNIX時間を使うととても簡単です。UNIX時間は、1970年1月1日午前0時0分0秒から何秒経ったかを表す時間です。秒数で表すため、日付同士を比較する時に引き算するだけで時間差がわかります。今回は1時間ですので、時間差が3600秒以上ならトークンをそのまま利用することにしました。

let now = Date.parse(new Date())/1000;

今のUNIX時間を取得する処理が上記になります。
また、トークンの有効期限は、先程のトークンの取得APIのレスポンスのサンプルを見るとわかるように、「2018-09-23T04:56:07.000+09:00」のように表記された値が取得できます。

これをUNIX時間に変換するには、先程のDateクラスと同じように処理してあげればできます。

let unixTime = Date.parse(json['expired_at'])/1000;

新規で取得したトークンと有効期限をUNIX時間に変更して格納すると、下の図のようになります。

HTML画面を表示させる

今回GASでHTML画面を表示しています。それについては、GASのHTMLにBootstrapを読み込ませる方法の記事も参照ください。

「名前」と「状態」をHTML画面に表示しています。これは変数をHTML側に渡す必要があります。

function doGet(e) {
 let template = HtmlService.createTemplateFromFile('index');
 template.username = '市川喬之';
 template.status = '退勤中';
 return template.evaluate();
}

GAS側では、HTMLを読み込ませたオブジェクトに変数を上記のように渡します。(実際は、名前はスプレッドシートから取得した社員名を変数に入れて渡します。)

<table>
        <tr>
          <th width="20%">名前</th>
          <td><?=username?></td>
        </tr>
        <tr>
          <th>状態</th>
          <td><?=status?></td>
        </tr>
</table>

HTML側では、<?=変数名?>と書くことで変数の値を出力することができます。

勤怠状態を表示する

先程HTML画面で表示する状態には、退勤中と固定で表示しましたが、実際は、勤務中なのか退勤中なのか判定する必要があります。

function get_stamplog(token,ieyasu_id,limit=1){
  let headers =
   {
     "Authorization" : 'Token '+token,
     "Content-Type" : 'application/json'
   };

   let options =
   {
     "method" : "get",
     "headers": headers
   };
   let response = UrlFetchApp.fetch('https://ieyasu.co/api/'+compay_name+'/v1/stamp_logs/user/'+ieyasu_id+'?stamp_type=1&limit='+limit, options);
   let json = JSON.parse(response.getContentText());
   return json[0]['stamp_type'];
}

勤務情報を取得するAPIを利用して、現在の状態を確認します。最後の打刻が出勤なら、現在出勤していることになりますし、最後の打刻が退勤なら現在は退勤中ということになります。

上記のget_stamplog関数では、引数にトークンとieyasuのIDを渡しています。その他にlimit=1と渡していますが、これは取得する勤怠情報の数です。最新の1件を取得すれば良いため1を初期値として入れています。

今回のAPIはGETで情報を取得します。APIのURLの最後に「?stamp_type=1&limit=’+limit」と stamp_type 変数と、 limit 変数を渡しています。

get_stamplog関数の戻り値は、打刻区分になります。(打刻区分 1. 出勤, 2. 退勤, 7. 休憩開始, 8. 休憩終了)

フォームからPOSTする

今回のWebアプリでは、「出勤」または「退勤」ボタンを押すと、データがPOSTされます。
HTML画面では下記のようにフォームを作成しています。

<form method="POST" action='<?=posturl?>'>
    <div class="table-responsive">
    <table class="table">
        <tr>
          <th width="20%">名前</th>
          <td><?=username?></td>
        </tr>
        <tr>
          <th>状態</th>
          <td><?=status?></td>
        </tr>
    </table>
    </div>

    <div id='realtime'></div>
    
    <div class="text-center">
      <input type="submit" value="<?=button_label?>" class="btn btn-primary">
      <input type="hidden" name='type' value="<?=stamp_type?>">
    </div>
 </form> 

button_label変数には、「出勤」か「退勤」の文字が入ります。勤怠の状態によって文字を切り替えています。
hidden値としてフォームに仕込まれているstamp_type変数には、打刻区分が入ります。出勤する場合は1、退勤する場合は2が入ります。

1行目のformタグの送信先はactionに指定します。
posturlにはこのWebアプリのURLが入るのですが、GASのWebアプリはデプロイするたびにURLが変わってしまいます。

template.posturl = ScriptApp.getService().getUrl();

上記のように、WebアプリのURLを取得する関数を実行して都度変数に格納するようにしましょう。

GASでは、GETされた場合は、doGet関数が最初に実行されますが、POSTの場合は、doPost関数が最初に実行されますので、POST値を受け取る場合は下記のように書きましょう。

function doPost(e){
  set_stamp(token,ieyasu_id,e.parameter.type);
}

先程HTML画面のhidden値にセットしたtypeという名前の値を受信する場合、e.parameter.typeと記載すると値が取得できます。
値を取得したら「打刻する」で紹介したAPIを実行してieyasuから打刻しましょう。

デプロイする

プログラムが完成したら、デプロイしてWebアプリを動かしてみましょう。
デプロイする方法については、GASでWebアプリをデプロイするを参照ください。
このシステムはログインしているユーザー情報を取得していますので、下記のように「ウェブアプリケーションにアクセスしているユーザー」で実行されるように設定してください。

ソースコードのダウンロード

下記よりソースコードを一式ダウンロードしていただきます。※スプレッドシートはダウンロードできませんので、別途ご用意ください。

ダウンロードしたソースコードはすべてtxtファイルになっています。下記のようにGASのスクリプトエディタ上に配置してください。

    < 利用規約 >
    ダウンロードして頂いたソースコードなど(以下、「ダウンロードファイル」と呼ぶ)は無償で利用することができます。弊社は、ダウンロードファイルを利用されて生じた一切の損害について、その賠償義務を負いません。
    < 個人情報の取扱いについて >
    ご提供いただいた個人情報につきましては、下記の目的の範囲内で取り扱います。
    その他の取扱いについては当社のプライバシーポリシーに従います。

    ・お客さまの本人確認・与信管理
    ・当社のサービスまたは製品のご案内のため(電子メールによるものを含む)
    ・当社によるアンケート、各種セミナーのご案内、市場調査のため
    ・当社のサービス品質改善に向けた利用分析及び統計データ作成のため
    ・その他重要なご連絡のため