（時給フリーランス専用） 会計freee×GASでタイムシートから請求書を作成する

# はじめに
この記事は`Qiita夏祭り2020`の `「会計」「勤怠」をハックしよう！freee API のTips募集`の参加記事になります。

- Qiita夏祭り  
https://qiita.com/summer-festival
- 「会計」「勤怠」をハックしよう！freee API のTips募集  
https://qiita.com/official-events/3f740f71bbdcb59d9959

# 背景
私は現在、フリーランスエンジニアで3社と契約中です。いずれも時給契約なので、毎月の収入が固定されておらず、請求書の作成に一苦労しています。

![timesheet.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37863/7dd83f63-cad0-0550-e703-331cdcf20625.png)


のようなタイムシートを付けていて、月末に取引先各社の稼働時間を集計して、そこから単価をかけて売上を算出し、請求書を作成しています。

取引先ごとに時給は違いますし、請求の品目も指定されていたりして取引先ごとに違ったりするので、請求書の作成は本当に大変です。

そこで、何とか自動化できないかと考えていました。

Webサービスを作ろうかと思ったのですが時間がかかりそうなので、まずはお手軽に使える

- GAS(Google Apps Script)
- freee API

の組み合わせでやってみました。

# ツールについて
作成したツール(Googleスプレッドシート)はこちらになります。
https://docs.google.com/spreadsheets/d/1dtkOqyOEmGzOHTqCx2UVdzgzTWR24l2OU6IABPcxjGY/edit?usp=sharing

freeeが用意してくれているサンプルをベースにしています。  
[【freee API】GASを用いてGoogleスプレッドシートと連携する ](https://support.freee.co.jp/hc/ja/articles/115005287286--freee-API-GAS%E3%82%92%E7%94%A8%E3%81%84%E3%81%A6Google%E3%82%B9%E3%83%97%E3%83%AC%E3%83%83%E3%83%89%E3%82%B7%E3%83%BC%E3%83%88%E3%81%A8%E9%80%A3%E6%90%BA%E3%81%99%E3%82%8B)


## 動作環境
動作に必要なものは下記になります。

- Googleアカウント (スプレッドシートなので当たり前かもしれませんが一応記載)
- freeeアカウント  
プランが複数あってよくわかりませんが、以下のメニューが使えるプランなら大丈夫だと思います。
    - 設定 -> 取引先の設定
    - 取引 -> 請求書

## 使い方
「使い方」シートに記載してありますが、不十分かもしれないので補足していきます。
※ 前提として、シート名の変更やセルの移動はしないで下さい。GASが動かなくなります。

### 準備
1. スプレッドシートをコピーして、編集できるようにして下さい。
1. 「使い方」シートの、`アプリケーションを登録する` の記載内容を実施して下さい。
Client ID と Client Secret を値を入力したら準備完了です。
![pre.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37863/a50f6e68-558d-0cfb-7843-bfa07598a648.png)

### シートの入力
1. 取引先の登録  
「取引先」シートの3行目以下を入力して下さい。  
※ 請求コードは何でもいいです。ご自身のルールで命名して下さい。空欄でも大丈夫です。  
※ 会社名はfreeeの取引先の取引先名と一致する必要があります。
1. 「timesheet」シートを入力して下さい。

### freeeメニュー
シートの入力が完了したらfreeeメニューが動作するようになります。
1. 「freeeメニュー -> 1. freeeと連係」を実行して下さい。
2. 「freeeメニュー -> 2. 事業所を選択」を実行して下さい。
「invoice」シートのB18に選択した事業が入力されます。
![set-company.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37863/307a13cc-55de-7627-d09a-c03441fb9ba6.png)
3. 「invoice」シートの4行目に、対象の年、月、取引先を入力して下さい。
4. 「freeeメニュー -> 3. 稼働時間と売上を集計」を実行して下さい。
「invoice」シートのC3、C4に集計結果が入力されます。
![set-hoursale.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37863/99c37153-e94d-a12c-bb96-6e6b96b7c81f.png)
5. 「invoice」シートの黄色セルに値を入力して下さい。
※ セル"C11"請求日と"C13"振込期限は、YYYY-MM-DD のフォーマットで入力して下さい。
6. 「freeeメニュー -> 3. 請求書を作成」を実行して下さい。
freee上に請求書が作成されます。
![freee-invoice.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37863/a6d5567e-8dc5-5813-bb03-c75c4fd2a2e7.png)


# 実装内容
GASのスクリプトは、スプレッドシートのメニューから 「ツール -> スクリプト エディタ」で参照できます。

freeeのサンプルをベースに、使用しない関数を削除して、稼働時間と売上の集計と請求書発行の関数を追加しました。

稼働時間と売上の集計に関しては、freee API は作成しておらず、セルを値をとってきてゴニョゴニョするという一般的なスクリプトなので説明は省略します。

## 請求書の作成

請求書の作成に freee API を使用しています。

[freee API オフィシャル仕様書](https://developer.freee.co.jp/docs/accounting/reference)

使用したのは、以下になります。

- POST `/invoices`
- GET `/partners?company_id={companyId}`
- GET `account_items?company_id={companyId}`

GETの2つは、POSTに必要なパラメータの値を取得するために使用しています。

請求書発行の関数

```js
/******************************************************************
function name |postInvoice
summary       |請求書sheetのパラメータからリクエストを作成してinvoice作成をPOST
request_url   |https://api.freee.co.jp/api/1/invoices
method        |POST
******************************************************************/
function postInvoice() {
  var freeeApp = getService();
  var accessToken = freeeApp.getAccessToken();
  var requestUrl = "https://api.freee.co.jp/api/1/invoices";
  var headers = { Authorization: "Bearer " + accessToken };

  // 請求書フォームの情報を取得
  var invoiceSheet = ss.getSheetByName("invoice");
  var companyId = getCompanyIdFromCompanyName("B18", "invoice");
  var partnerId = getPartnerIdFromPartnerName("C4", "invoice");
  var title = invoiceSheet.getRange("C10").getValue();
  var issueDate = invoiceSheet.getRange("C11").getValue();
  var issueDateRe = issueDate.getFullYear() + "-" + (issueDate.getMonth() + 1) + "-" + issueDate.getDate();
  var invoiceNumber = invoiceSheet.getRange("C12").getValue();
  var dueDate = invoiceSheet.getRange("C13").getValue();
  var dueDateRe = dueDate.getFullYear() + "-" + (dueDate.getMonth() + 1) + "-" + dueDate.getDate();
  var paymentBankInfo = invoiceSheet.getRange("C14").getValue();
  var invoiceMessage = invoiceSheet.getRange("C15").getValue();
  var notes = invoiceSheet.getRange("C16").getValue();
  var companyName = invoiceSheet.getRange("C20").getValue();
  var companyZipcode = invoiceSheet.getRange("C21").getValue();
  var companyAddress1 = invoiceSheet.getRange("C22").getValue();
  var companyAddress2 = invoiceSheet.getRange("C23").getValue();
  var companyContactInfo = invoiceSheet.getRange("C24").getValue();

  // 時給、請求品目、稼働時間、売上を取得して、消費税を計算
  var partner = invoiceSheet.getRange("C4").getValue();
  var unitPrice = getPartnerUnitPrice(partner);
  var invoiceItem = getPartnerInvoiceItem(partner);
  var workHour = invoiceSheet.getRange("C6").getValue();
  var sale = invoiceSheet.getRange("C7").getValue();
  var tax = 0;
  var hasTax = getPartnerHasTax(partner);
  if (hasTax == false) {
    tax = sale * 0.1;
  }

  //POSTする内容
  var requestBody = {
    company_id: companyId,
    issue_date: issueDateRe,
    partner_id: partnerId,
    invoice_number: invoiceNumber,
    title: title,
    due_date: dueDateRe,
    invoice_status: "draft",
    partner_display_name: "株式会社freeeパートナー",
    partner_title: "御中",
    company_name: companyName,
    company_zipcode: companyZipcode,
    company_address1: companyAddress1,
    company_address2: companyAddress2,
    company_contact_info: companyContactInfo,
    payment_type: "transfer",
    payment_bank_info: paymentBankInfo,
    message: invoiceMessage,
    notes: notes,
    invoice_layout: "default_classic",
    tax_entry_method: "exclusive",
    invoice_contents: [
      {
        order: 0,
        type: "normal",
        qty: workHour,
        unit: "時間",
        unit_price: unitPrice,
        vat: tax,
        description: invoiceItem,
        tax_code: 1,
        account_item_id: getAccountItemIdUrikake(),
      },
    ],
  };
  console.log(JSON.stringify(requestBody));

  // POSTオプション
  var options = {
    method: "POST",
    contentType: "application/json",
    headers: headers,
    payload: JSON.stringify(requestBody),
    muteHttpExceptions: true,
  };

  var res = UrlFetchApp.fetch(requestUrl, options);
  console.log(res);

  if (res.getResponseCode() != 201) {
    SpreadsheetApp.getUi().alert(res);
  }
}
```

POST /invoices のパラメータが多いため、随分長い関数になってしましたが、やってることは requestBody に必要な値を、セルやGET APIから取得してセットしているだけです。

`issueDate` と `dueDate` はセルの値取得時で Date型になっているので、getFullYear, getMonth, getDate で各値を取得して、文字列連結しています。getMonthは戻り値が0～11なので+1しています。

「取引先」シートの税込みカラムにチェックが入っていないときは、税率10％で計算して、その値を、resuesBody の invoice_contents の vat に入れています。税率の10％はハードコーディングです。

### POST /invoices の不明点

- partner_display_name  
`partner_display_name` というパラメータが必須なのですが、何の値をいれていいのかわかりません。API仕様書には「請求書に表示する取引先名」と記載されていますが、実際に生成される請求書は、partner_idの取引先の取引先名が入っているようなので、ここの値は使用されていないように思われます。とりあえずサンプルにある `株式会社freeeパートナー` を固定で入れるようにしました。

- invoice_contents の order  
invoice_contents の order は必須パラメータなのです。発注書との紐付けとかに使うのでしょうか？ よくわからないので`0`を固定で入れるようにしました。

- invoice_contents の account_item_id  
invoice_contents の account_item_id は必須パラメータです。請求書を発行したら取引に反映されるときの勘定項目なのでしょうか？ よくわからなかったので、`売掛金のID`を固定で入れるようにしました。


# 最後に

以上、**時給エンジニアは請求書発行が大変問題**についての解決案についてでした。

もともと自分専用のWebサービスを作ろうと思っていたのですが、QiitaのfreeeAPI企画を目にしたので、freeeAPIを使う方法でトライしてみました。

一応、請求書の作成まで持って行けたので本来やりたかったことはできました！

ただ問題は、freeeの有料プランとの契約が必要ということなんですよね・・・ 私は税務は税理士にお願いしているので、freeeを購入する必要が全くないので、どうしたものかと。