Help us understand the problem. What is going on with this article?

【freeeAPI×GAS】支払期日が1週間以内の未決済取引データ(支出のみ)をメール通知する

こんにちは、もり(@moripro3)です!

夏だ!祭りだ!わっしょいわっしょい!

暑い夏はサクッと仕事を終わらせて一杯飲みたいですね。GASとfreeeAPIを使って、毎日の手作業を自動化しちゃいましょう。

この記事の対象読者

このような方に向けて書いています。

  • WebサービスのAPIを少しさわったことある(リクエスト・レスポンスの意味がわかる)
  • GASでJSONデータを扱ったことがある
  • まずはGASだけを使ってfreeeAPIを操作してみたい

freeeAPIは「いろいろなWebサービス・アプリと連携できる」のが利点ですが、この記事ではシンプルに「GASだけ」を使用します。

複数アプリとの連携はハードルが高そう…と思っている方は、まず「GASだけ」でできる自動化から始めてみましょう!

事前準備:GASとfreeeAPI連携アプリの認証

この記事で紹介するスクリプトを動作させるには、事前に連携アプリの作成と認証が必要です。
手順はこちらの記事をご覧ください。

【freee×GAS】GoogleAppsScriptでfreeeAPIと連携認証する

途中で困ったことがあれば、Twitter(@moripro3)にご連絡ください。

支払期日が1週間以内の未決済取引データをメール通知する

今月も取引先への支払いがたくさん…夏休みに入る前に全部片づけておかなきゃ…

そんな時に「支払い漏れ」があったら大変ですね。

freeeに登録済みの取引データから、下記3条件に合致する取引一覧を抽出して、メールで通知するスクリプトを紹介します。

  • 支払期日:スクリプト実行日から「1週間以内」
  • ステータス:「未決済」
  • 収支区分:「支出」

メール通知ではなくSlackやChatworkに飛ばすこともできますが、この記事は、なるべくシンプルに「GASだけ」を使う方針なのでメール送信にします(SlackAPIやChatworkAPIの話が出てきてしまうので)

使用するエンドポイント

freeeAPIの公式リファレンスがこちら→会計APIリファレンス Version: 2020-06-15

下記4つのエンドポイントにアクセスします。

項目 メソッド エンドポイント 用途
取引(収入/支出) GET /api/1/deals 対象取引の取得
勘定科目 GET /api/1/account_items ID→名称変換用
品目 GET /api/1/items ID→名称変換用
取引先 GET /api/1/partners ID→名称変換用

※②~④は後述

スクリプト

グローバル変数company_idに事業所IDを設定します。

freee.gs
//事業所IDを設定
const company_id = '*******';

//エントリポイント
function main() {

  const date = new Date();
  date.setDate(date.getDate() + 7); //1週間後の日付
  const end_due_date = Utilities.formatDate(date,'JST','yyyy-MM-dd');

  const accessToken = getService().getAccessToken();
  const requestUrl = `https://api.freee.co.jp/api/1/deals?company_id=${company_id}&status=unsettled&type=expense&end_due_date=${end_due_date}&limit=100`;
  const obj = accessfreeeAPI_(accessToken,requestUrl);

  if (obj.meta.total_count) {

    const values = convertObjDealToArray_(obj.deals);
    const body = createAlertBody_(values);

    sendMail_(body);

  }

}

/**
* freeeAPIの指定URLにリクエストを送信してレスポンスを返す
*
* @param {string} アクセストークン
* @param {string} リクエストURL
* @return {Object} freeeAPIのレスポンス
*/
function accessfreeeAPI_(accessToken,url) {

  Utilities.sleep(1000); //短時間の連続アクセスを回避するため待機

  const params = {
    method : 'get',
    headers : {'Authorization':'Bearer ' + accessToken,
               'X-Api-Version': '2020-06-15'}
  };

  const response = UrlFetchApp.fetch(url,params);
  const obj = JSON.parse(response);

  return obj;

}

/**
* 取引データのオブジェクトを二次元配列に変換する
* (IDは日本語名称に変換)
*
* @param {Object} freeeAPIから取得した取引データ
* @return {Object[]} 取引データ
*/
function convertObjDealToArray_(objDeals) {

  let values = [];

  for (const i in objDeals) {

    //勘定科目ID,品目IDは明細行の1番目を取得    
    const {account_item_id,item_id} = objDeals[i].details[0];
    const {due_date,amount,partner_id} = objDeals[i];

    values.push([due_date,account_item_id,amount,partner_id,item_id]);

  }

  //IDを名称に変換する
  values = convertArrayData_(values);

  //支払い期日でソートする
  values.sort();

  return values;

}

/**
* 取引データに含まれるIDを日本語名称に変換する
*
* @param {Object[]} ID変換前の取引データ
* @return {Object[]} ID変換後の取引データ
*/
function convertArrayData_(values) {

  const index = {
    account_item: 1, //勘定科目
    partner: 3, //取引先
    item: 4 //品目
  };

  //freeeAPIから各種マスタを取得 
  const [objAccountItems,objPartners,objItems] = getfreeeMasterData_();

  for (const arr of values) {

    //勘定科目ID -> 勘定科目名
    const account_item_id = arr[index.account_item];
    arr[index.account_item] = fromIdToName_(objAccountItems,account_item_id);

    //取引先ID -> 取引先名
    const partner_id = arr[index.partner];
    arr[index.partner] = fromIdToName_(objPartners,partner_id);

    //品目ID -> 品目名
    const item_id = arr[index.item];
    arr[index.item] = fromIdToName_(objItems,item_id);

  }

  return values;

}

/**
* ID→名称変換用のマスタをfreeeAPIから取得する
*
* @return {Object[]} ID→名称変換用マスタデータ
*/
function getfreeeMasterData_() {

  //勘定科目一覧,取引先一覧,品目一覧
  const accountsUrl = `https://api.freee.co.jp/api/1/account_items?company_id=${company_id}`;
  const partnersUrl = `https://api.freee.co.jp/api/1/partners?company_id=${company_id}`;
  const itemsUrl = `https://api.freee.co.jp/api/1/items?company_id=${company_id}`;

  const arrUrls = [accountsUrl,partnersUrl,itemsUrl];

  const accessToken = getService().getAccessToken();

  const arrObj = [];

  for (const url of arrUrls) {

    const obj = accessfreeeAPI_(accessToken,url);
    arrObj.push(obj);

  }

  const objAccountItems = arrObj[0].account_items;
  const objPartners = arrObj[1].partners;
  const objItems = arrObj[2].items;

  return [objAccountItems,objPartners,objItems];

}

/**
* IDから名称を返す(例:勘定科目ID -> 勘定科目名)
*
* @param {string} ID
* @return {string} 日本語名称
*/
function fromIdToName_(obj,id) {

  let name = '';

  for (let i in obj) {
    if (obj[i].id === id) {
      name = obj[i].name;
      break;
    }    
  }

  return name;

}

/**
* 対象の取引データを元に通知用の本文を作成する
*
* @param {Object[]} 通知対象の取引データ
* @return {string} 通知本文
*/
function createAlertBody_(values) {

  let body = '';

  for (let i = 0; i < values.length; i++) {

    const due_date = values[i][0]; //支払期日
    const account_item = values[i][1]; //勘定科目
    const amount = values[i][2]; //金額
    const partner = values[i][3]; //取引先
    const item = values[i][4]; //品目

    body += `${due_date}, ${account_item}, ${amount}, ${partner},${item}\n`;

  }

  const cnt = values.length;
  const alert = '支払期日が1週間以内、かつ、未決済の取引が【' + cnt + '】件あります\n\n';
  const header = '支払期日, 勘定科目, 金額, 取引先, 品目\n';
  body = alert + header + body;

  return body;

}

/**
* メール送信する
*
* @param {string} メール本文
*/
function sendMail_(body) {

  const recipient = '*********@test.jp'; //送信先
  const subject = '【会計freee】支払期日1週間以内の未決済取引一覧';

  const options = {
    name: 'freee通知' //差出人名
  };

  GmailApp.sendEmail(recipient, subject, body, options);

}

トリガーで自動実行する

GASの強みといえば「トリガーによる自動実行」です!毎朝スクリプトが実行されるよう、任意の時刻にトリガーを設定しておきましょう。

※実行する関数は「main」です

実行結果

対象の取引データが存在する場合、このようなメールが届きます。これでうっかり支払い忘れも防げますね!

alt

特記事項:V8ランタイムを使用

このスクリプトはV8ランタイムで動作します。

古いランタイムを使用している方は、スクリプトエディタのメニュー「実行」→「Chrome V8 を搭載した新しい Apps Scriptランタイムを有効にする」をクリックして、V8ランタイムを有効にしましょう。

カスタマイズしたい

お好みでスクリプトを変更してみてください。

①10日以内のデータを通知したい

Dateオブジェクトの日付設定を変更しましょう。

const date = new Date();
date.setDate(date.getDate() + 10); //n日後の日付

②収入・支出の両方を通知したい

上記のスクリプトは「支出」のみを取得しています。

リクエストURLのtypeパラメータを削除します。

const requestUrl = `https://api.freee.co.jp/api/1/deals?company_id=${company_id}&status=unsettled&end_due_date=${end_due_date}&limit=100`;

③収入のみを通知したい

リクエストURLのtypeパラメータの値をincomeに変更します。

const requestUrl = `https://api.freee.co.jp/api/1/deals?company_id=${company_id}&status=unsettled&type=income&end_due_date=${end_due_date}&limit=100`;

工夫したところ

このスクリプトを書くうえで工夫した点をまとめます。

①レスポンスのJSONデータに含まれる「ID」を「名称」に変換する

取引(収入/支出)(/api/1/deals) で取得したデータの中から、下記の5項目をメール通知しています。

  1. 支払期日
  2. 勘定科目
  3. 金額
  4. 取引先
  5. 品目

上記5項目のうち、3項目は、JSONデータに「ID」しか含まれません。

  • partner_id : 取引先ID
  • account_item_id : 勘定科目ID
  • item_id : 品目ID

「ID」をメール通知してもわからないので、convertArrayData関数で「ID」を「名称」に変換しています。

alt

そこで必要になるのが、3つのマスタデータ。getfreeeMasterData関数でマスタデータ(JSON)を取得しています。

  • 勘定科目 : /api/1/account_items
  • 品目 : /api/1/items
  • 取引先 : /api/1/partners

「3つのJSONデータ」と「IDから名称を返すfromIdToName関数」で、IDを名称に変換します。

②短時間に過度のAPIアクセスをしない

短時間に過度のAPIアクセスをするとサーバーに負担がかかってしまうので、WebサービスのAPIを使う時のお作法(?)として、accessfreeeAPI関数内に「待機処理」を加えています。

1000ミリ秒 = 1秒です。

Utilities.sleep(1000);

freeeAPI公式リファレンスにもこのような記載があるので、APIを使う際は気をつけましょう。

freeeは一定期間に過度のアクセスを検知した場合、APIアクセスをコントロールする場合があります。

③1関数1処理!

このスクリプトは8個の関数で構成されています。1つの関数が長くならないよう「1関数1処理」を目標として書いてみました。

また、部品化できる処理はなるべく切り出して、1つの関数としてまとめています。

(例)accessfreeeAPI関数

「リクエストURL」と「アクセストークン」を渡すと、「レスポンス」を返す関数です。スクリプト全体で合計4回freeeAPIにアクセスするので、部品化しておくとスッキリしますね。

まとめ

freeeAPI×GASで「支払期日が1週間以内の未決済取引データをメール通知する」スクリプトを紹介しました。

人間はうっかり忘れてしまう生きものです。GASのトリガーを使った「リマインドBot」は大いに役立つので、ぜひfreeeAPIを使う最初の一歩としてチャレンジしてみましょう!

この記事は「GAS×freeeAPIでデータを取得すること」をメインテーマとしたので、通知方法は最もシンプルな「メール」としました。SlackやChatworkなどに通知したい方は、ぜひカスタマイズしてみてください。

個人ブログでもいくつかfreeeAPI×GASのネタを書いているので、よかったら見ていってください→「もりさんのプログラミング手帳:会計freee

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした