Challenge 1:レコード作成の自動化
思ったよりこれが長かった…なんとかなったのでまとめてみます。
設定について
書いてあることをそのまま実施
スキーマおよび Apex クラス&トリガのスタブコードの含まれた非管理パッケージをインストールします。ケースおよび商品を HowWeRoll のスキーマに合うように名称変更し、全てのプロファイルにそれらのオブジェクトのカスタム HowWeRoll ページレイアウトをアサインします。
指示されていないカスタム項目の作成などをしてもチェックに支障はない。
各 Apex ソースコードについて
既存のメソッドに引数を追加しても問題ない。
公式日本語訳より
(前略)…
Type (種別) が Repair (リペア) か Routine Maintenance (定期メンテナンス) の既存メンテナンスリクストがクローズされた際、将来の定期メンテナンスのための新しいメンテナンスリクストを作成します。この新しいリクエストは元のクローズされたサービスリクエストを元に同じ車両および同じ機器に紐づけられます。新しいリクエストの種別は Routine Maintenance (定期メンテナンス) に設定される必要があります。Subject (件名) は null であってはならず、Date Reported (報告日) 項目はリクエストが生成された日を反映します。別の側面として、部品は全て異なる寿命を持っている点があります。したがって関連する Work Part (作業部品) レコードに定義されているメンテナンスサイクルから計算し次の期日を設定する必要があります。もし複数の Work Part (作業部品)をメンテナンスリクエストで使用されている場合には、最も短いメンテナンスサイクルをサービス日に設定します。この自動化プロセスは単体のメンテナンスリクエストと一括リクエストの両方に対応するようデザインします。一緒にインポート予定の約 300 行のオフラインメンテナンスリクエストを正常に処理するためにシステムを一括化します。当面の間は Equipment (機器) レコード自体の変更については考慮する必要はありません。
このロジックは組織内の別の場所で使用されるため、トリガ(名称 : MaintenanceRequest) をハンドラ内のアプリケーションロジック (名称 : MaintenanceRequestHelper) と分離します。この設定によりアクションを委譲し、今後アプリケーションを拡張するのがより簡単になります。
整理すると
- 更新したレコードが以下 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 と同じもの
問題文に書かれていないこと
- After creating new Cases (Maintenance Requests), the “Work Parts” related to the closed case, must be updated with the new case.
- Work Parts are master detail to Cases, so in order to achieve the above requirement, we must enable re-parenting on the Work Part.
子オブジェクトである 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
(親)とたどりたいのであれば以下のようになる。
SELECT Case.Id,
(SELECT Work_Part__c.Equipment__r.Id FROM Work_Parts__r)
FROM Case
集計関数
平均、レコード数、最小、最大、合計と使い方は 普通の SQL と大体同じです。3 番目だけ注意。
- 項目で集計関数
- 適宜 GROUP BY
- Apex では集計関数を含んだクエリは
AggregateResult
オブジェクトのコレクションとして返される-
AggregateResult
オブジェクトはMap<String,Object>
のようなもの。 -
.get(項目名)
で取り出してキャストする。 - 集計関数の項目名は expr i ( i :0 始まりの数値)。別名をつけて使うこともできる。
-
コードなど
- トリガーは 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 つ
-
List<Case> listMaintenanceRequests - 更新後の
List<Case>
- SELECT 句は必要な項目を並べる。サブクエリは不要
- FROM Case
- WHERE 句に Type や Status の条件もつけるとコード量が減る。Id の条件は必須
-
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
-
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 対応を予め行うと楽
-
List<Case> listMaintenanceRequests - 更新後の
- listMaintenanceRequests
- workPartsWithMinCycles
- Map<Id,Decimal> minCycleDays に集計。IdはCase
- 新規 Maintenance Request を作成して insert
- 旧レコードステータスが Closed 以外であることも確認が必要
-
minCycleDays.containsKey(caseObj.Id)
を使って Date Due の場合分け。
Map<Id,Id> new_old_CaseIdMap
- workParts
- workParts を update
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;
}
}