14 minute read

この記事は Google Apps Script Advent Calendar 2023の 20 日目の記事です。

風音屋の兼業エンジニアの樋口(@toiroakr)です。

風音屋は主にデータにまつわるコンサルティングを提供していますが、それに伴った業務効率化の支援も行っています。 そのため、社内での業務改善にも積極的に取り組んでいます。 この記事では、業務委託メンバーの請求作業とその支払フローの改善についての取り組みを紹介します。

課題

issue
風音屋代表の yuzutas0 からある日飛んできた依頼

上の画像の依頼に加えてヒアリングを行った結果、課題は以下であることがわかりました。

  • 業務委託メンバーが個別に稼働時間の集計や請求金額の計算する必要がある
  • 業務委託メンバーが個別に請求書フォーマットを用意する必要があり、経理担当者も複数のフォーマットを取り扱うことになるため請求書の確認がしにくい
  • Slack の private チャンネルで個別に振込依頼が行われているため、情報が複数箇所に散らばって経理担当者が確認しにくい

解決策

概要

課題に対して、Google スプレッドシートを活用して以下の 3 つの機能を有するシステムを開発しました。

請求書の作成を支援する

業務委託メンバーが風音屋に対して提出する請求書の作成を支援する機能です。 案件の単価と稼働時間、振り込み先などの必要事項を入力することで、請求書が作成できます。

請求書の提出と確認作業を支援する

作成した請求書の提出とその確認作業を支援する機能です。 請求書の提出だけでなく、請求書の承認・差し戻し・再提出という一連の流れをこの機能上で行うことができます。

報酬の支払いを支援する

請求書の提出機能で承認された請求書について、オンラインバンキングでの振り込みを支援する機能です。請求書から振り込み先や金額の情報をまとめ、一括振り込みのための CSV ファイルを出力します。

使い方

請求書の作成

まずは、自分の稼働を入力するためのスプレッドシートを Slack のスラッシュコマンドで作成します。

"botからスプレッドシートのURLが送信される"
bot から作成されたスプレッドシートの URL が送信される

作成されたスプレッドシートの稼働記録シートに、稼働するごとに案件名と開始時間・終了時間を入力します。それらを入力すれば稼働時間や小計などは自動で計算されるようになっています。 ちなみに、スプレッドシートには現在の日付や時刻を入力するためのショートカットが用意されており、覚えておくと便利です(その他、セルに@を入力すると日付の補完ができたりします)

稼働時間を記録
稼働時間の記録

稼働記録をつけると、稼働集計シートに該当月の合計時間や合計金額が表示されます。ステータスについては後述しますが、「なし」「提出済み」「承認済み」などがあり、請求書の提出状況を表しています。

稼働集計の確認
集計された稼働時間と請求金額

締め日を過ぎたら請求書を作成します。

請求情報シートに請求者や振り込み先の情報を入力します(翌月以降は前月のものが自動入力されます)。

請求情報の入力
請求者情報、振り込み先の入力

ここまでの作業すれば、請求書シートに作成される請求書が表示されます。

作成される請求書
作成される請求書

請求書の提出

請求書の作成が終わったら、メニューからそのまま提出できます。

メニューから請求書を提出
メニューから請求書を提出

請求金額が稼働集計値と異なるなどの問題がなければ請求書の提出が完了し、スプレッドシートの稼働集計シートのステータスが「提出済み」に変更され、すべてのシートの編集がロックされます。

提出済み
「提出済み」ステータス

編集ロック
請求書を提出すると編集がロックされる

基本的には業務委託メンバーの作業はこれで終了です。

請求書が提出されると、経理担当者には bot から通知が届きます。スプレッドシートを開き、管理者用の請求確認メニューから内容を確認して請求書の承認/差し戻しが行えます。

請求書の提出通知
請求書が提出されたことの通知

管理者用のメニュー
管理者用のメニュー

差し戻しの理由
差し戻しの理由入力ダイアログ

請求書が承認されると、業務委託メンバーに請求書の PDF が送信されます。差し戻された場合は、理由が記載されたメッセージの送信とともにスプレッドシートの編集ロックが解除され再度作成・提出できるようになります。

報酬の支払いを支援する

経理担当者はすべての業務委託メンバーの請求書を承認後、報酬の支払いを行う必要があります。 金融機関にアップロードするための CSV の作成は同じくスプレッドシートの管理者用メニューから行うことができます(各メンバーが入力するスプレッドシートとは別のスプレッドシート上で行います)。

CSVの作成
メニューから CSV を作成

作成されたCSVのURL
Google Drive 上に CSV が作成され、URL が通知される

作成された CSV を Google Drive からダウンロードし、振り込み元の金融機関から全銀協規定形式に対応した振り込みを実施すれば振り込み作業は完了です。 (金融機関に CSV ファイルをアップロードし、振込依頼・承認する業務は別で半自動化されています)。

実装方法

本記事で紹介したシステムの特徴的な機能についての実装方法について解説します。

テンプレートとなるスプレッドシートの作成

すでに記載した通り、各メンバーが入力する請求書スプレッドシートにはコピー元となるテンプレートが存在します。 このテンプレートに対して、見た目を調整したり関数を設定したり編集ロックをしておくことでそれがそのまま各メンバー用に作られるスプレッドシートにも反映されます。

請求書シートは、 作成した PDF が 1 ページに収まるように調整しています。

また、Slack App からのリクエストを受け付けるのはこのスプレッドシートに紐づいている GAS(Google Apps Script)です。GAS を使った Slack App の実装については、多くの記事が存在しているので詳細については割愛します。

スプレッドシートのコピーの作成

スプレッドシートのコピーには Drive API サービスを追加する必要があります。claspを使用している場合には、appscript.json を以下のようにします。

{
  ...
  "dependencies": {
    "enabledAdvancedServices": [
      ...
      {
        "userSymbol": "Drive",
        "serviceId": "drive",
        "version": "v2"
      }
    ]
  },
  ...
}

ファイルのコピーの実装は以下のようになります。

const templateFile = DriveApp.getFileById("テンプレートのスプレッドシートのID");
const file = templateFile.makeCopy(
  `${fileName} - ${monthlyFolder.getName()}`,
  monthlyFolder
);
file.addEditor(email);

Google Drive には月ごとに経理担当者のみがアクセス権限を持つフォルダが作られるようになっており、作成されたコピーはそこに格納しています。 コピー作成時点では、格納するフォルダ全体に対する権限を持つ経理担当者だけに見える状態になるので、作成したメンバーに addEditor で編集権限を付与しています。 これによって、業務委託メンバー本人と経理担当者(+システム管理者)のみがアクセスできる状態を実現しています。

シートの動的な編集ロック

テンプレートの段階で一部のシートやセルに対しては編集ロックが設定されていますが、請求書を扱う都合上は無用なトラブルを避けるためにも提出後に編集できなくしておいたほうが安全です。

スプレッドシートの編集には Sheets API サービスを追加する必要があります。claspを使用している場合には、appscript.json を以下のようにします。

{
  ...
  "dependencies": {
    "enabledAdvancedServices": [
      ...
      {
        "userSymbol": "Sheets",
        "serviceId": "sheets",
        "version": "v4"
      }
    ]
  },
  ...
}

すべてのシートを編集ロックするには、以下のように実装します。

SpreadsheetApp.getActiveSpreadsheet()
  .getSheets()
  .forEach(() => {
    const protection = sheet.protect();
    protection.setWarningOnly(false);
    protection.setDomainEdit(false);
    protection.removeEditors(protection.getEditors());
    // protection.addEditorsで編集可能なユーザーを足すことも可能
  });

このコードはシステム管理者の権限で実行されるようになっており、システム管理者以外のすべてのユーザーが編集できない状態になります。

PDF の作成

PDF はスプレッドシートのエクスポート機能を利用して作成しています。

const spreadsheet = SpreadsheetApp.getActiveSpreadsheet()
let url = `https://docs.google.com/spreadsheets/d/${spreadsheet.getId()}/export?`;
const options = {
  exportFormat: 'pdf',
  gid: InvoiceSpreadsheet.getSheet(spreadsheet, 'invoice').getSheetId(),
  gridlines: false,
};
url += Object.keys(options).map(key => `${key}=${options[key]}`).join('&');

const token = ScriptApp.getOAuthToken();
const response = UrlFetchApp.fetch(url, { headers: { Authorization: `Bearer ${token}` } });
const blob = response.getBlob().setName(`${spreadsheet.getName()}_${new Date().getTime()}.pdf`);

const folder = /* 格納したいフォルダ */;
const pdf = folder.createFile(blob);
spreadsheet.getEditors().forEach(user => pdf.addViewer(user));

エクスポート用に用意されたエンドポイントへ必要なパラメータを設定してリクエストすることで PDF の blob データが得られ、それを Google Drive に保存するという流れになっています。

CSV の作成

CSV の作成はテキストからファイルを作る方法で行います。

const csvData: string[][] = [[/* 1行目のデータ */]];

const folder = /* 処理したい月のフォルダ */
const files = folder.getFilesByType('application/vnd.google-apps.spreadsheet');
while (files.hasNext()) {
  const file = files.next();
  const spreadsheet = SpreadsheetApp.openById(file.getId());
  // csvDataに行を追加
}

const csvString = csvData.map((row) => row.join(',')).join('\r\n');
const blob = Utilities.newBlob('', 'text/csv', `payment_${new Date().getTime()}.csv`).setDataFromString(csvString, 'Shift_JIS');
const folder = /* 格納したいフォルダ */;
const csvFile = folder.createFile(blob);

Utilities.newBlob で blob データを作成した後は PDF と同じです。

振り返り

ユーザーの反応

このシステムは風音屋において実際に使われていますが、「むしろ SaaS よりも入力が楽」といった意見もありおおむね好評です。

業務委託メンバーの感想
業務委託メンバーの感想

経理担当者の感想
経理担当者(当時は代表 yuzutas0)の感想

改善したいところ

タイムアウト

Slash コマンドでスプレッドシートの URL を取得する際は、当月用のフォルダにコマンド実行したユーザーが編集者になっているスプレッドシートが存在するか確認して、存在すればその URL を返し、存在しなければ新しく作成する非同期タスクを登録しています。

スプレッドシートを新しく作成する場合には Slash コマンドの 3 秒ルールに引っかかってタイムアウトのエラーがユーザーに表示されてしまうことが多いです(実行自体はされるので、待っていれば URL は返ってきます)。

タイムアウト
スプレッドシート作成時に Slash コマンドがタイムアウト

これを改善するためには、以下のような対策が考えられます。

  • スプレッドシートの存在確認を、キャッシュから行うようにする
    • キャッシュは定期的に実行するトリガーと、作成時に更新する
  • 現在は別のスプレッドシートを使って管理している非同期タスクを同じスプレッドシート内(の非表示シート)で行う

メニューの実行許可

請求書提出のためにスプレッドシートのメニューを実行することになっていますが、Drive API などを使っている都合上、初回の実行時は実行許可を求めるダイアログが表示され、許可したとしてもメニューの内容は実行されずに終了してしまいます。 許可した後に、もう一度メニューを実行することで初めてメニューの内容が実行されます。

認証確認
初回のメニュー実行時に許可を求めるダイアログ

使い勝手が悪い部分ではありますが、スプレッドシートの仕様上回避が困難なため、ユーザーに説明をした上で初回はメニューを 2 回実行するようにお願いしています。

Slack との連携強化

Slack との連携を強化していけば使い勝手が向上する部分はいくつかありそうです。

  • 業務委託メンバー
    • Slash コマンドでの稼働記録
      • 「コマンドの実行忘れで二度手間になりがち」といった意見もあったので実装を見送りました
      • スプレッドシート側に記録用のボタンを設置するという対応でもよさそう
    • App Home 連携
      • 稼働記録一覧表示・編集
        • スプレッドシートだから編集がスムーズな側面もあるので、複数編集は不向きかもしれません
      • 集計表示
      • 請求書の提出機能
  • 経理担当者
    • App Home 連携
      • 稼働記録しているメンバーとその請求書の承認状況の一覧表示・リマインド機能
      • CSV 出力機能

スプレッドシートとGASを使った実装の是非

今回の実装ではスプレッドシートと GAS を使ってシステムを実装しましたが、それについてのメリット/デメリットとして感じたことをまとめておきます。 総論としては、社内システムということもあってメリットの方が大きかったと考えています。

メリット

メリットとして、まず挙げられるのは金銭的なコストです。 Google Workspace(と Slack)を導入していれば、追加でコストをかけずに運用が可能です。

開発のコストについても、スプレッドシートの機能を活かせたことで工数を削減できたと思います。

スプレッドシートは稼働時間の記録のような用途に向いており、操作にも馴染みのある人が多いです。 稼働時間の記録にフォームの送信やページの遷移も行わないため、結果としてスムーズな情報の入力が提供できています。

また、非エンジニアでもスプレッドシートの変更だけで機能追加が可能であり、実際に「銀行名をプルダウンで選んだら銀行コードが自動で反映されるようにする」という改修がそのようにして行われました。

デメリット

改善したいところでも挙げた「タイムアウト」や「メニューの実行許可」は、スプレッドシートや GAS の制約によるところが大きく、デメリットとして挙げられます。

他にも、スプレッドシートは入力の自由度が高く、想定外の操作や入力が気軽に行われてしまいやすいという問題があるように思います。 ユーザーの反応にもある通り、イレギュラーにも対応しやすいというメリットの裏返しではあるのですが、使い方の案内や編集できるセルの制限などを適切に行う必要があります。

最後に

以下、風音屋代表 yusutas0 のコメントです。

風音屋は「改善サイクルを回し、今日よりも良い明日を。」というビジョンを掲げています。 我々はデータ活用の分野に強みを有していますが、データを見るのはあくまで改善サイクルを回すためです。

そのためクライアント向けに「業務フローの整備・改善」「KintoneやSFAの導入」「GASやSlackBotによる作業の自動化」といったプロジェクトを扱うこともあります。 データ整備と業務改善がセットになっているケースも多いです。

将来的には(データ分野に閉じずに)専門人材が集まり・育ち・活躍できる環境を整備し、必要な場所に必要な人材が必要な分だけサポートを提供できるように販路を開拓し、社内外のあらゆるステークホルダーが日々の改善サイクルを回せるようになってほしいと思っています。

ということで、最後に社内チャットで語り合った(つもりだったけど改めて読み直したら代表が1人で勝手に妄想していた)野望をシェアしたいと思います。

  • 社内で最高の体験を作って外販していきたい。①社内の業務改善 → ②オウンドメディアで取り組みを紹介 → ③クライアントから案件を受託 → ④よくある相談のパターンを分類 → ⑤ソリューションパッケージを販売、みたいな流れが作れたら最高。
  • クライアント企業の抱える課題:業務改善・DX推進、コーポレートエンジニア不足を解消したい。特に中小企業だと一部の担当者に作業が偏るので、SaaSのフォームUIよりもスプレッドシートで複数行選択できたり一括置換できるほうが圧倒的にラク。このSaaS全盛期の時代でも、コーポレートスタッフは結局スプレッドシートを捨てきれないし、そこにはやっぱり理由がある。
  • ソフトウェアエンジニアに提供する機会:個人開発者やプログラミング学習者が一気通貫(企画→開発→運用)で活躍できる場所を提供したい。NoCode系ツールに強いITコンサルタントやBizOps人材も活躍できるかも。社内ツールや業務改善ツールを作ると、社内のユーザーから直接フィードバックをもらえるので、ソフトウェア開発の一番楽しいところが詰まってる。
  • この請求書管理システムの進化で実現できそうなこと:副業時代の稼働管理SaaSとして売れそう。◯◯銀行の自動支払いに対応!freee や Money Forward にも連携!案件別の業務委託費などダッシュボードでコスト推移を把握!会計事務所のExcelシートへの変換はエンタープライズプランで個別対応!みたいな。
  • この請求書管理システムの横展開で実現できそうなこと:SaaSを置き換えるようなサービス群を大量生産できるのではないか。SaaSのWEBフォーム中心のUXだとユーザーは入力・編集しにくい。スプレッドシートやSlackAPPのUXに置き換えていけないか。スプレッドシートのほうがSaaSより使いやすいなと感じる場面は多々ある。メンテナンス性の悪さやシートが壊れてしまう問題をエンジニアリングで解決すれば、スプレッドシートのほうが喜ぶ層もいるのではないか。1ユーザーあたり100円/月で関連サービス群(例:◯◯打刻、◯◯アナウンス、◯◯管理会計)を使い放題!みたいな破壊的価格にして、平均ユーザー30人x10,000社=3,000万円/月くらいなら目指せないだろうか。「法改正に対応していくよ」と言えば、自前で作るより「100円ならいいかな」と思ってもらえないか。もしくはもっとシンプルなやつをグローバルで売るとか。担当者5名(プロダクト改善)+秘書会社BPO(手動作業や問い合わせ対応)のスモール体制でサービスを量産して、売上の1/3を人件費・業務委託費に振り分けるとして、担当者の給与を月200万円にまで伸ばせたら夢があるのではないでしょうか。5名だとキツいか。でも世の中探せばそういう会社ありそうじゃない?実力派シニアエンジニア数名の小規模ソフトハウスとか「えっ!?このサービスをこの人数で運営してるの!?」みたいな。WEB魚拓とかそんな感じじゃなかったっけ。少人数で運営できるように、いかにソフトウェアの保守運用をラクにするか、手作業をBPO化できるか、みたいなところがエンジニアリングの挑戦ポイントかなと。

長くなりましたが、要するに、業務改善プロジェクトをリードするソフトウェアエンジニアやコンサルタントを募集中です! 採用ページはまだ「準備中」となっておりますが、野望に共感してくれた方がいましたら、ぜひカジュアル面談でお話しましょう!

風音屋 採用ページ

Tags: , ,

Posted:

Author: toiroakr

樋口(@toiroakr)。 風音屋兼業エンジニア。 風音屋代表であるゆずたそのリクルート時代の後輩で、たびたび呼び出しを食らってお手伝いをしています。