プログラマ38の日記

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

Salesforce: 編集ボタンを上書きしたら、参照画面でインライン編集ができなくなることの対策

Visualforceで編集ボタンを上書きしたら、参照画面でインライン編集できなくなるんですね。

[編集ボタン上書き前の参照画面]

f:id:crmprogrammer38:20170609125901p:plain

[編集ボタン上書き後の参照画面]

f:id:crmprogrammer38:20170609130209p:plain

 

インライン編集できないと不便なので、参照ボタンも次のVisualforceで上書きしてインライン編集ができるようになりました。inlineEdit="true"にするのがポイントでした。 (Object1__cは、上書きしたいオブジェクトAPI名に変更してください)

<apex:page standardController="Object1__c">
<apex:detail
subject="{!Object1__c.Id}"
relatedList="true"
relatedListHover="true"
inlineEdit="true" />
</apex:page>

 

 

 

Salesforce: Decimal.round()とDecimal.setScale()は、RoundingModeを指定しないとHALF_EVENです

1円の単位で金額がおかしくなる現象が発生

Decimalのroundは引数を指定しないと、HALF_EVENでした。

RoundingMode.HALF_UPを引数で指定しないと四捨五入になりませんでした。

横着してはいけないですね。。

 

以下RoundingMode別のround後の値です。

 

RoudingMode
指定無 HALF_EVEN HALF_UP HALF_DOWN CEILING FLOOR UP DOWN
2.5 2 2 3 2 3 2 3 2
3.5 4 4 4 3 4 3 4 3
-2.5 -2 -2 -3 -2 -2 -3 -3 -2
-3.5 -4 -4 -4 -3 -3 -4 -4 -3

 

 また、setScaleも同様にRoundingModeを指定しないと、HALF_EVENです。

Decimal num1 =   0.25;
Decimal num2 =   0.35;

System.debug( num1.setscale(1) );
System.debug( num2.setscale(1) );

 上記の結果は、

num1: 0.2
num2: 0.4

 となるので、setScaleの際も、RoundingModeは省略しないで書く必要があります。

num1.setscale(1,RoundingMode.HALF_UP)

Salesforce: Id.getSobjectType() を使ってオブジェクトを特定する。

親オブジェクトがどれかを判断したいときがある

 

添付ファイル(Attachment)の 参照先 ID(ParentId)

ToDo(Task)の 関連先 ID(WhatId)
行動(Event)の 関連先 ID(WhatId)

など複数の参照先オブジェクトを指定できる項目があります。

 

トリガ処理などで、特定のオブジェクトの時に動かしたい処理があって、その時には、Id.getSobjectType() で特定ができます。

 

 

例えば、顧客の添付ファイルは、Excel以外はエラーにしたいという要件があったとします。

for( Attachment row : Trigger.New ){
if( row.parentid.getSobjectType() == Account.sobjecttype) {
if( row.Name.endsWithIgnoreCase('.xlsx') == false
&& row.Name.endsWithIgnoreCase('.xls') == false ) {
row.addError('Excelファイルを登録してください。');
}
}
}

こんな風にして、参照先のオブジェクトの判断ができます。

 

この関数を知らなくてオブジェクトのキープレフィックスを取得して、IDの頭3桁で比較したり、その前は、IDをSetにつめこんで、オブジェクトを1回検索して判断していました。

昔書いたコードは大分無理をしていました。。

 

 

Salesforce: 1度のSOQLで選択リストの値(表示ラベル)とAPI 参照名を取得する。

toLabelで表示ラベルを取得する

例えば次のような選択リスト項目「PickTest__c」があります。

f:id:crmprogrammer38:20170608132834p:plain

 

1) 項目をselectした場合

選択リスト項目は、オブジェクトには、API 参照名がセットされていて、Apexコードと実行結果は次の通りです。

[Apex]

List<Sample__c>  rows = [Select PickTest__c  From Sample__c];

for(Sample__c row : rows){
System.debug(row.PickTest__c);
}

[実行結果]

a
b
c

 

2) tolabelを使用した場合

tolabel関数で表示ラベルとなります。(翻訳が指定されている場合は、翻訳後の値となり、翻訳が指定されていなければ、値になります)ここでの注意したいのが、SOQLでtolabel関数を使用しても、Apexコード上は項目名をそのまま使用するという点です。

[Apex]

List<Sample__c>  rows = [Select tolabel(PickTest__c)  From Sample__c];

for(Sample__c row : rows){
System.debug(row.PickTest__c);
}

[実行結果]

ラベルA
ラベルB
ラベルC

 

3) 項目の値と、tolabel後の表示ラベルを両方取得する。

tolabelを使って、表示ラベルを取得しつつ、項目の値も取得するには、tolabelの項目に別名をつけます。(つけないとエラーになります)別名を取得する際には、SObject.getで取得します。

[Apex]

List<Sample__c>  rows = [Select PickTest__c, tolabel(PickTest__c) label_pick From Sample__c];

for(Sample__c row : rows){
System.debug('項目の値: ' + row.PickTest__c + ' 表示ラベル:' + row.get('label_pick'));
}

[実行結果]

項目の値: a 表示ラベル:ラベルA
項目の値: b 表示ラベル:ラベルB
項目の値: c 表示ラベル:ラベルC

 

その他) 参照先の項目にtolabelを指定する時は別名はつけられないようです。(エラーとなりました)

次のSOQLはエラー
List<SampleSub__c> rows = [Select tolabel(Parent__r.PickTest__c) alias_pick From SampleSub__c];

次のSOQLはエラーとならない
List<SampleSub__c> rows = [Select tolabel(Parent__r.PickTest__c) From SampleSub__c];

そして、親オブジェクトから子オブジェクトのサブクエリでは、tolabelを指定した項目に別名をつけなければ結果が返ってきます。が、別名をつけると、結果が返却されません。

[Apex]

//サブクエリで、別名はつけない
List<Sample__c> rows = [Select id , (select id , tolabel(PickSub__c) from SampleSub__r ) from Sample__c];

for(Sample__c row : rows ){
List<SampleSub__c> subrows = row.SampleSub__r;

for(SampleSub__c subrow : subrows ){
System.debug( subrow.getPopulatedFieldsAsMap()); //何が返却されているか
}
}

[実行結果] 結果にtolabel後の項目が含まれる

{Id=a0J0H00000gdbdzUAA, Parent__c=a04i0000010nS6NAAU, PickSub__c=ラベルsubc}
{Id=a0J0H00000gdbe4UAA, Parent__c=a04i0000010nS6OAAU, PickSub__c=ラベルsubb}
{Id=a0J0H00000gdZrOUAU, Parent__c=a04i0000010nS6PAAU, PickSub__c=ラベルsuba}

 

//サブクエリで、別名をつける
List<Sample__c> rows = [Select id , (select id , tolabel(PickSub__c) alias_pick from SampleSub__r ) from Sample__c];

for(Sample__c row : rows ){
List<SampleSub__c> subrows = row.SampleSub__r;

for(SampleSub__c subrow : subrows ){
System.debug( subrow.getPopulatedFieldsAsMap()); //何が返却されているか
}
}

[実行結果] 結果にtolabel後の項目自体がない

{Id=a0J0H00000gdbdzUAA, Parent__c=a04i0000010nS6NAAU}
{Id=a0J0H00000gdbe4UAA, Parent__c=a04i0000010nS6OAAU}
{Id=a0J0H00000gdZrOUAU, Parent__c=a04i0000010nS6PAAU}

 

 

※この記事は、APIVer39のタイミングで作成しました。

Salesforce: Excel VBAからSalesforce API呼び出しと、TLS1.0無効化の件

SalesforceとはTLS1.0では通信できない

脆弱性のため、TLS1.0は無効となり、ブラウザの設定変更、APIを使ったアプリケーションの対応が必要となりました。

 

javaなどバージョンアップすることで対応できるものはいいのですが、VBAは対応が難しいなと思っています。というのも、VBAの通信はOSのバージョンで決められているからです。

ExcelからSalesforceのデータの取得・更新、もしくはメタデータの操作などができるととても便利なんですが、Windows7でTSL1.0の通信となってしまうことが難点になります。

 

VBAからSalesforceAPIを利用する

SalesforceAPIVBAでコールします。次がログインする時のサンプルコードです。

 

Option Explicit

Sub sampleSF()
  Dim loginxml As String
  '接続用のxml
  loginxml = _
"<?xml version=""1.0"" encoding=""UTF-8""?><env:Envelope  " & _
"  xmlns:env=""http://schemas.xmlsoap.org/soap/envelope/"" xmlns:xsd=""http://www.w3.org/2001/XMLSchema"" " & _
"  xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"">  " & _
" <env:Body>  " & _
" <m:login xmlns:m=""urn:partner.soap.sforce.com"" xmlns:sobj=""urn:sobject.partner.soap.sforce.com"">   " & _
"  <m:username>sample@sample.user</m:username>   " & _
"  <m:password>samplepassword</m:password>  " & _
" </m:login> " & _
" </env:Body>  " & _
"</env:Envelope> "

Dim http As Object
'WinHttp.WinHttpRequest か MSXML2.XMLHTTP を使う
'Set http = CreateObject("WinHttp.WinHttpRequest.5.1")   
Set http = CreateObject("MSXML2.XMLHTTP")
http.Open "POST", "https://login.salesforce.com/services/Soap/u/38.0", False
http.SetRequestHeader "Content-Type", "text/xml; charset=utf-8"
http.SetRequestHeader "SOAPAction", "login"
http.Send loginxml

' 結果出力
MsgBox http.responseText

'XMLをパースする処理を作成する。
・・・・・
End Sub

Windows7だと、標準だとTLS1.0が指定されるのが問題

 上のサンプルは、Windows10では問題なく動作しますが、Windows7だと動作しません。Windows7の通信がTLS1.0だからです。

 

Windows7でTLS1.1以上にする指定はVBAのObjectごとに次の通りです。

WinHttp.WinHttpRequest.5.1:パッチをあて、レジストリを修正することで対応可能

MSXML2.XMLHTTP:対応方法無し

 

一括登録ツールとか、数式を駆使した入力フォームなんかVisualforceよりExcelの方が向いてるときが多いんですよね。でも、Windows7の壁が立ちはだかります。

2020年にWindows7のサポートが切れるのを気長に待ちますかね。。

 

でも、ここ最近Windows10を使うことが多くなってきたので気にせずにExcel VBAからSalesforce APIをコールするマクロを作成しています。

作成したのは次のExcelマクロになります。

  • ロール作成
  • カスタム項目作成(対応していないデータ型があります)
  • オブジェクト一覧と項目一覧の取得
  • カスタム表示ラベルの作成と一覧取得
  • explainの実行
  • AnonymousApexの実行
  • ナレッジのカテゴリ一覧の取得
  • レイアウト一覧とレイアウト項目の取得
  • プロファイルのIPアドレス制限一覧の取得
  • アプリケーション毎のタブ一覧の取得

[ロールの作成]

[カスタム項目の作成]

crmprogrammer38.hatenablog.com

[オブジェクト・項目の一覧取得]

crmprogrammer38.hatenablog.com

 


[カスタム表示ラベルの作成・一覧の取得]

crmprogrammer38.hatenablog.com

[explainの実行]

crmprogrammer38.hatenablog.com

[AnonymousApexの実行]

crmprogrammer38.hatenablog.com

[ナレッジのカテゴリ一覧の取得]

crmprogrammer38.hatenablog.com

[レイアウト一覧とレイアウト項目一覧の取得] 

crmprogrammer38.hatenablog.com

 [プロファイルのIPアドレス制限一覧の取得]

crmprogrammer38.hatenablog.com

crmprogrammer38.hatenablog.com

 

Salesforce: カスタムボタンで、WebServiceの戻り値をMsxml2.XMLHTTPでsendする際にはまったこと

カスタムボタンで、Msxml2.XMLHTTPを使って別のシステムと接続する際にはまったことのメモになります。

 

やりたかったことは、

1.カスタムボタンで、文字列を戻すカスタムWebServiceをコール。

2.戻り値で取得した文字列を、Msxml2.XMLHTTPのsendで別システムに送信。

3.送信した結果を画面表示(alert)。

です。

 

どこではまったかというと2のsendです。

なぜだかうまくいかなくて、取得した戻り値をtoString()して文字列にしたらsendできました。

//カスタムWebService
global class SampleWS {
webservice static String sample1(String param){
return 'returnvalue:' + param;
}
}
//カスタムボタンの処理
{!requireScript('/soap/ajax/39.0/connection.js')}
{!requireScript('/soap/ajax/39.0/apex.js')}

var ret = sforce.apex.execute('SampleWS' , 'sample1' , { param : 'パラメータ' } );

var xmlhttp = new ActiveXObject ("Msxml2.XMLHTTP");
xmlhttp.open('POST', 'http://xxxx.xxxx/xxxx/xxxx', false);
xmlhttp.send(ret); ← これだとエラー
xmlhttp.send(ret.toString()); ← これだとうまくいく

理由は明確にはわかりませんでしたがtoString()で動いたのでOKとしました。

 

Salesforce: コマンドラインのメタデータのエクスポートツールを作りました

メタデータを使うシーンは多いです。

1.開発時のバックアップ

2.デプロイ用のモジュール

3.モジュールリリース後の確認

 

上記のような場面で何かと必要となるメタデータですが、「Force.com IDE」や「Force.com Migration Tool」のメタデータダウンロード機能だと、少し使いづらさを感じてツールを作成しました。

下記からダウンロードできます。ソースコードも同梱していますので、どういうコードを書いているかを確認いただけます。※もちろんユーザ、パスワードを悪用はしていませんので確認いただければと思います。

 

メタデータダウンロードツールはこちら (APIVer46)

 

 ツールの概要は次です。

・Java8を利用します。ツールには含めていないので別途インストールが必要です。

・動作確認はWindows7, 8 ,10で実施しています。

 

ツールの使い方はWindowsのバッチファイル「metadatabakup.bat」を編集し、環境変数に接続情報や、通信設定を指定します。

その後、「metadatabakup.bat」を起動してください。

f:id:crmprogrammer38:20170515111120p:plain

環境変数
backup.dir

メタデータファイルを保存するパスのルート。

このパスの下に、「metadata_yyyyMMddHHmmss」のフォルダをツール側で作成してメタデータを出力します。

metadata.include

取得するメタデータを限定する場合に指定します。(任意)

「ApexClass, ApexComponent, ApexPage, ApexTrigger, CustomObject, ・・・」などメタデータAPIのlistMetadataで取得できるタイプで指定してください。「CustomField」など、listMetadataで取得できないタイプは指定できません。

下記のmetadata.excludeと一緒には指定できません。

metadata.exclude

除外するメタデータを限定する場合に指定します。(任意)

指定方法は、上記のmetadata.includeと同様です。

上記のmetadata.includeと一緒には指定できません。

sfdc.url

APIのログインURLを指定します。

https://test.salesforce.com/services/Soap/u/43.0

https://login.salesforce.com/services/Soap/u/43.0

になります。他には、インスタンスURLを直接指定する、マイドメインURLを直接指定するなどでしょうか。

sfdc.username ログインユーザ名
sfdc.password ログインパスワード。セキュリティトークンを使用する場合は、パスワード+セキュリティトークンの文字列となります。
http.proxyHost プロキシホスト。通信時にプロキシを使用する場合指定します。
http.proxyPort プロキシポート番号。通信時にプロキシを使用する場合指定します。
http.proxyUser プロキシユーザ。プロキシでユーザ認証する場合指定します。
http.proxyPassword プロキシパスワード。プロキシでユーザ認証する場合指定します。
http.auth.ntlm.domain NTLMドメイン。ntlmドメインを使用する場合指定します。
JAVACMD

javaのコマンドを指定します。javaは8以上のバージョンが必要です。

パスが通っていれば「java」のままで、パスが通っていない場合フルパスでjavaコマンドまでを指定します。

 

後書き

「Force.com IDE」だと、メタデータの量が多いと、メタデータの選択時点で時間がかかってしまうのと、そもそもGUIのため自動化できません。
「Force.com Migration Tool」は、package.xmlを書くのがハードルが高く使いづらさを感じて、このツールを作りました。
ソースコードもつけてますので、自由に改造して使ってもらえるとうれしいです。

 

修正履歴

2017/7/24 CustomFeedFilterのダウンロード時にエラーとなる事象の対応を行いました。具体的には、CustomFeedFilterはワイルドカード指定でリクエストするように変更しています。

 

 

 メタデータエクスポートの過去バージョンは次です。

API42

API41

API40

API39