「署名依頼が飛ばない」ワークフローの不具合とは、本来は「電子署名をもらってから請求書を送る」はずのZoho CRMとZoho Signの連携で、署名依頼が送られず請求書メールだけが先に届いてしまう状態のことです。Zoho CRMではエラー画面が出ないことが多く、気づくのが遅れがちです。

この記事では、当社が支援しているお客様のZoho CRMで実際に起きた不具合をもとに、原因の切り分け方と直し方を解説します。対象は、利用者に貸与する機器の保証金について「機器を紐づける → 確認書に電子署名をもらう → 署名が終わったら請求書を送る」という自動化です。

コードは、項目名を一般的な名前に置き換えています。

起きたこと:署名を飛ばして請求書が届き、2回目以降は何も起きない

テストで報告された症状は、次の4つでした。

  1. 機器を紐づけると、署名依頼が送られず、いきなり請求書メールが届いた(本来は「紐づけ → 署名依頼 → 署名完了 → 請求書」の順)
  2. 1回目は請求メールだけが届き、請求書のレコードは作られていなかった
  3. 2回目以降は、請求メールも請求書のレコードも作られなかった
  4. 請求書PDFの備考欄に、支払期限などの「詳細情報」が入らなかった

調べると、3つの不具合(原因A〜C)と、PDFの差し込みの不具合が見つかりました。ただし、症状3の「2回目以降に何も作られなかった」理由は、当社の記録からは確定できていません。原因Aの重複チェックの誤りは、理屈のうえでは「二重に作られる」方向に働くもので、「作られない」ことを直接は説明しないためです。これらを直したあとは、2回のテストで、請求書の作成から署名依頼、署名完了後の請求書メールまでが順に動くことを確かめました。

Zoho CRMとZoho Signの連携はどう設計していたか

機器の紐づけで請求書を作るワークフロー、請求書の作成で署名依頼を出すワークフロー、署名完了の通知で請求書メールを送る流れという本来の分担を示した図

正常に動くときの流れは、ワークフロー2本と署名完了の通知の組み合わせです。

段階 きっかけ 実行される処理
WF1 連絡先(利用者)に機器が紐づけられた 重複チェック → 請求書レコードを作成
WF2 件名に「保証金」を含む請求書が作られた Zoho WriterでPDFを作成 → Zoho Signで署名依頼 → 連絡先に「確認書送付済み」を記録
署名完了 Zoho Signからの完了通知(Webhook) 請求書メールを送付

ポイントは、請求書の作成(WF1)と署名依頼(WF2)を、別々のワークフローに分けていることです。WF1の関数は請求書を作るだけで、署名依頼はWF2が受け持ちます。

この分担が、修正前のコードでは崩れていました。

原因A:重複チェックのCOQLが、存在しない項目で検索していた

COQLとは、Zoho CRMのデータをSQLに似た書き方で検索するためのAPIです(公式のCOQL Overview)。WF1の関数は、同じ連絡先に請求書を二重に作らないよう、作成前にCOQLで既存の請求書を探していました。

修正前の条件は、次のようなものでした。

// 修正前:請求書モジュールに実在しない項目で検索していた
dupQuery = Map();
dupQuery.put("select_query","select id from Invoices where ((Rental_Device = '" + device_id + "') and (Subject = '機器貸与保証金確認書'))");

Rental_Device は「機器への参照項目」のつもりで書かれていましたが、請求書モジュールには正しく参照できる形で存在していませんでした。この状態では、検索結果の data が返りません。

問題は、コード側の判定です。

// 修正前の判定:data が無ければ「重複なし」とみなす
if(dupResponse.get("data") != null && dupResponse.get("data").size() > 0)
{
	info "既存の請求書があるため中断します";
	return;
}
// ここから請求書の作成へ進む

この書き方では、「本当に0件だった」のか「検索そのものが失敗した」のかを区別できません。重複チェックは見た目だけあって、実際には何も守っていない状態でした。

修正では、検索の条件を請求書に実在する連絡先の参照項目に変えました。

// 修正後:請求書の「連絡先」参照で、同じ連絡先の保証金請求を探す
dupQuery = Map();
dupQuery.put("select_query","select id from Invoices where ((Contacts = '" + contact_id + "') and (Subject = '機器貸与保証金確認書'))");

あわせておすすめしたいのが、エラー応答を明示的に判定する書き方です。同じお客様の別の関数では、次のように書いています。

dupResponse = invokeurl
[
	url :base_url + "/coql"
	type :POST
	parameters:dupQuery.toString()
	connection:"crm_connection"
];
info "重複チェック応答: " + dupResponse;
dupStatus = null;
dupData = null;
if(dupResponse != null)
{
	dupStatus = dupResponse.get("status");
	dupData = dupResponse.get("data");
}
// 検索に失敗したときは、作成に進まず止める
if(dupStatus == "error")
{
	info "【エラー】重複チェックのCOQLが失敗しました: " + dupResponse;
	return;
}
if(dupData != null && dupData.size() > 0)
{
	info "【スキップ】既存の請求書があります: " + dupData.get(0).get("id");
	return;
}

検索に失敗したら作らずに止める。この1行があるだけで、二重作成も「黙って何もしない」も、ログから原因を追えるようになります。

原因B:ワークフローの関数アクションで、引数が対応付けられていなかった

WF2から呼ばれる署名依頼の関数は、請求書のIDを引数 invoice_id で受け取る作りでした。ところが、CRMの設定画面で、WF2の関数アクションに「invoice_id に請求書のIDを渡す」という対応付けがされていませんでした。この状態では関数の中で invoice_id が null になり、請求書が見つからず署名依頼まで進みません。

修正は、コードではなくCRMの画面操作です。ワークフロールールの関数アクションで、引数 invoice_id に請求書のIDを対応付けました。修正後、関数の実行ログで invoice_id に値が入っていることを確かめています。

対応付けが空になる現象の気づき方・全数点検・再設定の手順は、ワークフローが「成功」なのに動かない事例でくわしく紹介しています。

原因C:関数から署名の関数を直接呼んでいた

修正前のWF1の関数には、請求書を作ったあとに、署名依頼の関数をAPI経由で直接呼ぶ処理も入っていました。

// 修正前:WF1 の関数の中から、署名依頼の関数を直接呼んでいた
signUrl = base_url + "/functions/depositconfirmation/actions/execute?auth_type=apikey&zapikey=" + zapi_key + "&invoice_id=" + invoice_id;
signResponse = invokeurl
[
	url :signUrl
	type :GET
];

このようにAPIキーをURLに付けて関数を呼ぶ書き方は、キーがログやURLに残りやすいため避け、呼び出しが必要な場合は接続(Connection)を使います。

これで、署名依頼の起動経路が「WF2」と「WF1の関数からの直接呼び出し」の2本になっていました。直接呼び出しのほうは必要な情報が足りずにエラーで失敗し、WF2のほうは原因Bで引数がnull。どちらの経路でも署名依頼は送られませんでした。請求書メールが署名より先に届いた正確な経路は、当社の記録からは確定できていません。

修正では、直接呼び出しを削除し、署名依頼はWF2だけが起動する形に戻しました。

// 修正後:署名依頼はワークフロー(WF2)に任せる
info "請求書を作成しました。署名依頼はワークフロー経由で実行されます。";

起動の経路が2本あると、どちらが動いたのか、なぜ1回目だけ動いたのかを後から説明できなくなります。「1回目は動いた」という報告は、この二重経路のどちらかが進んだ結果とみられます。経路を1本にすると、挙動が再現できるようになります。

もう1つの不具合:PDFの備考欄に詳細情報が入らない

4つ目の症状は、Zoho Writerの差し込み(テンプレートに値を流し込んでPDFを作る機能)の問題でした。

Writerのテンプレートには ${Description} という差し込み欄があり、関数から渡す merge_data の Description に値を入れる作りです。修正前は、テンプレート側の定型文だけを入れており、請求書ごとの詳細情報(支払期限など)が含まれていませんでした。

// 修正前:定型文だけを渡していた
merge_data.put("Description",template_description);

// 修正後:定型文と請求書ごとの詳細(支払期限)をつなげて渡す
pdf_description = template_description + "\n" + due_date_str;
merge_data.put("Description",pdf_description);

差し込みでは、テンプレートの差し込み欄の名前と、merge_data のキーを一つずつ突き合わせるのが確実です。キーが合っていても、渡す値の組み立てが違えば欄は空のままです。Writerの差し込み文書の作り方は、Zoho Writerの差し込み文書で今使っている請求書・見積書デザインをそのまま使う方法で紹介しています。

直ったかどうかはどう確かめるか

今回の修正後は、2つのテスト用アカウントで通しのテストを行い、請求書の作成、確認書のPDF作成とZoho Signの署名依頼、署名完了後の請求書メール、備考欄への支払期限の差し込みまでを確かめました。WF2の関数が受け取った invoice_id に値が入っていることも、実行ログで確認しています。

同じ不具合を直すときは、次の順で確かめるのがおすすめです。

  1. テスト用の連絡先に機器を紐づけ、請求書レコードが1件だけ作られることを確認する
  2. 同じ連絡先で操作を繰り返し、2件目が作られない(重複チェックが効く)ことを確認する
  3. 関数の実行ログで、WF2の関数が受け取った invoice_id に値が入っていることを確認する
  4. 確認書のPDFが作られ、Zoho Signの署名依頼が送られることを確認する
  5. 署名を完了し、その後に請求書メールが届くことを確認する
  6. 請求書PDFの備考欄に、支払期限などの詳細情報が入っていることを確認する

ポイントは、1回だけでなく2回目も試すことです。今回の不具合も、2回目以降の動きで初めて報告されたものがありました。

署名依頼が飛ばない・2回目以降に動かないときは何から確かめるか

同じ症状に出会ったときは、次の切り分けチェックリストの順で確かめると早く原因にたどり着けます。

  1. 関数の冒頭で引数をログに出す。 nullなら、ワークフローの関数アクションで引数の対応付けを確認する
  2. 重複チェックのCOQLを単体で実行する。 条件に使っている項目が、そのモジュールに実在するAPI名かを確認する
  3. COQLの応答をそのままログに出す。 status が error のときに「該当なし」と同じ扱いになっていないかを見る
  4. 同じ処理を起動している経路を数える。 ワークフローと、関数からの直接呼び出しが重なっていないかを確認する
  5. 差し込みのキーを突き合わせる。 Writerのテンプレートの差し込み欄と merge_data のキーと、渡す値の中身を確認する
  6. テストは2回以上、通しで行う。 1回目の結果だけで判断しない

再発を防ぐための4つの約束

今回の対応で、当社のZoho CRM開発では次の4点を約束事にしました。

  • 関数から別の自動化関数を直接呼ばない。 ワークフローを経由させ、起動の経路を1本にする
  • COQLの条件には、実在する項目のAPI名を使う。 「関連しそうな名前」で書かず、項目の設定画面かAPIで確かめる
  • COQLの応答は、データの有無とエラーを分けて判定する。 失敗したら作らずに止め、ログに残す
  • ワークフローの引数の対応付けは、画面で必ず確認する。 コードのレビューだけでは見つからない

Delugeでの自動化の基本は、Deluge関数でタスクの自動生成を設定した記事でも紹介しています。Delugeのループや文字列判定で処理が黙って飛ぶ別の落とし穴は、ループ内のreturnが残りの明細を飛ばす事例にまとめました。自動化が思いどおりに動かない、原因の切り分けを手伝ってほしいという方は、Zoho CRMカスタマイズ・設定代行サービスからお気軽にご相談ください。

よくある質問

ワークフローの実行履歴では成功になっているのに、何も作られないのはなぜですか?

ワークフローは関数を呼び出した時点で役目を終えるため、関数の中で早期に処理を抜けても失敗とは表示されないことがあります。関数の冒頭で受け取った引数をinfoで出力し、関数の実行ログで確認してください。

COQLの検索結果が0件なのか、エラーなのかはどう見分けますか?

応答の中身をそのままログに出し、dataがあるか、statusがerrorになっていないかを分けて判定します。「dataがnullなら該当なし」とだけ書くと、エラーも該当なしとして扱われてしまいます。

関数から別の関数を呼ぶのは避けるべきですか?

同じ処理をワークフローでも起動している場合は避けます。起動の経路を一つにすると、引数の管理と原因の追跡がしやすくなります。どうしても直接呼ぶ場合は、必要な引数をすべて渡しているか、エラー応答をどう扱うかを明示し、APIキーをURLに付ける呼び出しではなく接続(Connection)を使ってください。

引数のマッピングはコードで設定できますか?

当社が対応した時点では、ワークフローの関数アクションの引数は、CRMの設定画面で項目と対応付ける必要がありました。コードを直しても画面側の設定が空のままだと値は渡らないため、画面で必ず確認してください。