freee APIを利用し、スプレッドシートから見積書を作成する

この記事ではGASからfreeeAPIを使うことで、スプレッドシートで管理しているデータをfreeeに直接、データを送信や登録をする方法を紹介します。

事前準備

  • freeeのアカウントを保有している必要があります。
  • 他のユーザーが使用する際も実行前にfreee画面での認証作業が必要です。
    (認証ページへ遷移するメニューを作成します)
  • スプレットシートの作成。

freee APIのアクセストークンを取得する

アプリに対してhttpサービスへのアクセスを付与するフレームワーク(OAuth2)を使用し、アクセストークンを用いて保護されたリソースにアクセスします。

引用元:freee APIのアクセストークンを取得する

freeeの公式ヘルプセンターページの「freee APIのアクセストークンを取得する」を参考に、アクセストークンを取得する設定を行います。参照:freee APIのアクセストークンを取得する

連携アプリの作成

①スクリプトエディタで「設定」 →「 プロジェクトの設定」内の「スクリプトID」で確認します。

freeeアプリストアへアクセスし、ログイン後、右上の「アプリ管理」をクリックします。(freeeAPI経由でデータサーバーにアクセスするための「クライアントアプリ」を作成します。)

③「開発アプリ一覧」の「新規追加」をクリックします。

④アプリ名・概要を入力し、利用規約を読んで同意にチェックし、「作成」をクリックします。

「Client ID」と「Client Secret」が発行されます。
これは、freeeAPIにアクセスするためのIDとパスワードのようなものです。

⑤コールバックURLの登録

連携アプリに「コールバックURL」(連携完了後の遷移先URL)を設定します。

コールバックURLの形式:https://script.google.com/macros/d/スクリプトID/usercallback

スクリプトIDの部分に、「スクリプトID」をあてはめ、「下書き保存」をクリックすればコールバックの設定は完了です。

freeeAPI連携アプリ(OAuth)を認証する

スクリプトエディタを開き、「ライブラリを追加」を選択して、認証用ライブラリ(OAuth2)を追加します。

ライブラリのプロジェクトキー1B7FSrk5Zi6L1rSxxTDgDEUsPzlukDsi4KGuTMorsTQHhGBzBkMun4iDF

GoogleAppsScriptでfreeeAPIと連携認証する

①作業をしやすくするため、スプレットシートにメニューを作成します。

/**
* スプレッドシートに独自メニューを作成する
*/
function onOpen(){
  const ui = SpreadsheetApp.getUi()
  const menu = ui.createMenu('freeeAPIメニュー')
  menu.addItem('認証', 'alertAuth');
  menu.addItem('見積書の作成', 'procAdd');
  menu.addItem('取引先の登録', 'PartnerAdd');
  menu.addToUi();
}

②先ほど作成した連携アプリの「Client_ID」と「Client_Secret」をスクリプトエディタに貼り付けて、alertAuth関数を実行します。

//連携アプリ情報
const Client_ID = '**************'//Client IDを設定;
const Client_Secret = '*************'//'Client Secretを設定'

//スクリプトへのアクセスを許可する認証URLを取得
function alertAuth() {
  const authUrl = getService().getAuthorizationUrl();  //認証URL
  var ui = SpreadsheetApp.getUi();
  Logger.log(authorizationUrl);//認証用URLがログ出力される

  //ダイアログのボタンから認証URLに遷移するように設定
  var dialogHtml = HtmlService.createTemplateFromFile('Authentication');
  dialogHtml.text = authUrl;
  ui.showModalDialog(dialogHtml.evaluate(), '認証');
}

//freeeAPIのサービスを取得する関数
function getService() {
  return OAuth2.createService('freee')
    .setAuthorizationBaseUrl('https://accounts.secure.freee.co.jp/public_api/authorize')
    .setTokenUrl('https://accounts.secure.freee.co.jp/public_api/token')
    .setClientId(Client_ID)
    .setClientSecret(Client_Secret)
    .setCallbackFunction('authCallback')
    .setPropertyStore(PropertiesService.getUserProperties())
}

//認証コールバック関数
function authCallback(request) {
  const service = getService();
  const isAuthorized = service.handleCallback(request);
  if (isAuthorized) {
    return HtmlService.createHtmlOutput('認証に成功しました。タブを閉じてください。');
  } else {
    return HtmlService.createHtmlOutput('認証に失敗しました。');
  };
}

③認証ページへ遷移するダイアログを作成します。

<!DOCTYPE html>
<html>
  <head>
  <base target="_top">
  </head>
  <body>
    <h1></h1>
    <div>
      <p>下記の認証ページより認証を行ってください</p>
      <input type="button" onclick="window.open('<?=text?>')" value="認証">
    </div>
  </body>
</html>

④作成したスプレットシートのメニューから、認証作業を行います。

⑤ボタンをクリックして認証URLに遷移し、「許可する」をクリックします。


成功メッセージが表示されれば、認証作業は完了です。
クライアントアプリからfreeeのデータサーバにアクセスできるようになります。

事業所ID、事業所名の確認

freeeAPIを使ってデータ取得、送信をするためには「事業所ID」「事業所名」が必要なので、確認するコードを作成します。(下記はログで確認を行っています。)

確認した事業所の情報は後に記述する見積書作成時などのfreeeから情報を取得する際に必要になります。

/**
* 事業所名を返す
* @return {string} 事業所名
*/
function getCompanyName() {
  const accessToken = getService().getAccessToken();
  const requestUrl = 'https://api.freee.co.jp/api/1/companies';
  const obj = accessfreeeAPI_(accessToken,requestUrl);
  const objCompanies = obj.companies;
  for(var i=0; i<objCompanies.length; i++){
    console.log(objCompanies[i].name)
    console.log(getCompanyId_(objCompanies[i].name))
  }
  return objCompanies[0].name
}

/**
* 事業所名から事業所IDを返す
*
* @param {string} 事業所名
* @return {string} 事業所ID
*/
function getCompanyId_(company_name) {
  const accessToken = getService().getAccessToken();
  const requestUrl = 'https://api.freee.co.jp/api/1/companies';
  let obj = accessfreeeAPI_(accessToken,requestUrl);
  obj = obj.companies;
  
  for (const i in obj) {
    if (obj[i].name === company_name) {
      return obj[i].id;
    }
  }
}

スプレットシートから操作された際の共通処理を作成する

メニュー項目、ダイアログのボタンをクリックされたときの動作を設定します。

下記では、スプレットシートのシート名に書かれた取引先名で登録する「シート名から見積書作成」と、プルダウンから取引先を選択する「取引先を選択して見積書作成」の2パターンで作成できるようにしています。

/**
 * スプレットシートのメニューから「見積書作成」が選択された時の処理
 */
function procAdd() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet() //アクティブシート
  const sheetName = sheet.getSheetName();//シート名
  
  //ダイアログの作成
  var ui = SpreadsheetApp.getUi();
  var dialogHtml = HtmlService.createTemplateFromFile('CustomDialog');
  dialogHtml.text = sheetName;
  ui.showModalDialog(dialogHtml.evaluate(), '見積書作成');
}
/**
 * スプレットシートのメニューから「取引先の登録」が選択された時の処理
 */
function PartnerAdd() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet() //アクティブシート
  const sheetName = sheet.getSheetName();//シート名
  
  //ダイアログの作成
  var ui2 = SpreadsheetApp.getUi();
  var dialogHtml2 = HtmlService.createTemplateFromFile('CreatePartnerDialog');
  dialogHtml2.text = sheetName;
  ui2.showModalDialog(dialogHtml2.evaluate(), '取引先登録');
}
/**
 * ダイアログで「新規作成」ボタンが押された場合の処理
 *
 * @param inputText  入力されたテキスト
 */
function onOkButtonClick(inputText) {
  //見積書作成
  var result = createQuotations(inputText)
  // ダイアログ表示
  if(result != false){
    var ui = SpreadsheetApp.getUi();
    ui.alert('見積書を作成しました。\n' +'freee画面をご確認ください。');
  }
  return;
}

/**
 * ダイアログで「取引先追加」ボタンが押された場合の処理
 *
 * @param inputText  入力されたテキスト
 */
function onOkButtonClick2(inputText2) {
  //取引先追加
  var result = AddPartner(inputText2)
  return;
}

見積書作成や取引先登録時の共通の処理(主にfreeeからの情報取得処理)を記述します。

/**
* 指定のURLにリクエストを送信してレスポンスを返す
*
* @param {string} アクセストークン
* @param {string} リクエストURL
* @return {object} freeeAPIのレスポンス
*/
function accessfreeeAPI_(accessToken,url) {
  
  Utilities.sleep(1000); //短時間の連続アクセスを回避するため待機
  const params = {
    method : 'get',
    headers : {'Authorization':'Bearer ' + accessToken},
    muteHttpExceptions : true  //エラー全文表示
  };
  const response = UrlFetchApp.fetch(url,params);
  const obj = JSON.parse(response);
  return obj;
}

/**
* 取引先名から取引先IDを返す
* @parameter{string} 取引先名
* @return {string} 取引先ID
*/
function getPartnerId_(partner_name,company_ID) {
  
  const accessToken = getService().getAccessToken();
  const requestUrl = 'https://api.freee.co.jp/api/1/partners?company_id='+company_ID+'&limit=150';
  
  let obj = accessfreeeAPI_(accessToken,requestUrl);
  obj = obj.partners
  var partner_id = ""
  for (const i in obj) {
    if (obj[i].name == partner_name || obj[i].long_name == partner_name) {
      partner_id = obj[i].id;
      break
    }
  }
  return partner_id
}

/**
* 事業所コードから取引先名を返す
* @parameter{string} 事業所コード
* @return {array} 取引先名
*/
function getPartnersName_(company_ID) {
 try{  
    const accessToken = getService().getAccessToken();
    const requestUrl = 'https://api.freee.co.jp/api/1/partners?company_id='+company_ID+'&limit=150';
    let obj = accessfreeeAPI_(accessToken,requestUrl);
    obj = obj.partners;
  
    let array=[]
    for (const i in obj) {
    array.push(obj[i].name)
    }    
    return array

  }catch(exception){
    console.log("エラー発生"+exception)  
    var ui = SpreadsheetApp.getUi();
    ui.alert('【ERROR】'+exception);
  }
}

/**
* プルダウン用
* 取引先名一覧を返す
*/
function pertner(){
  let list = getPartnersName_(company_id)//取引先一覧
  
  return JSON.stringify(list);
}

スプレットシートから見積書を作成する

スプレットシートにフォーマットを作成し、freeeに送信して見積書を新規作成します。

①見積書用にスプレッドシートの準備します

タブに取引先の名称を入力します。

②見積書作成時のPOST作業を記述します。

/**
 * 見積書作成
 *
 * @param partnername  取引先名
 */
function createQuotations(partnername) {
 try{
  const sheet = SpreadsheetApp.getActiveSpreadsheet() //アクティブシート
  const sheetName = sheet.getSheetName();//シート名
  const partnerid = getPartnerId_(partnername,company_id)//取引先ID
  if(partnerid == ""){
    var test = SpreadsheetApp.getUi();
    test.alert('['+partnername+']は登録のない取引先です。\n' +'取引先の登録をしてください');
    return false
  }
  var notes = ""//備考欄

  //シートの内容を格納
  var input_list = sheet.getSheetByName(sheetName)
  var input_list_data = input_list.getDataRange().getValues()

  //行の種類の変換
  for (var i=1; i<= input_list_data.length; i++) {
      if(input_list_data[i][5]=='通常'){
        input_list_data[i][5]='normal'
      }
      else if(input_list_data[i][5]=='テキスト'){
        input_list_data[i][5]='text'
      }
      else if(input_list_data[i][5]=='割引'){
        input_list_data[i][5]='discount'
      }
      if(input_list_data[i][0] == '備考欄'){

        //備考欄に記入がある場合(記入がない場合はデフォルトの内容を表示)
        if(i == input_list_data.length-2){
          notes = input_list_data[i+1][0]
        }
        break
      }
  }
  const accessToken = getService().getAccessToken();


  //詳細内容の設定
  var contents=[];
  for(c=1;c<i;c++){
    contents.push(
      {id: 0,
       order: 0,
       type: input_list_data[c][5],
       qty: input_list_data[c][1],
       unit: input_list_data[c][2],
       unit_price: input_list_data[c][3],
       amount:  input_list_data[c][4],
       description: input_list_data[c][0],
       account_item_id: account_item_id,
       tax_code: 101

      }
    )
    if(c<i-1){
        contents.push(",")
    }
  }

  //取引先・事業所の情報設定
  let obj = { 
    company_id: company_id,
    partner_id: partnerid,
    partner_display_name: partnername,
    partner_title: '御中',
    notes: notes,
    quotation_contents: contents,
    Quotation_status: 'draft'
  }
    //JSONデータを、POST用に変換する
    let objQuotations = convertObjQuotations_(obj);
    
    //見積書作成
    const respose = postQuotations_(accessToken, objQuotations);
      //var ui = SpreadsheetApp.getUi();
      //ui.alert(respose);

    }catch(exception){
      var ui = SpreadsheetApp.getUi();
      ui.alert('見積書の作成でエラーが発生しました。\n' +exception.message);
      return false;
    }
    //return true
}

/**
* 日付オブジェクトをYYYY-MM-DD形式に整形
* @param {date} 日付オブジェクト
* @param {string} 文字例(YYYY-MM-DD)
*/
function yyyyMMdd_(date) {
  if (date) { 
    return Utilities.formatDate(date,'JST','yyyy-MM-dd');
  } else {
    return '';
  }
}

/**
* POST用に変換
* @param {object} JSONデータ
* @param {object} POST用に変換したJSONデータ
*/
function convertObjQuotations_(objQuotations) {
  
  //値無しのキーを削除
  for (const key in objQuotations) {
    if (objQuotations[key] == null || objQuotations[key] === '') { 
      delete objQuotations[key];
    }
  }
  
  //Quotation_contentsプロパティの処理
  const length = objQuotations.quotation_contents.length;
  
  //配列の要素を削除するため、逆向きループ
  for (let i = length-1; i >= 0; i--){
    //「通常・テキスト・割引」以外は行ごと削除してスキップ
    let type = objQuotations.quotation_contents[i].type
    if(type != 'text' && type !== 'normal' && type !== 'discount'){
      objQuotations.quotation_contents.splice(i, 1);
      continue;
    }
  
    let objContent = objQuotations.quotation_contents[i];
    for (const key in objContent) {
      //値無しのキーを削除
      if (objContent[key] == null || objContent[key] === '') { 
        delete objQuotations.quotation_contents[i][key];
      }
      //空配列を削除
      if (typeof objContent[key] === 'object' && !objContent[key].length) {
        delete objQuotations.quotation_contents[i][key];
      } 
    }
  }
  return objQuotations;
}

/**
* freeeAPIにリクエストを送信して見積書を作成する
*
* @param {string} freeeAPIアクセストークン
* @param {object} 見積書データ
*/
function postQuotations_(accessToken,obj) {
  Utilities.sleep(1000); //過度の連続アクセスを避けるため待機
  const params = {
    method : 'POST',
    contentType : 'application/json',
    headers : {Authorization:'Bearer ' + accessToken},
    payload : JSON.stringify(obj), //オブジェクトをJSON文字列に変換
    muteHttpExceptions : true //エラーが発生した場合、レスポンスにエラー全文を出力する
  };
  const requestUrl = 'https://api.freee.co.jp/api/1/quotations'; 
  const response = UrlFetchApp.fetch(requestUrl,params);
}

③見積書作成用のダイアログを作成します。

<!DOCTYPE html>
<html>
  <head>
  <base target="_top">
  <script type="text/javascript">
 
  //スプレッドシート側からデータを取得する
  google.script.run.withSuccessHandler(onSuccess).pertner();
  
  //取得したデータでドロップダウンメニューを作って設置
  function onSuccess(data){
    var json = JSON.parse(data);
    var datalength = json.length;
    var html = "<label></label>";
    var datalength = json.length;
  
    html += "<select title='プルダウンより選択' id='pertners'><option>取引先一覧</option>";
    for(var i = 0;i<datalength;i++){
      //空データの場合ループ脱出
      if(json[i] == ""){
        break;
      }
      //オプション項目を追加
      html += "<option>" + json[i] + "</option>"
    }
    html += "</select><p>";
    //プルダウンメニューを設置する
    document.getElementById("pertner").innerHTML = html;
  }
</script>

  </head>
  <body>
    <h1></h1>
    <div>
      <input type='text' id='inputText' disabled="readonly" value=<?=text?>>
       <input type='button' value="シート名で見積書作成" onClick='createA()' />
    </div>
    <div><p> </p></div>
    <div><p> </p></div>
    <div style="display: inline-block; _display: inline;" id="pertner">
      <img border="0" src="https://officeforest.org/wp/library/ProgressSpinner.gif" width="20" height="20">
    </div>
    <div>
      <input type='button' value="取引先を選択して見積書作成" onClick='createB()' />
    </div>
    <script>
      //入力欄に記入された取引先名を書き込み
      function createA() {
        var elm = document.getElementById('inputText');
        google.script.run
          .withFailureHandler(onFailure)
          .withSuccessHandler(onSuccess)
          .onOkButtonClick(elm.value);
      }
      //プルダウンで選択された取引先名を入力欄に反映
      function createB() {
        var elm = document.getElementById('pertners');
        google.script.run
          .withFailureHandler(onFailure)
          .withSuccessHandler(onSuccess)
          .onOkButtonClick(elm.value);
      }
      //エラー処理
      function onFailure(error) {
        alert(error.message);
        google.script.host.close();
      }
      //クローズ処理
      function onSuccess() {
        google.script.host.close();
      }
    </script>
  </body>
</html>

実行結果

スプレットシートから取引先登録機能の作成する

スプレットシートのメニューから取引先を新規追加するコードを作成します。

/**
 * 取引先登録
 *
 * @param partnername  取引先名
 */
function AddPartner(partnername) {
 try{
  const accessToken = getService().getAccessToken();  

  //取引先・事業所の情報設定
  let obj = { 
    company_id: company_id,
    name: partnername
  }
    //取引先の追加
    const respose2 = AddPartner_(accessToken, obj);
    }catch(exception){
      var ui = SpreadsheetApp.getUi();
      ui.alert(exception.message);
      return false;
    }
}

/**
* freeeAPIにリクエストを送信して取引先を登録
*
* @param {string} freeeAPIアクセストークン
* @param {object} 取引先登録データ
*/
function AddPartner_(accessToken,obj) {
  Utilities.sleep(1000); //過度の連続アクセスを避けるため待機
  const params = {
    method : 'POST',
    contentType : 'application/json',
    headers : {Authorization:'Bearer ' + accessToken},
    payload : JSON.stringify(obj), //オブジェクトをJSON文字列に変換
    muteHttpExceptions : true //エラーが発生した場合、レスポンスにエラー全文を出力する
  };

  const requestUrl = 'https://api.freee.co.jp/api/1/partners'; 
  const response = UrlFetchApp.fetch(requestUrl,params);
  var message = SpreadsheetApp.getUi();
  if (response.toString().match(/error/)) {
    message.alert("【エラー】" + response);
  }else{
    message.alert("取引先が追加されました")
  }
}

取引先追加のダイアログを作成します。

<!DOCTYPE html>
<html>
  <head>
  <base target="_top">
  </head>
  <body>
    <h1></h1>
    <div>
      <input type='text' id='inputText2' value=<?=text?>>
      <input type='button' value="取引先の新規登録" onClick='createPartner()' />
    </div>
    <script>
      //入力欄に記入された取引先名を登録
      function createPartner() {
        var elm2 = document.getElementById('inputText2');
        google.script.run
          .withFailureHandler(onFailure)
          .withSuccessHandler(onSuccess)
          .onOkButtonClick2(elm2.value);
      }
      function onFailure(error) {
        alert(error.message);
        google.script.host.close();
      }
      function onSuccess() {
        google.script.host.close();
      }
    </script>
  </body>
</html>

実行結果

freee画面での確認

以上の作業でスプレットシートから「見積書作成」と「取引先の登録」ができるようになります。