AppSheetで申請書等のPDFを生成してみた
AppSheetは様々なビジネス用のアプリケーションを作成可能ですが、ワークフローや請求書などのアプリに付き物である「PDFの生成」もAutomationで実現可能です。
このPDF生成はGoogleドキュメントに仕掛けをしておき、そのテンプレートを元に差し込み印刷のようにPDFを生成する事が可能になっています。欠かせない機能であるため、ここで2パターンのPDF生成をしてみようと思います。今回はGoogle Apps Scriptを利用せずに実装しています。
今回利用するスプレッドシート等
- AppSheetでPDF生成 - AppSheetテンプレート
- PDF出力テスト - Googleスプレッドシート
- 申請書雛形 - Googleドキュメント
- 請求書雛形 - Googleドキュメント
申請書のようなシンプルな1テーブルだけで構成されたファイルの生成と、請求書のように親テーブルとそれにぶら下がる子テーブルの2つで構成されてる場合の2パターンについて、ボタンクリックでPDF生成をしてみようと思います。
ボタンクリックでAutomationを発火させる方法については以下のエントリーで扱っています。
事前準備
テーブル側
今回のアプリは前述でも紹介してる「アクションボタンをクリックしてPDF出力を実行する」ものなので、以下のような準備が必要です。
- 各基準となるスプシのシートに「更新フラグ」用の列を用意する
- この列に対して後述のアクションにて値の書き込みにてUNIQUEID()で生成した値を入力し、Automationを発火させる
- 請求書タイプの場合には明細シートには自身用のID列と連結するための親ID用の請求書ID列を用意する
- Refを利用して親側のIDと子側の請求書IDを連結する
Refで請求明細側の請求書IDに対して親のテーブルを連結してるため、親テーブル側には「Related 請求明細s」というList側のフィールドが自動追加されて連結されます。
図:更新させて発火させる為の列の用意
図:明細側にリレーションを設定する
テンプレート側
申請書タイプ
申請書タイプは1レコードで完結するワークフローなど社内の各種申請で用いることが多いであろうテンプレートです。以下のような手順でGoogleドキュメントで体裁を作成します。
- Googleドキュメントである程度のフォーマットを作成する
- 差し込みたい項目の場所に、AppSheetの列の名前を元に「<<申請日>>」といった文字列を配置します。
- 表などを使ってレイアウト組をして順番に入力内容を配置するように構築しても良いでしょう。
思っているほど難しいものではないのですが、テーブルの列の名前で入れる必要があるため、少しでも誤りがあると挿入されませんので要注意です。
図:挿入文字列を配置していく
請求書タイプ
請求書タイプの場合、前述の申請書タイプに加えて親のレコードにぶら下がる明細レコードが複数存在しており、またその明細の数も不定であるため、前述の記法だけでは不十分です。親のレコード側にある内容は前述の通り入力すればオッケーです。こちらに公式ドキュメントがあります。
請求書の場合は以下のような手順で作成します。
- Googleドキュメントである程度のフォーマットを作成する
- 親レコードにある項目は前述と同様に該当箇所に「<<発行日>>」という形で挿入しておきます。
- また後述の「合計金額や税込金額計算」で用意した合計金額や税込金額などのVirtual Columnの項目も同様に「<<合計金額>>」といった列名で挿入が可能です。
- 問題の明細行ですが、1行分のみを用意しておきます。最初の列の品名がちょっと特殊で以下のような値を入れます
1<< Start: [ Related 請求明細s]>><<品名>>
startから始まり、Related 請求明細sの項目を指定してから品名を入れます。これでループで自動で行追加してくれます。 - 次の数量や単価は通常通りの指定方法で問題ありません。
- 最後の金額欄も品名同様に以下のような指定方法で閉じます。
1<<金額>><<End>> - startからendまでの1行のループで必要な明細分だけ自動で行が拡張されて追加される変わった仕組みになっています。
ただし、これは表形式でなければ旨く動作しないので、例えば箇条書きを実現できるか?といったらPDF出力されませんでした。同一のセル内にStartとEndを一緒にすると1行の中に記述されてしまうので注意。擬似的に箇条書きにしたい場合には以下のように構築します。Endは改行してから配置します。
1 2 3 |
<< Start: [ Related 請求明細s]>> ・<<品名>> <<End>> |
図:請求明細は1行のみだけ用意する
図:実際に出力すると複数行出てくる
実装方法
申請書タイプであれ請求書タイプであれ、アクションボタンからPDF生成までの流れは共通です。以下のような仕組みを構築して出力します。ここでは申請書のビューについて追加する手順として紹介します。同様に請求書側も装備してみましょう。
アクションボタンを追加
AppSheetの開発画面の左サイドバーからActionsという項目にてボタンを追加していきます。
- Add ActionをクリックしてCreate a new actionをクリックする
- Action Nameは適当にセットし、For a record of this tableは「申請書」テーブルを指定する
- Do thisは「Data: set the values of some columns in this row」を指定します。
- set these columnsにて、更新フラグ列にUNIQUEID()を入れる設定を記述します。
- PositionはProminentとして指定する
これでボタンをクリックすると申請書シートの更新フラグ列に値が毎回書き込まれ、それをトリガーにしてAutomationを発火させることが可能になります。
図:これで更新フラグに書き込まれる
図:ボタンクリックで発動します
Automationを設定する
イベントを作成する
前述まででボタンの設置まで用意できました。次に、この更新フラグの書き込みをトリガーにしてPDF発行用の処理を動かし、スプシの申請書シートにはファイルの出力先までのパスを書き込むようにします。
- AppSheetの開発画面の左サイドバーでAutomationをクリックする
- Create a new botのボタンをクリックする。
- ダイアログでCreate a new botをクリックする
- Confiture Eventをクリックする
- Create a new eventをクリックする
- 右サイドバーにてTableは「申請書」を指定する
- Data Change TypeはUpdateのみを選択するようにする
- Conditionでは以下のような数式を構築して更新フラグが変化していたら実行するように仕込む
1[_THISROW_BEFORE].[更新フラグ] <> [_THISROW_AFTER].[更新フラグ] - 右上のSAVEをクリックする
図:更新フラグを元に発火するように仕掛け
PDF生成ステップ
無事に更新フラグを元に発火できるようにしたら、いよいよGoogleドキュメントをテンプレにPDFを生成する仕掛けを作ります。前述のイベントの次より以下の手順でフローを追加します。
- Add a Stepをクリックする
- Run a taskのstepの右サイドバーに於いて、「Create a new file」を選択する
- HTTP Content TypeはPDFを選択する
- Templateのファイルアイコンをクリックして、ドライブから作成しておいた申請書雛形のGoogleドキュメントを指定する
- File Folder Pathは空でも問題ないですが、その場合「/appsheet/data/アプリ名/」以下に「Filesフォルダ」が作成された上で自動でファイルが作られますが、ここで「"雑多なファイル/pdffolder"」と指定すると、「/appsheet/data/アプリ名/雑多なファイル/pdffolder/」以下にファイルが作られるようになります。
- File Name Prefixは自動的につけるPDFのファイル名を指定します。ここでは関数でユニークなファイル名になるようにセットしてみます。
1[請求先] & "-" & TEXT([申請日], "yyyymmdd") & "-" & [更新フラグ] - Disable Timestamp?はスイッチをオンにします(でないとファイル名に勝手にタイムスタンプが追加されてしまう)
- Attachment Page OrientationはPDF出力方向(縦横)およびA4などの出力サイズを指定出来ます。
- 右上のSAVEをクリックする
これで、ドキュメントをテンプレとしてPDFを差し込み印刷的に出力してくれるようになります。
図:テンプレートファイルを指定する
図:ファイル名と出力形式を指定する
出力先パスを書き込む
このステップは特に無くても問題ないですが、出力先のGoogle Drive上のパスを申請書のPDFの場所列(型はFile型を指定する)に対して書き込むことが可能です。以下のようなフローを構築します。
- Add a Stepをクリックする
- Run a data actionを選択する
- 右サイドバーでは「set row values」を選択する
- Set these column(s)では、「PDFの場所」という列名を選択し、関数には以下のような内容を記述する(最後に拡張子の.pdfをつける点が異なるので注意)
1"雑多なファイル/pdffolder" & [アプリ名] & "-" & TEXT([申請日], "yyyymmdd") & "-" & [更新フラグ] & ".pdf"
※前述のフォルダのパスとファイル名を合体させたものを書き込ませます。 - 右上のSAVEをクリックする
図:出力先をスプシに書き込みする
図:書き込み先列はFile型である必要がある
File Not Found問題
前述に於いてFile型の列にPDF出力パスまでを入れています。そうするとAppSheet上にファイルアイコンが出て、クリックするとPDFファイルが開かれるようになります。しかし、実際に開いてみると「not found」とエラーが出て開けないというケースがあります。
Drive上のパスは間違っていないハズだし、ファイル名も正しいハズ。なのに開けない・・・この問題ですが、以下の数点をまず調べましょう。
- File Folder Pathが空の場合は「/appsheet/data/アプリ名/Files」以下にファイルが生成されます。
- 出力先パス書き込みは上記の場合、「"/Files/" & [顧客名] & "-" & TEXT([日付], "yyyymmdd") & "-" & [請求更新フラグ] & ".pdf"」といったように、/Files/から始まりPDFの拡張子付きのもので終わるように書き込みます。
- 最後に重要なのがスプシをソースとしてる場合、「/appsheet/data/アプリ名/」以下にスプシが配置されてる必要があります。別のフォルダの場合だとNGです。
このスプシやPDFのファイルの生成先については、左サイドバーのsettigs→infomation→Default app folderの中にあり、ここを例えば、/プロダクト/車屋管理/PDFと書き換えるとマイドライブ直下から始まるプロダクト以下のディレクトリに変更することができます。変更してる場合も、このケースの場合PDFフォルダ以下にFilesフォルダができて・・・となるので、PDFフォルダ内にスプシを配置しないといけません。
つまり指定のディレクトリからの相対パスで見てる為スプシの置き場が異なるために「not found」となってしまって開けなかったわけです。
図:ファイルアイコンからPDFを開ける
図:ファイルが見つからず開けなかった
図:スプシのあるフォルダにFilesフォルダが有る状態がOK
メールに添付して送信
ここまで来ると生成したPDFをお客様などにお見積りや請求書として添付してメールでそのまま送信する機能が欲しくなります。前述までで対象のPDFのパスはスプシの該当列に書き込まれているので、改めて生成ではなく生成済みPDFをメールに添付して送信するAutomationを作成します。
上記の流れで送信しても良いのですが、内容を確認できずに自動で送信するというのはUX上あまりよろしく無いので、以下の装備を行います。途中まではボタンクリックでGAS発動といった場合の仕掛けと同じものになります。
- スプシにメール送信フラグ列を用意し、Actionを用意しUNIQUEID関数で値を書き込む。
- 値が書き込まれたら、Automationが発動するように用意する。このときのData Change TypeはUpdateのみとし、Conditionでは以下のような数式を入れて、メール送信フラグ列に値が書き込まれた時だけ発火するように仕掛ける。
1[_THISROW_BEFORE].[メール送信フラグ] <> [_THISROW_AFTER].[メール送信フラグ] - Automationの次のプロセスはSend an emailを選択し、Toはお客様メアドの列を指定する。自分の場合メインのテーブルにはお客様メアドが無いけれど、そこからリレーションで参照してる顧客管理テーブルにはあるので、そちらを参照させています。
- Email Subject, Email Bodyを構築。Other ParametersのReply toは自分宛のメアドを入れる(でないとnoreply@appsheet.comがFromで飛んでくるのでお客様は返信ができない)
- 重要なAttachment settingsですが、新規に生成しないのでOther Attachmentsの項目にて生成済みPDFのパスが書かれてる列を指定する(こうすることで自動で添付されます)。
実際にこれでActionのボタンをクリックするとメール送信のAutomationが発火し、生成済みPDFを添付したメールが相手に送られます。GAS無しでここまでの処理を実装可能です。
図:イベントの設定
図:生成済みPDFを指定する
図:送信されたPDF添付のメール
出力した結果
実際にテンプレを元にPDF出力されたファイルを開いてみると以下の図のような形でファイルが生成されていました。色々と調整は必要ではあると思いますが、無事に差し込み印刷のようにPDFが作成されました。
請求書の場合にはあとは手動で電子署名をして相手にメールするといった形で仕事を終えることが可能です。
図:出力されたPDFの事例
図:出力先のパス
合計金額や税込金額計算
この項目は請求書タイプのPDFを生成する上で利用する「明細の合計金額」「消費税額」「税込み金額」の3つを計算する必要があります。今回は請求明細テーブルをRefで連結しているので、それを利用してこれらの値をVirtual Columnとして請求書シートに計算列として追加します。
この列名をGoogleドキュメントの差し込み項目として利用します。
- 請求書テーブルを開き、右上のAdd Virtual Columnをクリックする
- Column Nameを合計金額として、計算式は以下のようなものを入れます。
1SUM([Related 請求明細s][金額]) - 次に同じようにVirtual Columnを追加し、Column Nameは消費税額として計算式は以下のようなものを入れます。2.の列の値をそのまま利用します。
1[合計金額]*0.1 - 次に同じようにVirtual Columnを追加し、Column Nameは税込金額として計算式は以下のようなものを入れます。
1[合計金額]+[消費税額] - 右上のSAVEをクリックして保存する
これで、差し込みで合計欄3種類をそのまま差し込めるようになります。
図:Virtual Columnで計算させます
フォントが気になる点
Google Documentを雛形にして実際にAppSheetでPDFを生成してみると気になる点が1つ。「カスタムなフォントの指示を無視して、中国フォントっぽい表示になる点」。Googleドキュメント上でフォントをいくら指定しても反映されず、こちらのサイトでも指摘されていますが、フォントが固定になります。
このフォントは変更することが現時点でできず、気になる場合にはGASで自前でPDFを生成するロジックを実装する必要があると思います。スプレッドシートを使う手法もあるようですが、こちらは請求明細の生成で課題があります。
図:中国フォントっぽい表示
Secure PDF accessオプション
AppSheetのデフォルト設定では、PDF生成したファイルについては以下のような仕様になっています。
- ドライブにファイルが生成されるが、AppSheetサーバにもキャッシュのファイルが生成される
- 通常アプリから開かれる場合、ドライブ上のファイルではなくキャッシュから開かれる
- デフォルト設定だとこのPDFファイルはURLが分かれば誰でもアクセスが可能
最後の部分が重要で、お客様の情報などが記載されたPDFファイルがデフォルトだと誰でもアクセス可能な状態になっています。セキュリティ的にこのままではマズイので以下のオプションを必ず有効にするようにしましょう。
- AppSheetの開発画面左サイドバーのSecurityをクリックする
- Optionsをクリックする
- Secure PDF accessのチェックをオンにする
- 右上のSAVEボタンをクリックして設定を保存する
なお、このオプションはCoreプラン以上の機能であるため、Freeプランでは使えない。よって業務で使うならばFreeプランではなくGoogle Workspaceを導入して活用しましょう(単発で契約するよりもお得です)。
図:このオプションは常に有効にしよう
PDF再生成時の問題
概要
AppSheetで前述までの方法で値を変えてPDFを再度生成した場合、アプリ上でアイコンをクリックすればPDFが新規ウィンドウで開かれます。しかし、表示されてる内容が再生成時前の古いPDFが表示されているケースがあります。これはAppSheetの場合、以下のような仕様になっている為。
- 同名のファイル名の場合、Google DriveじゃなくAppSheetサーバー上のキャッシュが表示される(常に最新が表示されるわけではない)
- キャッシュは24時間有効で再生成しても反映するのに時間が掛かる
- UNIQUEID関数を使って毎回違うファイル名でファイルを生成するか?GASを利用してファイルのIDを取得してURLを構築するか?GetTableFileUrlを使っての対象のファイルのURLを参照する必要があります。
毎回違うファイル名で生成
前述まででテーブルの「更新フラグ」列を使ってAutomationを発動させています。問題はこの時毎回生成されるPDFのファイル名については、例えば時間などを使ってHHMMDDSSとすれば毎回違うファイル名になるじゃないかと考える。しかしこれではファイルパスを書き込む時にこのHHMMDDの値をどこかに保存しておかないと参照できないので困ったことになります。
そこで利用するのが更新フラグ列の値。この値はAutomation発動前にスプシにすでに書き込み済みで、PDF生成の度に毎回違う値が書き込まれてるので、この値をFile name prefixで利用してPDFを生成するようにすれば、毎回異なるファイル名でPDFが生成されるようになります。
例えば以下のような形で数式を入れます。
1 |
[お名前] & "様" & [申込み日付] & "の受付証" & "-" & [更新フラグ] |
ファイルパスの書き込みプロセスでも同様に数式を組んで書き込むようにしておきましょう。
できれば、アクションボタンをクリックした時点で、ファイルパスの列の値を空にしてから更新フラグ列に値を入れる処理にすると間違いがありません。
図:ファイル名で更新フラグ列の値を付け足す
GASでファイルのIDを取得して追記
すでに別エントリーで実装済みですが、GASに対して対象ファイルのIDを取得して追記させる方法が取れます。個人的にはこの方法がGASからの再利用も含めて一番しっくりきます。但しファイルだと同じファイルがフォルダ内に存在するので、前述同様にファイル名についてはUNIQUEID列(更新フラグ列)の値をファイル名に加えてファイルは生成しておきます。
あらかじめスプシ側にファイルIDというIDを書き込む為の列を追加しておきましょう。
- AutomationにてGASに対してレコードのIDを渡す
- GAS側はPDF生成フォルダを参照するようにし、DriveAppにて対象フォルダ内で引数のファイル名で検索する
- ファイル名が一致したら対象ファイルのIDを取得する
- ファイルIDをAppSheet側に返して別途追加しておいたファイルID列に追記するか?GAS側からレコードIDで探索して追記しておく
- AppSheet側はFile型の列を表示するのではなく、Url型の列を仮想列で用意する(ファイルリンクとでも命名する)
- 5.の仮想列の数式として、「"https://drive.google.com/file/d/" & [ファイルID] & "view?usp=drive_link"」といった式を入れる
- ユーザはこの仮想列をクリックするとGoogle Driveから対象のPDFが開かれるので常に最新のPDFが表示されるようになる
この仕組みのほうがAppSheet独特の仕様ではない見慣れた方法になるため、おすすめです。
以下はファイル名から対象のフォルダ内のPDFファイルのファイルIDを取得するGASのコードです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
function filesearch(targetFileName){ //folderidを指定する let folderid = "ここに探索先フォルダのIDを入れておく" // ファイルを特定しファイルIDを取得 let fileId const folder = DriveApp.getFolderById(folderid); const files = folder.getFiles(); while (files.hasNext()) { const file = files.next(); const fileName = file.getName(); //targetのファイル名と一致するか? if(fileName == targetFileName){ //ファイルIDとBlobを取得する fileId = file.getId(); break } } //ファイルIDを返す return fileId; } |
図:仮想列でPDFファイルへの直リンクを作る
参考動画
関連リンク
- 月60件超の請求書をAppSheetで自動作成 山喜製陶3代目のDX
- File column showing cached PDF, not linking to actual file
- Open File/Image with external browser without looking up cache
- AppSheetで撮影したデータを、URLとして格納したい
- Call to Action for Apps Constructing URLs to GetTableFileUrl
- AppSheetからPDFを自動で作成してみよう!
- AppSheet実践編シリーズ⑱~(小ネタ)PDF報告書に画像を表示してみよう!~
- AppSheetでPDF自動出力機能を実装
- AppSheet で請求書アプリ(第7回)Automationで請求書をPDFで発行
- AppSheet で請求書アプリ(第6回)請求書テンプレートを作成する
- AppSheet で PDF帳票出力!Automation 機能を活用しよう!
- AppSheetを用いてデスクレスワーカーの仕事をデジタル化する方法
- Defining the Correct File Path
- 【TIPS】Start 〜 End 構文の使い方
- PDF line breaks
- メールテンプレートの改行の置き換え
- AppSheetでGoogle Driveの共有ドライブを利用しようとしてハマった件
- AppSheetでPDFが開けない!
- File not found
- Appsheet PDF file not found, Html
- AppSheetでファイル作成後にファイル添付メール送信