プログラマ38の日記

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

Salesforce: 最終更新日(LastModifiedDate)とSystem Modstamp(SystemModstamp)の違い

最終更新日(LastModifiedDate)とSystem Modstamp(SystemModstamp)は、今まで特に意識もせず、同じタイムスタンプが入っているなーぐらいの認識でした。

 

ですが、「レコードの作成時に監査項目を設定」を有効化して、最終更新日(LastModifiedDate)に直接タイムスタンプを指定した時に違いがでるということを知りました。

 

例えば、監査項目を設定を有効にして次のデータを登録します。

NAME CREATEDDATE LASTMODIFIEDDATE
sample01 2017-07-27T00:44:38.000Z 2017-07-27T00:44:38.000Z
sample02 2017-07-27T00:23:48.000Z 2017-07-27T00:23:48.000Z
sample03 2017-07-27T00:31:56.000Z 2017-07-27T23:38:28.000Z

 すると登録後は、System Modstamp(SystemModstamp)は、登録時刻がセットされます。

NAME CREATEDDATE LASTMODIFIEDDATE SYSTEMMODSTAMP
sample01 2017-07-27T00:44:38.000Z 2017-07-27T00:44:38.000Z 2017-08-21T23:36:34.000Z
sample02 2017-07-27T00:23:48.000Z 2017-07-27T00:23:48.000Z 2017-08-21T23:36:34.000Z
sample03 2017-07-27T00:31:56.000Z 2017-07-27T23:38:28.000Z 2017-08-21T23:36:34.000Z

 

最終更新日(LastModifiedDate)で判定すると、直接、最終更新日(LastModifiedDate)に値を指定されると対象から漏れてしまうかもしれません。
Apexプログラムや、APIの中で、この項目の値の違いを理解して設計しておいた方がいいのだと思います。

 

最後に

監査項目に値を指定するために、以前はSalesforceの内部設定を変更してもらう必要があったのですが、Winter16から画面の設定でできるように変更になっていたんですね。やり方は次の通りでした。

設定>ビルド>ユーザインタフェース
「レコードの作成時に監査項目を設定」および「無効な所有者のレコードを更新」ユーザ権限を有効化 にチェック

f:id:crmprogrammer38:20170822090544p:plain
プロファイルで「レコードの作成時に監査項目を設定」をチェック

f:id:crmprogrammer38:20170822090758p:plain
さらに、プロファイルで「無効な所有者のレコードを更新」をチェックすると所有者に無効ユーザが指定できます

f:id:crmprogrammer38:20170822091032p:plain



Salesforce: Communityの共有セットでのデータ共有で気をつけること

Communityで、Customer Communityというライセンスの時に気をつけることのメモです。(Customer Community Plus、Partner Communityは関係ありません)

 

  1. 共有ルールは使えず、共有セットを使う
  2. 1に伴い、Sharingのオブジェクトは使えない
  3. 1に伴い、共有セットの制限を越えたデータを見せるには、without sharing指定をしたVisualforce画面を作成する
  4. 1に伴い、共有セットの制限を越えた添付ファイルを見せるには、without sharing指定をしたVisualforce画面にbase64形式で出力する
  5. 所有者がCommunityユーザの場合、内部の共有ルールは適用されない

1. 共有ルールは使えず、共有セットを使う

SalesforceユーザとCommunityユーザのデータの共有は共有セットで行います。共有セットでは、ロール、公開グループ、ユーザを指定することができ、指定したロール、公開グループ、ユーザには、常にCommunityユーザのデータが見えます。(条件に応じて見せるということができません)

f:id:crmprogrammer38:20170821091018p:plain

2. 1に伴い、Sharingのオブジェクトは使えない

共有ルールが使えないので、Sharingのオブジェクトにレコードを作成することで共有させることができません。

3. 1に伴い、共有セットの制限を越えたデータを見せるには、without sharing指定をしたVisualforce画面を作成する

共有セットでは、最大で、ユーザから辿れる取引先と参照するレコードから辿れる取引先とが一致するレコードしか参照できません。

f:id:crmprogrammer38:20170821091621p:plain
上記を超えた範囲のデータを見せる業務の場合は、Visualforceで開発することになります。(例えば、基本はそれぞれの代理店内のデータを見せるように共有セットを指定しているが、代理店を統括する代理店には、統括下のデータを見せるなどが考えられます)

あえて、without sharingのキーワードを入れましたが、classに何も指定しなければwithout sharingがデフォルトです。そして、Visualforce画面ではデータのセキュリティを個別に実装する必要になります。仮にURLパラメータでIDを直打ちされたとしても、参照できるデータのみ見せるような制御が必要です。

4. 1に伴い、共有セットの制限を越えた添付ファイルを見せるには、without sharing指定をしたVisualforce画面にbase64形式で出力する

通常の"/servlet/servlet.FileDownload?file=AttachmentのID"を使うと共有セットの範囲を超えた添付ファイルでは参照権限のエラーとなります。

そこでコントローラ側で工夫が必要になります。例えば画像ファイルを見せたい場合は、コントローラ側で添付ファイルをbase64文字列に変換して、img タグに直接文字列を指定します
「<img src="data:image/png:base64,xxxxx..." />」
バイナリファイルをダウンロードさせたい場合は、コントローラ側で添付ファイルをbase64に変換した値をVisualforceに書き出しておき、JavaScriptのFileWriterなどで保存させるような作りにします。(これ以外のやり方だとコピーして参照可能なレコードを作成することになりますが、このコピーしたデータを定期的に削除するような仕組みが必要です)こちらも3と同様に仮にURLパラメータでIDを直打ちされたとしても、参照できるデータのみ見せるような制御が必要です。

5. 所有者がCommunityユーザの場合、内部の共有ルールは適用されない

Communityのデータと、内部の共有ルールを組み合わせて運用したい場合があります。例えば、Communityのデータで指定された選択リストの値に応じて参照できる部門を変えたい場合などがあると思います。

この場合は、所有者がCommunityユーザのままでは、共有ルールの仕組みが動かないため、データ作成時に所有者を変更することで解決します。
気をつける点として、所有者はデータ作成時に変更されてしまうため共有セットでの指定で所有者が使えません。ですので、別に取引先または取引先責任者の別項目を設けて共有セットはその別項目で指定します。そして、データ作成時に、その別項目にユーザの取引先や取引先責任者の情報を保持する処理が必要です。

Java: interfaceやabstract classが理解できた時の思い出

最初のプログラム言語はJavaでした。インスタンスやクラス、インターフェースや抽象クラス、キャストなど色々な横文字が大量に出てきて何が何やらわからず、苦労した思い出があります。

 

ある時なんとなくJavaが理解できた時の思い出です。

 

インスタンスとは

クラスをnew するとインスタンス(実体)になります。インスタンスは常にどのクラスのインスタンスなのかを持っています。例としてBigDecimalを使います。

f:id:crmprogrammer38:20170818092626p:plain

コードで書くと次になります。

    new BigDecimal(100);
    new BigDecimal(200);

 変数とは

Javaでは、継承:extends と実装: implements の仕組みがあります。
その仕組みの中で"インスタンスの参照"を格納できる変数の型に制限があります。
その制限は、インスタンスのクラスの定義で、自身のクラス以上が継承したクラスや、自身のクラス以上が実装したインタフェースのみインスタンスの参照を保持できるというものです。

BigDecimalが継承したクラスと実装したインタフェースは次の通りで、次のクラスとインタフェースがBigDecimalインスタンスを格納できます。
f:id:crmprogrammer38:20170818093725p:plain

クラスから作成したインスタンスを格納できる変数の関係は次の通りです。

f:id:crmprogrammer38:20170818094647p:plain
コードにすると次になります。

    BigDecimal decimal100 = new BigDecimal(100);
    Object obj100 = decimal100;
    Number num100 = decimal100;
    Serializable sel100 = decimal100;
    Comparable com100 = decimal100;
    
    BigDecimal decimal200 = new BigDecimal(200);
    Object obj200 = decimal200;
    Number num200 = decimal200;
    Serializable sel200 = decimal200;
    Comparable com200 = decimal200;

サンプルとしてNumber型の変数num100を例にするとnum100に格納されているのは、BigDecimalクラスのインスタンスなので、キャストをすればBigDecimal型に格納し直すこともできます。

    BigDecimal decimal100_c = (BigDecimal)num100;

 

抽象クラス

いまいちインターフェースとの使い分けがわからなかったのですが、抽象クラスは実装メソッドが持てて、そのメソッドの中で抽象メソッドも使えるというのが理解できてなんとなく使い方がわかりました。


public abstract class AbstractSample {
  
  protected void start(){
  }
  
//抽象メソッド
  abstract protected void execute();
  
  protected void end(){    
  }
  
//メソッド内で、抽象メソッドを使っておく。
  public void process(){
    start();
    execute();
    end();
  }
}

共通の部分はabstarct クラス側で実装しておいて、個別の処理はextendしたクラス側で実装を強制させることができます。似たような処理をクラスを分けて実現する場合、きれいに書けると思います。(現実は、1つのメソッドの中で分岐で実現することがほとんどだと思いますが。。)

 

終わりに

Webのフレームワークや、Javaのクライアント側のSwingなどは、抽象クラス、インタフェースを書いて覚えるにはちょうど良いと思います。
あまりやりすぎた継承や実装は好きではないですが、ちょっと工夫することで効率が大きく変わるのでオブジェクト指向は楽しいと思いました。

Salesforce: 重複ルールで作成・編集を許可している時の、標準画面以外からのデータの登録について

Salesforceで、重複ルールという機能があります。(前はなかったのですが、とても欲しかった機能でした。実装されてちょっと嬉しかったです)

 

重複ルールの設定次第では、重複レコードが他にあった際に警告を出しつつそのまま保存ができます。

この設定にした際の、API実装や、データローダやApexで気をつけないといけない点になります。

 

API実装で、重複レコードの場合に保存する方法

SOAPHeaderに、DuplicateRuleHeaderを指定する。DuplicateRuleHeaderではallowSaveをtrueにする。

  PartnerConnection pcon = Connector.newConnection(connectorConfig);
  pcon.setDuplicateRuleHeader(true, false, true);

 この指定をすることで、エラーとならずに保存ができます。
ETLツールでもSOAPHeaderの指定が可能であればこの設定ができると思います。
指定しない場合allowSaveの初期値はfalseなので、常にエラーとなります。

 

そして、APIには、事前に重複ルールに該当するか調べるメソッドPartnerConnection.findDuplicatesが用意されています。PartnerConnection.findDuplicatesでは、データ登録前に重複データあるか、ある場合の重複する他のデータを取得することができます。

  SObject sobj = new SObject();
  sobj.setType("Sample__c");
  sobj.setField("KeyField__c", "Value01");
  
  FindDuplicatesResult[] result = pcon.findDuplicates(new SObject[]{sobj});

 

データローダでは、重複レコードの場合は無条件で登録できない

データローダでは、DuplicateRuleHeaderが指定できない(APIVer40時点)ため、重複ルールで該当すると無条件でエラーとなります。

データローダではエラーではなく登録させたい場合は、重複ルールの条件に工夫が必要となります。

例えば、データローダを利用するユーザのプロファイルやロールを重複ルールの条件に追加することで、そのユーザでは登録ができるようにする対応を行います。

ApexではDMLOptionで指定する

Apexで重複ルールに該当しても、そのまま登録するにはDMLOptonの指定を行います。

Database.DMLOptions dml = new Database.DMLOptions();
dml.DuplicateRuleHeader.allowSave = true;
dml.DuplicateRuleHeader.runAsCurrentUser = true;

Account duplicateAccount = new Account(Name='duplicate');

Database.SaveResult sr = Database.insert(duplicateAccount, dml);

 

最後に

重複ルールを使う場合、標準画面のチェックが便利になった分、それ以外の考慮が必要になります。データ連携があるシステムでは、データ連携への影響を検討が必要ですし、そのオブジェクトを利用するVisualforceページを作成する場合は、そのVisualforceページの動きの確認が必要です。

Excel: セルの表をtableタグで出力するマクロをバージョンアップしました

以前、Excelの表をhtmlのtableタグに変換するマクロを作成しました。

 

crmprogrammer38.hatenablog.com

 

使っていくうちにやっぱりこうしたほうがいいかなとか考えていき、次のように改良しました。

  1. マクロの中でセルの値をValueで取得していた箇所をTextで取得するように修正
  2. 文字列の位置(上揃え、下揃え、中央揃え、右揃え)をタグに反映する

 

1は日付のフォーマットや数値の区切りはそのままhtmlになってくれた方が便利だなと思い対応しました。

2は文字の揃えをvalignやalignに反映されたほうが楽だなと思い対応しました。(あくまでExcel上で指定した設定が反映されます。例えば数値は指定しなくても表示上は右揃えですが、Excel上で設定していなければalignには反映されません)

 

ダウンロードはこちらからになります。

 

最後に

同じようなマクロやツールは、他に探せば他に色々なあったのですが、結局自分で使いやすいようにいじくれるというのが便利で、結局これを使ってます。
もっと、使いやすいツールがあると思うのですが、自分で創意工夫ができる余白がある方が楽しいんですよね。

Salesforce: APIの「renderEmailTemplate」「renderStoredEmailTemplate」を使ってみた

前回SalesforceAPIについて書きました。使ったことのないメソッドが増えているなーと思い、まず「renderEmailTemplate」「renderStoredEmailTemplate」を使ってみました。

「renderEmailTemplate」

	String endpoint = "https://login.salesforce.com/services/Soap/u/40.0";
	ConnectorConfig pconfig = new ConnectorConfig();
	pconfig.setAuthEndpoint(endpoint);
	pconfig.setServiceEndpoint(endpoint);
	pconfig.setUsername("sampleuser@sample.user");
	pconfig.setPassword("samplepassword");
	PartnerConnection pConnection = new PartnerConnection(pconfig);

	RenderEmailTemplateRequest req = new RenderEmailTemplateRequest();
	//ここでテンプレート文字列を指定する。
	req.setTemplateBodies(new String[] {"aaaaa {!Sample__c.Id} bbbb {!Receiving_User.Name}"});
	//レコードのID
	req.setWhatId("XXXXXXXXXXXXXXX");
	//受信ユーザ項目に指定するユーザID
	req.setWhoId("005XXXXXXXXXXXX");
	
	RenderEmailTemplateResult[] result = pConnection.renderEmailTemplate(new RenderEmailTemplateRequest[]{req});
	
	System.out.println(result[0].getBodyResults()[0].getMergedBody());

RenderEmailTemplateRequest.setTemplateBodiesは引数は文字列の配列で、件名や本文をまとめて指定できるようになっています。

「renderStoredEmailTemplate」

	String endpoint = "https://login.salesforce.com/services/Soap/u/40.0";
	ConnectorConfig pconfig = new ConnectorConfig();
	pconfig.setAuthEndpoint(endpoint);
	pconfig.setServiceEndpoint(endpoint);
	pconfig.setUsername("sampleuser@sample.user");
	pconfig.setPassword("samplepassword");
	PartnerConnection pConnection = new PartnerConnection(pconfig);

	RenderStoredEmailTemplateRequest req = new RenderStoredEmailTemplateRequest();
	//ここでメールテンプレートのIDを指定する。
	//メールテンプレート(EmailTemplate)オブジェクトからIDを取得する処理が必要	
	req.setTemplateId("00X0H000001V8keUAC");
	//レコードのID
	req.setWhatId("XXXXXXXXXXXXXXX");
	//受信ユーザ項目に指定するユーザID
	req.setWhoId("005XXXXXXXXXXXX");
	//この指定は必須
	req.setAttachmentRetrievalOption(AttachmentRetrievalOption.MetadataOnly); 
	
	RenderStoredEmailTemplateResult result = pConnection.renderStoredEmailTemplate(req);
	System.out.println( result.getRenderedEmail().getPlainTextBody() );
	System.out.println( result.getRenderedEmail().getSubject() );

メールテンプレート(EmailTemplate)をSOQLで検索し該当のテンプレートのIDを取得する処理が必要になります。(上の処理では書いていませんが。。)

終わりに

APIだけでなく、ApexClassでも同様の処理が用意されていて、「Messaging.renderEmailTemplate」「Messaging.renderStoredEmailTemplate」が対応します。

メールで送る文字列そのものが必要な時にはとても重宝すると思います。
ちなみに、メールテンプレートは、カスタム表示ラベルを使うことができますが、いまだに差し込み項目の種類で選べないですね。。

Salesforce: APIで用意されている機能について

SalesforceにはいくつかのAPIが用意されています。こういうことをやりたいときにこれを使えばいいというのがわかるようにまとめてみました。Rest API、Bulk APIは外していて、SOAP APIに限定しました。(APIVer40の時に書いています)

 

メソッド 処理内容 どのAPIの機能か
Enterprise/Partner
Metadata
Apex
Tooling
convertLead リードを取引先にする。      
changeOwnPassword 自身のパスワードを変更する。      
create レコードを作成する。      
delete レコードを削除する。      
describeGlobal 主に組織内のSobject情報をリストで取得する。      
describeSObject   Sobject情報を取得する。      
describeXXX その他の情報XXXを取得できる。      
emptyRecycleBin   ゴミ箱を空にする。      
executeListView   リストビューを実行し、結果を取得する。      
findDuplicates 登録する前に重複ルールに該当結果を取得する。      
performQuickActions   アクションを実行する。      
login ログインする。(セッションIDと、インスタンスURLを取得する)    
logout ログアウトする。(ログイン時のセッションIDを終了する。)    
merge    レコードをマージする。(リード、取引先、取引先責任者に限定された機能)      
process   承認プロセスの操作をする。      
query    SOQLを実行し、レコードを取得する。      
queryAll   SOQLを実行し、削除済みレコードを含めてレコードを取得する。      
queryMore    query/queryAllの次の結果を取得する。(検索結果の件数が多い場合、何度かにわけて結果を取得する)      
renderEmailTemplate   メールテンプレート文字列とレコードの値をマージする。      
renderStoredEmailTemplate    定義したメールテンプレートとレコードの値をマージする。      
resetPassword    指定したユーザのパスワードをリセットし、リセット後のパスワードを取得する。(通知メールを送るわけではない)      
setPassword    指定したユーザに指定したパスワードをセットする(ログイン後、パスワードの変更画面が表示される。)      
retrieve   SalesforceIdを指定してレコードを取得する。      
search グローバル検索のようにレコードを検索し、結果を取得する。      
sendEmail    指定した宛先、件名、本文、添付ファイルでメールを送信する。      
sendEmailMessage   Sobject「メールメッセージ(EmailMessage)」のドラフトメールを送信する。      
undelete   ゴミ箱から復元する。      
update レコードを更新する。      
upsert レコードをアップサートする。      
getServerTimestamp Salesforceからサーバ時刻を取得する。    
getUpdated 指定されたオブジェクトに対して指定された期間内に更新されたレコードを取得する。      
getDeleted 指定されたオブジェクトに対して指定された期間内に削除されたレコードを取得する。      
getUserInfo ログインしたユーザ情報を取得する。    
invalidateSessions 指定されたセッションIDを終了する。    
deploy メタデータをデプロイする。      
checkDeployStatus デプロイの状況を取得する。(deployは非同期のため定期的に状況をチェックする。)      
cancelDeploy デプロイをキャンセルする。      
deployRecentValidation クイックリリースする。      
retrieve メタデータを取得する。      
checkRetrieveStatus メタデータの取得状況を取得する。(メタデータの取得は非同期のため定期的に状況をチェックする。)      
describeMetadata メタデータタイプの一覧を取得する。      
listMetadata 指定したメタデータタイプのコンポーネント一覧を取得する。      
createMetadata メタデータコンポーネントを作成する。      
upsertMetadata メタデータコンポーネントをアップサートする。      
updateMetadata メタデータコンポーネントを更新する。      
deleteMetadata メタデータコンポーネントを削除する。      
readMetadata メタデータコンポーネントを取得する。      
renameMetadata メタデータコンポーネントをリネームする。      
compileClasses クラスを作成する。(開発環境用)      
compileTriggers トリガを作成する。(開発環境用)      
compileAndTest クラスを作成しテストする。(本番環境用)      
runTests テストを実行する。    
executeAnonymous AnonymousApexを実行する。    
create Tooling API objectsを作成する。      
update Tooling API objectsを更新する。      
upsert Tooling API objectsをアップサートする。      
delete Tooling API objectsを削除する。      
search グローバル検索のようにTooling API objectsを検索し、取得する。      
query SOQLのようにTooling Api Objectsを検索し、取得する。      
queryAll 削除されたTooling API objectsを含めてSOQLのようにTooling API objectsを検索し、取得する。      
queryMore query/queryAllの次の結果を取得する。(検索結果の件数が多い場合、何度かにわけて結果を取得する)      
describeGlobal Tooling API objectsの一覧を取得する。      
describeSObject 指定したTooling API objectsの詳細を取得する。      
describeXXX その他のTooling API objectsの情報を取得する。      
retrieve Idを指定してTooling API objectsを取得する。      
getUpdated 指定された期間内に更新されたTooling API objectsを取得する。      
getDeleted 指定された期間内に削除されたTooling API objectsを取得する。      
runTestsAsynchronous 非同期でテストを実行する。      

 ※APIメソッドはすべてではありません。

 

まとめていく中で、Tooling APIの使い道がピンときませんでした。開発者コンソールの一部では使っていると思いますが、自分の中ではこういう所で使えそうというイメージが持てませんでした。

Enterprise/Partner APIと同じ名前のメソッドが、Tooling APIで定義されていますが、取得するのがTooling API Objectsというメタデータ寄りの定義情報になります。

メタデータの作成や取得をするならMetadata APIの方を使うことになりますし、Tooling APIだけが持っているメソッドはほぼありません。

Tooling API Objectsがいまいち理解できていないのだと思いますが、今は使う場面がありません。

 

あと、APIを使う上での注意事項は次になります。

  • 基本logoutは使いません。データ連携などで、同じユーザが同じタイミングで複数の処理を動作させている可能性があります。1つの処理でlogoutすると、同時に動いているほかの処理でセッションIDが無効となりエラーとなってしまいます。
  • APIのコール数の上限や、送信メール数の制限を超えないようにします。 
  • sendEmailでメールを送信する時は、エラーとなる宛先があるなどSalesforceに負荷がかかると処理が中断される場合があります。
  • レコードの作成、更新などでもSalesforceに負荷がかかると処理が中断される場合があります。