「朝にしかできない仕事」だった

Salesforceに登録された作業実績が、別の仕組みに正しく連携されているか。連携に失敗したものが残っていないか。これを毎日確認する仕事があった。

厄介だったのは、この確認が朝にしかできなかったことだ。

データを集計するバッチ処理が夜間に走る。だから前日分の結果が出揃うのは朝。そして確認した内容は朝礼で共有する必要がある。つまり毎朝、出社してから朝礼までのあいだに、Salesforceを開いてレポートを出して、未連携のものを拾い出す——という作業が固定で入っていた。

自動化の価値は「時短」だけではなかった

こういう話を書くとき、たいていは「作業時間が何分減りました」という数字を出すものだと思う。ただ、今回いちばん効いたのはそこではなかった。

朝のいちばん忙しい時間帯を、この作業のために空けておかなくてよくなった。

作業そのものの時間が長いわけではない。それでも「朝礼に間に合わせなければならない」という制約があると、その時間は他のことに使えない。別の急ぎが入れば焦るし、遅れれば朝礼に響く。時間の"長さ"ではなく"時間帯"を拘束されることの負担は、実際にやっている人にしか分かりにくい部分だと思う。

自動化してからは、朝礼の前にはもうTeamsに一覧が届いている。自分が何時に出社しようが関係ない。この安心感が、削減できた分数よりずっと大きかった。

同じように「たいした時間じゃないから」と我慢している作業がある人は、時間の長さではなく"いつやらなければいけないか"で見直してみるといいかもしれない。

作ったもの

構成はシンプルだ。

Salesforce のレポート(毎日 自動実行)
    ↓ CSVを添付したメールが届く
Power Automate
    ├ メールを受け取る(差出人と添付で判定)
    ├ CSVを1行ずつの形に分解する
    ├ 連携に失敗している行だけを抜き出す
    ├ 必要な4項目だけを取り出す
    └ 表の形にしてTeamsに投稿

Salesforceにはレポートを定期実行して結果をメールで送る機能がある。まずこれで毎日CSVが自分宛に届くようにして、そのメールをPower Automateが受け取って処理する、という流れだ。

なぜCSVとメールなのか

見ての通り、かなり古典的なやり方だ。今どきシステム同士を繋ぐならもっとスマートな方法がある。それでもこうした理由は2つある。

ひとつは、Claudeから直接Salesforceを触ることが、この時点ではできなかったこと。以前に社内環境で「何ができて何ができないか」を調べたときの結論がそのまま効いている(詳しくはこの記事に書いた)。

もうひとつは、メール経由なら管理者に頼まずに自分で組めたこと。システム同士を直接つなぐ設定には管理者の権限が要る。一方「レポートをメールで受け取る」「届いたメールを処理する」だけなら、一般ユーザーの権限で完結する。

つまりこれは、制約の中で自分だけで完結させるための選択だった。理想の構成ではないが、動くものが今日できることのほうが大事だと判断した。

ハマった6つの罠

ここからが本題。Power Automateの式を書く部分で、想像の3倍くらい詰まった。同じところで止まる人が絶対にいると思うので、全部書いておく。

罠1:文字化けする

届いたCSVの中身が文字化けした。原因はSalesforce側のCSV出力がShift-JISという古い文字の形式になっていたこと。

解決:Salesforceのレポート設定でエンコードをUnicode(UTF-8)に変更した。処理する側で頑張るより、出す側を直したほうが早い。

罠2:CSVが1行の塊のまま分解できない

CSVを行ごとに分けようとしたのに、全部がひと繋がりのまま出てくる。改行で区切っているつもりが区切られない。

原因は改行の正体がCR+LFという2文字の組み合わせだったこと。単純な改行文字で切ろうとしても一致しない。

解決:区切り文字を直接指定する形にした。

split(base64ToString(items('For_each')?['contentBytes']), decodeUriComponent('%0D%0A'))

呪文にしか見えないと思うが、意味が分からなくても問題ない。自分も細部は分かっていない。「CSVが1行にまとまってしまう」ときはこの形と覚えておけば足りる。

罠3:フィルターが全件を通してしまう

「連携に失敗した行だけ」を抜き出したいのに、全部の行が通ってしまう。条件が効いていない。

原因は、CSVの中の値が 0 ではなく "0" だったこと。前後にダブルクォートが付いていて、見た目は同じでも別物として扱われていた。

解決:比較する前にクォートを取り除く。

@and(greater(length(split(item(), ',')), 8), equals(trim(replace(split(item(), ',')[8], '"', '')), '0'))

「見た目は合っているのに一致しない」ときは、たいてい目に見えない何かが混ざっている。前にも列名に改行が紛れ込んでいて同じ目に遭った。この手の問題は本当に気づきにくい。

罠4:Teamsに「True」とだけ届く

通知が動いた。喜んで見たら、Teamsに投稿されていたのは True の1単語だった。

原因は、投稿する内容を選ぶところでメールのHTML本文のほうを間違って選んでいたこと。似た名前の項目が並んでいて、正しいものを選べていなかった。

解決:選択肢の検索欄で「テーブル」と検索して、目当ての出力を選び直した。Power Automateは選択肢が大量に並ぶので、目で探さず検索するのが確実だった。

罠5:表の先頭に [" という記号が付く

表がTeamsに出るようになったが、先頭と末尾に [" "] という余計な記号が付いていた。

原因は、表を作る部品が中身を「配列」という形式で返してくること。そのまま貼ると、その形式の記号ごと表示されてしまう。

解決:文字列に変換してから、余計な記号を削る。

replace(replace(string(body('HTML_テーブルの作成')), '["', ''), '"]', '')

罠6:「連携失敗のみ」で絞ったのに、空のデータが混ざる

これが一番厄介だった。

Salesforce側で「連携に失敗したもの(False)」という条件を付けたのに、まだ処理されていない空のデータまで一緒に引っかかってくる。空欄がFalseと同じ扱いになる仕様だった。

そして困ったことに、この挙動は公式のドキュメントに書かれていない。実際に動かしてみて、出てくるはずのないデータが混ざっているのを見て初めて気づいた。

解決:Salesforce側での絞り込みは諦めて、Power Automate側でもう一度きちんと条件を付け直した。

ドキュメントに無い挙動は、調べても出てこない。「おかしい」と思ったら自分の書き方を疑う前に、まず出力そのものを目で確認する——これが一番の教訓だった。

この構成の弱点も書いておく

動いてはいるが、この作り方には弱点がある。正直に書いておきたい。

  • 列の順番に依存している:「左から9番目の項目」といった位置で値を取っている。レポートの列構成を変えると全部ズレる
  • メール任せのタイミング:届く時刻はメール次第で、細かい制御ができない
  • 引き継ぎに弱い:レポートの定期実行は自分のアカウントに紐づくので、異動や退職があると止まる

あと、現時点では0件のときも通知が飛ぶようになっている。「今日は問題なし」と分かるのは悪くないのだが、毎日流れると見なくなるので、0件のときは投稿しない形に直す予定だ。

(なお現在はまだ試験運用中で、投稿先は本番のチャンネルに切り替える手前の段階にある)

8月末に、この記事は古くなる予定

ここまで書いておいてなんだが、この構成はまもなく作り直すことになる

ClaudeのSalesforceコネクタが、8月末に導入される予定だからだ。Claudeが直接Salesforceのデータを取りに行けるようになれば、CSVもメールも要らなくなる。列の順番に依存する弱点も、文字コードの問題も、まとめて消える。

それどころか、単に一覧を転送するだけでなく「今週の傾向はどうか」を要約させたり、「今の未連携を教えて」と聞いて答えてもらったりもできるはずだ。今のPower Automateは決められた通りに運ぶだけなので、そこは大きな違いになる。

じゃあ今回の苦労は無駄だったのか、というとそうは思っていない。コネクタがまだ用意されていない環境は世の中にいくらでもあるし、「管理者に頼まずに自分の権限だけで組む」という制約も、多くの人が抱えているものだと思う。それに、実際に一度組んでみたおかげで「何が面倒なのか」が身体で分かった。次の構成のありがたみも、比較できるからこそ分かる。

繋がったあとの話は、また別の記事に書くつもりだ。

おまけ:どれくらいかかったか

構築にかかったのは4日ほど。ただしこれは他の仕事の隙間にやっていたからで、まとまった時間が取れれば1日で組めたと思う。

初めて触るツールで、公式ドキュメントに無い挙動まで踏んだ結果がこれなので、悪くない速さだと思っている。式の組み立てを一緒に整理してくれる相手(Claude)がいなければ、罠2と罠6は自力では抜けられなかった。