見出し画像

freeeAPIとGASを利用して、Googleドライブのファイルを一括でファイルボックスにアップロードする(メモもアップロードできるよ)

ファームノートホールディングス 小森です。

facebookで他社さんのfreeeAPI投稿を見て触発されたので、自分でも色々検証中です。使い方はある程度理解できたので、サクッと効率化出来ることないかなぁと考えて、タイトルのGAS(Google Apps Script)を作ってみました。

ファイルボックスへのアップロードはDropboxからとメール送信により可能なのですが、Dropboxは全社的には利用していない点、アップロード時にメモ欄入力出来ない点がありました。googleドライブを利用できれば今後全社的にスケールできそうだなと思い作ってみました。

基本的にはfreeeさんのサンプルスクリプトを参照していますが、いくつかハマった点があるのでそこらへんを交えて解説していきます!

freeeへのアプリケーション登録

まず下記サイトは全体概要が書いてあるので目を通しておいた方が良いと思います。

まず初めにfreeeへのアプリケーション登録を行う必要があります。ここでのアプリケーションとは自身で作成するプログラム(スクリプト)の事を指しています。

このサイトを参考にしてアプリケーション登録を行います。読み飛ばしていると、コールバックURLとはなんぞや?、とハマっていたのですがちゃんと書いてありました。

https://script.google.com/macros/d/{スクリプト ID}/usercallback

スクリプトIDは、スクリプトエディタ→プロジェクトのプロパティから取得可能です。

アプリケーション登録が完了したら、AppIDとSecretを控えておいて下さい。その他の情報は今回は不要です。アプリケーションの登録画面にアクセスできないという場合はfreeeの管理者権限が付与されていない可能性がありますので、会社の管理者の方まで連絡してみて下さい。

スプレッドシートとスクリプトエディタのセットアップ

Gsuiteのスプレッドシートとスクリプトエディタは親和性が高いので同時に作成することが多いですが、スクリプトエディタだけでも作成可能です。今回は一部の表示やボタン用にスプレッドシートも作成しました。

普通にGsuiteのスプレッドシートを作成します。今回は「main」シートと「companydata」シートを使いますので、ブランクで作成します。

次にスクリプトエディタを起動します。スプレッドシート上部のツール→スクリプトエディタから作成できます。

コード.jsの中に下記コードが自動作成されていると思いますが、使わないので削除してしまってOKです。

function myFunction() {
  
}

次に、今回参照するライブラリの設定を行います。GASからfreeeへの認証のためOAuth2を利用しますが、このライブラリを利用するとGASから簡単にコーディングするためのものです。

リソース→ライブラリから登録画面を開きます。図書館て!

ライブラリを追加に下記プロジェクトキーを入力します。

1B7FSrk5Zi6L1rSxxTDgDEUsPzlukDsi4KGuTMorsTQHhGBzBkMun4iDF

タイトルにOAuth2と表示されたらOKです。ここまでで基本的なセットアップは完了です。

GASからfreeeAPIを使って認証する

まずコード.gsに下記コードをコピペして下さい。こちらが今回の全コードとなります。重要だったりハマった箇所をその後説明します。

/******************************************************************
参照ライブラリ
title        |OAuth2
project_key  |1B7FSrk5Zi6L1rSxxTDgDEUsPzlukDsi4KGuTMorsTQHhGBzBkMun4iDF
******************************************************************/

var ss = SpreadsheetApp.getActiveSpreadsheet();
var APP_ID = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
var SECRET = "YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY";
var TARGET_SHEET = "companydata";
var GETCOMPANIES = "https://api.freee.co.jp/api/1/companies";
var FILEBOX = "https://api.freee.co.jp/api/1/receipts";
var COMPANY_ID = "CCCCCC";
var FILEBOX_MAIN_FOLDER_ID = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
var FILEBOX_DONE_FOLDER_ID = "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB";

// 認証のエンドポイントとなるダイアログ
function alertAuth() {
  var service = getService();
  var authorizationUrl = service.getAuthorizationUrl();
  var template = HtmlService.createTemplate('リンクを別タブで開いて下さい。<br /><a href="' + authorizationUrl + '">認証する</a>');
  template.authorizationUrl = authorizationUrl;
  var page = template.evaluate();
  SpreadsheetApp.getUi().showModalDialog(page, "認証が必要です");
}

//freeeAPIのサービスを取得
function getService() {
  return OAuth2.createService('freee')
      .setAuthorizationBaseUrl('https://secure.freee.co.jp/oauth/authorize')
      .setTokenUrl('https://api.freee.co.jp/oauth/token')
      .setClientId(APP_ID)
      .setClientSecret(SECRET)
      .setCallbackFunction('authCallback')
      .setPropertyStore(PropertiesService.getUserProperties())
}

//認証コールバック(アクセストークンの取得)
function authCallback(request) {
  var service = getService();
  var isAuthorized = service.handleCallback(request);
  var access_token = service.getAccessToken();

  if (isAuthorized) {
    return HtmlService.createHtmlOutput('認証に成功しました。タブを閉じてください。');
  } else {
    return HtmlService.createHtmlOutput('認証に失敗しました。タブを閉じてください。');
  }
}

//会社情報取得Companies GET
function getCompaniesInfo() {
  var freeeApp = getService();
  var accessToken = freeeApp.getAccessToken();
  var requestUrl = GETCOMPANIES;
  var headers = { "Authorization" : "Bearer " + accessToken };
  var targetSheetName = TARGET_SHEET;
  var options ={
     "method"  : "get",
     "headers" : headers
   };
  var res = UrlFetchApp.fetch( requestUrl , options ).getContentText();
  //レスポンスのデータを配列に格納
  var parsedResponse = JSON.parse( res );
  var data = parsedResponse.companies;
  var param1 = [];
  var param2 = [];
  var param3 = [];
  var param4 = [];
  var param5 = [];
  for ( var i = 0 ; i < data.length ; i++ ) {
    param1.push( [ data[ i ].id ] );
    param2.push( [ data[ i ].name ] );
    param3.push( [ data[ i ].name_kana ] );
    param4.push( [ data[ i ].display_name ] );
    param5.push( [ data[ i ].role ] );
  };
  //取得したデータをシートに保存
  var sheet = ss.getSheetByName( targetSheetName );
  sheet.clear();
  sheet.getRange( 1 , 1 , param1.length , 1 ).setValues( param1 );
  sheet.getRange( 1 , 2 , param2.length , 1 ).setValues( param2 );
  sheet.getRange( 1 , 3 , param3.length , 1 ).setValues( param3 );
  sheet.getRange( 1 , 4 , param4.length , 1 ).setValues( param4 );
  sheet.getRange( 1 , 5 , param5.length , 1 ).setValues( param5 );
} 

//ファイルボックスへのアップロード Receipts POST
function postFilebox(file) {
  var freeeApp = getService();
  var accessToken = freeeApp.getAccessToken();
  var requestUrl = FILEBOX;
  var headers = {"Authorization" : "Bearer " + accessToken };
  var file_data = DriveApp.getFileById(file.getId()).getBlob();
  
  var payload = {
    "company_id":COMPANY_ID,
    "description":file.getName(),
    "receipt":file_data
  };
  
  var options = {
    "method":"POST",
    "headers":headers,
    "payload": payload,
    muteHttpExceptions: true
  };
  
  return res = UrlFetchApp.fetch( requestUrl , options );
}

//ファイルボックスへのアップロード実行
function exec(){
  var files = DriveApp.getFolderById(FILEBOX_MAIN_FOLDER_ID).getFiles();
  
  while(files.hasNext()) {
    var file = files.next();
    var res = postFilebox(file);
    var json = JSON.parse(res.getContentText());
    
    if(json["receipt"]["status"] == "confirmed"){
      //アップロード成功時にDoneフォルダに移動
      DriveApp.getFolderById(FILEBOX_DONE_FOLDER_ID).addFile(file);
      DriveApp.getFolderById(FILEBOX_MAIN_FOLDER_ID).removeFile(file);
    }
  }
}

下記サイトを参照しつつ、上手く動かない箇所を修正してあります。

まずはじめにfreeeへGASから認証(ログイン)します。コードはalertAuth()、getService()、authCallback()が関連します。ここはほぼサンプルのコピペです。getService()とauthCallback()は必ず必要なもの、と思っていれば特に問題ないかと思いますので、alertAuth()だけ説明します。

var APP_ID = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
var SECRET = "YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY";

// 認証のエンドポイントとなるダイアログ
function alertAuth() {
  var service = getService();
  var authorizationUrl = service.getAuthorizationUrl();
  var template = HtmlService.createTemplate('リンクを別タブで開いて下さい。<br /><a href="' + authorizationUrl + '">認証する</a>');
  template.authorizationUrl = authorizationUrl;
  var page = template.evaluate();
  SpreadsheetApp.getUi().showModalDialog(page, "認証が必要です");
}

まず上部のAPP_IDとSECRETに先程メモした値を貼り付けます。

この関数を実行するとスプレッドシート上にfreeeへの認証ダイアログが表示されます。認証は24時間(間違ってたらすいません)維持されますが、その後は再度認証が必要になってきますので、定期的に実行したいスクリプトを作りたい場合は工夫が必要になってきそうです。今回は都度実行を想定しています。

各種ダイアログはスクリプトエディタではなくスプレッドシート側に表示されるので注意して下さい。地味にハマりポイントです。ダイアログの「認証する」をクリックして下さい。もし上手く動かなかったらリンクを右クリックして別ウインドウ(別タブ)で開いてみて下さい。freeeの認証画面の後に「認証に成功しました。タブを閉じてください。」が表示されたら成功です。

会社情報を取得する

利用しているfreeeの会社情報を取得します。グループ会社があって複数利用している場合にも対応しています。こちらもほとんどコピペです。

var ss = SpreadsheetApp.getActiveSpreadsheet();
var APP_ID = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
var SECRET = "YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY";
var TARGET_SHEET = "companydata";
var GETCOMPANIES = "https://api.freee.co.jp/api/1/companies";

//会社情報取得Companies GET
function getCompaniesInfo() {
  var freeeApp = getService();
  var accessToken = freeeApp.getAccessToken();
  var requestUrl = GETCOMPANIES;
  var headers = { "Authorization" : "Bearer " + accessToken };
  var targetSheetName = TARGET_SHEET;
  var options ={
     "method"  : "get",
     "headers" : headers
   };
  var res = UrlFetchApp.fetch( requestUrl , options ).getContentText();
  //レスポンスのデータを配列に格納
  var parsedResponse = JSON.parse( res );
  var data = parsedResponse.companies;
  var param1 = [];
  var param2 = [];
  var param3 = [];
  var param4 = [];
  var param5 = [];
  for ( var i = 0 ; i < data.length ; i++ ) {
    param1.push( [ data[ i ].id ] );
    param2.push( [ data[ i ].name ] );
    param3.push( [ data[ i ].name_kana ] );
    param4.push( [ data[ i ].display_name ] );
    param5.push( [ data[ i ].role ] );
  };
  //取得したデータをシートに保存
  var sheet = ss.getSheetByName( targetSheetName );
  sheet.clear();
  sheet.getRange( 1 , 1 , param1.length , 1 ).setValues( param1 );
  sheet.getRange( 1 , 2 , param2.length , 1 ).setValues( param2 );
  sheet.getRange( 1 , 3 , param3.length , 1 ).setValues( param3 );
  sheet.getRange( 1 , 4 , param4.length , 1 ).setValues( param4 );
  sheet.getRange( 1 , 5 , param5.length , 1 ).setValues( param5 );
} 

freeeAPIでは取得したい情報毎に決まったリクエストURLが用意されています。会社情報の場合は下記を利用します。

https://api.freee.co.jp/api/1/companies

下記にリファレンスがまとまっていますので適宜参考にして下さい。

コード上はvar res = UrlFetchApp.fetch( requestUrl , options ).getContentText()が肝となっており、設定している各種パラメタをもとにfreeeAPIを実行しています。また、resの中には実行結果が保存されているので、結果に応じた挙動をGASでコーディングできます。

以降のコードは「companydata」シートにfreeeのデータを出力するものとなっています。実行結果は下記のとおりです。

A列は会社毎のIDとなっていて後ほど利用しますので控えておいて下さい。

特定のGoogleドライブのフォルダ内のファイルを一括してファイルボックスにアップロードする

本題のファイルボックスへのアップロードです。簡単に仕様を決めていきます。

・特定のGoogleドライブのフォルダ内のファイルをファイルボックスにアップロードする
・メモ欄にファイル名をそのまま登録する
・成功した場合は「Done」フォルダに移動、失敗した場合はファイルはそのまま
・スプレッドシートに設置したボタンで実行する

まずパラメタの設定です。

var ss = SpreadsheetApp.getActiveSpreadsheet();
var APP_ID = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
var SECRET = "YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY";
var TARGET_SHEET = "companydata";
var GETCOMPANIES = "https://api.freee.co.jp/api/1/companies";
var FILEBOX = "https://api.freee.co.jp/api/1/receipts";
var COMPANY_ID = "CCCCCCCC";
var FILEBOX_MAIN_FOLDER_ID = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
var FILEBOX_DONE_FOLDER_ID = "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB";

COMPANY_IDには先ほどメモしたIDを入力します。FILEBOX_MAIN_FOLDER_IDとFILEBOX_DONE_FOLDER_IDにはそれぞれのフォルダのIDを入力します。IDはURLのfolders/xxxxxのxxxxx部分です。

コードです。postfilebox()と実行用のexec()を用意しました。

//ファイルボックスへのアップロード Receipts POST
function postFilebox(file) {
  var freeeApp = getService();
  var accessToken = freeeApp.getAccessToken();
  var requestUrl = FILEBOX;
  var headers = {"Authorization" : "Bearer " + accessToken };
  var file_data = DriveApp.getFileById(file.getId()).getBlob();
  
  var payload = {
    "company_id":COMPANY_ID,
    "description":file.getName(),
    "receipt":file_data
  };
  
  var options = {
    "method":"POST",
    "headers":headers,
    "payload": payload,
    muteHttpExceptions: true
  };
  
  return res = UrlFetchApp.fetch( requestUrl , options );
}

ほとんど会社情報取得のコードと似ていますが、今回は取得(GET)ではなくアップロード(送信:POST)なのでpayloadデータを付与する必要があります。どんな情報が必要なのかはリファレンスを参照して下さい。今回のファイルボックス(Receipts)ではcompany_idとreceiptが必須項目です。また、メモ欄であるdescriptionも今回は利用します。

今回はreceiptに設定するfile形式でハマりました。結論から言うと、DriveApp.getFileById(file.getId()).getBlob();にてBlob形式で渡せば良い事がわかりました。

Blob(Binary Large OBject):BLOBとは、データベースのフィールド定義などで用いられるデータ型の一つで、テキスト(文字)や数値以外の任意の形式のバイナリデータを格納することができるもの。

なるほど。

//ファイルボックスへのアップロード実行
function exec(){
  var files = DriveApp.getFolderById(FILEBOX_MAIN_FOLDER_ID).getFiles();
  
  while(files.hasNext()) {
    var file = files.next();
    var res = postFilebox(file);
    var json = JSON.parse(res.getContentText());
    
    if(json["receipt"]["status"] == "confirmed"){
      //アップロード成功時にDoneフォルダに移動
      DriveApp.getFolderById(FILEBOX_DONE_FOLDER_ID).addFile(file);
      DriveApp.getFolderById(FILEBOX_MAIN_FOLDER_ID).removeFile(file);
    }
  }
}

最後に実行関数のexec()です。フォルダ内のファイル探索をするだけなのでシンプルです。アップロードの成功/失敗はstatus=confirmedかどうかで判断しています。また、成功した場合はDoneフォルダにファイルを移動しています。DriveAppにはmoveのようなファイル移動はないので、Doneフォルダへの追加→元ファイルの削除の順番で実行しています。コードは以上となります。

関数の実行の仕方は色々ありますが、今回は描画した画像にスクリプトを割り当てています。画像を右クリックすると点3つが出てきて、そちらをクリックするとスクリプトを割り当てることが出来ます。例えばexec()を割り当てる場合は「exec」と記入すればOKです。

おわりに

会計システムがAPI公開していると色々と連携できそうで夢が広がりますね。次は会計データの集計や人事労務freeeのAPIも使ってみようかと思います。

この記事が気に入ったらサポートをしてみませんか?