
Salesforceで簡単に作ったCSVをWindows仕様にして共有ドライブに置く力技(要Outlook)
はじめまして。エネルギーと食のベンチャーである弊社ですが、諸事情あってシステムサポートの体制を整えるにあたり、とりあえずのWordpressサイトを用意したので、情報システムのメンバーで交代でブログを書いていくことにしました。技術的な覚書を中心に書いていくので、拙いながらも、検索でたどり着いた似た悩みを持つ方のお役に立てると嬉しいです。
このドメインのサイトで紹介するサービスは、まもなく公開していきますので、それまでお待ちください。
ブログ第一回目は、まだ入社して一年経っていない新参者の私です。職歴はPHPが一番長いです。
この会社に入って初めてSalesfoeceに触れ、VBAを久しぶりに触りました。
さて、今日は、タイトルの通りのミッションをクリアしたときの覚書ですが、正確にはSalesforceで作ったCSVを、ミロク情報システム(MJS)のアプリに取り込める状態にするのが目的でした。
これまでVBAでマスタ管理をしていたのですが、それをSalesforceに移行することになりました。
それだけならよかったのですが、このマスタ情報は、最終的に経理で使用しているMJSに取り込む必要がありました。社内LAN上の共有ドライブに、MJSで設定した通りのフォルダに、設定した通りの名称と形式のCSVを入れます。これを、バッチで定期的に取り込んでいます。
- SalesforceでCSVを作成する
- CSVをLAN上の指定フォルダに入れる
これだけを行えばいいのですが、Salesforceのこともミロクのことも良く分かっていないので、覚書を残したくなるようなことがいくつかありました。
1 SalesforceでCSVを作成する
- apex
- Visualforceページ
- メールテンプレート
CSVを作る方法、この三通りを検討することにしました。一番楽そうなものからを試すことにしました。
何行もあるCSVを作るときは、apexとVisualforceの組み合わせなどで実現しますが、今回は、更新のあったレコード一行だけ、トリガでCSVにしてMJSが取り込める場所に送ればよかったので、簡単にできそうだと軽く思いました。
MJSがCSVを取り込める場所……。
本当はBUFFALOのLANディスクに直接送りたいのですが、難しかったので、メールで送ることにしました。
~メールテンプレート編~
結論を言えば、失敗です。その理由は後で書きますが、仕様が合うのであれば便利だと思うので、参考に記入しておきます。
設定画面で「メール」で検索すると 管理 > メールテンプレート が引っかかるので、そこから作成します。
メールテンプレートの「新規作成」をおすと、まず形式を求められるので、「Visualforce」を選択してください。ここから、本文と添付CSVをまとめて作れます。
1 2 3 4 5 6 7 |
<messaging:emailTemplate subject="なんとかマスタを更新しました" recipientType="User" relatedToType="Nantoka__c"> <messaging:plainTextEmailBody rendered="true"> マスタを更新しました。 </messaging:plainTextEmailBody> <messaging:attachment filename="なんとかマスタ.csv">レコード区分,データレコード区分,会社コード,商品コード,連想,フリガナ,正式名称,【略】 0,●●,,{!relatedTo.nantokaCd__c},,{!relatedTo.nantokaKN__c},{!relatedTo.Name},【略】</messaging:attachment> </messaging:emailTemplate> |
こんなふうにして、
<messaging:emailTemplate>で囲んだ内容がメールになります。
subject=”メールのタイトル”
recipientType=”メール受信者のオブジェクト(sfユーザならUser・取引先責任者ならContact)”
relatedToType=”このメールが関わるオブジェクト”
この二つのオブジェクトの内容を差し込んで表示するとができます。
<messaging:plainTextEmailBody>で囲んだ内容が、メール本文です。今回はなくてもいいくらいなので簡潔です。
そして
<messaging:attachment>で囲んだ内容が、そのまま、添付ファイルとして送られてくるのです。CSVだけでなくPDFにもできるそうです。
relatedToTypeで指定したオブジェクトの内容に{!relatedTo.nantokaCd__c}でアクセスできます。桁数などの成形にVisualforceタグが使えます。
素晴らしい! 簡単すぎる!
そう思ったのですが、
- ファイル名が固定(本当は日付を入れたい)
- ファイル内の日本語がURLエンコードされる
URLエンコード問題は、PDFだと文字コードを指定することで解決できるそうなのですが……。
きっと私が知らないだけだと思い、Salesforceの掲示板にきいてみました。
メールテンプレートではなく、トリガとコールアウトクラスを使うように言われたので、無理なんだな、と察しました。ありがとうございました。そのやり方を次に書きます。
~Visualforce編~
結論として、苦し紛れに成功です。
せっかくメールテンプレートにVisualforce形式でCSVを書いたので、これをコピペして使えるならその方がいいです(relatedTo→オブジェクト名への変換は必要ですが)。
管理画面から、ビルド > 開発 > Visualforce ページ でもいいですし、開発者コンソールでもいいですし。
繰り返しますが、今回は一行だけでいいので、apexで複数行を出すプログラムはいりません。更新したばかりの一行についてだけ、CSVを出力できればいいのです。
1 2 |
<apex:page standardController="Nantoka__c" contentType="text/csv;charset=Shift-JIS;#nantoka.csv" readOnly="true">レコード区分,データレコード区分,会社コード,商品コード,連想,フリガナ,正式名称,略 0,●●,,{!Nantoka__c.nantokaCd__c},,{!Nantoka__c.nantokaKN__c},{!Nantoka__c.Name},略</apex:page> |
これで ~salesforce.com/apex/nantokaCsv?id=【SalesforceのID】 とアクセスするだけで「nantoka.csv」というCSVに。
contentType=”text/csv;charset=Shift-JIS;#nantoka.csv” で、文字コードも指定できます。
改行できないので、Salesforce上のエディタだとすごく見づらいです。
以下に、成形の覚書。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
//<apex:outputtext>でフォーマット指定、間に挟んだ<apex:param>で引数指定です。 <apex:outputtext value="{0, number,0}" rendered="{!Nantoka__c.hinshuCd__c != null}"><apex:param value="{!Nantoka__c.hinshuCd__c}"></apex:param></apex:outputtext> //value="{0, number,0}" // 小数点以下は0桁。これがないと、勝手に.00で出力されるため、MJSが取り込めないと怒る。 //value="{0, number,0.00}" とすると、.00で出力される //rendered="{!Nantoka__c.hinshuCd__c != null}" // hinshuCd__cがnullでないときだけ出力。 // 上の桁合わせのとき、空白のとき0が出てくるため、空白のときは何も出さないようにする。 <apex:outputtext value="{0, date,YYMMdd}"><apex:param value="{!Nantoka__c.kikanSt__c}"></apex:param></apex:outputtext> //value="{0, date,YYMMdd}" // これは日付のフォーマットです。 |
ファイル名はテキトーでいいのです。
このCSVを送るときに指定するので。でも、上で指定した文字コードは有効です。
CSVをメールで送る方法は、トリガにapexです。
~apex編~
やっていません!
しかし終わりまで読むと、最初からapexで、ファイルを一行一行出力した方がいいかもしれないと判断する状況も、かなりありえるので、そのときは別のサイトを調べてがんばってください。
2.CSVをLAN上の指定フォルダに入れる
BUFFALOのウェブアクセスで直接送り込めないか、隅々まで調べましたが、無理そうなので、メールで送ることにしたのは、前述のとおり。
トリガで送ります。メールテンプレートが使えないので仕方ありません。
~SalesforceからのCSVメール送信~
トリガ
1 2 3 4 5 6 |
trigger NantokaUpsertTrgAfter on Nantoka__c (after insert,after update) { //csvをメールで送る for (Nantoka__c Im : Trigger.new) { NantokaCsvSend.sendCSVlight(Im.Id); } } |
こちらのサイトを非常に参考にさせていただきました。
[Salesforce]Apexで添付ファイルつきのメールを送信する
http://dackdive.hateblo.jp/entry/2015/06/20/172603
メール送信クラス
PageReference関数で、動的にファイルを取り込むことが、コールアウトクラスでないとトリガで呼び出せません。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
public class NantokaCsvSend { @future(callout=true)//CSV取込のため必須 public static void sendCSVlight(ID nantokaId) { Datetime nowDatetime = Datetime.now(); string strDate=''; strDate=nowDatetime.format('yyyyMMddHHmmss', 'JST'); Messaging.SingleEmailMessage msg = new Messaging.SingleEmailMessage();//メールのクラス msg.setSubject('なんとかマスタが更新されました');//メールタイトル msg.setPlainTextBody('なんとかマスタが更新されました。ID=' + nantokaId);//メール本文 msg.setToAddresses(new String[]{'kumiko.nantoka@mail.co.jp'});//メール宛先 Messaging.EmailFileAttachment att = new Messaging.EmailFileAttachment();//添付ファイルのクラス att.setFileName('なんとかマスタ1'+strDate+'.csv');//ファイル名指定。変数付き PageReference page = new PageReference('/apex/nantokacsv?id=' + nantokaId);//★csv取得★ Messaging.EmailFileAttachment att2 = new Messaging.EmailFileAttachment();//二つ添付CSVが必要なのでした att2.setFileName('なんとかマスタ2'+strDate+'.csv'); PageReference page2 = new PageReference('/apex/nantokacsv2?id=' + nantokaId); //テストではPageReferenceで取得した内容を使えないのでテスト時専用に仮ファイルを用意。面倒くさい。 if (Test.IsRunningTest()){ Blob bodyBlob=Blob.valueOf('TestAttachment'); att.setBody(bodyBlob); att2.setBody(bodyBlob);//二つ目の添付ファイルにも使い回し }else{//実際の動作 att.setBody(page.getContent()); att2.setBody(page2.getContent()); } msg.setFileAttachments(new Messaging.EmailFileAttachment[]{att,att2});//メールに添付ファイルをセット Messaging.sendEmail(new Messaging.SingleEmailMessage[]{msg});//メール送信 } } |
これでkumiko.nantoka@mail.co.jpあてに、CSVがメールで送られてくるようになりました。
~Outlookマクロによるファイル移動~
メーラーにOutlook2013を使っていたので、VBAでマクロを組み、ファイルを移動させることにします。(VBA脱却でSalesforceに移行するはずだったのにおかしいな……)
Outlookの「開発」タブ > VisualBasic からマクロを作ります。
「開発」タブがないときは、
「ファイル」タブ(アカウント情報などに戻ります) > オプション > リボンのユーザー設定 で、
右のメニュー選択から「開発」をチェックしてください。
こちらを非常に参考にさせていただきました。
受信したメールの添付ファイルを自動保存するマクロ
https://outlooklab.wordpress.com/2007/03/10/受信したメールの添付ファイルを自動保存するマ/
ファイル移動はすんなりできるようになったのですが、ここで問題が発覚。
MJSは、改行コードがCR+LFかつ、最後行にも改行がないと取り込んでくれない。
しかしSalesforceのVisualforceで書き出したCSVは、改行がLFで、最終行は改行なしでファイルが終わる。
Visualforceでなんとかできないものかと、お世話になっているフォーラムで質問したところ、APEXで改行コードを挿入しながらCSVを作るといいって回答をいただきました。ありがとうございました。
今更CSVから作り直すのが嫌すぎたので、VBAからDOSコマンドを実行して、Windowsの標準的なCSVに変換することにしました。最初からわかってたらapexでCSV作ったかもしれない。でもOutlookは使う気満々だったからわからないな。Outlookを使ってなかったら、どうしたんだろうなあ。そんなことをもやもや考えながらも、先に進みます。
非常に参考にさせていただきました。
MS-DOSコマンドの標準出力を取得する
http://officetanaka.net/excel/vba/tips/tips27.htm
最終的に、こんなマクロができました。さっきまでVisualforceとapexだったのに、VBAです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
Public Sub SaveNantokaCsv(ml As MailItem) 'メールが引数。メールの仕分けに仕込むと受信したメールが対象に Dim ps As String Dim ps2 As String Dim objSH Dim wExec Dim sCmd As String Dim sCmd2 As String Dim FileNumber As Integer Dim myPath As String Dim myPath0 As String Dim buf As String ps = "C:nantokacsv" 'ローカルで一時保存するフォルダ ps2 = "\共有きょうゆうシステムMJSマスタ更新" 'CSVを置く共有フォルダ Set objSH = CreateObject("WScript.Shell") 'Dosコマンドを動かすためにシェルを作成 For Each csv In ml.Attachments '添付ファイルの数だけループ csv.SaveAsFile (ps & csv.DisplayName) '上で指定したローカルドライブに保存 'Dosのmoreコマンドでファイル変換 'more:テキトーにつくられたcsvファイルを通すと、CR+LF改行、ラスト改行ありのWindows用CSVに変換してくれるコマンド sCmd = "more < " & ps & csv.DisplayName & " > " & ps & "_" & csv.DisplayName '同じファイル名で上書きできないので_つけたファイル名で Set wExec = objSH.Exec("%ComSpec% /c " & sCmd) 'コマンド実行 Do While wExec.Status = 0 'コマンドの実行終了を待つ DoEvents Loop myPath0 = ps & "_" & csv.DisplayName '変換されたファイル名を保持 If InStr(csv.DisplayName, "マスタ1") >= 1 Then 'マスタ1 がファイル名に含まれるときは myPath = ps2 & "ほにゃららマスタ.csv" 'MJSが取り込めるCSV名をフルパスで If Dir(ps2 & "ほにゃららマスタ.csv") = "" Then 'ほにゃららマスタが存在しなければ添付ファイルそのまま使う sCmd2 = "move " & myPath0 & " " & myPath 'Dosのmoveコマンドでファイル移動 Set wExec = objSH.Exec("%ComSpec% /c " & sCmd2) Do While wExec.Status = 0 DoEvents Loop Else 'ほにゃららマスタがすでに存在していれば、追記 '読み込むファイルを読み込みモードで開く Open myPath0 For Input As #1 'ファイル番号1で(これ下みたいに空き番号でもよかったな) Do Until EOF(1) '最終行まですすめて Line Input #1, buf '内容をbufにいれて Loop Close #1 '書き込みに使うファイル番号の空きをてきとうに取得 FileNumber = FreeFile '書き込むファイルを追記モードで開いて Open myPath For Append As #FileNumber '書き込む Print #FileNumber, buf 'bufを書き込み 'ファイル閉じる Close #FileNumber End If End If If InStr(csv.DisplayName, "マスタ2") >= 1 Then 'ほぼ同上なので略 End If Next csv Set wExec = Nothing Set objSH = Nothing End Sub |
これを、 移動 > ルール >仕分けルールの作成(管理の方でも) で、こんなふうに設定します。
「実行する」のところで、上のマクロの名前を指定しています。
これで、終了……かと思ったら、マクロがセキュリティで引っかかって動きません。自作なのにひどい話もあったものです。マクロの実行設定を変えても動きません。
~Outlookのセキュリティ回避~
すでに別の自作マクロが動いているOutlookには設定済かと思います。初めの時だけやってください。
参考
Office 2013 の「VBA プロジェクトのデジタル証明書」はどこに?
http://qiita.com/NAKADANobuhiro/items/ba8f8d61486d92231037
社内用ですから、てきとーな名前でいいので、デジタル証明書を作ってください。期限も長く。
office2013の場合は C:Program FilesMicrosoft Office 15rootoffice15SELFCERT.EXE これを手順通りに実行すると、作成できます。
「ファイル」タブ(アカウント情報などに戻ります) > オプション > セキュリティセンター >
「セキュリティセンターの設定」ボタン > 信頼できる発行元 に、作った証明書を追加します
~感想~
面倒くさかった。
~注意点~
マクロを仕込んだOutlookで受信したときにしか動かないので、Outlookを落とさないようにすること。
後日談