Apex Specialist Super Badge 復習[Challenge 1]

# Challenge 1:レコード作成の自動化

思ったよりこれが長かった…なんとかなったのでまとめてみます。

## 設定について

書いてあることをそのまま実施

> スキーマおよび Apex クラス&トリガのスタブコードの含まれた非管理パッケージをインストールします。ケースおよび商品を HowWeRoll のスキーマに合うように名称変更し、全てのプロファイルにそれらのオブジェクトのカスタム HowWeRoll ページレイアウトをアサインします。
>
> [Apex Specialist Super Badge 日本語訳](https://trailhead-quickstart.quip.com/gJ3QAkFy6boE/Apex-Specialist-Super-Badge-)より

指示されていないカスタム項目の作成などをしてもチェックに支障はない。

## 各 Apex ソースコードについて

既存のメソッドに引数を追加しても問題ない。

### 公式日本語訳より

> (前略)…
> Type (種別) が Repair (リペア) か Routine Maintenance (定期メンテナンス) の既存メンテナンスリクストがクローズされた際、将来の定期メンテナンスのための新しいメンテナンスリクストを作成します。この新しいリクエストは元のクローズされたサービスリクエストを元に同じ車両および同じ機器に紐づけられます。新しいリクエストの種別は Routine Maintenance (定期メンテナンス) に設定される必要があります。Subject (件名) は null であってはならず、Date Reported (報告日) 項目はリクエストが生成された日を反映します。別の側面として、部品は全て異なる寿命を持っている点があります。したがって関連する Work Part (作業部品) レコードに定義されているメンテナンスサイクルから計算し次の期日を設定する必要があります。もし複数の Work Part (作業部品)をメンテナンスリクエストで使用されている場合には、最も短いメンテナンスサイクルをサービス日に設定します。
>
> この自動化プロセスは単体のメンテナンスリクエストと一括リクエストの両方に対応するようデザインします。一緒にインポート予定の約 300 行のオフラインメンテナンスリクエストを正常に処理するためにシステムを一括化します。当面の間は Equipment (機器) レコード自体の変更については考慮する必要はありません。
>
> このロジックは組織内の別の場所で使用されるため、トリガ(名称 : **MaintenanceRequest**) をハンドラ内のアプリケーションロジック (名称 : **MaintenanceRequestHelper**) と分離します。この設定によりアクションを委譲し、今後アプリケーションを拡張するのがより簡単になります。
>
> [Apex Specialist Super Badge 日本語訳](https://trailhead-quickstart.quip.com/gJ3QAkFy6boE/Apex-Specialist-Super-Badge-)より

### 整理すると

- 更新したレコードが以下 2 つの条件を満たしたら新しい Maintenance Request (Case)を作成する。
  - Type は Repair または Routine Maintenance
  - Status が Close になった **(今回の更新で)**
- 新しい Maintenance Request の項目は以下の条件を満たす
  - Subject は何らかの文字列が入る(not null)
  - Type は Routine Maintenance
  - Date Reported はレコード生成した日
  - Date Due(期日)は後述する条件を満たす日
  - 車両(Vehicle)と機器(Equipment)は元の Maintenance Request と同じもの

#### 問題文に書かれていないこと

> 1. After creating new Cases (Maintenance Requests), the “Work Parts” related to the closed case, must be updated with the new case.
> 2. Work Parts are master detail to Cases, so in order to achieve the above requirement, we must enable re-parenting on the Work Part.
>
> [The Salesforce developer blog](http://lucascalegari.com/apex-specialist-superbadge-1/)より

子オブジェクトである Work Part の参照先も新しい Maintenance Request に関連付けを変える必要があります。そのためにはこの項目について re-parenting を許可する必要があります。

#### Date Due について

次の期日は**レコード作成日+メンテナンスサイクル**

メンテナンスサイクルを取得は以下のような二つの場合分けが生じる。

- Maintenance Request に紐付いている Work Part に紐付いている Equipment のメンテナンスサイクルの中で最小値を探す。
- そもそも Work Part が存在しない場合も考えうる。その場合は Maintenance Request に直接紐付いている Equipment のメンテナンスサイクルを参照する。(この部分は問題文からは読み取れない...読解力の欠如 orz)

### 自分が苦戦したこと

**SOQL の操作**がだいぶ大変でした。この一言に尽きます。自分が苦手なだけとも言う。
このチャレンジで使った SOQL に関する知識を挙げておきます。
~~また、トリガコンテキスト変数についてもまとめておきます。~~リサーチ中です

#### リレーションクエリ

詳しくは公式のドキュメントをどうぞ。要点は

- 子 → 親(多対一)を探すクエリは項目にドットを使ってリレーション名をつなげていく。
- 親 → 子(一対多)を探す場合はサブクエリを使う

`Case`(親)→`Work_Part__c`(子)→`Equipment__c`(親)とたどりたいのであれば以下のようになる。

```sql

SELECT Case.Id,
  (SELECT Work_Part__c.Equipment__r.Id FROM Work_Parts__r)
FROM Case
```

- 参考
  - [セールスフォースの豆知識](http://blog.flect.co.jp/salesforce/2010/07/soql-3dd0.html)
  - [Salesforce Developer:リレーションクエリの使用](https://developer.salesforce.com/docs/atlas.ja-jp.soql_sosl.meta/soql_sosl/sforce_api_calls_soql_relationships_query_using.htm)

#### 集計関数

平均、レコード数、最小、最大、合計と使い方は 普通の SQL と大体同じです。3 番目だけ注意。

1. 項目で集計関数
2. 適宜 GROUP BY
3. Apex では集計関数を含んだクエリは`AggregateResult`オブジェクトのコレクションとして返される
   - `AggregateResult` オブジェクトは `Map<String,Object>`のようなもの。
   - `.get(項目名)`で取り出してキャストする。
   - 集計関数の項目名は expr _i_ ( _i_ :0 始まりの数値)。別名をつけて使うこともできる。

- 参考
  - [Salesforce developers:SOQL 集計関数の使用](https://developer.salesforce.com/docs/atlas.ja-jp.apexcode.meta/apexcode/langCon_apex_SOQL_agg_fns.htm?search_text=AggregateResult)
  - [Salesforce developers:集計関数](https://developer.salesforce.com/docs/atlas.ja-jp.soql_sosl.meta/soql_sosl/sforce_api_calls_soql_select_agg_functions.htm?search_text=集計関数)

<details><summary>コードなど</summary><div>
書き方の参考までに。

- トリガーは isAfter && isUpdate のときに Helper を呼ぶ。
- Helper に Trigger.new,Trigger.oldMap をわたす
- カスタム項目として Maintenance Request オブジェクトに`Old Case`項目を作成し Id を String で格納
  - Work Part の Maintenance Request の付け替えに使ったが…もう少しうまい方法がある気がする
- Work Part オブジェクトの項目 Maintenance Request の re-parenting のチェックボックスにチェックを入れて有効化

* 作成した SOQL は 3 つ
    1. **List\<Case\> listMaintenanceRequests** - 更新後の `List<Case>`
        - SELECT 句は必要な項目を並べる。サブクエリは不要
        - FROM Case
        - WHERE 句に Type や Status の条件もつけるとコード量が減る。Id の条件は必須
    2. **List\<AggregateResult\> workPartsWithMinCycles** - 更新した Maintenance Request\_\_r.Id ごとの (Work Part の)maintenance cycle 最小値
        - SELECT 句は Maintenance_Request\_\_r.Id と MIN 関数。
        - From Work_Part\_\_c
        - WHERE 句に Maintenance_Request\_\_r.Id 条件
        - GROUP BY Maintenance_Request\_\_c
    3. **List\<Work_Part\_\_c\> workParts** - Maintenance Request を付け替えるための `List<Work_Part__c>`
        - SELECT 句は Id,Maintenance_Request\_\_c
        - FROM Work_Part\_\_c
        - WHERE 句に Maintenance_Request\_\_r.Id。Helper で作成した Case に対応する旧レコードの Id である必要がある。
`Map<Id,Id>` で新旧 Case の Id 対応を予め行うと楽

1. listMaintenanceRequests
2. workPartsWithMinCycles
3. Map\<Id,Decimal\> minCycleDays に集計。IdはCase
4. 新規 Maintenance Request を作成して insert
   - 旧レコードステータスが Closed 以外であることも確認が必要
   - `minCycleDays.containsKey(caseObj.Id)`を使って Date Due の場合分け。
5. `Map<Id,Id> new_old_CaseIdMap`
6. workParts
7. workParts を update

```java

public class MaintenanceRequestHelper {
    
    public static void updateWorkOrders( List<Case> maintenanceRequests, Map<Id, Case> mapOldRecords){
        // update workorders
        Set<Id> caseId=mapOldRecords.keySet();
        List<Case> listMaintenanceRequests=[SELECT
                                            Vehicle__c,
                                            Status,
                                            Equipment__c,
                                            Subject,
                                            Equipment__r.Maintenance_Cycle__c
                                            FROM Case
                                            WHERE Id IN :caseId
                                            AND Status='Closed'
                                            AND Type IN('Repair','Routine Maintenance')];
        if(listMaintenanceRequests.size()==0)
            return;
        List<AggregateResult> workParts=[SELECT
                                         Maintenance_Request__c,
                                         MIN(Equipment__r.Maintenance_Cycle__c) minCycle
                                         FROM Work_Part__c
                                         WHERE Maintenance_Request__r.Id IN :caseId
                                         GROUP BY Maintenance_Request__c];
        Map<Id,Decimal> minCycleDays=new Map<Id,Decimal>();
        For(AggregateResult workPart:workParts){
            minCycleDays.put((Id)workPart.get('Maintenance_Request__c'),(Decimal)workPart.get('minCycle'));
        }
        List<Case> newRequests=new List<Case>();
        For(Case c:listMaintenanceRequests){
            if(mapOldRecords.get(c.Id).Status=='Closed')continue;
            Case newReq=c.clone(false,true,false,false);
            newReq.Old_Case__c=c.Id;
            newReq.Status='New';
            newReq.Type='Routine Maintenance';
            newReq.Date_Reported__c=System.today();
            if(String.isBlank(c.Subject))newReq.Subject='New Routine ';
            if(minCycleDays.containsKey(c.Id)){
                newReq.Date_Due__c=System.today().addDays(minCycleDays.get(c.Id).intValue());
            }else {
                newReq.Date_Due__c=System.today().addDays(c.Equipment__r.Maintenance_Cycle__c.intValue());
            }
            newRequests.add(newReq);
        }
        Database.SaveResult[] sr= Database.insert(newRequests);
        
        Map<Id,Id> new_old_CaseIdMap=new Map<Id,Id>();
        Integer indexSR=0;
        For(Case c:newRequests){
            new_old_CaseIdMap.put(c.Old_Case__c,sr[indexSR++].getId());
        }
        List<Work_Part__c> workPartList=[SELECT Id,Maintenance_Request__c
                                         FROM Work_Part__c
                                         WHERE Maintenance_Request__c IN :new_old_CaseIdMap.keySet()];
        For(Work_Part__c wp:workPartList){
            wp.Maintenance_Request__c=new_old_CaseIdMap.get(wp.Maintenance_Request__c);
        }
        update workPartList;
    }
}
```
   </div>
   </details>

続き：[Apex Specialist Super Badge 復習[Challenge 2]](https://qiita.com/jitao/private/e4534b5d624c422d451a)