先日開催された「第3回 Japan M365 Dev User Group 勉強会」で“Office スクリプトとPower Automateで作る見積書発行ワークフロー”と題して、Office スクリプトとPower Automateとの連携についてお話ししてきました。
下記Webサイトにある通り、Office スクリプトがGAになったばかりなのでタイミングとしては非常に良かったと思います。
今回は発表したフローとスクリプトについて、補足を付け加えて解説していきます。
フロー概要
今回ご紹介したフローの概要は下図の通りです。
- 営業担当の太郎さんが見積書(xlsxファイル)をSharePointにある「見積書」フォルダにアップ。
- 1.をトリガーとしてフローが実行され、上司である花子さんの元に承認通知送信。
- 花子さんの承認後、太郎さんがアップしたxlsxファイルをPDF形式に変換。
- メールに添付して顧客宛に送信。
といった内容で、指定したフォルダにExcelファイルをアップするだけで、自動的に担当者印や社印を押した上で、顧客宛に見積書を発行するフローとなっています。
下準備
今回のフローを作成するにあたって、いくつかの事前準備が必要となります。
- 承認者である上司の設定
- ハンコ画像の準備
- フローから実行するスクリプトの作成
- Excelテンプレートの作成
今回のフローでは「上司の取得」アクションを使用して承認者である上司の情報を取得するため、事前に設定しておく必要があります。上司の設定方法については下記サイトが参考になります。
フロー内で担当者や上司のハンコを押すため、事前に透過PNG形式で画像を用意する必要が有ります。画像は「印鑑透過」(オンライン)や「クリックスタンパー」といったサービスやツールで作成できます。 ※作成した画像の利用については、作成元の利用条件をご確認ください。
また、各画像のファイル名は、「ユーザー名.png」(ユーザー名:abc@***.onmicrosoft.com の「abc」部分)としました。
フローから実行するスクリプトは事前に作成しておく必要が有ります。
今回使用するスクリプトの内容については後述します。
見積書の元となるExcelテンプレートを作成しておきます。
フローから実行するスクリプトの中で、特定のシートやセル、テーブルなどのオブジェクトを操作する場合は、対象となるオブジェクトに事前に名前を付けておいた方が良いでしょう。
使用するフォルダの説明
今回使用するSharePointフォルダの構成は下図の通りです。
- テンプレート:見積書の元となるExcelテンプレートを保存しておくためのフォルダです。
- 印鑑画像:各メンバーや会社印のPNG画像を保存しておくためのフォルダです。
- 見積書:作成した見積書をアップするためのフォルダです。このフォルダを今回のフローのトリガーとして使用します。
- 見積書(送信済み):顧客宛にメール送信後、見積書フォルダにアップされたファイルを移動し、保存しておくためのフォルダです。
フロー説明
処理ごとにスコープで分けていますが、下図がフローの全体図になります。
トリガー
SharePointコネクタの「ファイルが作成されたとき (プロパティのみ)」をトリガーとし、「見積書」フォルダを対象としています。また、「見積書番号」を変数とするため、変数初期化も行っています。
ファイルの拡張子判定
「条件」アクションでアップされたファイルの拡張子の判定を行い、xlsxファイルのみを対象としています。判定式は下記の通りです。
1 | last(split(triggerOutputs()?['body/{FilenameWithExtension}'], '.')) |
上司の取得
「上司の取得」アクションでファイルをアップした担当者の上司を取得します。
上司が設定されていない場合を考慮して、取得に失敗した場合はその時点で処理を終了します。
見積書番号・発行日取得
ファイルをアップした日時を見積書番号として使用するため、「タイムゾーンの変換」アクションで日本時間に変換しています。
ここは下記のようにconvertFromUtc関数を使っても良いでしょう。
1 | @{convertFromUtc(utcNow(), 'Tokyo Standard Time', 'yyyyMMddHHmmss')} |
担当者押印
「見積書発行_担当者押印」スクリプトによって、所定位置に担当者印を押す処理を行います。
「パスによるファイル コンテンツの取得」アクションで印鑑画像を取得し、Base64エンコードしたコンテンツをスクリプトの引数として渡します。
1 | /Shared Documents/印鑑画像/@{first(split(triggerOutputs()?['body/Author/Email'], '@'))}.png |
1 | @{base64(outputs('パスによるファイル_コンテンツの取得:担当者印')?['body'])} |
スクリプト:見積書発行_担当者押印
「見積書発行_担当者押印」スクリプトの内容は下記になります。
main関数の追加パラメーターとして「発行日」、「見積書番号」、「担当者印」を指定し、それぞれのセルの値としています。担当者印の画像は、(Worksheet).addImageメソッドによってシートに挿入しています。
function main( | |
workbook: ExcelScript.Workbook, | |
発行日: string, | |
見積書番号: string, | |
担当者印: string | |
) { | |
const sheet = workbook.getWorksheet("見積書"); | |
const rngStaffStamp = sheet.getRange("担当者印"); | |
//必要事項入力 | |
sheet.getRange("発行日").setValue(発行日); | |
sheet.getRange("見積書番号").setValue(見積書番号); | |
//担当者押印 | |
const imgStaffStamp = sheet.addImage(担当者印); | |
imgStaffStamp.setName("担当者印"); | |
imgStaffStamp.setLockAspectRatio(true); | |
//サイズや位置は適当に調整 | |
imgStaffStamp.setHeight(28); | |
imgStaffStamp.setWidth(28); | |
imgStaffStamp.setTop(rngStaffStamp.getTop() + 10); | |
imgStaffStamp.setLeft(rngStaffStamp.getLeft() + 25); | |
} |
見積情報取得
「見積書発行_見積情報取得」スクリプトによって、アップされたExcelファイルから顧客名や件名等の必要な情報を取得します。
スクリプト:見積書発行_見積情報取得
(Range).getValueメソッドで取得したセルの値をフローに返します。interfaceとして定義した値を返すことで、フロー上で値を利用しやすくなります。
function main(workbook: ExcelScript.Workbook): QuotInfo | |
{ | |
const sheet = workbook.getWorksheet("見積書"); | |
return { | |
顧客名: sheet.getRange("顧客名").getValue().toString(), | |
顧客担当者: sheet.getRange("顧客担当者").getValue().toString(), | |
顧客メールアドレス: sheet.getRange("顧客メールアドレス").getValue().toString(), | |
件名: sheet.getRange("件名").getValue().toString() | |
} | |
} | |
//見積情報 | |
interface QuotInfo { | |
顧客名: string; | |
顧客担当者: string; | |
顧客メールアドレス: string; | |
件名: string; | |
} |
上司承認
「開始して承認を待機」アクションで上司に承認を求め、承認された場合のみ以降の処理を続行します。却下された場合に、アップした担当者に通知を行う等の処理を入れても良いかもしれません。
1 | 見積承認:@{outputs('スクリプトの実行:見積情報取得')?['body/result/件名']} |
1 2 3 4 5 6 | @{outputs('上司の取得_(V2)')?['body/displayName']}さん お疲れ様です。 発行する見積書のご確認をよろしくお願いいたします。 担当:@{triggerOutputs()?['body/Author/DisplayName']} |
上司押印
「見積書発行_上司押印」スクリプトによって、所定位置に上司印と会社印を押す処理を行います。処理内容は「担当者押印」と同様です。
1 | /Shared Documents/印鑑画像/@{first(split(outputs('上司の取得_(V2)')?['body/mail'], '@'))}.png |
1 | @{base64(outputs('パスによるファイル_コンテンツの取得:上司印')?['body'])} |
1 | @{base64(outputs('パスによるファイル_コンテンツの取得:会社印')?['body'])} |
スクリプト:見積書発行_上司押印
function main( | |
workbook: ExcelScript.Workbook, | |
上司印: string, | |
会社印: string, | |
) { | |
const sheet = workbook.getWorksheet("見積書"); | |
const rngOfficerStamp = sheet.getRange("上司印"); | |
const rngCorporateStamp = sheet.getRange("会社印"); | |
//上司押印 | |
const imgOfficerStamp = sheet.addImage(上司印); | |
imgOfficerStamp.setName("上司印"); | |
imgOfficerStamp.setLockAspectRatio(true); | |
//サイズや位置は適当に調整 | |
imgOfficerStamp.setHeight(28); | |
imgOfficerStamp.setWidth(28); | |
imgOfficerStamp.setTop(rngOfficerStamp.getTop() + 10); | |
imgOfficerStamp.setLeft(rngOfficerStamp.getLeft() + 25); | |
//会社印 | |
const imgCorporateStamp = sheet.addImage(会社印); | |
imgCorporateStamp.setName("会社印"); | |
imgCorporateStamp.setLockAspectRatio(true); | |
//サイズや位置は適当に調整 | |
imgCorporateStamp.setHeight(48); | |
imgCorporateStamp.setWidth(48); | |
imgCorporateStamp.setTop(rngCorporateStamp.getTop() - 50); | |
imgCorporateStamp.setLeft(rngCorporateStamp.getLeft()); | |
} |
PDF変換
ExcelファイルをPDF形式に変換する処理ですが、今回はOneDrive for Businessコネクタの「ファイルの変換」アクションを使用するため、一度OneDriveにファイルをコピーしています。
プレミアムコネクタが使用できる環境であれば、Adobe PDF Toolsコネクタを使用しても良いでしょう。
顧客宛にメール送信
「メールの送信」アクションを使って、顧客宛にメール送信します。
宛先や件名等の必要事項は「見積情報取得」で取得した値を使用します。
1 | @{outputs('スクリプトの実行:見積情報取得')?['body/result/顧客メールアドレス']} |
1 | お見積り:@{outputs('スクリプトの実行:見積情報取得')?['body/result/件名']} |
1 2 3 4 5 6 7 8 | @{outputs('スクリプトの実行:見積情報取得')?['body/result/顧客名']} @{outputs('スクリプトの実行:見積情報取得')?['body/result/顧客担当者']}様 いつも大変お世話になっております。 株式会社ほげほげの@{triggerOutputs()?['body/Author/DisplayName']}です。 このたびは「@{outputs('スクリプトの実行:見積情報取得')?['body/result/件名']}」についてお問合せいただき、誠にありがとうございました。 ……… |
送信済みファイル移動
最後に送信が終わったファイルを送信済みフォルダに移動します。
「ファイルの作成」アクションでファイルのコピーを作成した後、元のファイルを削除するわけですが、このとき「ファイルの削除」アクションでは“共有するためにロックしています”エラーが発生したため、「SharePoint に HTTP 要求を送信します」アクションでREST APIを実行してファイルを削除しています。
この部分の処理については下記サイトを参考にしました。
1 | @{outputs('ファイル_コンテンツの取得:押印済みファイル')?['body']} |
1 | _api/Web/GetFileByServerRelativePath(decodedurl='/sites/DevTeam/@{encodeUriComponent(triggerOutputs()?['body/{FullPath}'])}')/recycle |
以上でフローの説明は終了です。
スライド資料
上記の通り、Office スクリプトとPower Automate連携によって見積書の自動発行ができました。
(もちろん、実際に上記フローを運用する場合はエラー処理等をもっと作り込んだ方が良いと思いますが)
今回の記事のようにOffice スクリプトは、短いコードでも十分に業務効率化に威力を発揮する可能性を秘めています。アイデア次第で活用の幅は広がりますので、興味のある方は是非、Power Automateとの連携を試してみてください!
この記事へのコメントはありません。