Slack Outgoing Webhookでスプレッドシートに打刻する

# Slackで勤怠管理してみませんか？
私の職場ではSlackを使用しているのですが、「#greetings」というチャンネルに、出勤時と退勤時に挨拶（＋勤務実績報告等）を書き込み、勤怠を管理しています。

しかし、各々の生活習慣から、毎日数分早く出社したり、また遅く帰ったり、ごく少数の時間ながら、残業時間が積み重なっていきがちです。

とはいえ、**「昨日は10分残業したから、今日は10分早く帰ろう！」** などとするのは、いくら認められた権利とはいえ、何となく気まずいものです。そういった**少しずつ積もった残業時間を正確に把握し、堂々と消化できる**のが、こちらの勤怠管理システムになります。

同じような勤怠管理の形式をとっている職場にお勤めの際は、ぜひ使用してみてください！

# 作るシステムのイメージ

・勤怠報告専用のチャンネルがあり、いつも **「おはようございます」** や **「お疲れ様です」** といったワードが飛び交います。 **スタンプで挨拶をする人もいます。**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/852882/0c344191-14b5-874d-fc1a-d926d68e3173.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/852882/7e14a912-1e6e-0b27-5c2a-7bcbc3fb99ff.png)
・時折、離席する人もいます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/852882/f6194b65-029c-3c92-0ea8-03440d6773dd.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/852882/b7c95fc7-667f-514e-d61b-9f7a84758ea0.png)

・それぞれの投稿において、**特定のキーワード（おはよう、お疲れ）やスタンプに反応し、現在時刻をスプレッドシートに打刻**してくれるようにします。
・さらに、**『退勤』の項目が打刻されたら、勤務時間と残業時間を算出**してくれるようにします。（私の職場は勤務時間が７時間３０分となっているので、図の例では、残業時間は０となります。）
<p>
  <img width="48%" margin-right="20px" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/852882/195bc9e3-aa25-ed29-100a-d71ba31c62fa.png" />
<img width="48%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/852882/64efd2e6-23e0-061a-c0c5-965da87713f2.png" />
</p>

・月ごとの累計残業時間も計算してくれます。（こちらはスプレッドシートのSUMIF関数で導出しています。）
<p>
  <img width="48%" margin-right="20px" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/852882/3d9c8f4a-b499-1dbf-20c3-758f90e180a0.png" />
</p>

# Outgoing Webhookの設定
・Slack側の設定は、**「Outgoing Webhook」** を使用します。

①左上のハンバーガーメニューをクリックし、ファイル＞設定と管理＞App管理＞カスタムインテグレーション　を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/852882/7ae15eb5-c979-e55e-5661-e9801783b9d9.png)

②Outgoing Webhook＞Slackに追加＞Outgoing Webhookインテグレーションの追加　を選択します。

<p>
  <img width="100%" margin-right="20px" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/852882/f0361124-9346-f198-0bbd-3abb038a0ad3.png" />
</p>
<p>
  <img width="57%" margin-right="20px" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/852882/949aee89-667b-18a0-f909-c6e02a751b18.png" />
  <img width="40%" margin-right="20px" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/852882/cd71b6c9-2c84-11de-af80-44bbe77cab39.png" />
<p/>

③設定画面を開くことができました。最初に、「Outgoingペイロードとレスポンス」を確認しておきましょう。（「開く」をクリック）。
**こちらに表記されているデータはPOST送信され、後ほど使用することになります。
特に「token」「user_id」「trigger_word」は実際に使用します。**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/852882/61283c18-aafa-95b6-e2ea-2174459274e4.png)

④チャンネル名、引き金となる言葉を設定します。
・「:おはようございます:」という表記がありますが、これはスタンプを表します。ホバーで確認できます。
・URLは後ほど設定します。
・トークンは後ほど使用します。変更する必要はありません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/852882/54fe8e94-84a8-fde6-b26a-6e4fb00b2476.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/852882/a23d72e5-ecfc-cf45-cdc1-255ce3d993d4.png)
・「名前をカスタマイズ」「アイコンをカスタマイズする」をお好みで設定したら、いったんSlackの準備は終了しますが、**まだ設定は保存しないでください。** Googleスプレッドシートの設定がまだ終わっていないためです。

# Googleスプレッドシートの設定
次にGoogleスプレッドシートの設定を行います。
ここでは **「Gas」(Google Apps Script)** という言語で記述していきます。

①まずは新規シートを作成してください。
・タイトルは好きなものを設定してください。
・シート名は、SlackのユーザーIDにしてください。Slackの個人プロフィールで確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/852882/3081977b-eb9c-106b-3b18-cfd2d6ff721c.png)

②拡張機能＞Apps Script　を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/852882/325c3162-8a53-5909-8625-6c5d5e3b331a.png)

③タイトル、ファイル名をお好みで設定してください。当記事では「勤怠管理」「attendance.gs」とします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/852882/b0a61b4f-c5cf-4283-22be-92c1faf68d12.png)

④プロジェクトの設定＞「appsscript.json」マニフェスト ファイルをエディタで表示する　を選択し、チェックマークをいれます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/852882/d382656f-b22b-30f9-c85f-13cef9009aa6.png)

⑤エディターでappsscript.jsonは次のように記載します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/852882/e787969d-6f25-48f5-6a63-6c762fc8128a.png)
```json:appsscript.json
{
  "timeZone": "Asia/Tokyo",
  "dependencies": {},
  "exceptionLogging": "STACKDRIVER",
  "runtimeVersion": "V8",
  "webapp": {
    "executeAs": "USER_DEPLOYING",
    "access": "ANYONE_ANONYMOUS"
  }
}
```

⑥テストコードとして、下記のように記載してください。
※詳しい文法は、別途調べてみてください。
```js:attendance.gs
function doPost(e) {
  // xxxxxxxxxxxxxxxxxxxxxxxはURLを入力してください。
  const ss = SpreadsheetApp.openByUrl("https://docs.google.com/spreadsheets/d/xxxxxxxxxxxxxxxxxxxxxxx"); 
  const sheet = ss.getSheetByName(e.parameter.user_id);

  const datetime = new Date();
  sheet.getRange(1,1).setValue(datetime);
  sheet.getRange(2,1).setValue('Post送信成功!');
  sheet.getRange(3,1).setValue(e.parameter.user_id);
  sheet.getRange(4,1).setValue(e.parameter.trigger_word);
  sheet.getRange(5,1).setValue(e.parameter.token);

  return;
}

```

⑦プロジェクトを保存し、デプロイ＞新しいデプロイ　を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/852882/73e22d6e-d081-bfa8-59ae-1932d071bf2c.png)


⑧アクセスできるユーザー「自分のみ」を選択し、「デプロイ」ボタンを押します。（「自分のみ」の権限は変更してもOKです。）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/852882/a30becd3-2744-faba-b774-4e51734ecc96.png)

⑨自分のGoogleアカウントでログインした後、詳細を表示＞（安全ではないページ）に移動＞許可　を選択します。
このあたりはこちらのページでも詳しく解説されています。
[Google Apps Scriptでデプロイしてウェブアプリとして公開する方法【2021年更新】](http://pineplanter.moo.jp/non-it-salaryman/2021/05/18/gas-deploy/)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/852882/14f33711-c9fb-55bb-c544-d4e9909e87e8.png)

⑩下記の画面が出れば完了です。そのままURLをコピーします。
**このURLをSlackの設定に使用します。**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/852882/eb841139-a499-d081-ef1f-5cca16527eda.png)

⑪再び、Slackの「Outgoing Webhook」の設定に戻ります。（**まだ設定は保存しないでください。** と記載した場所になります。）
URLに、先ほどデプロイしたアプリのURLを貼り付け、設定を保存します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/852882/f74bf115-48ab-5a62-6fae-1980d08c43c6.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/852882/fc41ff72-7a1e-3d88-1413-d2cf99310263.png)

# POST送信テスト
さて、テストコードは次のようになっていました。
```js:attendance.gs
function doPost(e) {
  // xxxxxxxxxxxxxxxxxxxxxxxはURLを入力してください。
  const ss = SpreadsheetApp.openByUrl("https://docs.google.com/spreadsheets/d/xxxxxxxxxxxxxxxxxxxxxxx"); 
  const sheet = ss.getSheetByName(e.parameter.user_id);

  const datetime = new Date();
  sheet.getRange(1,1).setValue(datetime);
  sheet.getRange(2,1).setValue('Post送信成功!');
  sheet.getRange(3,1).setValue(e.parameter.user_id);
  sheet.getRange(4,1).setValue(e.parameter.trigger_word);
  sheet.getRange(5,1).setValue(e.parameter.token);

  return;
}

```
実際に、Slackでトリガーワードの１つである **「出勤」** を含む投稿をしてみましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/852882/8f8b773b-5e2c-55ea-b460-3ce62d22d225.png)
すると、このように、スプレッドシートの中身が変わっていることが分かります。
無事にPOST送信が成功しました。
また、 **３～５行目の様子から、POSTデータが「e.parameter」の中に配列で格納されている**ことが分かります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/852882/d4f61dc0-45f1-249d-2616-205eedadbcd7.png)


# オリジナルの勤怠管理シートを作成
さて、残るは実際に使用するプログラムを記述するのみですが、会社によって、もちろん勤務制度は異なりますので、そのままコピーすることは難しいかと思います。
サンプルを記載しておきますので、良ければアレンジしてみてください。

【サンプルの仕様】
[スプレッドシート（ご自由にコピーしてお使いください）](https://docs.google.com/spreadsheets/d/16_jZchQ4S4UIzcFhA2RSDZFaLek5CEAV/edit#gid=41056949)
・勤務時間は７時間半とする
・４時間以上勤務する場合、１時間の休憩が認められる
・社員は３名と仮定（社員の分だけシート数を用意します。）

```js:attendance.gs
function doPost(e) {
  const ss = SpreadsheetApp.openByUrl("https://docs.google.com/spreadsheets/d/xxxxxxxxxxxxxxxxxxxx/edit#gid=0"); 
  const sheet = ss.getSheetByName(e.parameter.user_id); // スプレッドシートのシート名もslackのuser_idにしておくこと
  const token = 'xxxxxxxxxxxxxxxxxxxx';

  if (token !== e.parameter.token) return; // token値が一致していない場合は打刻を行わない

  let category = e.parameter.trigger_word;

  const startArr = ['おはよう', ':おはようございます:']; // コロンで囲まれているものはSlackのスタンプを表す
  const finishArr = ['お疲れ', ':おつです:', ':お疲れさまです:'];
  if (startArr.indexOf(category) >= 0) category = '出勤';
  if (finishArr.indexOf(category) >= 0) category = '退勤';

  let lastRow = sheet.getLastRow();
  let addRow = lastRow;

  if (sheet.getRange(addRow, 7).isBlank() === false) addRow++; // 「退勤」に打刻されていれば、新しい行に追加する

  const datetime = new Date();
  let timeArr = {
    'year': datetime.getFullYear(),
    'month': datetime.getMonth() + 1,
    'date': datetime.getDate(),
    '出勤': sheet.getRange(addRow, 4).getValue(),
    '離席': sheet.getRange(addRow, 5).getValue(),
    '再開': sheet.getRange(addRow, 6).getValue(),
    '退勤': sheet.getRange(addRow, 7).getValue(),
  };

  // 例：8時→08の後ろから２文字,17時→017の後ろから２文字
  timeArr[category] = (('0' + datetime.getHours()).slice(-2) + ':' + ('0' + datetime.getMinutes()).slice(-2));

  // 列番号を割り振らないと、シートに書き込みができないため。
  array = [[timeArr['year'],timeArr['month'],timeArr['date'],timeArr['出勤'],timeArr['離席'],timeArr['再開'],timeArr['退勤']]];
  sheet.getRange(addRow,1,1,7).setValues(array); // 行番号,列番号,行数,列数

  // 「退勤」に打刻されていれば、勤務時間・残業時間を計算する
  if (category === '退勤') {

    let workingHours = (Number(timeArr['退勤'].slice(0,2)) - Number(timeArr['出勤'].slice(0,2)) + Number(timeArr['再開'].slice(0,2)) - Number(timeArr['離席'].slice(0,2)));
    let workingMinutes = (Number(timeArr['退勤'].slice(-2))- Number(timeArr['出勤'].slice(-2))+ Number(timeArr['再開'].slice(-2))- Number(timeArr['離席'].slice(-2)));

    if (workingHours >= 4) workingHours--; // 4時間以上勤務していれば、昼休み１時間を付与
    if (workingMinutes < 0) {
      workingHours--;
      workingMinutes += 60;
    };

    timeArr['実勤務時間'] = ('0' + workingHours.toString()).slice(-2) + ':' + ('0' + workingMinutes.toString()).slice(-2);
    timeArr['残業時間'] = workingHours * 60 + workingMinutes - 450; // 残業時間＝実勤務時間ー７時間半（４５０分）
    sheet.getRange(addRow,8,1,2).setValues([[timeArr['実勤務時間'], timeArr['残業時間']]]); // 行番号,列番号,行数,列数
  }

  return;
}

```

# おわりに
以上で **「Slack Outgoing Webhookでスプレッドシートに打刻する」** の解説を終わります。
ぜひとも皆さんの職場でも導入し、勤務の手助けになれば幸いです。