プログラマ38の日記

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

Salesforce: apex:outputText で 日付/時間型はGMTで表示される

outputTextでは、日付/時間型の項目がGMTで表示される仕様ではまりました。

時間まで出力していれば、ずれてるのは比較的気づくのですが日付までしか表示していなかったため発見に時間がかかりました。。

 

[日付がずれる書き方]

<APEX:OUTPUTTEXT value="{0, date, yyyy/MM/dd}">
    <APEX:PARAM value="{!items.datetimefield }"></APEX:PARAM>
</APEX:OUTPUTTEXT>

 [日付がずれない書き方]

<APEX:OUTPUTTEXT value="{0, date, yyyy/MM/dd}">
    <APEX:PARAM value="{!items.datetimefield +9/24}"></APEX:PARAM>
</APEX:OUTPUTTEXT>

 

上記のように9/24を足すことで9時間の補正となります。

ユーザ別の各タイムゾーンに対応したい場合は、apex class内でDatetime.formatでフォーマットした文字列を表示をしたほうがいいです。

 

というのは、yyyy/MM/dd などのフォーマット自体が各地域で異なりますので。

Salesforce: JavaScript FileReaderとsforce.connectionを使って添付ファイルを登録する。

写真を保存したり、書類のPDFを保存したりなどSalesforceにファイルを保存することは多いと思います。

 

写真はプレビューで確認してから登録したり、複数ファイルをまとめて登録する時など、ファイルを登録する際に便利なのがJavaScriptのFileReaderだと思いますが、FileReaderを使って添付ファイルを登録する際のメモです。

 

<script src="/soap/ajax/39.0/connection.js"></script>


<script>

var attachments = [];

//onchangeなどでファイル名とファイルデータを取得後呼ばれる
function fileload(filename,filedata){

var attachment = new sforce.SObject("Attachment");
attachment.parentId = {!parentId}; //どこかのオブジェクトID
attachment.name = filename;

reader.onload = function(e) {

var bytes = new Uint8Array(reader.result);
var binaryText = '';
for (var index = 0; index < reader.result.byteLength; index++) {
binaryText += String.fromCharCode(bytes[index]);
}
attachment.body = window.btoa(binaryText);
attachments.push(attachment);
};

reader.onloadend = function () {
//ここで登録ボタンを押せるような制御を行う
}
reader.readAsArrayBuffer(filedata);
}

//ファイル登録ボタンなどから呼ばれる
function createAttachment(){
sforce.connection.sessionId = "{!$Api.Session_ID}";
var result = sforce.connection.create(attachments);

//登録後の処理
}
</script>

Salesforceの添付ファイル(Attachment)のbodyには、base64エンコードした文字列をセットする必要があるのがポイントです。

Salesforce: キューをデータローダで作成する。

キューをデータローダで作成したい

キューもかなりの数になると画面から設定で作成するのは大変ですし、ミスも増えてくると思います。そこでデータローダでキューを作成する手順の紹介です。

 

[キューに関連するデータモデル]

f:id:crmprogrammer38:20170609142039p:plain

オブジェクトとキューの登録画面のセクションとの対応は次の通りです。

  • グループ:キュー名とアドレス
  • キューのSオブジェクト:サポートされるオブジェクト
  • グループメンバー:キューメンバー

 

[データローダでの登録手順]

 

1) グループに登録する。登録したキューに振られたIDを取得する。

2)1)で取得したキューのIDとSオブジェクト種別(LeadやCase、カスタムオブジェクトなど)でキューのSオブジェクトを登録する。

3)1)で取得したキューのIDとユーザID/公開グループやロールIDで、グループメンバーを登録する。

 

[補足]

キューを作ると、勝手にそのキューに対応するビューが作成されてしまいます。

 

 

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のタイミングで作成しました。