Power Apps で予約系アプリを作成する際の予約重複対策


# はじめに

Power Apps では、何かの予約をする業務を効率化するアプリを作成することが可能です。

以下は備品の予約の例ですが、まず、データソースとして、備品および備品の予約をする SharePoint リストを作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3074511/7ab99ed8-679f-5cd5-e769-eb19014f9102.png)



![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3074511/424c5622-0858-4883-e555-458bda8302f1.png)

そして、上記データソースをベースに、備品の予約をするアプリを作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3074511/d205e87f-bda3-258f-d5c7-6c318f172863.png)

このようなアプリを作成する際、予約が重複しないようにしたいといった相談をいただくことがあります。

例えば、複数の人が同じ時間帯にアプリを開いており、予約したい時間帯について予約がないと判断して予約したものの、実はアプリを開いている間に他の人が予約をした際にダブルブッキングになってしまうという懸念があります。

簡単な対策として、例えば、予約のデータを定期的に Refresh して最新のデータを参照出来るようにすることが考えられますが、あまり頻繁にやりすぎるとちょっと効率が悪いため、別のアプローチが必要と考えます。

そのため、今回は、そのような要件に対するアプローチについて紹介いたします。

# Power Apps のみで実装する場合

Power Apps で実装する場合は、送信 (つまり予約) ボタンを押した際に、その備品、期間で予約データがないか最終確認し、データが無ければ予約をするといった処理にします。

以下、処理の例です。予約したい日時の開始と終了と既存のデータの開始と終了を比較し、条件に一致する (予約が重複する) データを取得します。取得結果が空の場合は重複する予約がないと判断しています。データを登録する部分は割愛しています。

いつも思いますが、日時の比較部分が少し複雑ですね。私もいつも悩みます笑。

```
If(
    IsBlank(
        LookUp(
            EquipmentReservations,
            EquipmentID = Value(EquipmentID_DataCard1.Update) && StartDateTime_DataCard1.Update < EndDateTime && EndDateTime_DataCard1.Update > StartDateTime
        )
    ),
    Notify(
        "予約可能です。",
        NotificationType.Success
    );
    SubmitForm(frmReservationDatails),
    Notify(
        "予約できません。",
        NotificationType.Error
    )
);

```

10 時から 17 時まで既存の予約がある場合、以下のような結果となる想定です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3074511/ae780a12-54d4-13ce-661e-2b98a59ae86c.png)

式を基に確認してみます。9 時から 11 時で予約しようとした場合、以下の式はそれぞれ true になり、予約データが返されることになります。

```
StartDateTime_DataCard1.Update < EndDateTime 
&& 
EndDateTime_DataCard1.Update > StartDateTime

9 時 (新規予約の開始日時) < 17 時 (既存予約の終了日時) 
&& 
11 時 (新規予約の終了日時) > 10 時 (既存予約の開始日時)
```

実際にアプリをテストしてみます。

9 時から 10 時のため、連続していますが予約は可能です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3074511/5042c4f2-9263-e68d-cf0e-94ee60b6a6cd.png)

9 時から 11 時のため、終了が 10 時過ぎているため予約出来ません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3074511/2883de78-4a28-3c3e-5c0c-aa0ad1ca1a9f.png)

開始も終了も 10 時から 17 時の間のため予約出来ません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3074511/1b41fde2-e6c6-8539-58ff-0253fd2ef16f.png)

開始が 17 時より前のため予約出来ません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3074511/8bf67156-acb0-78eb-0b45-8085fe4c5be3.png)

開始が 17 時からのため、連続していますが予約可能です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3074511/684a015b-b08a-be5f-c7e5-a4cf71585a01.png)

上手く動作しているようです。こちらの方法により、予約前に最終チェックすることで、基本的には、よほどのことがなければ、予約の重複は回避できると思います。

※強いて言えば、最終チェックして、予約をするまでの数ミリ秒以下の間に別の人が予約した場合、予約が重複する可能性がありますが、可能性は極めて低いと考えます。

# Power Automate で実装する場合

同じようなことを Power Apps から Power Automate をキックして実現します。
ポイントは、以下のように、コンカレンシー制御をオンにして 1 にします。これにより、仮に他の人がほぼ同時に予約をしても、順番にフローが実行されるため、予約処理の排他制御が出来ます。

基本的には上述の Power Apps 側の実装でも問題ないとは思いますが、こちらの方がより厳密に予約の重複を防ぐことが可能です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3074511/26fa27d6-5edb-dbf1-6c83-a20e4d5ab702.png)

試しに以下のようなアプリ、フローを作成してみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3074511/548981dd-3eb0-b507-5a3f-b6d61e696c9f.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3074511/c146a3b7-bc28-2806-f58c-7749caae1e4c.png)

複数のユーザーでフローを実行してみます。片方は実行中、片方は待ちとなっています。期待通りです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3074511/1d917a9a-684c-7385-944e-132773690445.png)

それでは、こちらの設定を行った上で、Power Apps と同様に、予約しようとしている時間帯にデータが存在するか判断し、Power Apps に予約可否の応答をします。

こちら複雑ですが、以下のような感じになると思います。SharePoint 上に予約しようとしている時間帯と重複する予約があればデータを返すようフィルターをします。


```
(EquipmentID eq @{int(triggerBody()['text_2'])}) and ('@{triggerBody()['text_3']}' lt EndDateTime) and ('@{triggerBody()['text_4']}' gt StartDateTime)
```

取得したデータの件数が 0 件の場合、つまり重複する予約がない場合に予約をするようにして申込者に通知します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3074511/5696006e-8f16-41a4-ae41-a6a2793a0d18.png)

:::note

コンカレンシー制御をオンにしている状態で、Power Apps に応答を返す場合、非同期応答をオンにする必要があります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3074511/593e34ed-a4a3-40b7-9451-4a49dcd1d1de.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3074511/3d1001ff-16d5-4496-9f0a-8023ffdecffd.png)

この場合、処理の最後で、以下のようにして Power Apps に予約可否に関する情報を出力で返すようにしても、Power Apps 側では出力を上手く受け取れなくなるため、予約可否の結果は Teams で別途通知するようにしています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3074511/abc8aada-7116-4433-b615-96f2ff01e33c.png)
:::

取得したデータの件数は、`length`関数の結果を元に判断します。

```
length(outputs('複数の項目の取得_重複チェック')?['body/value'])
```

上記 Power Automate のフローを呼ぶ Power Apps 側の処理のイメージは以下のような感じです。

```
IfError(
    予約重複チェックフロー.Run(
        ComboBox1.Selected.ID,
        Text(
            DateValue1.SelectedDate + Time(
                Value(HourValue1.Selected.Value),
                Value(MinuteValue1.Selected.Value),
                0
            ),
            "yyyy-mm-ddTHH:mm:ssZ"
        ),
        Text(
            DateValue2.SelectedDate + Time(
                Value(HourValue2.Selected.Value),
                Value(MinuteValue2.Selected.Value),
                0
            ),
            "yyyy-mm-ddTHH:mm:ssZ"
        )
    ),
    Notify(
        "予約処理でエラーが発生しました",
        NotificationType.Error
    ),
    Notify(
        "予約処理を受け付けました。結果は Teams で通知します",
        NotificationType.Success
    )
)
```

全部のパターンは書きませんが、Power Apps のときと同じテスト結果になりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3074511/f01bea74-e406-4e3f-b2f7-f98ce62b22f8.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3074511/967e340a-d168-4318-bfa8-b148c9eff3d3.png)



# まとめ

今回は、Power Apps で予約系アプリを作成する際の重複排除についてアプローチを紹介しました。

:::note
今回は、時間帯を指定して予約をするというアプリをベースにアプローチを紹介しましたが、以下のように、あらかじめ管理者の方で予約可能なスロットを作成しておき、予約をするような場合もあると思います。

このような場合においても、予約確定や予約者の列の更新を上記のようなアプローチで制御しながら更新することが可能です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3074511/bdb93642-8596-57ca-9179-7f4645d6b9f5.png)

:::


予約系のアプリを作成したいといった相談をいただくことは多いため、参考にしていただけると幸いです。