定期的にWebサービスの請求書を自動でダウンロード＆LINEで通知

# 定期的に発行される請求書を自動でダウンロードする

副業や、サービスで利益を上げている場合は、確定申告をする必要があります。その中で、月額で費用がかかるものがあります。

僕の場合は

- Heroku のサーバー費用
- 仕事用の携帯代や、ネット回線料金

などがあります。

Heroku は請求書が永久に保存される（っぽい？）ので問題がないのですが、ソフトバンク光のネット回線や、ソフトバンクの携帯の請求書は、6ヶ月でダウンロードできなくなってしまうので、スクリプトで自動収集して、Googleドライブに保存しています。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29024/eb8b8f76-77a5-8d30-3415-99bc7e811230.png">

# この定期ダウンロードシステムを改善したい

これを改善して

- 自分以外でも利用できるようにしたい
- 請求書をダウンロードしたらLINEで通知されるようにしたい
  - 通知がきたら確定申告システムに手動で入力する

## このシステム名

freee と invoice （請求書）をあわせて invoiceee という名前にしています。

GitHub リポジトリは以下です。Larvel のタスクスケジューラーが使いたいのと、 PHP が一番使い慣れている言語なので、 PHP Laravel を使って開発しています。

https://github.com/yoshikyoto/invoiceee

## 今回やったこと

- freee 連携でログイン/アカウント作成
- Heroku と連携して請求書をダウンロード
- 簡易 LINE 連携

## 今回できなかったこと

- LINE 連携でログイン/アカウント作成
- LINE 連携＆通知の作り込み
- Google Drive などのストレージと連携し、そこに請求書を保存
- 取得した請求書をもとに freee にデータを追加
- デザインを整える
- 動く環境にデプロイ
- Laravel タスクスケジューラーをつかって自動収集
- その他作り込み

主に時間の問題でできませんでした。

# できること

デザインはできてないのでおおめに見てください。

最初のページは質素で、 freee と連携してアカウント作成します。アカウントがすでにあったらログインになります。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29024/cce93218-e861-03af-900e-cd14ee3e8a2d.png" width="50%">

freee と連携します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29024/5ceff23c-57bc-43bb-8aac-a3260bafa32e.png" width="50%">

LINE で通知を受けるには、現状「LINE ユーザー ID を入力して連携」に自分の LINE ID を入力したうえで、LINE の invoiceee のアカウントと友達になる必要があります。ここは、もっと簡単にできるよう、改善したいです。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29024/60b96aab-3416-44c5-5d63-214a0b9e55d4.png" width="50%">

現状、Heroku とだけ連携できるので、「Herokuと連携」をクリックして、連携すると、「連携済みアカウント」に追加されます。

![スクリーンショット 2020-12-20 3.32.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29024/f7c20b68-7994-ec86-a439-ca4f822ced74.png)

この状態でバッチが実行すると、まだダウンロードされていない請求書が自動でダウンロードされます。バッチは `php artisan invoice:collect` で実行できます。

```sh
$ php artisan invoice:collect
請求書ファイルを収集します
[2020-12-19 17:58:30] local.INFO: 以下のLinkageについて処理 {"userId":1,"linkageServiceName":"heroku","linkageServiceAccountId":"85dace0d-405b-4cdf-a25c-2bed5a336f75","lastInvoiceDownloadedAt":null}
[2020-12-19 17:58:37] local.INFO: 請求書を処理します {"createdAt":"2020-04-01 00:00:00"}
[2020-12-19 17:58:37] local.INFO: 請求書をダウンロードします
[2020-12-19 17:58:39] local.INFO: 請求書を保存します {"name":"2020-04-01-heroku-0e93c5f2-fbd5-41ca-b1f6-6326f5cfd3f3.html"}
[2020-12-19 17:58:39] local.INFO: 請求書を保存しました
[2020-12-19 17:58:39] local.INFO: 請求書を処理します {"createdAt":"2019-03-01 00:00:00"}
[2020-12-19 17:58:39] local.INFO: 請求書をダウンロードします
[2020-12-19 17:58:42] local.INFO: 請求書を保存します {"name":"2019-03-01-heroku-17101b37-c27f-4616-9e92-9b27ca490d9f.html"}
[2020-12-19 17:58:42] local.INFO: 請求書を保存しました
```

# 今回できなかったことについて

**LINE 連携でログイン/アカウント作成**

freee を使っていない人のために、LINEでもアカウントを作成できるようにしたいです。

**LINE 連携と通知の作り込み**

今は自分の LINE ID を入力したうえで、invoiceee アカウントと友達になる必要があります。これを LINE 連携でいい感じにしたいです。

LINE 連携で LINE ID を取るようにします。

LINE 連携時に、 invoiceee アカウントと友達になることをオススメできるので、この機能をつかって invoiceee アカウントと友達になってもらいます。

**Google Drive などのストレージ連携**

今のところダウンロードした請求書をローカルに保存していたり、僕の Google Drive 決め打ちで保存していたりするので、 Google Drive 連携して、ユーザーの Google Drive に保存するようにします。

**取得した請求書をもとに freee にデータを追加**

せっかく自動で取得できるので、freee に自動で経費を追加したいのですが、請求書の形式によっては、金額が取れなかったりするので難しいです。現状は LINE で通知をうけて手動で freee（や他の会計ソフト）に入れる必要があります。

**デザインを整える**

別に綺麗なデザインにしなくても、全然使えるので、後回しですが、綺麗にしたいです。

**動く環境にデプロイ**

本当は、NTTPCが提供している、格安で話題のサーバー「indigo」にデプロイする予定でしたが、まさかの NTT 側のリソース不足により、インスタンスが建てられませんでした。

【2020.11.20更新】Indigo Linuxインスタンスの在庫不足
https://web.arena.ne.jp/news/2020/1027.html

12月中旬に設備追加が完了する予定になっているのですが、間に合いませんでした。他の VPS を使うという手もありましたが、 indigo を使ってみたい、というのが目的の一つでしたし、まだ完成度がそこまで高くないので、もう少し待ちます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29024/d35f1873-257c-102c-a995-7612aa320646.png)


**Laravel タスクスケジューラーをつかって自動収集**

Laravel にはタスクスケジューラー機能があるので、indigo のインスタンスができたら、こちらを利用して動かしてみたいです。

タスクスケジュール 5.7 Laravel
https://readouble.com/laravel/5.7/ja/scheduling.html

**その他作り込み**

まだ Heroku しか連携できないなど、機能として不足しているので、ガンガン機能を追加していきたいです。

また、ユニットテストなどもまだ不足しているので改善したいです。

# 技術的な話

## GitHub Actions で コーディング規約チェック

PHPCS でのコーディング規約チェックを GitHub Actions でします。PHPCS についてはブログに記事を書いています。

PSR-2 に変わって新しく制定された PSR-12 のチェックを PHP_CodeSniffer で行う
https://www.utakata.work/entry/php/phpcs-psr12-intellij-jenkins

↑の記事だと PHPCS ですが、 GitHub Actions でやってみると、結構いい感じに表示されることがわかりました。詳細別記事で書こうと思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29024/9e518091-3031-3aeb-e42a-4b7ef72daab1.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29024/065df7d8-6863-2ca1-538f-a30af3187bda.png)


## コードの抽象化をして処理をわかりやすく

例えばこのコード、Heroku のアカウント連携をするためのコードなのですが

* invoiceee でログイン状態じゃなかったら `index` ページに飛ばす
  * ログインしていない時は `index` ページにログインボタンが表示されるからです
* ログインしていたら、OAuth2 の code をリクエストパラメータ取得し、 Heroku の api token を取得する。成功したら連携完了。 `index` ページに飛ばす
  * ログインしている時は `index` ページに連携状態一覧が表示されるからです

```php

class Controller
{
    public function auth(Request $request)
    {
        // userResolver セッションからユーザー情報を解決するモジュール
        $user = $this->userResolver->getUser($request);
        if ($user === null) {
            return redirect()->route('index');
        }

        // OAuth2 の code から Heroku の api token を取得
        $code = $request->input('code');
        $token = $this->heroku->getToken($code);
        $this->userAccountLinker->createHerokuLinkage($user, $token);
        return redirect()->route('index');
    }
}
```


このコードだと、どちらの分岐でも `index` ページに飛んでいるので、非常に見づらいです。そこで、以下のように抽象化しています。

```php
class Controller
{
    public function auth(Request $request)
    {
        // userResolver セッションからユーザー情報を解決するモジュール
        $user = $this->userResolver->getUser($request);
        if ($user === null) {
            return $this->loginPage();
        }

        // OAuth2 の code から Heroku の api token を取得
        $code = $request->input('code');
        $token = $this->heroku->getToken($code);
        $this->userAccountLinker->createHerokuLinkage($user, $token);
        return $this->successPage();
    }

    private function loginPage()
    {
        return redirect()->route('index');
    }

    private function successPage()
    {
        return redirect()->route('index');
    }
}
```

`loginPage()` も `successPage()` も中身は同じですが、意図が違うコードからわかり、読みやすくなります。もしログインページを別にした場合も対応しやすいです。

`$user === null` は、ログインしていない状態を判定しているんですが、これを例えば `$this->isLogin($user)` にしたり、 `userResolver` で null を返すのをやめて、 `$user->isLogin()` みたいにしても、コードの意図がわかりやすくなって良いかと思います。

## Eloquent::chunk で DB からちょっとづつデータをフェッチ

全部のアカウント連携のデータを `all()` 等のメソッド一気にDBから引っ張ってくると、メモリが足りません。Laravel で使われているクエリビルダー Eloquet には `cursor()` や　`chunk()` といったメソッドがあるので、これを使います。

Laravel(Eloquent): chunk() vs cursor() - Qiita
https://qiita.com/ryo511/items/ebcd1c1b2ad5addc5c9d

# まとめ

freee は API が公開されているので、こういう自動化が簡単にできるのが良いですね。

> freeeは「オープンプラットフォーム」を合言葉に、各種サービスのAPIを公開しています。

今回作ったシステムは、個人的にも非常に便利なシステムになっているので、仕事と副業の間に頑張って開発し、完成させたいと思います。