1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

freee APIで業務を楽しく便利にハックしよう!2021【PR】freeeAdvent Calendar 2021

Day 1

freeeの申請で特定申請のみをSlack通知して楽に情報把握したい!

Last updated at Posted at 2021-11-30

こんにちは!
この記事はアドベントカレンダーfreee APIで業務を楽しく便利にハックしよう!2021の1日目の記事になります!

課題 / やりたいこと

課題

  • 情報把握のために特定範囲の申請内容を見たい (※特定範囲 = 自身が管理する組織/人の内容など)
  • freeeに共有機能があるが、画面上から都度共有を行ってもらう必要があるが手間で共有を忘れることがある
  • 共有可能なfreeeの機能としてSlack連携がありますが、こちらは全申請内容が流れてしまうため目的に合致しない

やりたいこと

WebHook + APIを活用して、把握する必要がある申請内容のみをSlackに通知することで申請内容をリアルタイムで共有を行う!

例) エンジニアのリーダー層には、エンジニアが申請した内容を把握しておいてほしい等(エンジニア以外の申請は見えて欲しくない)

2021-11-30_19h56_38.png

使う技術

※注意1 : freee会計のWebhookはまだβ版となります
※注意2 : AWSの部分は省略してます


STEP1 : freeeアプリ作成

基本的にはこちらを参考にすれば問題なく進めると思います。

権限設定

権限設定に関しては、参照のみで以下を設定しました。

  • [会計] 各種申請 : 参照
  • [会計](ログインユーザー以外の)ユーザー関連情報 : 参照

Webhook設定

通知を送信する条件に関して、作成のみで以下を設定しました。

  • 各種申請 : 申請の作成

注意点

申請情報やユーザー情報にアクセス可能な人が、アプリ作成を行う必要があります。

STEP2 : トークン取得

こちらを参考にすれば問題なく進めると思います。

アクセストークン / リフレッシュトークンについて

アクセストークン/リフレッシュトークンは、 24時間 で有効期限が切れるので、リフレッシュトークンを使って定期的にトークンの再取得を行ってください。

トークン取得サンプル
async function getToken()
{
    // Dynamodb等に保存しておく
    let tokenData = {
        'access_token': 'クライアントID',
        'expires_in' : 86400,
        'refresh_token': 'クライアントシークレット',
        'created_at': 1638251549
    };

    const expireTime = tokenData.created_at + tokenData.expires_in;
    const now = dayjs();

    if (now.unix() < expireTime) {
        // トークン有効期限内 (実際にはギリギリを狙わずに余裕を持つ方がいいと思います)
        return tokenData.access_token;
    }

    const params = {
        grant_type: 'refresh_token',
        client_id: 'クライアントID',
        client_secret: 'クライアントシークレット',
        refresh_token: tokenData.refresh_token,
        redirect_uri: 'リダイレクトURI'
    };
    const post = bent('https://accounts.secure.freee.co.jp/', 'POST', 'json', 200);
    const response = await post('public_api/token', params);


    // この値をDynamodb等に保存しなおしておく
    tokenData = {
        'access_token': response.access_token,
        'expires_in' : response.eexpires_in,
        'refresh_token': response.refresh_token,
        'created_at': response.created_at
    };

STEP3 : WebHookを受け取る

リクエストのチェック

リクエストがfreeeのWebHookからのアクセスか、ヘッダーに含まれているx-freee-token と 検証用トークンが同一かをチェックする
※検証用トークンは、アプリ設定画面のWebhook設定画面にあります

Webhookアクセスチェック
function checkFreeeAccess(headerToken)
{
    const VERIFICATION_TOKEN = '検証用トークン';

    if (VERIFICATION_TOKEN == headerToken) {
        return true;
    } else {
        return false;
    }
}

STEP4 : Webhookからのリクエスト内容

リクエストのサンプル以下になります。

{
    "id": "固有ID",
    "application_id": "アプリID",
    "resource": "リクエスト種別の判別用?",
    "action": "処理内容",
    "created_at": "時間",
    "approval_request": {
        "id": "申請ID",
        "company_id": "企業ID",
        "status": "申請のステータス",
        "applicant_id": "ユーザーID"
    }
}

このリクエスト内容のうち以下を活用してます。

キー 内容
company_id APIをコールするのに利用します
approval_request.status 以下を判断することができます
下書き修正のたびに通知が来ると邪魔なのでin_progress(申請中)のみ通知が来るように分岐をした方がいいと思います。

draft(下書き)
in_progress(申請中)
approval_request.id 後ほど申請内容の詳細をAPIから取得します
approval_request.applicant_id 後ほど申請者の情報をAPIから取得します

STEP5 : 申請内容を取得

こちらを参考にすれば問題なく進めると思います。

※申請フォームが複雑な場合は、request_itemsの入力種別(type)に応じて、入力値が金額なのか、日付なのか、ファイルなのかなどで処理を分岐する必要があります

キー 内容
approval_request_form.parts フォームの内容が入っています
approval_request_form.request_items フォームに入力された内容が入ってます
注意点は、typeに応じて処理内容を分岐する必要があります
form_id 今回利用してないですが、formの種類を判別できるようです
申請内容取得
async function getApprovalRequest(token, id, companyId)
{
    const params = {
    };
    const headers = {
        'Authorization': 'Bearer ' + token
    };
    const get = bent('https://api.freee.co.jp/', 'GET', 'json');
    const response = await get('api/1/approval_requests/' + id + '?company_id=' + companyId, params, headers);

    const requestItems = response.approval_request.request_items;
    let index = 0;
    let dataList = [];
    for(let v of response.approval_request.approval_request_form.parts) {
        let value = requestItems[index].value;
        if (requestItems[index].type == 'amount') {
            // ※typeに応じて処理内容を分岐する必要がある
            // ※この場合は、金額の入力であればカンマ区切りを行う
            value = Number(value).toLocaleString();
        }

        dataList.push({
            label: v.label,
            value: value
        });
        index++;
    }

    return dataList;
}

STEP6: ユーザー情報を取得

こちらを参考にすれば問題なく進めると思います。
※ユーザーを全件取得するので、キャッシュの検討レコード件数の上限を超えた場合にどうするか などの検討が必要です
※名前を取得するだけですので、人数が少ない場合/固定されている場合は、わざわざAPIを叩く必要がないかもしれません

申請者名の取得
async function getUserName(token, companyId, userId)
{
    const params = {
    };
    const headers = {
        'Authorization': 'Bearer ' + token
    };
    const get = bent('https://api.freee.co.jp/', 'GET', 'json');
    const response = await get('api/1/users?company_id=' + companyId, params, headers);

    let name = '';
    for (let v of response.users) {
        if (v.id == userId) {
            // 全件取得して、ユーザーIDが一致する人を取り出す
            name = v.display_name
        }
    }
    
    return name;
}

STEP7: Slack通知

Slackの特定チャンネルに通知するだけです。

async function sendSlack(userId, list)
{
    let blocks = [];

    for (v of list) {
        blocks.push({
            type: 'section',
            text: {
                type: 'mrkdwn',
                text: "*" +v.label +"*\n" + " " + v.value
            }
        });
    }

    // @申請者のIDに応じてSLACK_WEBHOOK_URLのURLを分岐します
    // @特定チームの場合、特定業種の場合など

    const url = "SLACK_WEBHOOK_URL";
    const webhook = new IncomingWebhook(url);
    await webhook.send({
        text: '新しい申請が登録されました',
        blocks: blocks
    });
}

申請が上がったタイミングで以下の通知をSlackのチャンネルに共有します

2021-11-30_18h45_31.png

明日の記事

明日は、@hagurin の「CakePHPでfreeeAPIを叩くラッパーを作成した話」の話です

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?