📧

GAS で問い合わせチケット管理システムを自作した話

に公開

2か月前からNPO組織でシステム管理系ボランティアを始めている。そこはかなり前近代的な運営であるので、ユーザーからの問い合わせ対応フローがメールベースだった。私が所属するシステム関連部門が対応することもあれば、専門部署の担当者に案件を回したりすることもあった。もちろんメールを転送する形で。久しぶりにRe:Re...が果てしなく続くメールを何十件も見た。気分だけは社会人を始めた2000年代にタイムスリップしたかのようだった。

幸いなことにNPO向けのGoogleWorkSpaceを利用しているので、Google系のアプリで基本的なものは使える。そこでユーザー問い合わせをフォームに集約しようと考えた。だが、問い合わせフォームの回答はスプレッドシートベースで集計される。個別の案件管理をするとなると非常に面倒なことは明らかである。かといってJIRAやセールスフォースのようなチケット管理のSaaSを導入するほどでもないので、GASを利用して問い合わせチケットシステムを作成した。

システム構成内容と得られた知見を備忘として残しておくので、非エンジニアの方々やコストをかけられないNPO関係者/中小企業関係者にも参考になれば幸いである。

構成サービス概要

Googleワークスペース縛りがあるので、非常にシンプルな内容。全体設計と事前にhtmlベースでUIモックを作成後、ChatGPTに実装コードを書いてもらい、細かい部分をマニュアルで修正していった。

Googleフォーム

- ユーザーの問い合わせ内容を収集
- リンクを知っているユーザーは誰でもアクセス可能

Googleスプレッドシート

スプレッドシートは目的により2つのファイルに分割

Sheet(1): FormResponses

- フォームデータ収集時に自動作成
- 新規内容書き込み時に次の加工用scriptを仕込む
- UniqueID(UID)の発行
- チケット対応履歴ログシート(TicketLogs)へ転記
- ユーザーへUIDをメール送信
- 担当者向けのGoogleChatスレッドへも通知

Sheet(2): TicketLogs

- データベースの代替
- 管理者以外はアクセス不可
- 編集記録は末尾に追記されていく

GoogleAppScript

- 問い合わせ内容の閲覧/回答用UI
- 表示内容はLogsの最新行の情報のみ
- アクセスは組織ドメイン縛り

構成図

flow

Webアプリ関数一覧と処理概要

UI画面キャプチャ

チケット管理画面のキャプチャがこちら。簡易なフィルタリングは実装。
チケット

編集ボタンを押すとモーダルが開き、個別チケットの編集が可能。
モーダル

Code.gs

Code.gsがスプレッドシートのデータを取得するバックエンドを担う。

関数名 概要
doGet() WebアプリとしてのUIを返す。HTMLファイルを画面に表示させるエントリーポイント。
onFormSubmit(e) Googleフォーム送信時に呼び出される関数。チケットIDの発行、ログへの記録、メール通知、チャット通知を行う。
sendToGoogleChat(ticketId, name, category, inquiry) チケットの新規受付情報をGoogle Chatに通知する。Webhookを利用。
getTickets() スプレッドシートのログから、各チケットの最新状態だけを抽出して配列で返す。UIでの一覧表示に使用。
updateTicket(updateData) 指定されたチケット情報に基づき、最新情報をログに追記する(上書きせず履歴として記録)。

チケット画面(HTML + JS)関数一覧と処理概要

GoogleAppsScriptのHtmlServiceによって表示されるチケット管理画面。HTML(Bootstrap + FontAwesome)と JavaScript により、UI構築と GAS 呼び出しを担当。

関数・要素名 役割・処理内容
loadTickets(isRefresh) getTickets() を呼び出し、データ取得 → applyFilters() を実行
applyFilters(initialLoad) 入力フィルター(受付番号・カテゴリ・ステータス・担当者)に基づき一覧を絞り込む
renderTickets(tickets) チケット一覧をHTMLに変換して #ticketList に出力
openEditModal(...) 編集モーダルにチケットデータをセットし表示
saveModalTicket() モーダルから編集データを取得 → updateTicket() 呼び出し → モーダルを閉じて再描画
escapeHtml(str) HTMLエスケープ処理(XSS防止)
DOMContentLoaded イベント 初期化処理。各種フィルターにイベント登録。初回表示で未完了チケットを読み込み

HTML構成要素一覧

セレクタ/構成 内容
.filters 絞り込みUI:受付番号、カテゴリ、ステータス、担当者など
#ticketList チケット一覧の表示ブロック
.modal 編集モーダル(Bootstrap Modal構成)

備忘録

GASのWebApp化

GASでやっていること自体はスプレッドシートの内容を専用UIで表示/加工できるというもの。スプレッドシートでも同様のことはできるが、データの記録/編集が一つのシートで行われると不便。またスプレッドシートのセルに直接対応履歴を書き込む場合、入力もしづらく複数対応が重なると履歴を上書きする/追記するでユーザー判断も分かれそう。統一したフォームを提供することで、担当替えが発生した場合にも過去の対応が追いやすくなることを狙っている。

スプレッドシートの運用

問い合わせ書き込み時のシートと加工ログ記録用シートは切り分けにより、異なるデータの混在を防ぐ。ログ用のシートを用意することで、過去の編集履歴を管理者は追えるようにした。また管理者以外はベースとなるデータへのアクセスを遮断。これによりデータ書き換えの可能性を減らす。バージョン管理のように過去との差分を比較できるとより良かったがちょっと機能過多なので見送り。

データ集約/標準化

問い合わせ窓口を集約し、データ形式を統一することで、将来的にFAQチャットボットを作るための教師データを蓄積できるはず。これまでのFAQも人間が経験で作成したものなので、そこはデータを使って改善の余地はあるはず。

生成AIベースの実装

シンプルな実装からスタートして、課題を明確にフォーカスしながら修正するといい。ChatGPT出力コードがシンプルすぎてClaudにレビューさせたら思いっきり改変しすぎて動かなくなり修正もできなかったのは苦い思い出。フロントエンドはほとんど経験がなかったため、できるだけシンプルな状態でちょっとずつグレードアップしていった結果、最後の細かい修正やエラー原因調査の勘所が早めに身につけられたと思う。

まとめ

設計やレビューに注力しつつ、実装自体は数時間程度で完成した。手軽に低コストで必要な機能だけに絞ってシステムリリースできるのはやはり内製の魅力である。

一方、今後は「保守」という新たな課題がある。非技術者でもAIと協力すればメンテナンスできるものなのか。世の中には、生成AIにお願いすれば何でもできるという風潮はあるにせよ、システムが人や組織と関わる以上、一定の技術的基礎知識がなければ説明責任が果たせないし、そもそも“何を作るべきか”の判断を誤るリスクもある。大変悩ましい。保守用のコンテキスト情報を得るために、AIが担当者にヒアリングするシステムを作る必要があるのかもしれない。まぁそうなるとそのシステムの保守はどうするんだ、という問題が再帰的に発生するのだ。悩みが尽きない。

Discussion