竹澤です。
最近、齋藤 孝さんの「上機嫌の作法」という本を読みました。その本によると「中年と言われる年齢にさしかかった人たちは、実際には不機嫌ではないにしても、ふつうにしているだけで不機嫌にみえるという十字架を背負っている」のだそうです。私もその例外ではないと思いますので、ぜひとも著者がいう上機嫌力を1年ぐらい掛けて身につけていきたいと思っています。
さて、このブログでは7月の開設以来、mitocoの各機能について紹介してきました。
今回は少し違った視点で、mitocoに対する高度なカスタマイズについて書きたいと思います。
実はmitocoはSalesforceのForce.comの上に構築されているので、Force.comが持つ豊富な機能を利用することが出来ます。ほんの一例ですが、次のようなことが可能になります。
- データの集計・レポーティング(例: 担当別のToDoの消化状況を管理者が確認する)
- APIで他システムとデータ連携(例: 在庫管理システムで在庫が更新されたらコミュニティに在庫数をつぶやく)
- Visualforceを使ったPDF帳票(例: 承認されたワークフローの証跡をPDFで残す)
このなかで、3つめのPDF帳票を作る部分のカスタマイズ例を今日は紹介したいと思います。
実現したいこと
ワークフローで最終承認がなされたら、申請内容と承認者の情報をPDFにして「ドキュメント」の「証跡」フォルダに格納するようにします。ドキュメントフォルダに保管するのは、あとから管理・監査部門の担当者がまとめて確認しやすい、という点を考慮してそのような実装にしました。
想定しているワークフロー
対象となる申請は「書籍購入申請」というものを想定したいと思います。ワークフローの設定については前回のブログ「ワークフローを使ってみよう!」で触れていますので、今回は設定済みのものとして省略します。ちなみにこのような承認プロセスの設定をしています。
事前確認
カスタマイズを行うために必要なForce.comの情報を確認しておきましょう。
書籍購入申請の情報が格納されるオブジェクトは次の通りです。このオブジェクトはmitocoのパッケージに含まれています。
オブジェクト名 | API参照名 |
Data | WF_Data |
オブジェクトに含まれていて、PDFに出力したい項目は次の3つです。
項目名 | API参照名 | 説明 |
件名 | Name | 書籍名を入力します。 |
備考 | Remark__c | 購入目的などを入力します。 |
金額 | Price__c | 書籍の価格を入力します。 |
また、今回のPDFを出力するために使用するmitocoワークフローオブジェクトの情報を簡単にまとめておきます。
オブジェクト名 | API参照名 | 使用目的 |
Process Instance | WF_ProcessInstance__c | 承認されるとStatus項目がApprovedになります。 |
Step Instance | WF_StepInstance__c | order by StepNumberとするとステップを処理された順にとることが出来ます。 |
Step | WF_Step__c | ステップ名(「課長承認」や「部長承認」)が格納されています。 |
Actor Instance | WF_ActorInstance__c | 承認者と承認したか否認したかが格納されています。 |
実装
まず「ドキュメント」タブにPDFの格納先となる「証跡」フォルダを作成し、そのIdを取得します。
私の場合は、Id=00l28000001dgy2 でした。これはあとのApexで使います。
次にPDFを出力するApexとVisualforceを作成します。Visualforceの1行目に書かれているrenderAs="pdf"によって、このVisualforceを開くとPDFとして描画されることになります。
Apex (名前: RenderPDFController)
public class RenderPDFController { /** ProcessInstanceのId */ private Id processInstanceId; /** * 初期化。 */ public RenderPDFController() { //パラメータからProcessInstanceのIdを取得 processInstanceId = ApexPages.CurrentPage().getParameters().get('id'); } /** * 申請データのIdを取得する。 */ public Id getTargetRecordId() { MNTTSTWF__WF_ProcessInstance__c pi = [select MNTTSTWF__ProcessTarget__r.MNTTSTWF__TargetRecordId__c from MNTTSTWF__WF_ProcessInstance__c where Id=:processInstanceId]; return pi.MNTTSTWF__ProcessTarget__r.MNTTSTWF__TargetRecordId__c; } /** * 申請データの各項目値を取得する。 */ public MNTTSTWF__WF_Data__c getData() { Id targetRecordId = getTargetRecordId(); return [select Name, MNTTSTWF__Remark__c, Price__c from MNTTSTWF__WF_Data__c where Id=: targetRecordId]; } /** * 承認ステップの情報を取得する。 */ public List<MNTTSTWF__WF_StepInstance__c> getSteps() { List<MNTTSTWF__WF_StepInstance__c> steps = [select MNTTSTWF__Step__r.Name, MNTTSTWF__Status__c, (select MNTTSTWF__Actor__r.Name, MNTTSTWF__Status__c from MNTTSTWF__ActorInstances__r) from MNTTSTWF__WF_StepInstance__c where MNTTSTWF__ProcessInstance__c=:processInstanceId order by MNTTSTWF__StepNumber__c]; return steps; } }
Visualforce (名前: RenderPDFPage)
<apex:page controller="RenderPDFController" renderAs="pdf" applyHtmlTag="false" showHeader="false"> <head> <style> body { font-family: Arial Unicode MS; } </style> </head> <h1>申請データ</h1> <ul> <li>件名: <apex:outputText value="{!data.Name}" /></li> <li>価格: <apex:outputText value="{!data.Price__c}" /></li> <li>備考: <apex:outputText value="{!data.MNTTSTWF__Remark__c}" /></li> </ul> <h1>承認ステップ</h1> <apex:repeat var="step" value="{!steps}"> <h2><apex:outputText value="{!step.MNTTSTWF__Step__r.Name}"/> (<apex:outputText value="{!step.MNTTSTWF__Status__c}"/>)</h2> <ul> <apex:repeat var="approver" value="{!step.MNTTSTWF__ActorInstances__r}" > <li> <apex:outputText value="{!approver.MNTTSTWF__Actor__r.Name}"/> (<apex:outputText value="{!approver.MNTTSTWF__Status__c}"/>) </li> </apex:repeat> </ul> </apex:repeat> </apex:page>
プロセスビルダーでの実行
ワークフローが承認済みとなったときに、上記で実装したVisualforceを開き、そのPDFをドキュメントフォルダに保存させる処理は、プロセスビルダーを使ってバックエンドで行わせます。
- プロセスには「Process InstanceオブジェクトのStatus項目がApprovedになったときにApexを呼び出す」という内容を設定しています。
- 「Visualforceを開き、そのPDFをドキュメントフォルダに保存させる」処理はプロセスから呼び出されるApexで行っています。
そのApexのコードはつぎのような感じです。
- @InvocableMethod でプロセスビルダーから呼び出せるメソッドを定義しています
- @InvocableVariable でそのメソッドにプロセスビルダーから渡す引数を定義しています
処理の流れとしては、StatusがApprovedになったProcess InstanceのIdを受け取って、それをVisualforceに引数として渡してPDFを描画しています。
Apex (名前: GeneratePDF)
public class GeneratePDF { public class GeneratePdfRequest { @InvocableVariable public Id processInstanceId; } @InvocableMethod(label='GeneratePDF') public static void generatePdf(List<GeneratePdfRequest> requests) { for(GeneratePdfRequest request: requests) { saveAsPdf(request.processInstanceId); } } @future(Callout=true) public static void saveAsPdf(String processInstanceId) { PageReference pdf = Page.RenderPDFPage; pdf.getParameters().put('id', processInstanceId); Blob content = pdf.getContent(); //取得したファイルでDocumentフォルダに書き込み Document doc = new Document(); doc.Name = DateTime.now().format(); doc.Body = content; doc.ContentType = 'application/pdf'; doc.FolderId = '00l28000001dgy2'; insert doc; } }
プロセスビルダーの設定はつぎのような感じです。
真ん中の「Apex呼び出し」のところは次のようになっていて、呼び出すApexクラスと、引数にProcess InstanceのIdを渡すという設定がされています。
わすれずに作成したプロセスを有効化して実装は完了です。
動作確認
では実際に申請をして最終承認まで行ってみます。
最終承認まで行ったあと、ドキュメントフォルダを見てみるとPDFがちゃんと格納されていました!
スタイルは全く入れていない点、あと証跡として残すのであればタイムスタンプなどもう少し情報が必要ですが、コードが長くなりすぎるので今回は割愛しました。ご了承ください。
まとめ
今回はForce.comのVisualforce、Apex、プロセスビルダーなどの機能を使って高度なカスタマイズを行いました。このようにかなり柔軟にカスタマイズできるのもmitocoの魅力の1つだと思います。
今回の記事が、mitocoを導入したエンドユーザーのシステム部門の方や、mitocoの導入支援をいただくパートナー様の参考になれば幸いです。
なお最後に1点お断りしておきたいことがあります。Apexのソースコードに含まれている名前空間はMNTTSTWFとなっていますがこれは私の試験環境の名前空間です。本番環境ではTSMNTWFですので、置き換えていただけますようお願いします。