Trailhead: 一括Apexトリガー

# 一括Apexトリガー
https://trailhead.salesforce.com/content/learn/modules/apex_triggers/apex_triggers_bulk?trail_id=force_com_dev_beginner

このチャレンジでは、**商談（Opportunity）オブジェクトのフェーズ（StageName）が "Closed Won" になった際に、ToDo（Task）を自動的に作成する Apex Trigger** を作成します。

### ✅ **要件整理**
1. **トリガー名**: `ClosedOpportunityTrigger`
2. **対象オブジェクト**: `Opportunity`
3. **トリガーの発火タイミング**: `after insert` および `after update`
4. **条件**: `StageName` が `"Closed Won"` の場合
5. **処理**:
   - `Task`（ToDo）を作成する
   - **Subject**: `"テスト ToDo をフォローアップする"`
   - **WhatId**: `Opportunity.Id`（商談と関連付け）

6. **バルク処理対応**（トリガーが一括処理に対応するようにする）

---

## ✅ **Apex Trigger の実装**

以下が要件を満たす `ClosedOpportunityTrigger` のコードです。

```apex
trigger ClosedOpportunityTrigger on Opportunity (after insert, after update) {
    List<Task> tasksToInsert = new List<Task>();

    for (Opportunity opp : Trigger.new) {
        // フェーズが "Closed Won" になった商談のみ処理
        if (opp.StageName == 'Closed Won') {
            Task followUpTask = new Task(
                Subject = 'テスト ToDo をフォローアップする',
                WhatId = opp.Id,
                OwnerId = opp.OwnerId, // 商談の所有者をタスクの所有者に設定
                Status = 'Not Started', // デフォルトのステータスを設定
                Priority = 'Normal' // 優先度を設定
            );
            tasksToInsert.add(followUpTask);
        }
    }

    // 一括挿入処理（DMLの回数を削減）
    if (!tasksToInsert.isEmpty()) {
        insert tasksToInsert;
    }
}
```

---

## ✅ **コードの解説**

### **1. `after insert, after update` のトリガー**
- 商談が**作成（insert）されたとき**、または**更新（update）されたとき**にトリガーが実行されます。

### **2. `Trigger.new` をループ処理**
- `Trigger.new` には、**更新された商談のリスト**が入っているので、すべてのレコードをループします。

### **3. 条件: `StageName == 'Closed Won'`**
- 商談の `StageName` が `"Closed Won"` になった場合のみ ToDo を作成します。

### **4. `Task` オブジェクトのリスト `tasksToInsert` を作成**
- **DML（データ操作言語）処理の最適化**のため、`Task` をリストに格納し、一括 `insert` します。

### **5. `insert tasksToInsert;` でまとめて ToDo を作成**
- **DML の回数を最小限に抑える**ため、リストに `Task` を追加した後、1 回の `insert` でデータを挿入します。

---

## ✅ **バルク処理対応のポイント**
このコードは、一度に 200 件以上の商談を処理できるようになっています。  
具体的には、以下の点でバルク処理対応をしています。

1. **リストを使用して DML を 1 回の実行に抑える**
   - `Task` を `List<Task>` に格納し、最後に `insert tasksToInsert;` することで、DML 回数制限を回避。
   
2. **ループ内で DML 操作を行わない**
   - `insert` をループ内で実行すると、DML 制限（1 トランザクションあたり 150 回）を超える可能性があるため、バッチ処理で一括 `insert` する。

3. **`if (!tasksToInsert.isEmpty())` で不要な DML を防ぐ**
   - `Closed Won` の商談が 1 つもなかった場合、無駄な `insert` を実行しない。

---

## ✅ **動作確認**
### **テストデータを作成**
Apex Developer Console で以下のコードを実行して、トリガーの動作をテストできます。

```apex
// サンプル商談の作成
Opportunity testOpp = new Opportunity(
    Name = 'Test Opportunity',
    StageName = 'Closed Won',
    CloseDate = Date.today()
);
insert testOpp;

// 作成された Task を確認
List<Task> createdTasks = [SELECT Id, Subject, WhatId FROM Task WHERE WhatId = :testOpp.Id];
System.debug(createdTasks);
```

### **期待される結果**
- `テスト ToDo をフォローアップする` という Subject の ToDo が作成される。
- `WhatId` が `Opportunity.Id` と一致する。

---

## ✅ **まとめ**
- `after insert, after update` のトリガーで `StageName == 'Closed Won'` の場合に `Task` を作成。
- `Task` をリストに格納し、最後に **一括 `insert` することでバルク処理に対応**。
- `DML 操作をループの外で実行` し、ガバナ制限を回避。
- `OwnerId` や `Status` を設定し、より実用的な `Task` にする。

これで、この Trailhead チャレンジの要件を満たす **効率的でスケーラブルな Apex Trigger** になりました！ 🚀