# 【Power Apps】業務記録アプリに後から一括登録機能を実装する方法

- 公開日: 2025-11-26
- カテゴリ: Power Apps
- 著者: tan（@kama_bizdev）
- URL: https://flow-with-tech.com/powerapps-bulk-registration-logic/
- サイト: 改善紀 — 好奇心の赴くままに。

---

業務アプリ開発の持論として、「作業が終わったその瞬間にスマホやPCで記録するしかない仕様にする」というものがあります。

しかし、実際の現場はそう甘くありません。 作業中に手が離せなかったり、会議が連続していたりと、**「物理的にスマホを取り出して記録すること自体が不可能」**な場面が多々あります。

結果として、一日の終わりや休憩時間に「記憶を頼りにまとめて入力する」ことになるのですが、Power Appsの標準的なフォームでこれをやろうとすると、「新規作成 → 入力 → 保存 → 新規作成…」を繰り返すことになり、非常にストレスフルです。

そこで今回、**「後からまとめて振り返る」**ことに特化した、**「買い物かご方式の一括登録機能」**を実装しました。 技術的な工夫点、特に**「ユーザーの記憶を補助するロジック」**を中心に紹介します。

![](https://flow-with-tech.com/media/wp/2025/11/image.png)

## 実装した機能の概要

今回作成したのは、以下のようなUIです。

- **一時保存（コレクション）機能：** 1件ずつ送信せず、手元でリストを作成する。
- **時刻の自動連携：** 直前のタスクの終了時間を、次のタスクの開始時間に自動セットする。
- **一括送信：** 最後にボタン一つでDataverseへ一括登録する。

![](https://flow-with-tech.com/media/wp/2025/11/CleanShot-2025-11-26-at-18.25.04@2x.png)

## 1. 課題：後から入力する時の「記憶の寸断」を防ぎたい

後からまとめて日報を書く時、一番頭を使うのは**「時系列の整理」**です。 「えーっと、Aの会議が10時に終わって、そのあとBの資料作成をやって…」と思い出しながら入力している時に、毎回時間を手動で設定し直すのは思考のノイズになります。

そこで、**「前の行（タスク）の終了時間を、次の行の開始時間の初期値にする」**というロジックを組み込みました。これにより、ユーザーは作業内容を入れることだけに集中できます。

## 2. 技術的な実装ポイント

ここからは具体的なコード解説です。 データの格納先にはDataverseを使用し、アプリ内ではコレクション（`colWorkLogs`）で一時データを管理しています。

### ① 「直前の終了時間」を取得して次につなげる（AddRecord）

「行追加ボタン」を押した時の処理です。 ここでのポイントは、**`Last()`関数でコレクションの最終行を取得**し、その終了時間を次の開始時間の基準にしている点です。

**コード**

```js
// AddRecord ボタンの OnSelect

// 1. 直前の行の終了時間を計算して取得
Set(
    varPrevEnd,
    If(
        CountRows(colWorkLogs) = 0,
        // 初回（0件）の場合は、DB上の最新データの終了時間などを取得
        If(IsEmpty(WorkLog_Transaction), Now(), ... ),

        // 2件目以降：コレクションの最後の行（Last）を取得
        With(
            {r: Last(colWorkLogs)},
            // 日付と時間を合成してDateTime型にする
            r.EndDate + Time(r.EndHour, r.EndMinute, 0)
        )
    )
);

// 2. 新しい行の開始時間を「直前終了時間の1分後」にセット
Set(
    varStart,
    DateAdd(varPrevEnd, 1, TimeUnit.Minutes)
);

// 3. コレクションに追加（時・分は分解して保持）
Collect(
    colWorkLogs,
    {
        RowNo: CountRows(colWorkLogs) + 1,
        LocalId: GUID(),
        StartDate: DateValue(varStart),
        StartHour: Hour(varStart),
        StartMinute: Minute(varStart),
        // ... (その他の項目)
    }
);
```

`With`関数を使うことで、「直前の行（`r`）」という定義を明確にし、コードの可読性を高めています。これで「A作業が終わったら、即B作業」という連続した入力をスムーズに行えます。

### ② 安全な一括登録（CreateRecord）

最後に、溜めたコレクションをデータベースに書き込む処理です。 ここでは単に保存するだけでなく、**「データの健全性チェック（バリデーション）」**を行ってから送信しています。

特に重要なのが**「開始時間が終了時間より後になっていないか（時刻逆転）」**のチェックです。

**コード**

```js
// CreateRecord ボタンの OnSelect

// 1. 未入力チェック & 時刻逆転チェック
If(
    CountRows(...) > 0,
    Notify("未入力があります", ...),

    // 逆転（Start >= End）があるかチェック
    With(
        {
            badRows: Filter(
                colWorkLogs As r,
                // 開始日時と終了日時を合成して比較
                (r.StartDate + Time(r.StartHour, r.StartMinute, 0)) >=
                (r.EndDate   + Time(r.EndHour,   r.EndMinute,   0))
            )
        },
        If(
            CountRows(badRows) > 0,
            Notify("開始が終了以上の行があります。修正してください。", NotificationType.Error),

            // 2. 問題なければ一括登録（ForAll + Patch）
            With(
                { toSave: SortByColumns(colWorkLogs, "RowNo", SortOrder.Ascending) },
                ForAll(
                    toSave As r,
                    With(
                        {
                            // ここで最終的なDateTime型を生成
                            st: r.StartDate + Time(r.StartHour, r.StartMinute, 0),
                            et: r.EndDate   + Time(r.EndHour,   r.EndMinute,   0)
                        },
                        Patch(
                            WorkLog_Transaction,
                            Defaults(WorkLog_Transaction),
                            {
                                ActionName:   r.ActionName,
                                StartTime:    st,
                                EndTime:      et,
                                Duration:     DateDiff(st, et, TimeUnit.Minutes) // 所要時間も自動計算
                            }
                        )
                    )
                );

                // 完了後の後始末
                Clear(colWorkLogs);
                Notify("登録しました。", NotificationType.Success)
            )
        )
    )
);
```

`ForAll`ループの中でも`With`関数を活用しています。 `st`（開始時間）と`et`（終了時間）を一時変数として定義することで、`Patch`関数の中が非常にスッキリし、所要時間（Duration）の計算などもしやすくなっています。

## おわりに

業務アプリ開発において「UI/UX」というと、見た目の綺麗さが注目されがちですが、本当の使いやすさとは**「ユーザーの業務フロー（文脈）に沿っているか」**にあると思います。

今回は「その場で入力できない」という制約を前提に、「後から思い出しやすい」仕組みをコードで表現しました。 Power Appsのコレクションと`With`関数、そしてちょっとしたロジックの工夫で、現場に寄り添ったアプリは作れます。同じような課題を持つ方の参考になれば幸いです。
