プログラマ38の日記

主にプログラムメモです。

Salesforce: リードの取引の開始をApexで行う時の注意事項

リードの取引の開始をまとめて行う場合に、Apex(もしくはAPIの)を使う場合があります。その際の注意事項を書いておこうと思います。

 

ApexでDatabase.convertLead(APIでconvertLead、以下Apex前提で記載します)を行う場合次の2点の考慮が必要です。

  1. リードの所有者がキューか
  2. 既存の取引先責任者、既存の商談に関連付けを行うか

1のリードの所有者がキューの場合、そのままDatabase.convertLeadを行うとエラーとなるので、Database.convertLeadを行う前に、所有者をユーザへ変更しておく必要があります。(所有者がユーザであれば、たとえそのユーザが無効ユーザであってもエラーになりません・・・)

2の既存の取引先責任者、既存の商談に関連付けする場合、LeadConvertクラスで

  • setAccountId(accountId)
  • setContactId(contactId)
  • setOpportunityId(opportunityId)

を指定しますが、setAccountIdで指定した取引先と、setContactIdで指定する取引先責任者の取引先は同じである必要があります。

同様に、setAccountIdで指定した取引先とsetOpportunityIdで指定する商談の取引先も同じである必要があります。
なので、既存の取引先責任者、商談に関連付けを行う場合はあらかじめ取引先をそろえてからDatabase.convertLeadをするか、Database.convertLeadをする前に取引先が同じかの判定をいれておく必要があります。


最後に

取引の開始では項目の対応付けの設定を使います。項目の対応付けは、Winter'20まではメタデータでしかリリースできず、メタデータの「LeadConvertSettings」で取得し、別環境へデプロイをしていました。
ですが、Spring'20で変更セットでリリースできるようになりました。

Salesforce: ディスクストレージの考慮事項

1レコード2KBだとずっと認識していました。大枠は正しいのですが、細かな部分が違っていたのであらためてまとめておこうと思います。
内容は以下のヘルプに記載の通りで、基本1レコード2KBですが、キャンペーンや記事などが例外となっています。(キャンペーンが8KB、キャンペーンメンバーが1KB、記事が4KB、メールメッセージはメールのサイズに応じて変化)

 

Salesforce ヘルプ [各種レコードサイズを教えてください]
https://help.salesforce.com/articleView?id=000318951&language=ja&type=1&mode=1

Salesforceヘルプ [データおよびストレージリソースの監視]

https://help.salesforce.com/articleView?id=admin_monitorresources.htm&type=5


そして、見落としがちなのが [データおよびストレージリソースの監視]の一番下に記載されている
有効またはアーカイブ済みの商品、価格表、価格表エントリ、および納入商品は、ストレージ使用量には含まれません。
という記載です。

 

商品、価格表、価格表エントリは商品情報を登録するものでそこまでストレージに影響はしませんが、納入商品は万能なオブジェクトなので使いやすくストレージへの貢献度を大きくあげることができます。最初から取引先、取引先責任者、商品への参照関係項目が用意されていて納入商品の中で階層構造になるように親、および階層のルートへの参照関係項目があります。

パレートの法則はストレージの容量にも当てはまり(例外もあると思いますが)、「使用ストレージの8割は利用オブジェクトの2割で占めている」となります。
利用オブジェクトの2割の中の1オブジェクトを納入商品にすることで大きくストレージの消費を抑えることができます。1オブジェクトだけ突出して件数が多い場合、ストレージを考慮して納入商品を使うメリットは最大化されます。
ただし、納入商品ではプロファイルの「レコードの作成時に監査項目を設定」をチェックしても作成者、作成日、更新者、更新日は設定できないなど他オブジェクトとの違いはあるので使い方がはまるかは事前の検証は必要です。(逆に個人的にはこれ以外で他オブジェクトとの違いは把握していませんが・・)

 

最後に

ストレージを消費しない標準オブジェクトでは、他に商談商品(商談の子オブジェクト)、品目名スケジュール(商談商品の子オブジェクト)、注文商品(注文の子オブジェクト)があり階層を持つオブジェクトでは子オブジェクトはストレージを消費しない傾向があります。

ただし見積の子オブジェクトの見積品目名はストレージを消費しますので都度調査は必要です。見積だけ特殊なのかもしれませんが。

 

雑記: ブラウザをVivaldiに変えたらSalesforce開発が楽しくなった

まず、最初にブラウザのVivaldiはサポートされていないことはわかっています。
ですが、Vivaldiを使ってちょっと便利になってSalesforce開発が楽しくなったので設定や使い方を書いておこうと思います。

 

1.タブスタッキングが便利過ぎる

f:id:crmprogrammer38:20200224184251p:plain
タブをグループで管理できます。私はChromeなどで画面上部でタブが多くなってくるとすぐに探せなくなってしまうためこの機能があるととても助かります。
Salesforceで開発を行う際には、開発中は設定画面、開発機能の動作検証用画面、ヘルプサイトやその他の情報検索の画面など数多くの画面をタブで開くので、用途に分けてタブをグループしています。
そして、Salesforceのデプロイを行う際は複数のSandboxを扱うことが多くなり、その際には環境毎でタブをグループ化しています。こうすることで環境毎の設定の確認などやりやすくなりました。
タブスタッキングに名前を付けるにはツール>設定>タブから「タブスタックの名前の変更を許可する」の設定が必要です。

f:id:crmprogrammer38:20200224185753p:plain

2.開いているウィンドウの一覧が便利

f:id:crmprogrammer38:20200224185210p:plain


この画像は1のタブグループのタブをウィンドウ表示しています。画面が小さい場合、常にウィンドウを表示させておくのは難しいかもしれませんが、ある程度余裕があれば常にウィンドウ表示をしておくことで自分の使いたいタブを探す時間が短縮できます。

3.開発者コンソールが別ウィンドウにならなくて便利

Chromeだと開発者コンソールが別ウィンドウになります。これはこれで便利だという人はいると思いますが、個人的に別ウィンドウは非常に煩わしいと感じていました。
Vivaldeではツール>設定>外観から「タブでポップアップを開く」の設定をすることで別ウィンドウではなく同一ウィンドウでの表示となります。

f:id:crmprogrammer38:20200224192141p:plain

この設定をすると、例えばタブスタイルやClassicのルックアップ画面などのポップアップもタブでの表示となりますが特に気にはなりませんでした。

最後に

今まではChromeを使っていましたが、個人的にタブが多すぎた時の対処に困っていました。(拡張機能などで対処可能なのだと思いますが、色々試しても自分に合う拡張機能が見つけられませんでした)
冒頭にも書いていますが、Vivaldiの動作はサポートはされていないので最終的な動作はChromeなどで実施することになりますが、開発中くらいは好きなブラウザを使って開発作業を楽しみたいと思います。

Salesforce: アプリケーションメニューから、コミュニティを非表示にする

結論としては、アプリケーションに表示されているコミュニティは、プロファイルのアプリケーション設定ではなくアプリケーションメニューから非表示にできます。

 

以下詳細です。

コミュニティのメンバーとなったプロファイルや権限セットを持つユーザではアプリケーションでそのコミュニティが表示されます。

[コミュニティのメンバー]

f:id:crmprogrammer38:20200224115655p:plain

[アプリケーションの表示]
コミュニティ1、コミュニティ2、コミュニティ3がログインユーザがメンバーとなっているコミュニティ

f:id:crmprogrammer38:20200224115912p:plain

 

アプリケーションが不要の場合には、設定>アプリケーションメニューで非表示にできます。

f:id:crmprogrammer38:20200224120505p:plain


そうするとアプリケーションからコミュニティを非表示にできます。範囲は組織全体となります。

f:id:crmprogrammer38:20200224120808p:plain

アプリケーションメニューの設定は、
Spring'20時点ではメタデータとして設定が取得できないようです。

Salesforce:Lightning showToastイベント(e.force:showToast)について調べたこと

データ保存時にメッセージを表示するのに、showToastイベント(e.force:showToast)を使うことがあります。

showToastイベントについて調べたことの備忘メモです。

1.mode (画面の表示の仕方)  は 3 種類

dismissible : ×クリックで消える。durationで指定した時間経過で消える。

f:id:crmprogrammer38:20190903193244p:plain
sticky : ×クリックで消える。時間経過では消えない。

f:id:crmprogrammer38:20190903193255p:plain
pester : ×クリックできない(表示がない)。durationで指定した時間経過で消える。

f:id:crmprogrammer38:20190903193316p:plain

2.type (色やアイコン) は 5 種類

f:id:crmprogrammer38:20190903194049p:plain : info

f:id:crmprogrammer38:20190903194107p:plain : success

f:id:crmprogrammer38:20190903194121p:plain  : warning

f:id:crmprogrammer38:20190903194131p:plain : error

 f:id:crmprogrammer38:20190903194138p:plain : other

3.一度に表示できるのは 3個 のメッセージ

連続してshowToastイベントを実行しても一度の画面表示は3つまでのようです。
同時に3つ以上実行しても、表示されているメッセージが消えてから次のメッセージを表示するという動きをします。

f:id:crmprogrammer38:20190903194603p:plain

4.タイトルを指定すると、メッセージのフォントが小さくなる

f:id:crmprogrammer38:20190903194919p:plain

5.メッセージを改行するには別途スタイルシートが必要

次のような改行区切りのメッセージをToastで表示しようとすると、うまく改行されません。
メッセージ内容:
 表示メッセージ1
 表示メッセージ2
 表示メッセージ3

画面表示: 

f:id:crmprogrammer38:20190911122550p:plain

Toast内で改行する場合は、スタイルシートの指定が必要です。

1.次のスタイルシートを静的リソースに登録します。

.toastMessage.forceActionsText{
     white-space : pre-line !important;
}

2.Lightning Componentのcmpで、外部CSS リソースを読み込みます。

<ltng:require styles="{!$Resource.MultiLineToast}" />

 ※静的リソースには「MultiLineToast」として登録しました。


上記1,2を実施後の画面表示は次の通りとなります。

f:id:crmprogrammer38:20190911123514p:plain

 

最後に

この調査をするにあたって作成したLightning Componentのサンプルは次になります。

cmp:

<aura:component implements="lightning:isUrlAddressable,force:appHostable,flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId,forceCommunity:availableForAllPageTypes,force:lightningQuickAction" access="global" >
  <ltng:require styles="{!$Resource.MultiLineToast}" />
    
    <lightning:layout multipleRows="true">
        <lightning:layoutItem  size="4" padding="around-small" largeDeviceSize="4" smallDeviceSize="12">

    <lightning:card title="Toastイベントサンプル">
        <table class="slds-table slds-table_bordered slds-table_cell-buffer">
            <thead>
              <th>#</th>
              <th>mode</th>
              <th>type</th>
              <th></th>
              
            </thead>
            <tbody>
                <tr><td>1</td><td>dismissible</td><td>info</td><td><lightning:button aura:id="dismissible info" variant="brand" label="showToast" onclick="{!c.showMyToast}" /></td></tr>
                <tr><td>2</td><td>dismissible</td><td>success</td><td><lightning:button aura:id="dismissible success" variant="brand" label="showToast" onclick="{!c.showMyToast}" /></td></tr>
                <tr><td>3</td><td>dismissible</td><td>warning</td><td><lightning:button aura:id="dismissible warning" variant="brand" label="showToast" onclick="{!c.showMyToast}" /></td></tr>
                <tr><td>4</td><td>dismissible</td><td>error</td><td><lightning:button aura:id="dismissible error" variant="brand" label="showToast" onclick="{!c.showMyToast}" /></td></tr>
                <tr><td>5</td><td>dismissible</td><td>other</td><td><lightning:button aura:id="dismissible other" variant="brand" label="showToast" onclick="{!c.showMyToast}" /></td></tr>

                <tr><td>6</td><td>sticky</td><td>info</td><td><lightning:button aura:id="sticky info" variant="brand" label="showToast" onclick="{!c.showMyToast}" /></td></tr>
                <tr><td>7</td><td>sticky</td><td>success</td><td><lightning:button aura:id="sticky success" variant="brand" label="showToast" onclick="{!c.showMyToast}" /></td></tr>
                <tr><td>8</td><td>sticky</td><td>warning</td><td><lightning:button aura:id="sticky warning" variant="brand" label="showToast" onclick="{!c.showMyToast}" /></td></tr>
                <tr><td>9</td><td>sticky</td><td>error</td><td><lightning:button aura:id="sticky error" variant="brand" label="showToast" onclick="{!c.showMyToast}" /></td></tr>
                <tr><td>10</td><td>sticky</td><td>other</td><td><lightning:button aura:id="sticky other" variant="brand" label="showToast" onclick="{!c.showMyToast}" /></td></tr>

                <tr><td>11</td><td>pester</td><td>info</td><td><lightning:button aura:id="pester info" variant="brand" label="showToast" onclick="{!c.showMyToast}" /></td></tr>
                <tr><td>12</td><td>pester</td><td>success</td><td><lightning:button aura:id="pester success" variant="brand" label="showToast" onclick="{!c.showMyToast}" /></td></tr>
                <tr><td>13</td><td>pester</td><td>warning</td><td><lightning:button aura:id="pester warning" variant="brand" label="showToast" onclick="{!c.showMyToast}" /></td></tr>
                <tr><td>14</td><td>pester</td><td>error</td><td><lightning:button aura:id="pester error" variant="brand" label="showToast" onclick="{!c.showMyToast}" /></td></tr>
                <tr><td>15</td><td>pester</td><td>other</td><td><lightning:button aura:id="pester other" variant="brand" label="showToast" onclick="{!c.showMyToast}" /></td></tr>
            </tbody>
        </table>
    
    </lightning:card>
            </lightning:layoutItem>
        </lightning:layout>
</aura:component>

 js:

({
  showMyToast : function(component, event, helper) {
        
        var modestr = event.getSource().getLocalId().split(' ')[0];
        var typestr = event.getSource().getLocalId().split(' ')[1];
        
        
        var toastEvent = $A.get("e.force:showToast");
        toastEvent.setParams({
            mode: modestr,
            type : typestr,
            //title: '表示タイトル',
            message: '表示メッセージ1\n表示メッセージ2\n表示メッセージ3'
        });
        toastEvent.fire();
  }
})

 

Salesforce:Lightning Promiseを使ってサーバコールを順番に処理する

Lightning Componentのクライアントサイドコントローラで業務ロジックを書く時に、サーバサイドコントローラのメソッドを順次呼び出したり、全部の処理が完了した後にメッセージ表示をしたりしたい時があります。

例えば、次のようなhelperとcontrollerを用意したとします。

//helper.js    
    invokeApex1 : function(arg, component, event, helper) {
        var serveraction = component.get('c.sampleCall1');
        serveraction.setParams({
            "value": arg
        });
        
        serveraction.setCallback(this, function(response){
            var state = response.getState();
            if (state === "SUCCESS") {
                //ロジック
            }
        });
        $A.enqueueAction(serveraction);
    }
    ,
    invokeApex2 : function(arg, component, event, helper) {
        var serveraction = component.get('c.sampleCall2');
        serveraction.setParams({
            "value": arg
        });
        
        serveraction.setCallback(this, function(response){
            var state = response.getState();
            if (state === "SUCCESS") {
                //ロジック
            }
        });
        $A.enqueueAction(serveraction);
    }    
//controller.js    
    handleClick : function(component, event, helper) {
        helper.invokeApex1('invoke01',component, event, helper);
        helper.invokeApex2('invoke02',component, event, helper);
		
        var toastEvent = $A.get("e.force:showToast");
        toastEvent.setParams({
            "title": "Success!",
            "message": "処理が完了しました。"
        });
        toastEvent.fire();
    }

 上記controllerの handleClick 実行時には、helper.invokeApex1 helper.invokeApex2 が完了していなくても、"処理が完了しました。" と表示されます。
これは、$A.enqueueAction が非同期のためです。

 

 上の処理をPromiseを使って書き直すと

//helper.js   
invokeApex1 : function(arg, component, event, helper) {
        return new Promise(function(resolve, reject){
            var action = component.get('c.sampleCall1');
            action.setParams({
                "value": arg
            });
            
            action.setCallback(this, function(response){
                var state = response.getState();
                if (state === "SUCCESS") {
                    //業務ロジック
                    resolve(response.getReturnValue());
                } else {
                    reject(new Error(response.getError()));
                }
            });
            $A.enqueueAction(action);            
        });
    }    
    ,
    invokeApex2 : function(arg, component, event, helper) {
        return new Promise(function(resolve, reject){
            var action = component.get('c.sampleCall2');
            action.setParams({
                "value": arg
            });
            
            action.setCallback(this, function(response){
                var state = response.getState();
                if (state === "SUCCESS") {
                    //業務ロジック
                    resolve(response.getReturnValue());
                } else {
                    reject(new Error(response.getError()));
                }
            });
            $A.enqueueAction(action);
            
        });
    }    
//controller.js   
    handleClick : function(component, event, helper) {
        
        const invokeApex1 = helper.invokeApex1('invoke01' ,component, event, helper);
        
        invokeApex1.then(function (response){
            //response は、invokeApex1のresolveの引数なので、invokeApex1の実行結果が使えます
            return helper.invokeApex2('invoke02' ,component, event, helper);
        }).then(function (response){
            //response は、invokeApex2のresolveの引数なので、invokeApex2の実行結果が使えます
            var toastEvent = $A.get("e.force:showToast");
            toastEvent.setParams({
                "title": "Success!",
                "message": "処理が完了しました。"
            });
            toastEvent.fire();                
        } , function (error){
            //エラー発生時の制御
        });
    }

 上記のように書き換えることで、非同期処理でも順番に実行することができます。

最後に

次のようにサーバサイドコントローラのメソッドをコールする場合、$A.enqueueAction(action); を一度に実行されます。そして、例えばSOQLの発行回数などはまとめて実行したメソッド全体で加算されていきます。

helper.invokeApex1('invoke01',component, event, helper); 
helper.invokeApex2('invoke02',component, event, helper);

特に、PromiseやsetCallback の中で次のactionを順番に制御しなくてもいい場合でも、まとめて実行してしまうと予想外のガバナ制限エラーが発生してしまうので注意です。

Salesforce:Lightning tableのヘッダを固定する

以前のVisualforceではtableタグのヘッダ固定は、JavaScriptスタイルシートで自前で設定してきました。
ですが、Lightning Componentのスタイルシート(すなわち、SLDSのCSS)ではあらかじめヘッダ固定用のスタイルが定義されています。
※今のVisualforceではSLDSのタグを使えるので、Lightning Componentと同様にヘッダが固定できます。(こちらのサイトにサンプルがありました)

これを使うと簡単にヘッダ固定の次のようなtableが作れます。

f:id:crmprogrammer38:20190814143608p:plain


ポイントですが、下記のソース中に青文字にしています。

<div class="slds-table--header-fixed_container slds-color__border_gray-5" style="height:300px; border-style:solid;border-width:1px;">
  <div class="slds-scrollable_y" style="height:100%;">
    <table class="slds-table slds-table_bordered slds-table--header-fixed slds-table_cell-buffer">
      <thead>
        <tr class="slds-text-title_caps">
          <th><div class="slds-cell-fixed" title="取引先名">取引先名</div></th>
          <th><div class="slds-cell-fixed" title="業種">業種</div></th>
          <th><div class="slds-cell-fixed" title="国(請求先)">国</div></th>
          <th><div class="slds-cell-fixed" title="都道府県(請求先)">都道府県</div></th>
          <th><div class="slds-cell-fixed" title="市区郡(請求先)">市区郡</div></th>
          <th><div class="slds-cell-fixed" title="町名・番地(請求先)">町名・番地</div></th>
          <th><div class="slds-cell-fixed" title="郵便番号(請求先)">郵便番号</div></th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td><div class="slds-truncate" title="Pyramid Construction Inc.">Pyramid Construction Inc.</div></td>
          <td><div class="slds-truncate" title="Construction">Construction</div></td>
          <td><div class="slds-truncate" title="USA">USA</div></td>
          <td><div class="slds-truncate" title="CA">CA</div></td>
          <td><div class="slds-truncate" title="San Francisco">San Francisco</div></td>
          <td><div class="slds-truncate" title="The Landmark @ One Market">The Landmark @ One Market</div></td>
          <td><div class="slds-truncate" title="75251">75251</div></td>
        </tr>
        <!-- 以下trタグを繰り返し  -->
      </tbody>
    </table>
  </div>
</div>

 

上記のように、タグとCSSを指定することでヘッダ固定が簡単にできます。
簡単にできる分、手を加えるのが難しそうです。

以下自分で試してみてできなかったこと(ホントはできるのかもしれません)

  • ヘッダを1行ではなくて、複数行で固定する
  • ヘッダの色を変更する(きっとできるんでしょうけど。。)
  • 列も固定

最後に

上記のタグを使って書くと、ヘッダは淡いグレーになります。
白の背景色にヘッダ固定のtableを配置すると、ヘッダの色の境目があいまいになってしまうため枠線を指定しています。(ソース内で緑色にした所です)

多少タグやスタイルを書くだけでヘッダ固定できてしまうのは便利です。列の固定は難しそうですが、あえて固定にこだわらなくても、title属性を使って行にホバーさせれば必要な情報が出るというような対応も取れそうだなと思いました。

<-- 行のタイトルをつけるサンプル  trの中のタグにtitleを付けるとtitleの表示は上書きされます -->
<tr title="固定タイトル">
  <td><div class="slds-truncate">Pyramid Construction Inc.</div></td>
  <td><div class="slds-truncate">Construction</div></td>
  <td><div class="slds-truncate">USA</div></td>
  <td><div class="slds-truncate">CA</div></td>
  <td><div class="slds-truncate">San Francisco</div></td>
  <td><div class="slds-truncate">The Landmark @ One Market</div></td>
  <td><div class="slds-truncate">75251</div></td>
</tr>