Movatterモバイル変換


[0]ホーム

URL:


株式会社microCMS株式会社microCMS
株式会社microCMSPublicationへの投稿
🍣

Slackでスタンプを押すだけで勤怠打刻・勤怠サマリレポートしてくれる仕組みを作った

に公開
2023/01/06

🐣 はじめに

みなさん、勤怠打刻してますか?

先日、このようなツイートをしたところ、思わぬ反響がありました。
https://twitter.com/paranishian/status/1575646345876340736
そこで、この仕組みの全体像や工夫した点などをまとめることにしました。
SlackやGASを使ったOps自動化に興味がある人に読んでもらえたら嬉しいです。

きっかけ

そもそもSlackにはfreeeが公式で提供している人事労務用のSlack appがあり、スラッシュコマンドを使って勤怠打刻できます。便利ですね。
https://slack.com/apps/AD98ZD3EV-freee

ただ、このアプリ、コマンドを打つのがとにかくめんどくさかったりします。
あるとき、同僚が「もっと気軽に勤怠打刻できたらええのになぁ」と言っているのを耳にしました。

そこで、スタンプで勤怠打刻できる仕組みを作り、運用を始めました。

それから数ヶ月後、会社にフレックスタイム制が導入されました。
「ワークライフバランス!!さいこう!」となりつつも「今月はあと何時間働けば良いんだっけ?」をいちいち計算しないといけないつらみが出てきました。

そこで、スタンプ勤怠打刻時に勤怠サマリレポートも通知するように改善しました。

🎯 主な機能

スタンプを押すと、出勤・退勤打刻ができ、勤怠サマリも通知してくれます。

出勤時:Slack workflowが定時に挨拶してくるので、:shukkin: スタンプを押すと

人事労務freeeへの出勤打刻と勤怠サマリレポートを通知してくれます。

退勤時:Slack workflowが定時に挨拶してくるので、 :taikin: スタンプを押すと、

人事労務freeeへの退勤打刻の旨を通知してくれます。

🌏 実装の全体像

ざっくりとした処理の流れは以下の通りです。

Slackbot、GAS、freee APIで構築しています。
Slackbotでreaction_addedイベントをsubscribeし、リクエストはGASが受け取ります。
そしてGAS側でユーザーの突合、freeeへの勤怠打刻、サマリ取得、Slack通知を実装しています。

一部になりますが、GASのコードも載せておきます。(記事用に少し改変しています)

constSHUKKIN='shukkin'constTAIKIN='taikin'constCOMPANY_ID=1234567functiondoPost(e){const params=JSON.parse(e.postData.getDataAsString());// SlackのEvent SubscriptionのRequest Verification用if(params.type==='url_verification'){returnContentService.createTextOutput(params.challenge);}const event= params.eventconst user= event.userconst reaction= event.reactionconst eventId= params.event_id// 特定のチャネル以外のスタンプは無視if(event.item.channel!=='C12345678')return;// shukkin, taikin 以外のスタンプは無視if(reaction!==SHUKKIN&& reaction!==TAIKIN)return;    // Slackからの再送リクエストを無視するためにキャッシュを使う(10分)const cache=CacheService.getScriptCache()const cached= cache.get(eventId)if(cached){Logger.log(`すでに処理したイベントなのでスルーします。(eventId:${eventId})`)return}  cache.put(eventId,true,60*10)if(reaction===SHUKKIN){const todayShukkinSheet=SpreadsheetApp.getActive().getSheetByName('today_shukkin_log')const users= todayShukkinSheet.getRange("B2:B").getValues().flat();// すでに出勤打刻済みなら無視if(users.includes(user))return;}elseif(reaction===TAIKIN){// 退勤は上書きしてよい}// Slack -> Freeeのユーザー突合const freeeUser=findFreeeUser(user)if(!freeeUser)return;// freeeに打刻const type= reaction===SHUKKIN?'clock_in':'clock_out'timeClocksToFreee(freeeUser.employeeId, type)const lines=[]  lines.push(`<@${user}>`)if(reaction===SHUKKIN){    lines.push(`おはようございます!今日も頑張っていきましょう:muscle:`)    lines.push(`✅ freee打刻済`)// 勤怠サマリを取得してテキスト整形する    lines.push(getWorkTimeText(freeeUser.employeeId))}else{    lines.push(`お疲れ様でした!また明日:wave:`)    lines.push(`✅ freee打刻済`)}const text= lines.join("\n")// Slackに通知notifyToKintaiChannel(text)// logに記録const logSheet=SpreadsheetApp.getActive().getSheetByName('kintai_log')const datetime=Utilities.formatDate(newDate(event.event_ts*1000),"Asia/Tokyo","yyyy-MM-dd HH:mm:ss")  logSheet.appendRow([datetime, user, reaction])}functionfindSlackEmail(id){const sheet=SpreadsheetApp.getActive().getSheetByName('slack_users')// [[id, name, real_name, email], [id, name, real_name, email], ...]const user= sheet.getRange("A2:D").getValues().find((u)=> u[0]=== id);return!!user? user[3]:undefined}functionfindFreeeUser(slackUserId){const email=findSlackEmail(slackUserId)if(!email)returnundefinedconst sheet=SpreadsheetApp.getActive().getSheetByName('employee_freee_users')// [[メールアドレス, id], ...]const row= sheet.getRange("A2:B").getValues().find((u)=> u[0]=== email);return!!row?{email: row[0],employeeId: row[1]}:undefined}

GASでfreeeのアクセストークンを取得する方法は、freeeさんが丁寧な記事を書いてくれています。ぜひ参考にしてください。
【freee API】GASを用いてGoogleスプレッドシートと連携する

また、今回使用しているAPIは以下の2つです。

  • タイムレコーダー(打刻)POST
  • 勤怠情報サマリGET

https://developer.freee.co.jp/reference/hr/reference

freeeアプリには以下の権限を付与しています。

  • [人事労務] 打刻 の参照・更新
  • [人事労務] 勤怠 の参照

注意点として、freeeアプリの認証は人事労務freeeの管理者が行ってください。
アプリを認可しただけでは、APIで権限が足りないと弾かれてしまいます。
このあたりめっちゃハマりました。
https://developer.freee.co.jp/reference/hr#ログインユーザーの情報を取得する

なお、Slackユーザーとfreeeユーザーの突合には、メールアドレスを利用しています。

💡 工夫したポイント

その1:シートの設計

こういったGASの自動化においては、シート設計(=DB設計)が肝だと考えています。
今回は以下の構成にしました。

  • kintai_log
    • 「いつ」「だれが」「出勤・退勤したか」が保存されているシート
  • today_shukkin_log
    • kintai_logシートから今日の出勤をQUERYしたシート
    • 出勤済みユーザーかどうかの判定に利用する
  • slack_users
    • Slackのユーザー情報が保存されているシート
    • SlackのユーザーIDからメールアドレスを取得するために利用する
  • freee_users
    • freeeeのユーザー情報が保存されているシート
    • メールアドレスからfreeeのユーザーIDを取得するために利用する

また、Slackユーザーのメールアドレスは、日次でユーザー一覧を取得するAPIを叩いてslack_usersシートに保存しています。

constSLACK_BOT_TOKEN='xoxb-12345'functionfetchSlackUsers(){// https://api.slack.com/methods/users.listconst apiResponse=callWebApi(SLACK_BOT_TOKEN,"users.list",{});const json=JSON.parse(apiResponse);const users=[]  json.members.filter((m)=>!m.deleted&&!m.is_bot).forEach((m)=>{    users.push([m.id, m.name, m.real_name, m.profile.email])});const sheet=SpreadsheetApp.getActive().getSheetByName('slack_users')  sheet.getRange("A2:D").clear()  sheet.getRange(2,1, users.length,4).setValues(users)}functioncallWebApi(token, apiMethod, payload){const response=UrlFetchApp.fetch(`https://www.slack.com/api/${apiMethod}`,{method:"post",contentType:"application/json; charset=UTF-8",headers:{"Authorization":`Bearer${token}`},payload:JSON.stringify(payload),});return response;}

その2:Slackの再送イベントの処理

Slack Events APIには、さまざまなリトライ条件があります。
https://dev.classmethod.jp/articles/slack-resend-matome/
GASでは頻繁にこれらの条件に引っかかってしまうので、キャッシュを利用して再送時の処理をスキップしています。

その3:「労働」ということばを使わない

労働・・・したくないですよね。
人事労務freeeでは、「労働日数」や「所定内労働」など、「労働」が多用されていますが、Slackに通知する際は「勤怠」というやわらかめのワードに置き換えています。(とってもだいじ)

🤝 今後やっていきたいこと

残業時間の計算なども要望として上がっているので、追加実装して試験運用中です。
働きすぎをそっと防止する仕組みを作っていきたいです。

🍵 おわりに

ってなかんじで、Slackbot、GASを使った勤怠Ops自動化について書いてみました。
今回はfreeeでの実装でしたが、ほかの勤怠管理システムでもAPIがあれば実現可能だと思います。
めんどくさい作業はどんどん自動化して気持ちよく働きたいですね。

ちなみに、この仕組みはmicroCMS社に導入して運用しています。
エンジニア企業らしく、社内業務の自動化に積極的に取り組んでいるとても素敵な会社です。
https://microcms.co.jp/recruit

もしこの記事を読んで参考になりましたら、いいねやTwitterのフォローをしていただけるととても嬉しいです。

その他複数の企業で、Ops自動化・効率化をお手伝いしています。
興味がありましたらDM等でご相談ください。
それでは!👋

にっしー

フリーランスのRevOpsエンジニア。レベニュー組織の業務改善、自動化やってます。paranishian.com

株式会社microCMS

microCMSはAPIベースの日本製ヘッドレスCMSです。Reactなど好きな言語・フレームワークで開発でき、柔軟なAPIと直感的な管理画面でコンテンツ管理をアップデートします。

バッジを贈って著者を応援しよう

バッジを受け取った著者にはZennから現金やAmazonギフトカードが還元されます。


[8]ページ先頭

©2009-2025 Movatter.jp