gitlabのロゴ
読みこみ中

会計データを管理する Web アプリケーションの開発

電卓とお金の流れを書いた紙Illustration by pixaday

概要

  • 人数: 6人 (マネージャー1人、品質保証(テスト)およびサブマネージャー1人、フロントエンド1人、デザイナー1人、バックエンド1人(自分)、引継ぎ1人)

航空券の予約情報とそれに紐づく請求書や仕訳情報を保存するデータベースの設計と、その管理をおこなうための Web アプリケーションの開発を行いました。

主な業務内容

  • 会計の仕組みの把握と、それを表現できるデータベースの設計
  • データベース上のデータを閲覧・編集するための Web アプリケーションの設計・開発
  • 請求書pdfファイルの生成・ダウンロード機能の開発
  • ビジネス要件に対応した、より直観的なデータの登録方法(複数の予約をまとめて作るなど)の設計・提案・開発
  • GKEでのデプロイ環境の構築
  • データベース上のデータを加工するスクリプトの開発

どのように業務を進めたか

まずはどんな機能が欲しいのかについて聞き出すところから始まった。「既存のアプリと同じような機能」と言われても、何がやりたいのかわからないので、どんな使い方をするのかについて聞き取りを行った。

このアプリケーションの目的は、新しく航空券予約のサービスを展開するうえでその予約や請求情報を管理するデータベースが欲しかったからのようだ。その会社の持つ別のサービスで予約等を管理するアプリケーションはあったのだが、そちらは外注で作ったものだったりして機能を追加するのが難しいらしい。

まずはUIだけ作り、どうしたいのかを確認していった。

並行して、会計自体の仕組みについても詳しく調べた。こちらは個人事業主として確定申告書を作るときにも役立っている。

UIについての確認が取れたら、次はそのUIに表示するためのデータベースの設計に取り掛かる。会計の仕組みと表示するデータをもとにデータベースのテーブル設計を行い、ER図を描いた。このER図は聞き出した要件をもとに少しづつ書き足していった。

会計データのデータベース設計については、会計データをデータベースで表現するで説明する。

さて、アプリケーションの制作にはフルスタックフレームワークの Blitz.js を使用した。これは Next.js に、ORM の Prisma.js と認証機能を組み合わせたものだ。UIについては Material UI を使用した。

Blitz.js および Material UI はこのホームページの開発でも使っている。

WebアプリケーションのAPIは、たとえばデータベースのレコードを一つ作成するなどの、シンプルな実装にした。複雑な機能はこれをクライアント側で組み合わせて呼び出す形になる。

実装としては、シンプルなAPIだけを実装し、複雑な機能はそのAPIの組み合わせで実現するような形になった。

たとえば、入金消込の機能を求められたが、APIとして実装したのは仕訳項目の作成APIだけだ。入金消込は、既存の仕訳項目から新しい仕訳項目を作り出す処理といえるので、実装としてはクライアント側で既存の仕訳項目を取得し、そこから入金消込の結果生成するデータを計算し、計算結果の仕訳項目を仕訳項目作成APIで呼び出せばよいことになる。

さて、単純にデータベースにデータを登録するための機能以外では、請求書のファイルの作成機能と、商品の購入機能を作った。

請求書のファイルの作成は、 react-pdf を使用して、データベース内のデータを埋め込んだPDFファイルを生成する機能だ。

少し詳細に説明する。データベース上では、一つの請求書を表すのに以下のようなテーブルを使っている。

  • 請求書の表紙
  • 請求項目
  • 予約
  • 仕訳項目

なお、このテーブル設計はどのような請求書が欲しいのかのサンプルを作ってもらい、そこから考えた。適宜、サンプル中の表示内容が何を意味するものなのかを聞き取りながら、データベースのテーブルとフィールドに落とし込んでいった。

請求書の表紙に入るのは、例えば請求書の宛先や、請求書の発行日といった情報だ。

請求項目に入るのは、請求項目名とその金額、そして表記上の税率だ。請求項目は木構造で表現した。つまり、木構造のルートの請求項目は請求書の表紙に書く合計金額ということになり、その下に内訳(詳細な請求項目)が続く。

予約には予約日や、予約の詳細情報、予約の実行日、金額などが含まれる。予約の種類によってその詳細情報は違くなるが、そこは種類ごとに別のテーブルを用意する形にしている。予約は請求項目のどれかに紐づけることができる。一つの予約を複数回に分けて請求するケースが考えられるから、予約の金額と請求項目の金額は別にしている。

予約の実行日は、「予約サービスの種類に応じて使うデータを変えてほしい」という要件を解決するために考えた。サービスの種類によって参照するフィールドが違うのは意味がわからないので、共通する概念を考えて導入した。(同じ場所に表示されるデータは同じ意味にするべきというのが僕の考えだ。)実行日は、「予約が有効になる日付」を表す。

請求書の予約内容に入っていてほしいのは予約日だと僕は思ったしそう提案したのだが、どうしても実行日のほうを表示したいらしいのでそうした。

仕訳項目には仕訳日と、借方、貸方の勘定科目が含まれる。仕訳項目は請求書上では表示はしないが、請求書に紐づいた仕訳内容を見たいという要件は当然あったので紐づいている。上記の実行日の表示において、特定のサービスでは仕訳日を表示してほしいといわれていたのだが、仕訳帳の中身を請求書で表示するというのも変なので実行日の概念を導入したのは妥当だったと考えている。

作成した請求書は Google Cloud Storage に保存される。なお、保存先は環境変数により変えられるようにしていた。

商品の購入機能は、商品ごとに決めておいた内容の請求項目・予約・仕訳項目を一括で作成する機能だ。

たとえば一年の契約で月ごとに請求書を発行する場合、購入時に12ヶ月分の請求項目を作成する必要がある。これは面倒なので、まとめて作成したいという要望があった。予約や請求項目の複製機能は用意したのだが、それだと12回複製したうえでフィールドの値をいじらないといけなくて面倒だと言われたので、商品の購入機能という概念を考えて作った。

12ヶ月分の請求項目を作る機能を別に作ることも考えられる。しかし、そうするとちょっとした変更、例えば12ヶ月分ではなく6ヶ月分にするとか、商品の値段自体を変えるとかでプログラム自体を書き換えなければいけなくなる。ちょっとの変更のたびに新しいバージョンのアプリケーションを作り直さなければならないのは不便だ。

なので、データベース上にどんな予約を作るのかを保存するための仕組みを考えた。この仕組みならデータベースの中身さえ変えれば変更できるし、また航空券の予約に限らず、あらゆる商品に適用できる。

この仕組みでは、ユーザーがする操作は以下の通りだ。

  1. 商品一覧または詳細ページから商品の「購入」ボタンを押す。
  2. 予約の作成に必要な情報、たとえば何月分から開始するなどを入力し確定する。

この仕組みでは、2つのテーブルを使う。一つは、予約のフィールドの値を計算するための式を格納したテーブルだ。たとえば、「予約名のフィールドの文字列は"startMonth+1月 予約"という文字列にする」という計算式自体をテーブルに保存する。

上記で startMonth という変数を使ったが、これはユーザーが入力した値(パラメータ)を使う。もう一つのテーブルでは、パラメータをユーザーに入力させるためのフォームを定義する。上記の例でいえば、このテーブルには「パラメータ名がstartMonth」「数字入力」「表示名は"開始月"」といった情報が入ることになる。

他に、GKEによるデプロイ環境の構築もおこなった。kubernetesに設定するための設定ファイル類は、github上でこのプロジェクトとは別に用意した。

Google Cloud SQL をどのように公開するのがよさそうか(GKEではCloud SQL Auth Proxy経由で接続するのが望ましい)、Gmail で送信できるようにする(請求書生成の通知メールを送るのに必要)にはどんな設定が必要か、設定ファイルをきれいに書くにはどうしたらいいか(kustomizeを使った)、GCSの権限設定でユーザーごとに請求書ファイルのダウンロード権限を分けられないか(結果的にはユーザーと請求書ファイルの権限を管理するテーブルを用意した)、といった内容を検討して実装した。

他には、データベース上のデータを加工するためのスクリプトの開発もおこなった。こちらは cron などで定期的に実行する想定のスクリプトだ。たとえば、予約日が特定の期間の予約を集計して、それに関する請求書データを作るといった機能を作った。

こちらの実装は TypeScript で、commander.js を使用してCLIインターフェースを作った。

このスクリプトもgithub上で別プロジェクトとして用意した。試験的に pnpm の workspaces 機能を使ってみた。これによりインターフェースを定義するモジュールと、データベースに対する処理内容を定義するモジュールを分けることができた。