crmprogrammer38の日記

主にDWHやSalesforceのプログラムメモです。

【Salesforce】Apexで共有オブジェクト(・・Share)にロールを指定する

Salesforceで特定のユーザにデータを参照・編集させる設定は、共有設定を使うか、Apexで共有オブジェクトを操作するかになります。

 

共有オブジェクトに特定のユーザのロールを指定したい時があります。

例えば、代表者(User)を入力すると、その代表者は「参照・編集」、その代表者のロールに所属するユーザは「参照」ができるという仕組みにしたいとします。

その際はApexで共有オブジェクトを操作するのが簡単です。次のようになります。

 

  1. Apexにて、"編集"の「カスタムオブジェクトのアクセス権」で、代表者のユーザIDを共有オブジェクトに登録。
  2. Apexにて、"参照"の「カスタムオブジェクトのアクセス権」で、代表者のユーザのロールに対応するグループIDを共有オブジェクトに登録。

上記2のユーザのロールに対応するグループIDの関係は次のようになります。

f:id:crmprogrammer38:20170721130637p:plain

1つのロールに対してグループは複数あります。データイメージは次の通りです。

f:id:crmprogrammer38:20170721131335p:plain

 種別の内部の値は次の通りで、この中から指定したい種別を選びます。

種別 内部で指定する値
ロール Role
ロール & 内部下位ロール RoleAndSubordinates
ロール、内部 & ポータル下位ロール RoleAndSubordinatesInternal

 

上記を踏まえて、2の代表者のユーザのロールに対応するグループIDを共有オブジェクトへ登録する際には次のようなデータになります。

ラベル 名前 セットする値
参照先 ID ParentId 参照するレコードID
ユーザ / グループ ID UserOrGroupId ユーザのロールに対応するグループID
カスタムオブジェクトのアクセス権 AccessLevel Read
テリトリー割り当て方法 RowCause Manual もしくは 「Apex 共有の理由」の理由名

共有ボタンから手動でも参照させたい人を指定する場合、 「Apex 共有の理由」を作成してその理由名を、テリトリー割り当て方法にセットした方がいいと思います。

というのは、ApexからもManualで登録してしまうと、判断がつかなくなりますので。

【Salesforce】ユーザの「承認申請メールを受信する」項目をまとめて更新したい時

結論として、データローダや、ApexClassでまとめて更新する手段が用意されていないようです。少し作業を効率化できるちょっとしたアイデアを書きます。

 

「承認申請メールを受信する」項目をまとめて更新したい時

例えば、そもそも承認申請のメールが不要であったり、別のプログラムで承認申請時のメールを飛ばすので標準のメールは飛ばしたくない場合、ユーザの「 承認申請メールを受信する」 を”受信しない”にします。

f:id:crmprogrammer38:20170720161307p:plain

でも、 「 承認申請メールを受信する」項目は、データローダでは更新できない項目で、100名、1000名のユーザに対してでもユーザの編集画面から1件、1件変更が必要になります。

その作業を少し楽にする方法として次を考えました。

 

1.ユーザのカスタム項目で次の数式を作成します。

HYPERLINK(“/” + Id + “/e?receiveApprovalsEmails=SEND_NONE”, “初期値:受信しない" , "_blank")

2.ビューを作成し、1で作成した数式項目を表示するようにしておきます。

3.上記数式項目のリンクをクリックすると「 承認申請メールを受信する」の初期値で”受信しない”が指定された状態で表示されるので、そのまま保存します。

4.これを対象ユーザ数分繰り返します。

 

結局1件1件ですが、画面のスクロールと選択リストから選ぶ操作が省けます。

 

補足

上記の例では”受信しない”を初期値としていますが次のようにパラメータを変えることでその他の選択肢を初期値にすることができます。

「承認申請メールを受信」の値 パラメータで指定する値
承認者または代理承認者である場合 SEND_ALL
自分が承認者である場合のみ SEND_APPROVER
自分が代理承認者である場合のみ SEND_DELEGATED
受信しない SEND_NONE

【SQL】MS-ACCESSで、結合条件で大文字・小文字を区別する

MS-ACCESSの結合は、通常では大文字・小文字の区別がありません。

たまに、大文字・小文字を区別して結合をしたい時があります。
例えば、Salesforceの15桁のSalesforceIDで結合する場合などです。

 

そんな時には、「strcomp(項目1, 項目2, 0) = 0 」を使うことで、大文字・小文字を区別して結合することができます。

 

[大文字・小文字を区別しないで結合するサンプルSQL]

SELECT * FROM sample1 
INNER JOIN sample2 
ON (sample1.field1 = sample2.field1)

 

[大文字・小文字を区別して結合するサンプルSQL]

SELECT * FROM sample1 
INNER JOIN sample2 
ON (sample1.field1 = sample2.field1)
and (strcomp(sample1.field1 , sample2.field1,0)=0)

上記のように通常の結合条件の後に、strcompでバイナリ比較を行うことで大文字・小文字を区別して結合することができます。

 

[補足]

普通に結合条件の結果だけ考えれば、下記のように通常の結合条件は不要でstrcompだけで欲しい結果は得られます。

SELECT * FROM sample1 
INNER JOIN sample2 
ON  (strcomp(sample1.field1 , sample2.field1,0)=0)

 ですが、上記の記述では、結合する項目にインデックスがあっても使ってくれません。(直積の実行プランになります)

思いのほか結合処理に時間がかかってしまうので、面倒でも結合条件とstrcompの比較条件と両方の記載が必要です。

 

strcompでの比較と通常の文字の比較は次のような結果になります。

No 比較の内容 値1 値2 strcomp(値1,値2,0) iif( 値1 = 値2 , 'equal' , 'not equal')
1 同じ値での比較 ABC ABC 0 equal
2 大文字と小文字の比較 ABC abc -1 equal
3 小文字と大文字の比較 abc ABC 1 equal
4 空文字と空文字の比較     0 equal
5 文字とnullの比較 ABC <null> <null> not equal
6 nullと文字の比較 <null> ABC <null> not equal
7 nullとnullの比較 <null> <null> <null> not equal

※nullと空文字の表記が難しかったので、nullは<null>と表現しています。

 

【Salesforce】メタデータ「フィード条件(CustomFeedFilter)」エクスポート時にエラーとなる件

エクスポートしようとするとエラーが発生するメタデータコンポーネントがあります。
それは、「フィード条件(CustomFeedFilter)」なのですが、エクスポートしようとすると
"UNKNOWN_EXCEPTION msg: null: Need to specify full name, Name:XXX, Delimiter:."となります。(XXXは、フィード条件の名前です。)

 

他のメタデータコンポーネントと同様に、
PackageTypeMembers.setNameで"CustomFeedFilter" , PackageTypeMembers.setMembersでFilePropertiesのfullNameを指定しているので、プログラムの書き方の問題ではないと思います。

 

APIVer39とAPIVer40のメタデータエクスポートツールを作成していますが、CustomFeedFilterのエクスポートでエラーが発生する場合は、除外するメタデータコンポーネントにCustomFeedFilterを指定してご利用下さい。

set metadata.exclude=CustomFeedFilter

 

 

crmprogrammer38.hatenablog.com

 

 

crmprogrammer38.hatenablog.com

 

【Salesforce】データローダのコマンドライン操作のメモ

ETLツールがない場合など、Salesforceとのバッチ連携はCSVファイルとデータローダのコマンドラインの組み合わせになると思います。(APIで開発は意外と手間がかかりますし)

データローダをコマンドラインで使った際のメモを書いておこうと思います。

 

日付型の形式

yyyy-MM-dd 形式(例えば、2017年7月14日なら、2017-07-14)とし、sfdc.timezoneをUTCにする。他のやり方(JSTにして、フォーマットに時間まで含める)もありますが、こちらの方が違和感ないと思います。

日付/時間型の形式

yyyy-MM-dd'T'HH:mm:ss'.000+0900'形式(例えば、日本時間2017年7月14日8時55分13秒なら、2017-07-14T08:55:13.000+0900)

日本時間でデータを入れるケースが多いと思うのでこうしました。GMT形式で9時間マイナスする処理が不要になります。

※補足ですが、SOQLで日付/時間項目への検索条件もこの形式で可能です。

マッピングファイル(sdlファイル)の書き方のポイント

  1. Salesforceの項目名は、大文字小文字を区別する
  2. 参照関係項目に、参照先の外部IDを使用する時は「CSV項目=参照先名:参照先の外部ID項目名」の形式
  3. 固定値をセットした場合は、ダブルクオートで区切ってセットする「"固定値"=項目名」

2の外部IDを使用する場合ですが、複数の親オブジェクトを指定できる項目へは使えません。また、外部IDしか使えないのが辛いところです。

APIで開発する場合は、複数の親オブジェクトを指定する項目にも外部IDを指定することができます。さらに外部IDだけでなく、IdLookupが可能な項目が全て使えますので、ユーザオブジェクトの場合、ユーザオブジェクトの外部IDだけでなく、ユーザ名(Username)、メール(Email)、SAML 統合 ID(FederationIdentifier)も使うことができます。ETLツールのアダプタによっては対応してるものもあると思います。)

サンプルで用意されているprocess-conf.xmlxml宣言が省略されている

なぜかサンプルでは、xml宣言が省略されているのでutf-8で保存しないとエラーになります。マルチバイトを使わなければ問題はないですが、マルチバイトを使った時にうっかりshift_jis(CP932の方)で保存すると実行時にエラーになります。

バッチ処理の中でprocess-conf.xmlを出力したいことが多いと思いますし、その際にutf-8文字コードでの保存が面倒なので、きちんとxml宣言を追加してshift_jisで保存するようにしておけば楽だと思います。

sampleのprocess-conf.xml

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
    <bean id="accountMasterProcess"
          class="com.salesforce.dataloader.process.ProcessRunner"
          singleton="false">
        <description>AccountMaster job gets the Customer record updates from ERP (Oracle financials) and uploads them to salesforce using 'upsert'.</description>
        <property name="name" value="accountMasterProcess"/>
 ・・・・

 文字コードを指定した後のprocess-conf.xml

<?xml version="1.0" encoding="Windows-31J" standalone="no" ?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
    <bean id="accountMasterProcess"
          class="com.salesforce.dataloader.process.ProcessRunner"
          singleton="false">
        <description>AccountMaster job gets the Customer record updates from ERP (Oracle financials) and uploads them to salesforce using 'upsert'.</description>
        <property name="name" value="accountMasterProcess"/>
 ・・・・

 

所感

process-conf.xmlのテンプレートをextract、insert、upsert、deleteで用意しておいて、処理別に値を書き換えて使うようにすると使いやすいなと感じています。

また、個人的には、値の書き換えは環境変数を展開するようにしています。

 

環境変数の展開前(extractの場合)

<?xml version="1.0" encoding="Windows-31J" standalone="no" ?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
  <bean id="export" class="com.salesforce.dataloader.process.ProcessRunner" singleton="false">
    <description>export</description>
    <property name="name" value="export"/>
    <property name="configOverrideMap">
      <map>
        <entry key="process.operation"                 value="extract"/>
        <entry key="process.enableExtractStatusOutput" value="false"/>

        <entry key="sfdc.endpoint"                     value="%sfdc.endpoint%"/>
        <entry key="sfdc.username"                     value="%sfdc.username%"/>
        <entry key="sfdc.password"                     value="%sfdc.password%"/>
        <entry key="sfdc.timeoutSecs"                  value="600"/>
        <entry key="sfdc.loadBatchSize"                value="200"/>
        <entry key="sfdc.timezone"                     value="UTC"/>
        <entry key="sfdc.insertNulls"                  value="false"/>
        <entry key="sfdc.extractionRequestSize"        value="500"/>


        <entry key="sfdc.entity"                       value="%sfdc.entity%"/>
        <entry key="sfdc.extractionSOQL"               value="%sfdc.extractionSOQL%"/>

        <entry key="dataAccess.name"                   value="%daaAccess.name%"/>
        <entry key="dataAccess.writeUTF8"              value="false"/>
        <entry key="dataAccess.type"                   value="csvWrite"/>

      </map>
    </property>
  </bean>
</beans>

としておいて、環境変数に上記の変数を用意しておきます。

そして、VBScriptのWScript.Shellオブジェクトの、ExpandEnvironmentStrings関数で環境変数を展開するのが便利です。

 

次がExpandEnvironmentStrings関数を利用して環境変数を展開するプログラムサンプルです。

仕様:引数にShift_JISのファイル名を指定し、ファイルのテキストの環境変数を展開して標準出力に出力する

Option Explicit

Dim objFSO      ' FileSystemObject
Dim objFile     ' ファイル読み込み用

Dim file        'ファイル名
Dim filetxt     'ファイル文字列

file=WScript.Arguments(0)
Set objFSO = WScript.CreateObject("Scripting.FileSystemObject")
If Err.Number = 0 Then
    Set objFile = objFSO.OpenTextFile(file)
    If Err.Number = 0 Then
        filetxt = objFile.ReadAll
        objFile.Close
    Else
        WScript.Echo "ファイルオープンエラー: " & Err.Description
    End If
Else
    WScript.Echo "エラー: " & Err.Description
End If

Set objFile = Nothing
Set objFSO = Nothing

'変数
Dim objShell
Dim tmp
 
'シェルオブジェクト作成
Set objShell = CreateObject("WScript.Shell")
 
'環境変数展開
tmp = objShell.ExpandEnvironmentStrings(filetxt)
 
'環境変数表示
WScript.Echo tmp
 
'シェルオブジェクト破棄
Set objShell = Nothing

 

【Salesforce】データ移行時のトリガスキップについて考えたこと

データ移行では、既存のデータを確実に移行したいのでトリガを動かしたくない時があると思います。その制御の方法について考えてみました。

No.3のユーザのカスタム項目の制御がいいかなと思っていますが、要件として、トリガをスキップしたい時スキップしたくない時がある場合は、No.4のカスタムオブジェクトの項目の制御になると思います。

 

No 制御で使う設定 スキップの制御 メリット デメリット
1 カスタム表示ラベル 制御用にカスタム表示ラベルを用意し、その値がtrueの時はトリガの先頭でreturnする。 実装がシンプル。

beforeトリガ/afterトリガも確実にスキップできる。
カスタム表示ラベルをtrueにすると他のユーザもトリガスキップされてしまうため、運用開始後に、再度移行が難しい。
2 ユーザのプロファイル 移行用のプロファイルを用意し、そのプロファイルの時は、トリガの先頭でreturnする。 実装がカスタム表示ラベルの次にシンプル。

beforeトリガ/afterトリガも確実にスキップできる。

移行のためだけにプロファイルを用意するのは意外と手間。

3 ユーザのカスタム項目 ユーザオブジェクトに、制御用のカスタム項目を用意し、その値がtrueの時はトリガの先頭でreturnする。

実装がカスタム表示ラベルの次にシンプル。

 

beforeトリガ/afterトリガも確実にスキップできる。

あまりないかなと思います。
4 カスタムオブジェクトの項目 データを登録したいカスタムオブジェクトに制御用のカスタム項目を用意し、その値がtrueの時はトリガの先頭でreturnする。 移行時だけでなく、どのユーザでもトリガをスキップしたいタイミングでスキップが可能。

beforeトリガは確実にスキップできる。
実装が込み入っている。
また、複数行の更新時に、トリガをスキップしたい行とスキップしたくない行が混在すると制御がほぼ不可能なので、まとめて更新する際は、トリガのスキップの指定を混在できない。

仕様によってはafterトリガのスキップが困難な場合がある。

 

実装例は次のようになります。

No.1  カスタム表示ラベルの制御

trigger SampleiTrigger on Sample__c (after delete, after insert, after undelete, after update,
        before delete, before insert, before update) {


  if(System.Label.IKOFLAG == 'TRUE'){
    return;
  }
//処理

 No.2 ユーザのプロファイルの制御

trigger SampleiTrigger on Sample__c (after delete, after insert, after undelete, after update,
        before delete, before insert, before update) {
  
  User u = [select Profile.Name from User where Id =:Userinfo.getUserId()];
  
  if(u.Profile.Name == '移行'){
    return;
  }

 No.3 ユーザのカスタム項目の制御

trigger SampleiTrigger on Sample__c (after delete, after insert, after undelete, after update,
        before delete, before insert, before update) {
  
  User u = [select TriggerSkipFlag__c from User where Id =:Userinfo.getUserId()];
  
  if(u.TriggerSkipFlag__c){
    return;
  }

 

 No.4 カスタムオブジェクトの項目の制御

trigger SampleiTrigger on Sample__c (after delete, after insert, after undelete, after update,
        before delete, before insert, before update) {
  
  Boolean triggerskip = false;

   for(   Sample__c s : Trigger.New ){
    if( s.TriggerSkipFlag__c ){
      triggerskip = true;
//trueのままにしておくと、次に更新した際に無条件にトリガがスキップされる
//のを防ぐためフラグは常にfalseに戻す
      s.TriggerSkipFlag__c = false;
    }
  }

  if ( triggerskip ){
    //afterトリガ用にstatic変数にトリガスキップのフラグをたてる。
    Cls_StaticValue.triggerskip = true; 
  }

  if( triggerskip || Cls_StaticValue.triggerskip ){
    return;
  }

 

No.4の実装にすると、1つのトランザクション内で、何度も同じオブジェクトを登録・更新する場合、どこかでトリガをスキップすると、トリガはスキップされ続けてしまいます。Trigger.InsertやTrigger.Afterなどを使えばある程度まで細かく制御はできると思います。

 

【Salesforce】EventLogFileのユニークキー

監査ログとしてEventLogFileの内容をデータベース、Database.com、AnalyticsCloudなど別のシステムに蓄積したいという要件があると思います。

 

ユニークなキーが欲しいなと思い調べたのですが、結論として、イベントログのCSVの中には、キー項目はありませんでした。(ある程度の項目を連結すればユニークにはなると思いますが、それもちょっと違うかなと思いました)

 

一瞬、「REQUEST_ID」項目がユニークかなと期待したのですが、重複しているレコードがあり使えませんでした。

 

なので、ユニークな項目は処理の中で用意することになりますが、個人的には次がいいかなと思います。

 

「TIMESTAMP」項目の先頭8桁(yyyyMMddで取得できます)とCSVの行番号を連結

 

行番号は処理の中で作り出さないといけないのですがこれならユニークになるので、同じファイルを2回取り込んでしまっても、upsertの動きが実現できます。

 

※余談ですが、TIMESTAMP項目はGMTなので、日本時間で使う際には9時間足す必要があります。