1. 主要ページへ移動
  2. メニューへ移動
  3. ページ下へ移動

QES ブログ

記事公開日

最終更新日

Power Appsで予定表から空き時間を確認する【アルゴリズム/API呼び出し】

  • このエントリーをはてなブックマークに追加

こんにちは。DXソリューション営業本部の吾妻です。

Power Platformを活用した業務改善の一環として、予定表のUIや機能をカスタマイズしたいといったご要望を、お客様からいただくことがあります。例えば、設備・備品の利用効率を向上させるために、利用時間の統計を取得したり、ハイブリッドワークでの会議・打ち合わせへの招待(会議URL発行)を効率化するために、日程調整を自動化させたりすることが挙げられます。予定のデータソースとしては、OutlookやTeamsの予定表を利用したり、独自の形式・権限管理で管理するためにSharePointリストを利用したりします。いずれも、バックエンドでロジックを実行するクラウドフローは組織全体で共通のものを用意しつつ、UIは(利用者自身が使いやすいと思う画面を)各自で用意することが多いように思います。

日本マイクロソフト社でも、会議出席者の日程調整を簡単に行うためのアプリサンプルを公開しており、配布ドキュメントや紹介動画も用意されていることから、広く必要とされているテーマなのではないかと思います。

そこで今回は、空き時間を探して日程調整したり、予定登録したりするキャンバスアプリを自前で実装する際に必要な知識について、簡単にご紹介します。具体的には、予定表から空き状況を確認する際に利用する「重複判定のアルゴリズム」や、Graph APIで予め用意されているAPIの呼び出し方法を解説したいと思います。




予定の重複チェックを行うアルゴリズム

個々の予定を配列で管理している予定表と、ユーザーが指定した開始時刻から終了時刻までの期間と重複する予定を抽出することを想定します。

以下の図は、①から⑨までの予定が予定表に登録されていて、それらのうち、12:00から16:00までの範囲と重複している予定を抽出することを示しています。このとき、⑦のように12:00ちょうどに終了する予定や、⑧のように16:00ちょうどに開始する予定は、結果に含めないことにします(後ほど出てきますが、判定条件の等号の有無が変わってきます)。

この場合、条件判定に使用する式は、「範囲開始日時 < 予定終了日時 かつ 予定開始日時 < 範囲終了日時」のようになります。図の例の場合、「12:00 < 予定終了日時 かつ 予定開始日時 < 16:00」の条件を満たしている①から⑤が、範囲と重複する予定ということになります。



paolcheck01a.png


判定条件の可視化

「範囲開始日時 < 予定終了日時 かつ 予定開始日時 < 範囲終了日時」という条件式で、重複判定できることを視覚的に表したいと思います。

この条件式は、「かつ(AND条件)」の前後で2つの部分からなるので、分割してからそれぞれ見ていきます。

まず、前半の「範囲開始日時 < 予定終了日時」(範囲の開始日時である12:00と、各予定の終了日時)を比較します。このとき、⑥と⑦の予定は条件を満たさないので、結果から除外します。

次に、後半の「予定開始日時 < 範囲終了日時」(各予定の終了日時と、範囲の終了日時である16:00)を比較します。こちらは、⑧と⑨の予定が条件を満たさないので、結果から除外します。

この2つの結果から、共通(AND条件)の予定を取り出したものが最終的な判定結果となります。図の場合、①から⑤の予定が共通しているので、①から⑤が指定された範囲に重複している予定として抽出できることになります。



paolcheck02a.png


条件式の導出

重複判定する条件式」を導出する際には、直接重複判定するための条件を洗い出そうとするのではなく、逆に「範囲と重複しない予定を抽出」してから、否定(NOT)をとるアプローチで考えると分かりやすいです。

以下の図には、先ほどと同じように①から⑨までの予定が予定表に登録されているのですが、今度は、12:00から16:00までの範囲と重複していない予定を抽出したいと思います。⑥や⑨のように範囲から離れているものだけでなく、⑦のように12:00ちょうどに終了する予定や、⑧のように16:00ちょうどに開始する予定も、重複していないと見なします。

すると、左半分のように「予定終了日時 <= 範囲開始日時」を満たす、または(OR)、右半分のように「範囲終了日時 <= 予定開始日時」を満たすような予定が、「範囲と重複しない予定」ということになります。つまり、「予定終了日時 <= 範囲開始日時 または 範囲終了日時 <= 予定開始日時」という条件式を用いることで、重複しない予定を抽出することができます。



paolcheck03a.png


この、「予定終了日時 <= 範囲開始日時 または 範囲終了日時 <= 予定開始日時」という条件式の否定(NOT)をとると、「範囲と重複すること」を判定する条件式となります。否定をとる際には、ド・モルガンの法則を用います。以下のようなベン図において、Aを「予定終了日時 <= 範囲開始日時」、Bを「範囲終了日時 <= 予定開始日時」とします。





図のように「『AまたはB』の否定」は、「『Aではない』かつ『Bではない』」となります。さらに、条件式の中に2か所ある大小比較(AとBの中身)の否定をとると、不等号の向きと等号の有無が変化します。なので、結果として、「範囲と重複すること」を判定する条件式として、「予定終了日時 > 範囲開始日時 かつ 範囲終了日時 > 予定開始日時」が得られます。不等号の向きを揃えるために、右辺/左辺の順番を入れ替えれば、「範囲開始日時 < 予定終了日時 かつ 予定開始日時 < 範囲終了日時」となり、一番はじめに示した条件式と同じものになります。



paolcheck01a.png


この、予定の重複判定を行うアルゴリズムを応用すると、数値範囲(金額帯など)や経路、線分などの重なりを調べたり、2次元(縦横)で考えることで、矩形同士の重なり(当たり判定)を調べたりすることができます。フリーアドレスでの在席位置チェックのようなシナリオで活用できるかもしれません。



キャンバスアプリでの利用例

この、予定の重複判定を行う条件判定式を、Power Appsキャンバスアプリに組み込んでみたいと思います。

実装方針としては、配列(コレクション)に個々の予定を格納しておき、UIで指定された開始時刻から終了時刻までの期間と重複する配列の要素を抽出することで実現します。



まず、予定を格納する配列を用意します。実際にはSharePointリストなどの外部データソースから、予定表の配列データを取得してきて格納することがほとんどですが、ここではサンプルとして、名前付き計算式(App.Formulas)に配列変数「Schedules」を定義しておきます。

// 本日の、JSTでの9:00(UTCでの0時)を基準時刻として取得
BaseHour=DateTimeValue(First(Split(Text(DateAdd(Today(), -TimeZoneOffset(), TimeUnit.Minutes), DateTimeFormat.UTC),"T")).Value&"T00:00:00Z");

// 予定の項目をいくつか配列に追加
Schedules=[
    {予定名:"09 20", 予定開始日時:DateAdd(BaseHour,0,Hours), 予定終了日時:DateAdd(BaseHour,11,Hours)},
    {予定名:"10 16", 予定開始日時:DateAdd(BaseHour,1,Hours), 予定終了日時:DateAdd(BaseHour,7,Hours)},
    {予定名:"11 15", 予定開始日時:DateAdd(BaseHour,2,Hours), 予定終了日時:DateAdd(BaseHour,6,Hours)},
    {予定名:"09 10", 予定開始日時:DateAdd(BaseHour,0,Hours), 予定終了日時:DateAdd(BaseHour,1,Hours)},
    {予定名:"10 11", 予定開始日時:DateAdd(BaseHour,1,Hours), 予定終了日時:DateAdd(BaseHour,2,Hours)},
    ...
    {予定名:"16 17", 予定開始日時:DateAdd(BaseHour,7,Hours), 予定終了日時:DateAdd(BaseHour,8,Hours)},
];

次に、重複していると判定された予定を表示するために、ギャラリーをスクリーンに配置します。

このとき、①重複しているか否かに関わらずギャラリーにはすべての予定を表示する場合、②重複判定された予定だけをギャラリーに表示する場合、の2通りが考えられます。また、①の場合、重複の有無によって、コントロールの表示/非表示(Visibleプロパティ)を切り替えたり、ラベル(テキストコントロール)に表示するメッセージやフォントを変更したりすることが想定されます。



今回は、以下の図のように、①として、ギャラリーコントロールにすべての予定(Schedules)を表示しておき、行ごとに電球アイコンの表示状態を切り替える例と、②として、条件に合致するアイテムだけをギャラリーコントロールに表示する例を示します。





数式を以下の表に示します。この中で、接頭辞がdrpとなっているのは、ドロップダウンコントロールの名称です。①のギャラリーコントロールのItemsプロパティには配列変数「Schedules」を設定している前提とします。



実装の種類 ①電球アイコンの表示/非表示切り替え ②条件に合致する予定のみ表示
数式を設定するコントロール ギャラリー内のアイコンコントロール ギャラリーコントロール
数式を設定するプロパティ Visible Items
数式
 ThisItem.予定開始日時 < 
     drp終了日時.Selected.Value && 
     drp開始日時.Selected.Value <
     ThisItem.予定終了日時
 
 Filter(
     Schedules,
     予定開始日時 <
         drp終了日時.Selected.Value &&
         drp開始日時.Selected.Value <
         予定終了日時

 )




Graph APIを利用した重複チェック

現実的には、予定の管理や重複チェック(空き時間検索)を自前で管理するのではなく、Outlookの予定表で予定データを管理して、UIだけ独自に実装することがほとんどです。Power Appsキャンバスアプリや、Power Automateクラウドフローから利用できるコネクタが提供されているので、必要なパラメーターを指定してアクションを実行するだけで、これらの機能を実現することができます。



具体的には、Office 365 Outlookコネクタに含まれている、イベントのカレンダー ビューの取得 (V3)アクション(GetEventsCalendarViewV3())や、会議の時間を検索 (V2)アクション(FindMeetingTimesV2())を利用します。

それぞれのアクション(キャンバスアプリでは関数)の実行方法を次の表に示します。これらの数式を、ギャラリーのItemsプロパティに記述したり、Set関数などを用いて変数に格納したりすることで、キャンバスアプリ内でアクションの戻り値を活用できます。アクション名に含まれている「バージョン(V2やV3など)」が異なると、引数の個数や、引数のデータ型といった仕様が異なる場合があるので、注意が必要です。

また、数式の中で、カレンダーIDと呼んでいるのは、カレンダーの取得 (V2)アクション(CalendarGetTablesV2())を呼び出して得られる情報のうち、「id」列に格納されている値を指します。
実装上は、 Set(calendarId, LookUp(Office365Outlook.CalendarGetTablesV2().value, name = "Calendar" || name = "予定表", id)) のように定義しておくと良いでしょう。既定の予定表を利用しているのであれば、言語設定に左右されることなく取得できるはずです。



アクション イベントのカレンダー ビューの取得 (V3) 会議の時間を検索 (V2)
数式
Office365Outlook.GetEventsCalendarViewV3(

// カレンダーID(テキスト型)
calendarId,

// UTCの開始日時(テキスト型)
Text(Today(), DateTimeFormat.UTC),

// UTCの終了日時(テキスト型)
// DateAdd()関数は、未指定だと「日」単位
Text(DateAdd(Today(), 1), DateTimeFormat.UTC),

{
// 検索用のODataクエリ(テキスト型)
'$filter': "isAllDay eq false",

// 並べ替え用のODataクエリ(テキスト型)
'$orderby': "createdDateTime desc",

// スキップする件数(整数型)
'$skip': 20,

// 取得する件数(整数型)
'$top': 30,

// 件名の検索キーワード(テキスト型)
search: "定例会"
}
).value
Office365Outlook.FindMeetingTimesV2(
{

// 必須出席者のメールアドレス(セミコロン区切り
RequiredAttendees: "user@example.com;",

// 任意出席者のメールアドレス(セミコロン区切り
OptionalAttendees: "guest@example.com;",

// リソースのメールアドレス(セミコロン区切り
ResourceAttendees: "room1@example.com;",

// 会議の長さ(分単位;整数型)
MeetingDuration: 30,

// 候補の開始日時(日時型)
Start: Today(),

// 候補の終了日時(日時型)
End: DateAdd(Today(), 24, TimeUnit.Hours),

// 最大候補数(整数型)
MaxCandidates: 15,

// 開催者が任意参加か(真偽値型)
IsOrganizerOptional: false,

// アクティビティドメイン(テキスト型)
// 稼働時間を考慮するか否か
// (Work / Personal / Unrestricted)

ActivityDomain: "Work",

// 出席者の最小割合(テキスト型)
MinimumAttendeePercentage: "100"
}
).meetingTimeSuggestions



このように、キャンバスアプリからOutlook予定表を参照・更新する構成とすることで、Microsoftから公式に提供されているOffice 365 Outlookコネクタ経由でGraph  APIを呼び出すことができます。すると、本記事の前半でご紹介したようなアルゴリズムや、データソースでの予定情報の格納方法を意識することなく、日程調整したり予定登録したりするアプリを容易に実現することができます。

ただし、Power AutomateとOffice 365 Outlookコネクタを組み合わせてOutlookの予定表を利用する場合には、原則として(「会議の時間を検索」アクションを除いて)自分自身の予定にしかアクセスできないというデメリットがあります。そのため、自分自身以外の予定にアクセスしたい場合には、別途必要なアクセス許可を付与したEntra ID アプリを登録しておいて、HTTPアクションで直接Graph APIを呼び出す必要があります。




まとめ

今回は、空き時間を探して日程調整したり、予定登録したりするキャンバスアプリを自前で実装する際に必要な知識について、簡単にご紹介しました。

QES では Power Platform の開発支援、QAサポート、開発者教育、ガバナンス整備など、組織で Power Platform を活用するためのサポートを包括的にご提供しています。Power Platform 活用についてご興味がある/利用中だが課題を感じていらっしゃるお客様はまずはお気軽にお問い合わせください。



このブログで参照されている、Microsoft、Windows、その他のマイクロソフト製品およびサービスは、米国およびその他の国におけるマイクロソフトの商標または登録商標です。

  • このエントリーをはてなブックマークに追加

お問い合わせ

Contact

ご質問やご相談、サービスに関する詳細など、何でもお気軽にご連絡ください。下記のお問い合わせフォームよりお気軽に送信ください。

お問い合わせ

資料ダウンロード

Download

当社のサービスに関する詳細情報を掲載した資料を、下記のページよりダウンロードいただけます。より深く理解していただける内容となっております。ぜひご活用ください。

資料ダウンロード