[Apex]レポートの集計値を取得する方法


この記事は [Lancers（ランサーズ） Advent Calendar 2020](https://qiita.com/advent-calendar/2020/lancers) 14日目のエントリーです。

## はじめに
ランサーズ株式会社でSalesforce管理者兼開発者を担当している久保です。
4月に新卒として入社し、6月ごろからSalesforceチームメンバーとして、ゴリゴリ開発させていただいております。（ランサーズでのSalesforce運用の歴史は[コチラ](https://qiita.com/m-tsuchiya/items/6a14a5ba866ea6761190)）

さて、今日はApexでレポートデータを取得する過程を深ぼろうと思います。
というのも、「公式のApex開発者ガイドに取得方法が載っているが、一体どういうロジックなのか分からない」という人の助けになれたらと思ったためです。

レポートの集計値を取りたいと思ったキッカケは、日次の売上金額推移をSlack通知したいと思ったことからです。レポート作成スナップショットで日次の金額をレコードに保存し、レポートでそれらのレコードの金額集計を弾き出して、Slack通知させるロジックを書くときに必要になりました。

## 前提
今回の解説では、下記レポートの枠部分の値を取得することを目標とします。
つまり、フェーズ別商談金額レポートのフェーズがClosedWonの時の金額合計値を取得します
（下記はTrailheadのPlayground組織のテストデータです。詳細行はカットしてます）
![スクリーンショット 2020-12-13 14.47.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631504/8f926893-eef6-d2e8-18f2-470db157478e.png)
## 取得手順
①抽出対象のレポートからIdを取得、実行
②レポートの最初のダウングルーピングを取得
③ファクトマップの取得
④取りたいサマリー値の取得

```Apex
//①抽出対象のレポートからIdを取得、実行
List <Report> reportList = [SELECT Id,DeveloperName FROM Report where DeveloperName = 'new_report_mdR'];
String reportId = (String)reportList.get(0).get('Id');
Reports.reportResults reportResults = Reports.ReportManager.runReport(reportId, true);

//②レポートの最初のダウングルーピングを取得
Reports.Dimension dim = reportResults.getGroupingsDown();
Reports.GroupingValue groupingVal = dim.getGroupings()[8];

//③ファクトマップの取得
String factMapKey = groupingVal.getKey() + '!T';
Reports.ReportFactWithDetails factDetails = (Reports.ReportFactWithDetails)reportResults.getFactMap().get(factMapKey);

//④取りたいサマリー値の取得
 Reports.SummaryValue sumVal = factDetails.getAggregates()[1];
```

①〜④でそれぞれ何をしているのかを以下で解説します。

## ①抽出対象のレポートからIdを取得、実行

```Apex
List <Report> reportList = [SELECT Id,DeveloperName FROM Report where DeveloperName = 'new_report_mdR'];
String reportId = (String)reportList.get(0).get('Id');
Reports.reportResults reportResults = Reports.ReportManager.runReport(reportId, true);
```
まず、SOQL文で取得対象のレポートの抽出をします。
ここで条件にある`DeveloperName` とは「レポートの一意の名前」のことを指します。
下記キャプチャのように、レポート編集画面のプロパティで確認できます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631504/c82c6fcf-c6cd-a71c-5524-8764467dbea5.png)

そして、取得したリストから`get(0).get('Id')`でレポートのIDを取得します。
取得条件により、リストの中の要素は唯一つですので、`get(0)`で対象のレポートを選択できます。

次に、`ReportManager`クラスの`runReport`メソッドにより、レポートを実行します。
ただし、２番目の引数は詳細行を含むか含まないかの選択を意味します。
（※因みに`runAsyncReport`メソッドというのもあり、非同期にレポート実行させることもできます）

## ②レポートの最初のダウングルーピングを取得

```Apex
Reports.Dimension dim = reportResults.getGroupingsDown();
Reports.GroupingValue groupingVal = dim.getGroupings()[8];
```
今度は、①の`runRepot`メソッドにより実行した結果からグルーピングの情報を抽出します。
具体的には`repotrResults`クラスの`getGroupingsDown`メソッドを用いて、ダウングルーピング（＝行グルーピング）の情報を抜き取ります。（※因みに、`getGroupingsAcross`メソッドで列グルーピングの情報を取得できます）

実際、`System.debug(dim)`で中身を見てみると、確かに行グルーピングしたフェーズの情報が順に書いてあります。

```
DEBUG|Reports.Dimension[groupings=(Reports.GroupingValue[groupings=null, key=0, label=Prospecting, value=Prospecting], Reports.GroupingValue[groupings=null, key=1, label=Qualification, value=Qualification], Reports.GroupingValue[groupings=null, key=2, label=Needs Analysis, value=Needs Analysis],,,,
```

そして、`Dimension`クラスの`getGrouping`メソッドで、取得したグルーピング情報をリストとして受け取ります。
今は、取得したい値はレポートのグルーピングの上から９番目の項目である、ClosedWonの時の値なので`dim.getGroupings()[8]`とすれば、ClosedWonのキーやラベルが手に入ります。

## ③ファクトマップの取得

```Apex
String factMapKey = groupingVal.getKey() + '!T';
Reports.ReportFactWithDetails factDetails = (Reports.ReportFactWithDetails)reportResults.getFactMap().get(factMapKey);
```

さて、`GroupingValue` クラスの`getKey`メソッドで、行グルーピングのキーを取得し、ファクトマップキーを作成します。
ここでファクトマップキーとは、「どのグルーピング項目か」を表すキーのことを言います

例えば、`0!T`なら「第１グルーピングの最初の項目」、`1!T`なら「第１グルーピングの２番目の項目」、`0_1!T`なら「第１グルーピングの最初の項目と第２グルーピングの２番目の項目」のことを指します。


このファクトマップキーを用いて、`reportResults.getFactMap`メソッドでファクトマップを取得します。
取得したファクトマップは以下です。確かに、ClosedWonの時の小計値である期待収益や金額が並んでいます

```
Reports.ReportFactWithDetails[aggregates=(Reports.SummaryValue[label=￥3,645,000, value=3645000.000000000000000000], Reports.SummaryValue[label=￥3,645,000, value=3645000.000000000000000000], Reports.SummaryValue[label=18, value=18]), key=8!T, rows=(Reports.ReportDetailRow[dataCells=(Reports.ReportDataCell[label=Express Logistics and Transport, value=0012r000006VpxRAAS], Reports.ReportDataCell[label=Express Logistics Standby Generator, value=0062r000003ymAnAAI],,,
```


## ④取りたいサマリー値の取得

```Apex
 Reports.SummaryValue sumVal = factDetails.getAggregates()[1];
```

最後に、ファクトマップから取りたい小計値を取得します。
`ReportFactWithDetails`クラスの`getAggregates`メソッドで小計値のリストを取得します。
そして、今回の場合、レポートの左から２番目の行項目である「金額」を取得したいので、リストの２番目を指定すれば、完了となります。

## 最後に

レポートの集計値取得はフロー等ではできない(と思われる)ため、Apexを駆使することになります。
しかし、取得したい集計値は特定可能な数個になるかと思います。そのため、フローのApexアクションで呼び出すことができるので、取得結果を活用する事に限っては、十分フローが使えます。

以上の事柄がSalesforce開発者の方々の役に立てば本望です。
最後までお読みいただきありがとうございました。


##参考
[Apex開発者ガイド レポートデータの取得](https://developer.salesforce.com/docs/atlas.ja-jp.226.0.apexcode.meta/apexcode/apex_analytics_report_data.htm)
[Apex開発者ガイド ファクトマップの復号化](https://developer.salesforce.com/docs/atlas.ja-jp.apexcode.meta/apexcode/apex_analytics_fact_map.htm)