初めまして。開発事業本部の深町です。
今回はPowerAutomateを活用した稟議申請システムを作成しました。稟議以外にも申請フローとして応用できると思うので紹介したいと思います。
また、PowerAppsでテスト環境を構築する方法なども記述しているので参考になればと思います。

1. 概要

稟議

社内で承認を得る必要がある事柄に対して関係者から承認を得ること。

背景

2023年7月以前について稟議はYammerで申請するようになってますが、承認忘れがたびたび発生しておりいつまでも承認されない稟議が多発しています。Yammerを普段利用しない上に通知を逃すとリマインドされないためこれを改善します。

2. 利用したシステム

Teams

チャットを中心に様々な機能を利用できるツール。普段利用するシステムでないとYammerと同じことになるためTeamsで利用できるものにします。

PowerApps

Microsoft社が提供しているシステムです。ローコードでアプリケーションを作成することができます。今回はTeamsに追加できるアプリを構築するために利用しました。

PowerAutomate

Microsoft社が提供しているシステムです。ローコードでシステムの自動化を行うフローを作成できます。今回は承認フローなどを自動化するために利用しました。

承認アプリ

承認申請をすると承認者として設定された人のTeamsに承認要求通知が届くアプリです。元々Teamsで利用できるアプリです。

SharePoint

Microsoft社が提供しているサービスの1つです。ファイル管理を行うことができます。稟議申請時に必要な添付ファイルのアップロード先になります。

SharePointLists

Sharepointで利用できるサービスです。SharePoint上に保存されるデータベースのようなものです。

3. 構成図

次のような構成にしました。

4. Microsoft Dataverse for Teams

PowerAppsを利用するためには2種類の方法があり、Microsoft DataverseとMicrosoft Dataverse for Teamsに分けられます。今回はMicrosoft Dataverse for Teamsを利用しました。

小規模で利用する場合はMicrosoft Dataverse for Teamsの利用を推奨します。Microsoft Dataverseでは組織ごとにプロジェクトを管理することに対して、Microsoft Dataverse for Teamsではチーム単位でプロジェクトを作ることができるので小規模で開発することに向いています。

Dataverse for Teams vs Dataverse - Power Apps | Microsoft Learn

5. ソリューション

ソリューションとは一般的なフォルダと同じようなイメージです。Microsoft Dataverse for Teamsではソリューションの中にアプリやフローを作成します。

プロジェクトに関係するアプリやフローなどを1つのまとまりとして管理できるので、パッケージ化して管理したり、環境変数を設定することで横展開したりすることができます。

参照方法

  1. TeamsにてPowerAppsを開く
  2. ビルドに移動
  3. チームを選択
  4. すべて表示

すべて表示を押した後上にあるPowerAppsで開くを押しブックマークしておくとすぐにソリューションを開くことができます。

6. 実装

  1. PowerAppsで申請画面を作成
  2. PowerAutomateで申請フローを作成
  3. 申請ボタンを押すとフローが動くように設定
  4. PowerAppsで申請一覧画面を作成
  5. PowerAutomateで削除フローを作成
  6. リマインドフローを作成

6-1. PowerAppsで申請画面を作成

6-1-1. 初期値設定

PowerAppsではアプリを起動した時や画面が表示された時に動く処理を設定できます。
ツリービューのAppのOnStartではアプリを起動した時に、画面のOnVisibleでは画面を起動した時にそれぞれ設定したプログラムが動きます。
以下はAppのOnStartに環境変数を読み込むよう設定した例です。環境変数については後述しています。

Set(TeamID, LookUp('Environment Variable Values','Environment Variable Definition'.'Schema Name' = "****_TeamID").Value);
Set(ChannelID, LookUp('Environment Variable Values','Environment Variable Definition'.'Schema Name' = "****_ChannelID").Value);
6-1-2. チーム参加者取得

承認者選択のコンボボックスにはTeamsのチームに参加している人のみ表示させています。
コンボボックスのItemsOffice365グループ.ListGroupMembers(TeamID).value を入れています。これによりチームの参加者一覧を取得できます。

6-1-3. コレクション

コンボボックスで選択したユーザーを追加ボタンでコレクションに追加します。コレクションとはプログラミングでいう配列です。配列の中にユーザー情報を追加していき、それをギャラリーで表示することでコレクションの中身を確認できます。
コレクションの中の指定した値を削除することもできます。

6-1-4. 添付ファイル

添付ファイルをSharePointにアップロードする方法は2種類あります。
1つはSubmit関数を使う方法でもう一つはPowerAutomateのフローでアップロードする方法です。
どちらを利用するにしても添付ファイルのコントローラーを出す必要があります。

  1. 挿入から編集フォームを追加
  2. Formの中にある添付ファイルのDataCardValueを切り取る
  3. てきとうなところに貼り付ける
  4. 元のFormを消す
  5. DataCardのエラーが出てるプロパティの中身を消す

6-2. PowerAutomateで申請フローを作成

まず一番最初にトリガーを消します。
このトリガーはPowerAppsからデータを取得することを想定していないものなのでトリガーを消してしまいます。
そして新たにPowerApps(V2)を設定します。

6-2-1. 申請フローの変数の初期化

プログラミング同様変数の初期化は一番最初にするようでフローの最初の部分で設定します。

6-2-2. 稟議番号の設定

今回は稟議番号を指定する必要があったのでここで設定します。
SharePointListsからデータを取得し、稟議番号が存在すれば+1して設定。なければ1を設定するような処理を行っています。
注意する点としてフィルタークエリや並べ替え順に関してリストのカラムを指定するときODataを使用する必要があります。

※ODataについて

カラムを設定するとき日本語だとODataという形に変換されるようです。

確認方法

  1. SharePointからサイトコンテンツを開く
  2. リストを選択し、設定
  3. 画面中央にあるカラムを押すとページ遷移
  4. URLの後ろの部分&Field=_x****__x****__x****=以降をOData_に加えるとフィルターとして指定できる
OData__x****__x****__x**** desc
6-2-3. 承認者配列の取得

1つの項目に複数の情報を入れたい場合JSONを利用します。承認者の数が1~5人と可変であったため、JSONを利用しました。そのためそれを解析し、配列に取得しています。

6-2-4. SharePointListsにデータを追加
  • 環境変数を利用してサイトのアドレスとリスト名を可変にしています。
  • 稟議番号は数値4桁になるようにフォーマットしています。
formatNumber(variables('稟議番号'),'0000','jp-jp')
  • 承認者は配列から1つずつデータを抽出しています。
variables('承認者')[0]['Mail']

基本的には動的なコンテンツで設定しますが、データを編集して設定したりする場合はで設定します。

6-2-5. ファイルのアップロードとアップロード先のURLの取得
  • アップロードされたファイルはJSONなので解析します。
  • Apply to eachSharePointのアクション でファイルを1つずつアップロードします。
  • アップロードしたフォルダの共有リンクを作成するためにItemIdが必要なのでフォルダーメタデータの取得ItemId を取得します。
  • ファイルまたはフォルダーの共有リンクの作成 でリンクを作成します。
6-2-6. 承認アプリの実行
  • 承認者の数を取得し承認者の数だけループするようにDo Untilで設定します。
  • 承認結果を取得した結果、承認なら次の承認者を設定しループを継続。否認ならその場でループから抜けるよう設定します。
  • 起案者に承認者のコメントを表示するため承認者のコメントを取得します。
  • 承認結果をSharePoint Listsに反映します。
※Do untilを利用するときの注意

ループの途中でエラーもしくはタイムアウトなどをした場合現在実行されているループでの処理は止まりますが、処理自体が止まるわけではなく次のループ以降は動き続けデフォルトで最大60回処理が走ります。

例えば1.承認を作成2.データの更新3.承認を待機4.承認者を次の承認者に変更するといった処理をループ内に作成します。
実行し1回目のループの3.承認を待機で処理が止まりそのままタイムアウトさせます。1回目のループの4.承認者を次の承認者に変更するは実行されませんが2回目のループの1.承認を作成は実行されます。そのまま23と進んでいき再度タイムアウトすることで4は実行されません。これが合計60回ループされます。タイムアウトのデフォルト期間は30日なので30日ごとに新たに承認要求が作成され同じ人に通知が届きます。

次に2.データの更新でエラーを起こしてみます。
更新する予定のデータを1回目のループの3で待機している間に削除することで2回目のループの2でエラーが発生し、それ以降の処理が止まります。
1回目のループは123で止まりタイムアウトします。2回目のループ以降は、2でエラーが発生することで、24は実行されずそのまま1が59回実行され続けます。これは、タイムアウトしたら59回連続で承認を作成することになり、通知が59回連続で届くといった現象が起きます。

これらを回避するためにアクションの実行条件の構成から

  • 成功したとき
  • 失敗したとき
  • スキップされたとき
  • タイムアウトされたとき

の4つを選択できるので失敗orタイムアウト時の処理を作成しておく必要があります。

タイムアウト期間はデフォルトでは30日となっていますが、アクション事に設定することができます。

Pが開始文字となっており、年月日はそれぞれY/M/D、時分秒はそれぞれH/M/Sとなっています。時間を指定する場合頭の部分にTを付ける必要があります。最大30日までとなっています。

// 1日
P1D

// 1日10時間
P1DT10H

// 5分
PT5M
6-2-7. 起案者に承認結果を通知
  • 起案者をメンションし承認結果を知らせるメッセージをTeamsチャネルに投稿します。
  • <at>を利用することでメンションできます。
<at>"メールアドレス"</at>

6-3. 申請ボタンを押すとフローが動くように設定

PowerAutomateのフローを実行

添付ファイルをSharePointにアップロードするだけならSubmitForm関数を使えばできるのですが、今回はPowerAutomateにデータを送る方法で実装しました。
PowerAppsのボタンからPowerAutomateのフローを実行するためにはボタンのOnSelectに設定してあげる必要があります。

Testという名前のフローの場合(日本語でも良い)

Test.Run();

引数(PowerAutomateにデータを送る)がある場合

Test.Run(data1, data2);

PowerAutomateの設定で任意項目がある場合

text_2, text_3の部分はPowerAutomate側で確認する必要がある

Test.Run(data1, {text_2: data2, text_3: data3});

ちなみにOnSelectなどの関数が動くプロパティでは;が使えるので複数のプログラムを記述することができます。

6-4. PowerAppsで申請一覧画面を作成

6-4-1. データテーブル

表の表示方法はデータテーブルとギャラリーの2種類あります。
データテーブルは大きなデータを処理することができ、読み込み速度が早くヘッダーを自動で作成してくれますが複数選択は不可になります。ギャラリーは読み込みに時間がかかるため表示する情報量が少ない時に利用するとと良いです。
データテーブルではカラムの表示名や幅を変更したり、表示するデータを加工したりすることで表を見やすくすることができます。

データテーブルに表示されている元のデータのレコード数は取得できますが、画面上のデータテーブルに表示されているレコードの件数を取得することはできないので注意してください。

データテーブルのItemsでデータソースを指定することでデータを表示することができます。
データソースにSortやSearch、Filterなどの加工を行うことができ、カラム名を指定する場合はODataを使用します。

Sort("データソース", "カラム名", Descending)
Search("データソース", "検索名", "カラム名")
6-4-2. 表示フォーム

データテーブルと連携させることで選択したデータの詳細を表示させることができます。
データによってコントローラーを変更することでカスタマイズが可能である。HTMLテキストコントローラーを利用することでURLからWebページに飛ぶことができるようにできます。

6-4-3. データ処理中の画面

処理を実行し、データを処理中に他の操作ができなくなるようにする処理を実装します。
図形コントローラーのVisibleプロパティを最前面に置き、truefalseで切り替えることで画面を操作することができなくなるようにできます。変数を利用することで同時に複数のコントローラーを表示することができ、Notを利用することで一つのボタンで切り替える機能を実装できます。

画面-OnVisible

Set(popup, false);

ボタン-OnSelect

Set(popup, Not(popup));

図形コントローラー-Visible

popup
6-4-4. ページング機能

ページング機能を実装するためにはデータテーブルを動的に加工する必要があります。

・総ページ数を取得

データソースのレコード数を取得し、表示件数で割った数を繰り上げしています。
以下1ページに表示するデータの数cntViewと表示します。

RoundUp(CountRows("データソース") / cntView, 0)
・現在のページ数を変数で操作

OnVisibleに変数の初期化を追加します。

Set(cnt, 0)
・表示するページのレコードを取得

FirstNとLastNを利用することでページングの機能を実装できます。
例えば全データを100件、cntViewを10とすると、

  • cntが0の場合、100件のデータの最後から100件のデータを取得後、頭から10件のデータを取得すると全データの頭から10件のデータを取得できます。
  • cntが2の場合、100件のデータの最後から80件のデータを取得後、頭から10件のデータを取得すると全データの中の21件から30件のデータを取得できます。
FirstN(LastN("データソース", CountRows("データソース") - cnt * cntView), cntView)
・ボタンでcntを増減する機能の実装

ボタンのOnSelectcntを増減させる処理を実装します。

  • ページ数増加ボタン
// OnSelect
If(cnt + 1 < CountRows("データソース") / cntView, UpdateContext({cnt: cnt + 1}))

// DisplayMode
If(cnt + 1 < CountRows("データソース") / cntView, Edit, Disabled)
  • ページ数減少ボタン
// OnSelect
If(cnt > 0, UpdateContext({cnt:cnt - 1}))

// DisplayMode
If(cnt > 0, Edit, Disabled)

6-5. PowerAutomateで削除フローを作成

削除するデータを判別するための値をPowerAppsから取得します。

6-6-1. 削除フローの変数の初期化

フロー内で使用する変数を初期化します。

6-5-2. データの取得

SharePointの複数の項目の取得アクションを利用して、PowerAppsから取得した情報をもとにデータを取得します。

6-5-3. データの削除

以下の3つを削除する機能を実装しました。

  • アップロードした添付ファイルのフォルダ削除
  • SharePoint Listsに登録したレコードデータの削除
  • 承認テーブルから承認レコードの削除

承認テーブルはDataVerse行を削除するアクションから取得できます。

6-5-4. 起案者に通知の送信

申請フローと同様削除されたことを起案者にTeamsアクションを利用し通知します。

6-6. リマインドフローを作成

リマインドフローはPowerAppsと関係ないのでPowerAutomate単体で作成できますが、環境変数や横展開などを考え同じソリューションの中で作成します。

6-6-1. スケジュールの設定

リマインドフローを起動するスケジュールをRecurrenceというアクションを利用して設定します。
頻度を1週間に設定することで、曜日の指定や時間の指定を行うことができます。

6-6-2. リマインドを行うデータを取得

複数の項目の取得を利用してリマインドを行うレコードを取得します。

6-6-3. 承認待ちになっている承認者に対して通知の送信

Teamsのチャネルに投稿アクションを利用し、承認待ちになっている承認者をメンションしてお知らせします。

7. テスト環境の作成

7-1. 環境変数

環境変数を作成し、設定することでソリューションをパッケージ化し横展開することができます。環境変数はソリューション内のPowerApps、PowerAutomate両方で利用できます。

PowerAutomateでSharePointのアクションなどを利用する際に、アドレスなどの値を変数に入力し使用するとカラムなどの設定が読み込まれませんが、環境変数を利用することで読み込まれこれに対応できます。

ソリューションで環境変数を使用する - Power Apps | Microsoft Learn

7-2. 横展開

  1. チームを作成
  2. Teamsアプリ上でソリューションを開く
  3. エクスポートしたいオブジェクトを選択
  4. エクスポートの文字が表示されるのでソリューションとしてエクスポート
  5. Zipファイルがエクスポートされることを確認
  6. 別のチームを選択し、インポート
  7. 接続で他のシステムと接続
  8. フォルダやリストの作成
  9. 環境変数を一度削除し、再度設定し直す
  10. アプリを選択し、Teamsに追加する
  11. チームに追加
  12. PowerPlatform管理センターで権限を付与
  13. アクセス許可

7-3. 権限の付与方法

アプリを実際に追加してもユーザー側に権限がないと操作できません。
PowerPlatform管理センターから権限を付与することで操作できるようになります。Teamsのチームの所有者のみ操作を行うことができます。

  1. PowerPlatform管理センターを開く
  2. 環境を押す
  3. 権限を付与する環境を選択
  4. リソース→PowerAppsを選択
  5. アプリを選択
  6. 共有
  7. 権限を付与したいユーザーを選択し付与する

権限はチーム単位でも付与することができるので特別な状況ではない限りチームごと権限を付与すると良いと思います。

8. 困ったことと解決方法

実際に開発を行った際に困ったこととそれに対する解決方法を記述しておきます。

8-1. 承認アプリのレコード削除

承認アプリでは作成した承認要求を作成することはできるのですが、編集や削除などを行えません。
では、どこから行うのかというとソリューションからMicrosoft Flow 承認コアソリューション承認テーブルからレコードの編集や削除を行うことができます。

8-2. 操作を行ったが反映されない

PowerPlatformでは操作を行った後反映されるまでに少し時間がかかるようです。

8-3. PowerPlatformで構築の整理方法

Teams→アプリ→PowerPlatformで構築を開くと作ったアプリがたくさん表示されています。
ソリューション内にあるアプリが表示されているようでアプリをこの環境から削除しますを選択すると数時間後には消えます。
また、アプリの名前についても変更後反映されるまでに時間がかかるようです。

8-4. アプリをインポート後フローを有効化することができない

環境を新たに構築し、エクスポートしたファイルをインポートした後にフローを有効化する際に有効化できないエラーメッセージが表示された場合、環境変数を一度この環境から削除しますを押して削除した後再度環境変数を登録するとエラーが消えます。

8-5. PowerAutomateで任意項目として設定している項目に値を入れていない時にエラーが出る

PowerAutomateで任意項目として設定している値に対して、PowerAppsでフローを実行する時値を入れていないと以下のエラーが出る場合があります。

The input body for trigger 'manual' of type 'Request' did not match its schema definition. Error details: 'Invalid type. Expected String but got Null.'.

こちら具体的な解決策が見つからなかったのでIf文を利用し、Nullの場合Nullではなく空白を入れるよう修正して対応しました。

8-6. 添付ファイルをSharePointにアップロードした際にファイルが壊れている

添付ファイルをPowerAppsからPowerAutomateのフローを実行し、SharePointにアップロードする処理を行った際にアップロードされたファイルが壊れていました。

PowerAutomateで実行履歴からアップロードされたファイルのログを確認すると、appres://blobmanagerという文字があると思います。こちらはPowerAppsのファイルが保存される場所となっていて、URIテキストという形式となっているそうです。
これをバイナリーデータとして読み込むためには、画像コントロールを用いてURIテキストを読み込んだ後バイナリーデータに変換する必要があるとのことでした。

まず、ギャラリーコントロールを作成し、画像コントロールにファイルのデータを設定します。

申請ボタン-OnSelect

ForAll(
    "ギャラリー".AllItems,
    Collect(
        FileCollection,
        {
            Name:Name,
            Value:Substitute(JSON("画像コントロール".Image, JSONFormat.IncludeBinaryData), """", "")
        }
    )
);
Set(jsonFiles, JSON(FileCollection, JSONFormat.IndentFour));

ギャラリー-Items

"添付ファイルコントロール".Attachments

画像コントロール-Image

ThisItem.Value

その後PowerAutomateでJsonからデータを抽出し、dataUri型をバイナリー型に変換します。

JSONの解析-スキーマ

{
    "type": "array",
    "items": {
        "type": "object",
        "properties": {
            "Value": {
                "type": "string"
            },
            "Name": {
                "type": "string"
            }
        },
        "required": [
            "Value",
            "Name"
        ]
    }
}

ファイルの作成-ファイルコンテンツ

dataUriToBinary(items('Apply_to_each')['Value'])

以上の修正を行うとファイルを正常にアップロードすることができると思います。

8-7. レスポンシブデザインの設定

アプリの設定→表示から画面の設定を行うことができます。

8-8. 通知が大量に届く

6-2-6 でも記述しましたが、PowerAutomateではループ途中にエラーが起きた際全ての処理が止まるわけではなく次のループ以降の処理は実行されます。実際に30日前に実行したフローがタイムアウトして30日後に突然大量の通知が届きました。ループ途中でエラーが起きた際の処理を記述しておくのが良いかと思います。

9. さいごに

今回は承認テーブルのレコードを削除する方法で実装しましたが、恐らく処理的には承認テーブルのレコードのカラムをCanceledにするのが良いかと思います。
また、他にも改善の余地はたくさんあるので今後PowerPlatformで何かを作成する際に役立てたいと思います。

お問い合わせはコチラ