
freee APIで下書き請求書を一気にチェックしよう!
今日も元気にノンプロ研でGAS(Google Apps Script)勉強中です。
ノンプロ研とは、「ノンプログラマーのためのスキルアップ研究会」の略称で、ノンプログラマー(プログラミングを本職にしない人)たちが集まって、プログラミングを武器にすべくスキルを磨いているコミュニティです。
最近、freee API講座を受講してAPIでデータを取得したりできるようになりました。
freee会計は、もともとノンプログラマーと非常に相性の良い会計ソフトだと思っていたのですが、APIを使えるようになることで、さらにその利便性がアップすると感じています。
今日はそんな事例のひとつをご紹介。
面倒な請求書の送付前チェック(税率や部門など)
freee会計では、請求書の作成→取引(会計)登録→入金消込の一連の作業をシームレスにできるのが魅力のひとつだと思います。
運用としては、各担当者(営業部門等)が作成した請求書を社外の顧客にメールや郵送するまでfreee会計内で完結します。
しかし、担当者が増えてくると、各担当者の会計知識には当然ばらつきがあり、税率の選択や品目タグや社内の部門タグの選択などの社内ルールに適した作成ができていないケースも生じます。
消費税の税率選択に関しては、社外の請求書送付先にも影響があるので、事前に経理担当者や責任者がチェックするという運用が考えられます。
また、社内で厳密なチェックがなされていない場合でも、顧問税理士や会計事務所が定期的にチェックするというケースも想定できます。
1-2枚だとそんなに負担ではないのですが、チェックする枚数が増えてくると、対象の請求書をいちいち開き、場合によっては明細をさらに確認してという作業が手間になってきます。


チェック対象の請求書を探し出して1件1件開くのが大変
freeeは非常にUIが優れていますが、クラウドサービスのためレスポンスの鈍さを感じる時があります。
例えば、表示している請求書の次の請求書を開く矢印ボタンを押しても読み込みに多少の時間がかかります。そのため、大量に請求書をチェックしたいときには地味にストレスです。

また請求書一覧の画面で「下書き」の請求書や日付などの条件で絞り込むことができますが、この絞り込んだ請求書だけをどんどん開いていきたい時に、一括で開く方法がありません。

一覧画面で絞り込んだ後、個別の請求書のページで「次の請求書を開きます」と表示される矢印を押して、条件で絞り込んだ請求書間で遷移していければ良いのですが、残念ながら絞り込み条件に関係なく登録されている請求書の元の順番のまま遷移していきます。

指定条件の請求書を一括で開いて確認するには…
この一括で開くというのはブラウザの操作の問題であるので、freee会計側に改善を求めるのは少し酷だとは思います(一覧で条件絞り込み後の請求書のみでのページ遷移は実現してほしいですが…)。
しかし、APIと直接関係ないですが、freeeの請求書ページは、実は請求書ごとにユニークなURLを持っています。
https://secure.freee.co.jp/docs_v2/invoice/********/edit
上記の********の部分に請求書固有のIDが入ります。このIDを指定条件で一括取得すれば、確認したい請求書だけをサクサクと開けるのではないかと睨んで、ここでfreee APIの登場です。
freee APIで取得できる請求書IDと請求書の固有URLのID部分は一致するのか?
まず確認したのが、請求書固有のURLのIDと思われる部分とfreee APIで取得できる請求書IDが一致するのかという疑問です。
この確認のため、まずは1件でもfreee API で請求書に関するデータを取得する必要があります。
Google Apps Scriptでfreee APIを操作するには事前に準備が必要ですが、今回のノンプロ研のfreee API講座の講師であったITライターのもりさんが、ご本人のブログでもその方法を公開されています。
この手順に従って、認証作業をまず終えます。
会計APIリファレンスを確認しつつ請求書一覧を取得してみる
事前準備が終わったら早速、請求書一覧を取得します。ここで情報を取得したい事業所のIDが必要になります。
先述のもりさんのブログに事業所IDを確認するためのサンプルコードも紹介されているので、そちらを参考に事業所IDをまず確認します。
ちなみにfreee会計にログインした時に右上の事業所名にカーソルをあわせた時に表示される事業所番号とAPIで使用する事業所IDは別物なのでご注意を。

ひとまず、とくに細かい条件を指定しないで請求書一覧を取得するコードは以下になります。
function getInvoices() {
// 取得したい事業所IDの指定
const company_id = '7桁の事業所ID';
// 請求書一覧を取得するリクエストURL
const url = `https://api.freee.co.jp/api/1/invoices?company_id=${company_id}`;
const accessToken = getService().getAccessToken();
const params = {
headers: { 'Authorization': 'Bearer ' + accessToken },
method: 'get'
};
const response = UrlFetchApp.fetch(url, params).getContentText();
const obj = JSON.parse(response);
console.log(obj);
}
APIで取得したIDはやはり、請求書固有のURLのIDだった!
実行すると各請求書のデータがオブジェクト形式で取得できました。その中のidに注目します。

このIDを先程確認した個別の請求書を表示するURLのIDの部分に代入して、ブラウザを開いてみます。すると無事に請求書が表示されました。

これで、freee APIを使って指定条件の請求書一覧を取得し、そのIDを決まったURLに代入することで、対象の請求書を一括で開くめどがつきました。
次は具体的にスプレッドシートを使って、絞り込み条件の指定、請求書ID等の取得とシートへの記入などをやっていきます。
スプレッドシートを使用してfreee会計から請求書の一覧を取得する
指定した条件の請求書を一括でチェックするために、まずはスプレッドシートに該当する請求書の詳細を取得します。
手順としては、
スプレッドシートから検索のためのパラメーターを指定する
APIで取得した請求書データから必要な項目を抽出しスプレッドシートに転記する
スプレッドシートに一覧で出力された請求書の固有URL一覧から各ページを一括表示させる
となります。
スプレッドシートに検索条件をパラメーターに変換するためのテーブルを準備
freeeの通常のUIからは、「下書き」や「送付待ち」といった日本語でステータスを指定しますが、APIを利用する際には、これらを指定の文字列に変換してパラメーターとして使用する必要があります。
もちろんGASのスクリプト内でこうした変換をすることも可能ですが、初心者には手間も時間もかかるため、さくっとスプレッドシートで変換用のテーブルを用意して、関数などで呼び出すようにしました。

検索条件を指定するシートを準備する(日付の取扱い注意!)
続いて、検索条件を指定するシートを作成します。
今回は全ての検索条件(=パラメーター)を網羅せず、以下のものに絞りました。
請求日の開始日
請求日の終了日
期日の開始日
期日の終了日
取得件数(最大100)
入金ステータス
請求書ステータス
ここで注意が必要なのは、日付をAPIのパラメーターとして指定する場合、yyyy-MM-dd形式の文字列である必要がある点です。
GASで処理する場合は、
const date = <スプレッドシートの特定セルからgetValue()メソッド等で日付を取得>;
const stringDate = Utilities.formatDate(date, 'JST', 'yyyy-MM-dd');
とUtilities.formatDate()メソッドを使うのが王道ですが、今回はスプレッドシートのTEXT関数を使用してフォーマットを整えてみました。
またスプレッドシートの日付入力セルの書式設定をyyyy-MM-dd形式にしてgetDisplayValue()メソッドを使って、見た目のままの文字列を取得する方法もあります。
また入金ステータスや請求書ステータスは、データの入力規則を使用してプルダウンで検索条件を指定させ、先程用意したパラメーター変換テーブルから対応する値をVLOOKUP関数で取得しています。

さあこれで準備万端です。条件を自由に指定できるようになったのでスクリプトで請求書のデータを取得してみましょう。
GASでfreee会計の請求書一覧を取得するスクリプト
いよいよGASとAPIの登場です。スクリプトの処理の流れとしては以下のようになっています。
検索条件のシートからパラメーターを取得して定数に代入する
請求書一覧を取得するリクエストURLを生成する
請求書一覧を格納したオブジェクトからシートに必要な項目を書き込み
ちょっと長いですがコードはこちら。
function getInvoicesList() {
// 検索条件をシートから二次元配列で取得する
const ss = SpreadsheetApp.getActiveSpreadsheet();
// getValues()でデータを取得すると二次元配列になる
const queries2D = ss.getSheetByName('検索条件').getRange('D2:D8').getValues();
// 配列の中の配列の要素[[要素1],[要素2]...]を直接取り出したいので一次元配列にしたい
const queries1D = queries2D.flat();
// 配列も分割代入可能。分割代入を覚えると楽
[start_issue_date, end_issue_date, start_due_date, end_due_date, limit, payment_status, invoice_status] = queries1D;
// 一つづつ代入する場合は以下の例
// const start_issue_date = queries1D[0] // queries1Dの1番目(インデックス0)の要素
// const end_issue_date = queries1D[1] // queries1Dの2番目(インデックス1)の要素
// const start_due_date = queries1D[2] // queries1Dの3番目(インデックス2)の要素
// ...以下同じように
// ここまでで検索条件を各定数(start_issue_date等)に代入終了
/** ----------------------------------- */
// 取得したい事業所IDの指定
const company_id = '7桁の事業所ID';
// 請求書一覧を取得するリクエストURL
let url = `https://api.freee.co.jp/api/1/invoices?company_id=${company_id}`;
// 検索条件の有無を判定して、条件指定が有るときだけリクエストURLを上書きしていく。
// ifの判定は「暗黙の型変換」を利用
if (start_issue_date) { url = url + `&start_issue_date=${start_issue_date}`; }
if (end_issue_date) { url = url + `&end_issue_date=${end_issue_date}`; }
if (start_due_date) { url = url + `&start_due_date=${start_due_date}`; }
if (end_due_date) { url = url + `&end_due_date=${end_due_date}`; }
if (limit) { url = url + `&limit=${limit}`; }
if (payment_status) { url = url + `&payment_status=${payment_status}`; }
if (invoice_status) { url = url + `&invoice_status=${invoice_status}`; }
const accessToken = getService().getAccessToken();
const params = {
headers: { 'Authorization': 'Bearer ' + accessToken },
method: 'get'
};
const response = UrlFetchApp.fetch(url, params).getContentText();
const obj = JSON.parse(response);
// ここまででfreee APIから請求書一覧を格納したオブジェクト(= obj)を取得終了
/** ----------------------------------- */
// スプレッドシート書き込み用に二次元配列を見出し行付きで準備
const ary2D = [['売上計上日', '請求書ID', '取引ID', '請求先名', '合計額', '消費税額', '備考', '請求書URL', '取引URL']];
// 請求書一覧のオブジェクト(= obj)のinvoicesプロパティに格納されている請求書一覧のデータ全てから必要な値を取得してスプレッドシート書き込み用の二次元配列に追加
for (const invoice of obj.invoices) {
const urlInvoice = `https://secure.freee.co.jp/docs_v2/invoice/${invoice.id}/edit`;
const urlDeal = `https://secure.freee.co.jp/deals#deal_id=${invoice.deal_id}`;
ary2D.push([invoice.booking_date, invoice.id, invoice.deal_id, invoice.partner_display_name, invoice.total_amount, invoice.total_vat, invoice.description, urlInvoice, urlDeal]);
}
// 書き込み用のシートを取得し一度現在のデータを削除してから書き込み
const sheetInvoices = ss.getSheetByName('取得請求書一覧');
sheetInvoices.getDataRange().clearContent(); // clearContent()メソッドは指定した範囲のデータを削除する
const rangeData = sheetInvoices.getRange(1, 1, ary2D.length, ary2D[0].length); // 二次元配列の各次元の要素数=スプレッドシートの行列数の範囲を取得
rangeData.setValues(ary2D);
/** ----------------------------------- */
}
請求書一覧を取得するリクエストURLの作り方
freee API のリクエストURLは、パラメーターによる条件の指定がある時だけ &limit=100 のような文字列をURLに追加します。
逆に言えば、条件指定がない場合には何も追加してはいけないということで、例えば請求の開始日を指定しない場合に &start_issue_date= のような文字列をURLに加えるとエラーになります。
これを暗黙の型変換を利用したif文による条件分岐によって検索条件の有無を判定して、条件指定が有るときだけリクエストURLを上書きし、必要なリクエストURLを生成しています。
// 請求書一覧を取得するリクエストURL
let url = `https://api.freee.co.jp/api/1/invoices?company_id=${company_id}`;
// 検索条件の有無を判定して、条件指定が有るときだけリクエストURLを上書きしていく。
// ifの判定は「暗黙の型変換」を利用
if (start_issue_date) { url = url + `&start_issue_date=${start_issue_date}`; }
if (end_issue_date) { url = url + `&end_issue_date=${end_issue_date}`; }
if (start_due_date) { url = url + `&start_due_date=${start_due_date}`; }
if (end_due_date) { url = url + `&end_due_date=${end_due_date}`; }
if (limit) { url = url + `&limit=${limit}`; }
if (payment_status) { url = url + `&payment_status=${payment_status}`; }
if (invoice_status) { url = url + `&invoice_status=${invoice_status}`; }
各請求書の固有URLと対応する取引の固有URLもシートに書き込もう!
各請求書には固有のURLがあることはすでに述べましたが、実は取引にも固有のURLがあります。
今回の記事の主題である「下書き」の請求書は、当然まだ取引は作成されていないのですが、発行され「送付待ち」や「送付済み」になれば取引が作成されます。
税率やタグなどのチェックをする場合は、個別の請求書ではなく取引の明細を確認したほうが早いケースもあると思います。
そのため、請求書固有URLに加えて取引固有URLも取得しました。
固有URLに代入したい取引IDは、レスポンスから解析したオブジェクトのdeal_idプロパティに格納されています。
const urlInvoice = `https://secure.freee.co.jp/docs_v2/invoice/${invoice.id}/edit`;
const urlDeal = `https://secure.freee.co.jp/deals#deal_id=${invoice.deal_id}`;l_id}`;
スプレッドシートの各セルに記載されたURLを一括で開く
ということでスクリプトを実行すると指定したシートに以下のようにデータが書き込まれます。

ここでURLが書き込まれたセル範囲を選択し、右クリックメニューから「セルでの他の操作項目を表示」→「リンクを開く」の順に選んでいくと、対象のURLが新しいタブで一気に開きます。

注意点としては、スプレッドシートでURLを開く前に「別のブラウザタブでfreee会計の対象の事業所にログインしている」状態にしておくことです。
あと同時に開こうとするスプレッドシートのURLが多すぎるとエラーが出ますので、その場合は適当な数に分割して開いていただけるとよいかと思います。

これで自身で絞り込んだ条件の請求書のみを効率的にチェックできるようになりました。