Power Apps・Power Automate・Teamsによる承認者数を自在に設定できるワークフローを作成する

:::note info
 - 2024.07.04　Power Appsの関数微修正
 - 2024.07.04　Power AppsのYAML大公開
 - [GPT-4oによる解析レポ](https://qiita.com/DEmodoriGatsuO/items/2588a77929fc054eff5d)追加
:::

https://qiita.com/DEmodoriGatsuO/items/2588a77929fc054eff5d

# はじめに
[Power Automateには承認機能](https://learn.microsoft.com/ja-jp/power-automate/get-started-approvals)が存在します。

`Teams`と`Outlook`で申請に対する承認・否認、コメントの付与ができ、簡単なワークフローの構築が実現できます。

https://learn.microsoft.com/ja-jp/power-automate/get-started-approvals

`Udemy`や`Kindle`、`Web`上にも承認ワークフローの例が豊富に出てくるので、実装までの道のりはそう遠くないです。

**■ Udemyおすすめ**

https://www.udemy.com/share/107o84/

https://www.udemy.com/share/107P1O/

**■ Kindleおすすめ**
 - [Power Automateではじめる業務の完全自動化](https://amzn.to/3vnmUKw)

[Microsoft learnにも例がある](https://learn.microsoft.com/ja-jp/power-automate/modern-approvals)ので、ステップバイステップで本格的なワークフローの構築も目指せます。

https://learn.microsoft.com/ja-jp/power-automate/modern-approvals

https://learn.microsoft.com/ja-jp/power-automate/approval-attachments

https://learn.microsoft.com/ja-jp/power-automate/sequential-modern-approvals

https://learn.microsoft.com/ja-jp/power-automate/parallel-modern-approvals

https://learn.microsoft.com/ja-jp/power-automate/all-assigned-must-approve

https://learn.microsoft.com/ja-jp/power-automate/group-approvals


## 動的な承認段階への対応
ワークフローを作成する中で、 **承認段階が固定的ではないと対応が難しい**、この点に課題感を感じていました。

多段階への対応を実装するにも、非常にPower Automateフローが冗長になる傾向を感じています。

実際の業務を想定すると、ワークフローを要するシーンは多く、承認の段階・対象者も、かなり動的な属性を持ちます。

そこで今回は、`Power Apps`・`Power Automate`による承認ワークフローを作成する中で、動的な承認段階に対応するワークフローの作成にトライしてみました。

# 構造
今回は単一のファイルを添付して、指定の対象者に承認を要求するワークフローの実現を目指しました。

1. Power Appsで`タイトル`、`詳細`と`ファイルを添付`、`ギャラリー コントロール`を利用し、承認を要求する対象者を指定する画面を作成
    - ギャラリー コントロールで承認を要求する人を必要な人数分、自由に設定できるようにする
1. Power Appsで承認を要求する人達を[JSON関数](https://learn.microsoft.com/ja-jp/power-platform/power-fx/reference/function-json)で、オブジェクトを格納した配列にし、`Power Automate`に渡す
1. Power Automateで`JSONの解析`アクションを用いて、文字列を配列として評価しなおす
1. `Do until`と同じ要領で`For each`を実現する
    1. 繰り返し処理を継続し続けることを判定するためのBoolean変数を用意する
    1. `Boolean`が`True`の間は承認アクションを繰り返す、一度でも`false`になった場合は、以後は処理を実行しない
    1. 承認履歴は`項目を作成する`アクションで`SharePoint Lists`に蓄積する

https://learn.microsoft.com/ja-jp/power-platform/power-fx/reference/function-json

:::note warn
今回作成する仕組みの中で、**承認を要求する人数が複数人にわたる要件には沿っていません。**
エラー対応も含めていないため、参考程度に見ていただければ幸いです。
:::

# SharePoint Lists
`SharePoint Lists`は二つ設けました。

 - 申請別の結果・詳細・添付ファイルを取りまとめるリスト
 - 承認結果を承認担当者別に全て記録するリスト

**■ 申請別の結果・詳細・添付ファイルを取りまとめるリスト**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/512c2fd1-fd85-8d7f-9f74-d756e083e59b.png)


こちらは主に`Power Apps`の[SubmitForm関数](https://learn.microsoft.com/ja-jp/power-apps/maker/canvas-apps/working-with-forms)で申請を記録し、`Power Automate`で承認結果に応じて、列を更新していくリストになります。

|列|種類|備考
|---|---|---
|ワークフローID|1 行テキスト|申請用のユニーク値
|タイトル|1 行テキスト|申請タイトルとして使用
|詳細|複数行テキスト|申請の詳細用に使用
|申請者|ユーザーまたはグループ|申請者を登録 - 登録者列とイコール
|申請日時|日付と時刻| - 登録日時列とイコール
|現担当者|ユーザーまたはグループ|承認要求を判断している現担当者
|承認担当者|ユーザーまたはグループ|承認要求を依頼される予定の人全て
|ステータス|選択肢|`対応中`・`終了`
|結果|選択肢|`未対応`・`承認`・`否認`・`対応待ち`

**■ 承認結果を承認担当者別に全て記録するリスト**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/b1e84f84-9dcf-b232-6108-b261f6dd79f5.png)

承認結果を判断した履歴を蓄積するリストです。
前述のリストと`ワークフローID`で同じ値を保ちます。

|列|種類|備考
|---|---|---
|ステップ|数値|何番目の承認段階か数値で表現
|タイトル|1 行テキスト|申請タイトルとして同じ値
|ワークフローID|1 行テキスト|申請用のユニーク値
|コメント|複数行テキスト|承認者のコメントの記録
|結果|選択肢|承認担当者の結果を記録

:::note warn
`一次承認`、`最終承認`のような書き方は一旦無視しています。
どこかで置き換えをするなりで、実運用の場合は対応が必要です。
:::

# Power Apps - 承認の要求画面
**承認を要求する画面のみ**作成しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/75a5daa1-57f7-472e-de4b-17b3198acf79.png)

左側は`フォーム`、右側は`ギャラリー`です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/e5dc2186-1838-eb6a-ba62-2feaf04a4269.png)

## コントロール
■ Workflow（画面の名前）

 - ScreenContainer
     - HeaderContainer - ヘッダー
         - - Header - ヘッダーコントロール
     - BottomContainer - ボディ
         - SidebarContainer
             - formTitle
             - Form - フォーム
         - MainContainer
             - rootTitle
             - galRoot - 承認要求を出す対象者　`Items: colStep`
                     - icoPlus - 承認要求を出す人を増やすためのアイコン
                         - `Image: If(!IsBlank(cmbPerson.Selected),Office365ユーザー.UserPhotoV2(cmbPerson.Selected.Email),SampleImage)`
                     - badgeIndex - 何段階目の承認か示すバッジ
                     - cmbPerson - 承認担当の選択
                     - imgPerson - 承認担当の顔写真
                     - shpBorder - 罫線
             - conFooter - フッター
                 - btnSubmit - 申請実行ボタン

承認要求を設定する`ギャラリー コントロール`には、数値のみのコレクションを設定しています。
初期化は画面の`OnVisible`で実行

```plaintext:OnVisible
ClearCollect(colStep,{Step:1});
```

### フォーム
~~画像のとおりですが、非表示で一意の値を制御する`ワークフローID`用のカードコントロールを配置しています。
`Default`に下記の関数を設定します。~~

<details><summary>以前</summary>
```plaintext:Default
"WF-" & Text(Last(フォームのデータソース).ID + 1,"0000")
```
</details>

~~SharePoint Listsの内部の`ID`列を使ってユニーク値を保ちます。`Update`は`Self.Default`。~~

:::note info
上記をやめ、SubmitForm関数を実行時に工夫する方法へ変えました
:::


## SubmitForm関数 + Power Automate実行

:::note info
**アップデート！**
`Form.LastSubmit.ID`の活用を採用しています！
:::

>LastSubmit – 任意のサーバーが生成したフィールドを含む、最後に正常に送信されたレコード。
>>このプロパティは 編集フォーム コントロールのみに適用されます。
一意の番号を持つ ID フィールドなどの、データ ソースが任意のフィールドを自動的に生成または計算する場合、SubmitForm が正常に実行された後、LastSubmit プロパティにはこの新しい値があるようになります。
このプロパティの値は OnSuccess 式で利用できます。

https://learn.microsoft.com/ja-jp/power-apps/maker/canvas-apps/controls/control-form-detail

`btnSubmit`の`OnSelect`プロパティに下記の関数を設定します。

```plaintext:OnSelect
// 1. フォームの値を先に取得
UpdateContext(
    {
        Title: valueTitle.Value,
        Details: valueDetails.Value
    }
);

// 2. ギャラリーコントロールの値を申請用のコレクションに格納
ClearCollect(
    colWorkflow,
    ForAll(
        galRoot.AllItems,
        {
            Step: Value(badgeIndex.Content),
            Claims: cmbPerson.Selected.Email,
            Name: cmbPerson.Selected.DisplayName
        }
    );
    
);
// 3. フォームをSubmit
SubmitForm(Form);
// 4. Power Automateの実行
DynamicWorkflow.Run(
    JSON(colWorkflow),
    Title,
    Details,
    Form.LastSubmit.ID,
    "WF-" & Text(
        Form.LastSubmit.ID,
        "0000"
    )
);

// 5. 通知
Notify(
    "承認ワークフローを実行しました。",
    NotificationType.Information
);

// 6. リセット
NewForm(Form);
ClearCollect(
    colStep,
    {Step: 1}
);

```

[JSON関数](https://learn.microsoft.com/ja-jp/power-platform/power-fx/reference/function-json)によってテーブルをJSONに変換し、配列をPower Automateに（文字列で）渡すことができます。

■ Notify
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/3f5cddc9-a5bd-eb31-1e3d-da6900486073.png)

複数回の実験結果ですが、
1. SubmitForm関数
1. Power Automate実行

~~上記で処理の順序は保たれるので、`ワークフローID`を使い、[SubmitForm関数]()で登録したSharePoint Listsの項目を後程取得します。~~

:::note info
`Form.LastSubmit.ID`を活用し、SubmitForm関数で追加された値を正確に取得するための工夫を追加しました。
**Twitterのアドバイスに感謝！**
:::

## 承認画面イメージ
余談になりますが、承認画面は
 - Teams
 - Outlook
 - Power Automate（モバイル）

といった複数の画面で承認が実行されます。
結果も同期されることが素晴らしいです。

:::note info
Teamsで承認した後、Outlookに通知された承認の画面
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/e1c0b7cc-f93d-af17-1d66-64b157caf1b6.png)
:::

**■ Outlook**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/b5c12726-15c2-1346-88a3-240bba1488e3.png)

:::note info
添付ファイルがダントツで見やすいです。通知が若干遅い。
:::

**■ Teams**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/438a7991-ae51-cec1-27ce-280ef607e95b.png)

:::note info
気軽に承認ができます。通知がえげつないです。`Markdown`を使って、レイアウトをリッチにできます。

■ テーブルをMarkdownで挿入した例
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/1dee1adc-e67f-c027-5f0d-a6b761aedc9b.png)
:::

https://learn.microsoft.com/ja-jp/power-automate/approvals-markdown-support

**■ Power Automateモバイル**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/a16be061-3724-1283-a2ef-43bdefa0237b.png)

:::note info
~~めくら判に最適です~~
:::

# Power Automate
## 1. 変数の初期化など
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/3601f1b6-2538-f1f2-1e4a-28c1f1d2b949.png)

:::note info
Power Appsから受け取る値を増やしました
2024.03.24
:::

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/7cc3c8e1-13ee-323c-d916-3a1ea950423a.png)

 - `bool`は後ほど登場する`For each`用の処理判定

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/a3d49cc4-266c-3cab-7d0e-2735f03e3a87.png)

 - 添付ファイルをまとめて追加できるように空配列を準備

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/23c1c60f-1344-d608-f5a9-7ca3121f80ff.png)

 - **`JSON の解析`は、文字列として受け取ったJSONを再評価します。これにより配列としてPower Appsから受け取った値を取得できます**

```json:スキーマ
{
    "type": "array",
    "items": {
        "type": "object",
        "properties": {
            "Claims": {
                "type": "string"
            },
            "Name": {
                "type": "string"
            },
            "Step": {
                "type": "integer"
            }
        },
        "required": [
            "Claims",
            "Name",
            "Step"
        ]
    }
}
```

:::note info
手軽に配列を取得できる手段として重宝します。
:::

 - `選択`は`項目の更新`アクションで複数人の選択した承認担当者を設定するために実行しているアクションです

## 2. SubmitForm関数で登録された値の取得、進捗状況の更新

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/aeaffb17-c2f5-4a6a-ef64-b03e1be633fc.png)

 - Teamsで表示する承認の履歴を確認するためのMarkdownを設定します

```plaintext:値
## 詳細
@{triggerBody()['text_2']}

## 承認状態

|承認No.|担当者|結果|コメント
|---|---|---|---

```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/9ddd5171-7b74-61ab-3356-2c8ba2de8aec.png)

 - 項目の更新で`ステータスの更新`と`承認担当者の更新`をします
     - 申請者、申請日時は登録日時を書き写しているたいして意味のない行為です
 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/1c5efabd-2fe3-45fd-cde7-7c346abdedb7.png)

 - 添付ファイル コンテンツを取得し、承認の添付用に値を取得します
 - `Apply to each`で配列変数に添付ファイルを格納します
     - これにより複数ファイルの添付に対応できます

```plaintext:配列変数に追加
{
  "name": @{items('Apply_to_each')?['DisplayName']},
  "content": @{body('添付ファイルのコンテンツの取得')}
}
```


## 3. 【ここから繰り返し処理】
 - `bool`が`true`の間だけ繰り返します。`はいの場合`のみに処理が格納されます
 - `項目の更新`で`承認担当者`と`ステータス`を現在の値に設定します

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/1a8251b3-6506-9ae9-dbd1-db1ae5cbcee6.png)

## 4. 承認ログの記録、承認を待機

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/fafd4643-fcd4-641d-d774-baeb6803a512.png)
 
 - `■ 承認結果を承認担当者別に全て記録するリスト`に担当者と承認段階の数値を設定します

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/09d96126-1b5a-44e7-bd15-e6e5f4039c24.png)

 - 承認を作成して承認に移ります

:::note info
下記に対応しました✨
> `選択`で上のアレイを作成して、設定したほうが早そうですね👆
:::

## 5. 承認結果の反映
 - 担当者別に結果をログに反映します
 - **`否認`の場合、`bool`を`false`に設定し、繰り返し処理から実質的に抜け出します**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/d01ecba7-9b26-3843-8371-151608119ced.png)

:::note info
`For each`で繰り返しを行っていることから、無限ループに陥らないところがミソです
:::

```plaintext:文字列変数に追加 - Markdown文字列の追加
|@{items('For_each')['Step']}|@{items('For_each')['Name']}|@{body('開始して承認を待機')?['outcome']}|@{body('開始して承認を待機')['responses'][0]?['comments']}

```

:::note warn
回答者が単一である場合を想定した設定です。複数人に求める場合、コメントの値を結合するなど処理が別途必要になります。
:::

## 6. 最終結果の反映
`■ 申請別の結果・詳細・添付ファイルを取りまとめるリスト`に結果を記録します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/307e4002-6df3-c9f8-ff54-18bb4e2b939e.png)

# おわりに
ワークフローは奥が深い領域です。
しかしながら`Microsoft 365`のライセンスの範囲内で、**このような機能が作れることは本当に魅力的です！**

**本当にPower Apps/Power Automate Love！**

制限事項には目を通してご利用ください！

https://learn.microsoft.com/ja-jp/power-automate/approvals-known-issues


```yaml:Power Appsの画面
- Workflow:
    Control: Screen
    Variant: autoLayout_Sidebar_ver1.0
    Properties:
      OnVisible: |-
        =ClearCollect(colStep,
            {
                Step:1
            }
        );
    Children:
    - ScreenContainer:
        Control: GroupContainer
        Variant: verticalAutoLayoutContainer
        Properties:
          Fill: =RGBA(245, 245, 245, 1)
          Height: =Parent.Height
          LayoutAlignItems: =LayoutAlignItems.Stretch
          LayoutDirection: =LayoutDirection.Vertical
          LayoutGap: =16
          LayoutMode: =LayoutMode.Auto
          PaddingBottom: =16
          PaddingLeft: =16
          PaddingRight: =16
          PaddingTop: =16
          Width: =Parent.Width
        Children:
        - HeaderContainer:
            Control: GroupContainer
            Variant: horizontalAutoLayoutContainer
            Properties:
              AlignInContainer: =AlignInContainer.SetByContainer
              Fill: =RGBA(255, 255, 255, 1)
              FillPortions: =0
              Height: =75
              LayoutMode: =LayoutMode.Auto
            Children:
            - Header:
                Control: Header
                Properties:
                  Logo: =stamp
        - BottomContainer:
            Control: GroupContainer
            Variant: horizontalAutoLayoutContainer
            Properties:
              AlignInContainer: =AlignInContainer.SetByContainer
              Fill: =RGBA(245, 245, 245, 1)
              LayoutAlignItems: =LayoutAlignItems.Stretch
              LayoutGap: =16
              LayoutMode: =LayoutMode.Auto
              LayoutWrap: =true
              PaddingBottom: =2
              PaddingLeft: =2
              PaddingRight: =2
              PaddingTop: =2
            Children:
            - SidebarContainer:
                Control: GroupContainer
                Variant: verticalAutoLayoutContainer
                Properties:
                  AlignInContainer: =AlignInContainer.SetByContainer
                  Fill: =RGBA(255, 255, 255, 1)
                  FillPortions: =5
                  LayoutDirection: =LayoutDirection.Vertical
                  LayoutMode: =LayoutMode.Auto
                  PaddingLeft: =10
                  PaddingRight: =5
                  PaddingTop: =5
                Children:
                - formTitle:
                    Control: Text
                    Properties:
                      Size: =20
                      Text: ="■ 承認ワークフロー"
                      VerticalAlign: ='TextCanvas.VerticalAlign'.Middle
                      Weight: ='TextCanvas.Weight'.Semibold
                      AlignInContainer: =AlignInContainer.Stretch
                      Height: =52
                - Form:
                    Control: Form
                    Variant: PowerApps_CoreControls_Form
                    Layout: vertical
                    Properties:
                      DataSource: =DynamicApprovalWorkflow
                      DefaultMode: =FormMode.New
                    Children:
                    - crdTitle:
                        Control: TypedDataCard
                        Variant: fluentV9TextualEditCard
                        Properties:
                          DataField: ="Title"
                          Default: =ThisItem.タイトル
                          DisplayName: =DataSourceInfo([@DynamicApprovalWorkflow],DataSourceInfo.DisplayName,タイトル)
                          MaxLength: =DataSourceInfo([@DynamicApprovalWorkflow], DataSourceInfo.MaxLength, タイトル)
                          Update: =valueTitle.Value
                          DisplayMode: =Parent.DisplayMode
                          Height: =92
                          Width: =Parent.Width
                        Children:
                        - StarVisible1:
                            Control: Text
                            Properties:
                              Align: ='TextCanvas.Align'.Center
                              Text: ="*"
                              Height: =30
                              Width: =30
                              Y: =keyTitle.Y
                        - ErrorMessage1:
                            Control: Text
                            Properties:
                              Text: =Parent.Error
                              Height: =30
                              Visible: =And(!IsBlank(Parent.Error), Parent.DisplayMode=DisplayMode.Edit)
                              Width: =Parent.Width - 48
                              X: =24
                              Y: =valueTitle.Y + valueTitle.Height
                        - valueTitle:
                            Control: TextInput
                            Properties:
                              AccessibleLabel: =Parent.DisplayName
                              Mode: ="'TextInputCanvas.Mode'.TextInputModeSingleLine"
                              Required: =Parent.Required
                              ValidationState: =If(IsBlank(Parent.Error), "None", "Error")
                              Value: =
                              DisplayMode: =Parent.DisplayMode
                              Width: =Parent.Width - 48
                              X: =24
                              Y: =keyTitle.Y + keyTitle.Height + 4
                        - keyTitle:
                            Control: Text
                            Properties:
                              Size: =16
                              Text: =Parent.DisplayName
                              Weight: ='TextCanvas.Weight'.Semibold
                              Wrap: =false
                              Height: =22
                              Width: =Parent.Width - 48
                              X: =24
                              Y: =10
                    - crdDetail:
                        Control: TypedDataCard
                        Variant: fluentV9TextualEditCard
                        Properties:
                          DataField: ="Details"
                          Default: =ThisItem.詳細
                          DisplayName: =DataSourceInfo([@DynamicApprovalWorkflow],DataSourceInfo.DisplayName,詳細)
                          MaxLength: =DataSourceInfo([@DynamicApprovalWorkflow], DataSourceInfo.MaxLength, 詳細)
                          Update: =valueDetails.Value
                          DisplayMode: =Parent.DisplayMode
                          Width: =Parent.Width
                          Y: =1
                        Children:
                        - StarVisible2:
                            Control: Text
                            Properties:
                              Align: ='TextCanvas.Align'.Center
                              Text: ="*"
                              Height: =30
                              Width: =30
                              Y: =keyDetails.Y
                        - ErrorMessage2:
                            Control: Text
                            Properties:
                              Text: =Parent.Error
                              Height: =30
                              Visible: =And(!IsBlank(Parent.Error), Parent.DisplayMode=DisplayMode.Edit)
                              Width: =Parent.Width - 48
                              X: =24
                              Y: =valueDetails.Y + valueDetails.Height
                        - valueDetails:
                            Control: TextInput
                            Properties:
                              AccessibleLabel: =Parent.DisplayName
                              Mode: ='TextInputCanvas.Mode'.Multiline
                              Required: =Parent.Required
                              ValidationState: =If(IsBlank(Parent.Error), "None", "Error")
                              Value: =
                              DisplayMode: =Parent.DisplayMode
                              Height: =120
                              Width: =Parent.Width - 48
                              X: =24
                              Y: =keyDetails.Y + keyDetails.Height + 4
                        - keyDetails:
                            Control: Text
                            Properties:
                              Size: =16
                              Text: =Parent.DisplayName
                              Weight: ='TextCanvas.Weight'.Semibold
                              Wrap: =false
                              Height: =22
                              Width: =Parent.Width - 48
                              X: =24
                              Y: =10
                    - crdWFid:
                        Control: TypedDataCard
                        Variant: fluentV9TextualEditCard
                        Properties:
                          DataField: ="WorkflowId"
                          Default: ="WF-" & Text(Last(DynamicApprovalWorkflow).ID + 1,"0000")
                          DisplayName: =DataSourceInfo([@DynamicApprovalWorkflow],DataSourceInfo.DisplayName,ワークフローID)
                          MaxLength: =DataSourceInfo([@DynamicApprovalWorkflow], DataSourceInfo.MaxLength, ワークフローID)
                          Required: =true
                          Update: =Self.Default
                          DisplayMode: =Parent.DisplayMode
                          Height: =70
                          Visible: =false
                          Y: =2
                    - crdAttachment:
                        Control: TypedDataCard
                        Variant: attachmentsEditCard
                        Properties:
                          DataField: ="{Attachments}"
                          Default: =ThisItem.添付ファイル
                          DisplayName: =DataSourceInfo([@DynamicApprovalWorkflow],DataSourceInfo.DisplayName,添付ファイル)
                          Update: =valueAttachment.Attachments
                          DisplayMode: =Parent.DisplayMode
                          Height: =260
                          Width: =Parent.Width
                          Y: =3
                        Children:
                        - StarVisible3:
                            Control: Label
                            Properties:
                              Text: ="*"
                              Align: =Align.Center
                              Height: =keyAttachment.Height
                              Width: =30
                              Wrap: =false
                              Y: =keyAttachment.Y
                        - ErrorMessage3:
                            Control: Label
                            Properties:
                              Live: =Live.Assertive
                              Text: =Parent.Error
                              AutoHeight: =true
                              Height: =10
                              PaddingBottom: =0
                              PaddingLeft: =0
                              PaddingRight: =0
                              PaddingTop: =0
                              Visible: =Parent.DisplayMode=DisplayMode.Edit
                              Width: =Parent.Width - 60
                              X: =30
                              Y: =valueAttachment.Y + valueAttachment.Height
                        - valueAttachment:
                            Control: Attachments
                            Properties:
                              Tooltip: =Parent.DisplayName
                              BorderColor: =If(IsBlank(Parent.Error), Parent.BorderColor, Color.Red)
                              BorderThickness: =0.5
                              DisplayMode: =Parent.DisplayMode
                              Font: =Font.'Segoe UI'
                              Height: =180
                              PaddingBottom: =5
                              PaddingLeft: =If(Self.DisplayMode = DisplayMode.Edit, 5, 0)
                              PaddingRight: =5
                              PaddingTop: =5
                              Width: =Parent.Width - 60
                              X: =30
                              Y: =keyAttachment.Y + keyAttachment.Height + 5
                        - keyAttachment:
                            Control: Label
                            Properties:
                              Text: =Parent.DisplayName
                              AutoHeight: =true
                              Color: =RGBA(0, 0, 0, 1)
                              Font: =Font.'Segoe UI'
                              FontWeight: =FontWeight.Bold
                              Height: =34
                              Size: =12
                              Width: =Parent.Width - 60
                              Wrap: =false
                              X: =30
                              Y: =10
                    - crdDateTime:
                        Control: TypedDataCard
                        Variant: fluentV9DateTimeEditCard
                        Properties:
                          DataField: ="DateTime"
                          Default: =Now()
                          DisplayName: =DataSourceInfo([@DynamicApprovalWorkflow],DataSourceInfo.DisplayName,申請日時)
                          Update: =Now()
                          DisplayMode: =Parent.DisplayMode
                          Height: =70
                          Visible: =false
                          Y: =4
            - MainContainer2:
                Control: GroupContainer
                Variant: verticalAutoLayoutContainer
                Properties:
                  AlignInContainer: =AlignInContainer.SetByContainer
                  Fill: =RGBA(255, 255, 255, 1)
                  FillPortions: =5
                  LayoutDirection: =LayoutDirection.Vertical
                  LayoutMode: =LayoutMode.Auto
                  PaddingBottom: =5
                  PaddingLeft: =5
                  PaddingRight: =5
                  PaddingTop: =5
                Children:
                - galRoot:
                    Control: Gallery
                    Variant: BrowseLayout_Vertical_TwoTextOneImageVariant_ver5.0
                    Properties:
                      Items: =colStep
                      DelayItemLoading: =true
                      Layout: =Layout.Vertical
                      LoadingSpinner: =LoadingSpinner.Data
                      TemplatePadding: =0
                      TemplateSize: =80
                    Children:
                    - icoPlus:
                        Control: Classic/Icon
                        Variant: Add
                        Properties:
                          OnSelect: |-
                            =UpdateContext({i:Last(colStep).Step + 1});
                            Collect(colStep,{Step:i});
                          Color: =RGBA(122, 138, 143, 1)
                          Height: =44
                          Icon: =Icon.Add
                          Width: =44
                          X: =581
                          Y: =21
                    - badgeIndex:
                        Control: Badge
                        Properties:
                          Content: =ThisItem.Step
                          Font: =Font.'Segoe UI'
                          FontSize: =20
                          Height: =36
                          Width: =36
                          X: =12
                          Y: =12
                    - cmbPerson:
                        Control: Classic/ComboBox
                        Properties:
                          OnSelect: =Select(Parent)
                          DisplayFields: =["DisplayName","Email","Picture"]
                          Items: =Choices([@DynamicApprovalWorkflow].申請者)
                          SearchFields: =["DisplayName"]
                          SelectMultiple: =false
                          BorderColor: =RGBA(139, 154, 159, 1)
                          BorderThickness: =0.5
                          ChevronBackground: =RGBA(122, 138, 143, 1)
                          Font: =Font.'Segoe UI'
                          HoverFill: =RGBA(180, 214, 250, 0.2)
                          SelectionFill: =RGBA(56, 96, 178, 1)
                          Width: =360
                          X: =159
                          Y: =24
                    - imgPerson:
                        Control: Image
                        Properties:
                          OnSelect: =Select(Parent)
                          Image: =If(!IsBlank(cmbPerson.Selected),Office365ユーザー.UserPhotoV2(cmbPerson.Selected.Email),SampleImage)
                          Height: =60
                          RadiusBottomLeft: =30
                          RadiusBottomRight: =30
                          RadiusTopLeft: =30
                          RadiusTopRight: =30
                          Width: =60
                          X: =73
                          Y: =13
                    - shpBorder:
                        Control: Rectangle
                        Properties:
                          OnSelect: =Select(Parent)
                          BorderColor: =RGBA(122, 138, 143, 1)
                          BorderThickness: =1
                          DisplayMode: =DisplayMode.Disabled
                          Fill: =RGBA(0, 0, 0, 0)
                          Height: =80
                          Width: =647
                - rootTitle:
                    Control: Text
                    Properties:
                      Size: =20
                      Text: ="■ 承認担当者"
                      Weight: ='TextCanvas.Weight'.Semibold
                      AlignInContainer: =AlignInContainer.Stretch
                      Height: =52
                - conFooter:
                    Control: GroupContainer
                    Variant: manualLayoutContainer
                    Properties:
                      Fill: =RGBA(214, 221, 224, 0.3)
                      FillPortions: =0
                      Height: =75
                    Children:
                    - btnSubmit:
                        Control: Button
                        Properties:
                          OnSelect: "=UpdateContext(\n    {\n        Title: valueTitle.Value,\n        Details: valueDetails.Value\n    }\n);\nClearCollect(\n    colWorkflow,\n    ForAll(\n        galRoot.AllItems,\n        {\n            Step: Value(badgeIndex.Content),\n            Claims: cmbPerson.Selected.Email,\n            Name: cmbPerson.Selected.DisplayName\n        }\n    );\n    \n);\nSubmitForm(Form);\n// Form.LastSubmit.ID\r\nDynamicWorkflow.Run(\n    JSON(colWorkflow),\n    Title,\n    Details,\n    Form.LastSubmit.ID,\n    \"WF-\" & Text(\n        Form.LastSubmit.ID,\n        \"0000\"\n    )\n);\nNotify(\n    \"承認ワークフローを実行しました。\",\n    NotificationType.Information\n);\nNewForm(Form);\nClearCollect(\n    colStep,\n    {Step: 1}\n);\n"
                          FontSize: =18
                          Text: ="Submit"
                          DisplayMode: |-
                            =If(
                                And(
                                    IsBlank(valueTitle.Value),
                                    IsBlank(valueDetails.Value),
                                    CountRows(valueAttachment.Attachments) = 0
                                ),
                                DisplayMode.Disabled,
                                DisplayMode.Edit
                            )
                          Height: =52
                          Width: =120
                          X: =264
                          Y: =12
```

# GPT-4oによる解説レポート - おまけ
## フローの概要
- **フロー名**: DynamicWorkflow
- **状態**: Started
- **共有タイプ**: CommonDataService

## 接続
1. **SharePoint**:
   - **表示名**: SharePoint
   - **API ID**: /providers/Microsoft.PowerApps/apis/shared_sharepointonline
   - **ソース**: Invoker

2. **承認**:
   - **表示名**: 承認
   - **API ID**: /providers/Microsoft.PowerApps/apis/shared_approvals
   - **ソース**: Invoker

## トリガー
- **トリガータイプ**: Request
- **トリガー名**: manual
- **トリガー仕様**:
  ```json
  {
      "type": "object",
      "properties": {
          "text": { "title": "JSON", "type": "string", "description": "解析するJSON" },
          "text_1": { "title": "Title", "type": "string", "description": "Workflow Title" },
          "text_2": { "title": "Details", "type": "string", "description": "Workflow Details" },
          "number": { "title": "ID", "type": "number", "description": "Workflow ID" },
          "text_3": { "title": "WorkflowID", "type": "string", "description": "Workflow ID Text" }
      },
      "required": ["text", "text_1", "text_2", "number", "text_3"]
  }
  ```

## アクション
1. **JSONの解析**:
   - **タイプ**: ParseJson
   - **入力**:
     ```json
     {
         "content": "@triggerBody()?['text']",
         "schema": {
             "type": "array",
             "items": {
                 "type": "object",
                 "properties": {
                     "Claims": { "type": ["string", "null"] },
                     "Name": { "type": ["string", "null"] },
                     "Step": { "type": "integer" }
                 },
                 "required": ["Claims", "Name", "Step"]
             }
         }
     }
     ```

2. **For Each**:
   - **タイプ**: Foreach
   - **ループ対象**: @outputs('JSON_の解析')['body']
   - **アクション**:
     - **条件**:
       - **タイプ**: If
       - **条件式**: @equals(items('For_each')['Step'], @length(body('JSON_の解析')))
       - **真の場合のアクション**:
         - **項目の更新_ワークフロー終了**:
           - **タイプ**: OpenApiConnection
           - **入力**:
             ```json
             {
                 "host": { "apiId": "/providers/Microsoft.PowerApps/apis/shared_sharepointonline", "connectionName": "shared_sharepointonline_1", "operationId": "PatchItem" },
                 "parameters": {
                     "dataset": "{SharePoint-URL}",
                     "table": "{SharePoint List ID}",
                     "id": "@body('項目の取得_マスタ')?['ID']",
                     "item/Status/Value": "終了",
                     "item/Result/Value": "承認"
                 }
             }
             ```
       - **偽の場合のアクション**:
         - **項目の更新_マスタ承認**:
           - **タイプ**: OpenApiConnection
           - **入力**:
             ```json
             {
                 "host": { "apiId": "/providers/Microsoft.PowerApps/apis/shared_sharepointonline", "connectionName": "shared_sharepointonline_1", "operationId": "PatchItem" },
                 "parameters": {
                     "dataset": "{SharePoint-URL}",
                     "table": "{SharePoint List ID}",
                     "id": "@body('項目の取得_マスタ')?['ID']",
                     "item/Status/Value": "対応中",
                     "item/Result/Value": "承認"
                 }
             }
             ```

## その他のアクション
- **変数の初期化**
  - **変数**: bool, Markdown, files
- **項目の取得**: SharePoint の項目を取得
- **項目の更新**: SharePoint の項目を更新
- **添付ファイルの取得**: SharePoint の添付ファイルを取得
- **添付ファイルのコンテンツの取得**: SharePoint の添付ファイルのコンテンツを取得
- **配列変数に追加**: 取得したファイルを配列変数に追加
- **承認の開始と待機**