Zoho FormsとZoho CRMの連携とは、Webフォームに入力された内容を、CRMのリードや連絡先のレコードへ自動で登録・更新する仕組みです。Zoho Flow(Zohoのアプリ同士やほかのサービスをつなぐ自動化ツール)を間に挟むと、登録の前後に処理を足すこともできます。

便利な仕組みですが、「行が増える表」を持つ項目を扱うときに落とし穴があります。この記事では、医療系専門職の人材紹介会社(中小)の応募フォームで実際に起きた問題と、当社が採った設計を紹介します。

何が起きたか:応募フォームで職歴が消えた

このお客様は、求職者がWebの応募フォームから送った内容を、Zoho CRMの連絡先に入れていました。フォームには次のような項目があります。

  • 氏名、メールアドレス、住所などの基本項目
  • 職歴(勤務先・職種・開始日・終了日を、何行でも追加できる表)
  • 転職サポートを希望するかのチェック項目
  • 資格証明書の画像やPDFのアップロード

CRM側では、職歴を「サブフォーム」で持っていました。サブフォームとは、1件のレコードの中に表形式で複数行のデータを持てる項目です。

構築当初は、FormsとCRMの標準連携で項目を対応づけていました。ところが、すでに職歴が入っている連絡先にフォームから送信すると、既存の職歴が消えることが分かりました。

なぜサブフォームが消えるのか:3つの問題が重なっていた

調べると、問題は1つではなく3つありました。

問題1:チェック項目が文字列の“true”で届く

フォームのチェック項目(はい/いいえを選ぶ項目)は、Flowに渡る時点で "true" という文字列になっていました。CRM側の項目は真偽値(true/falseの2値)です。Flowの標準アクション「Update module entry(レコードの更新)」でそのまま対応づけると、型が合わずにエラーになりました。

問題2:サブフォームが空で上書きされる

標準の更新処理にサブフォームを含めると、フォームから届いた内容でサブフォームが丸ごと置き換わりました。届いた内容が空だったり形式が合わなかったりすると、空で上書きされて既存の行が消えます。

なお、Zoho CRMのAPIの公式ドキュメント(Update Subforms)では、API v3以降はIDのない行を送ると既存の行に追加され、サブフォームに空の配列を渡すと全行が削除される、と案内されています。どの形で値が渡るかによって結果が変わるため、実際に送られている値をログで確かめることが大切です。

求職者が過去に登録した職歴は、紹介の判断に使う大事な情報です。消えてしまうと、担当者が聞き直すしかありません。

問題3:後から直そうとしても、もう消えている

最初に考えたのは「更新のあとにカスタム関数を動かして、職歴を直す」方法です。しかし処理の順番は次のとおりでした。

  1. Formsで送信される
  2. CRMのレコードが更新される(ここで職歴が消える)
  3. カスタム関数が動く

関数が動く時点で、既存の職歴はもうありません。後ろに処理を足すだけでは、消えたデータは取り戻せないということです。

既存のサブフォームを残すには?Flow標準とカスタム関数で「分業」する

フォームの内容のうち基本項目は標準の更新で、サブフォームなど手順が要る項目はカスタム関数で処理し、既存の行を残して新しい行を足す分業の設計を示した図

そこで、処理を役割で2つに分けました。

担当 処理する項目 理由
Zoho Flowの標準アクション 氏名・メール・住所などの基本項目 単純な対応づけで済み、画面で設定を確認しやすい
カスタム関数(Deluge) 職歴サブフォーム、チェック項目、資格証明書ファイル 型の変換・既存データの読み出し・ファイル名の変更など、手順が必要

ポイントは、標準の更新処理から、サブフォームとチェック項目の対応づけを外すことです。こうすれば、標準の更新処理が職歴に触らなくなり、問題3の「先に消える」が起きません。基本項目の更新は、関数の後でも前でも構いません。標準の更新処理がサブフォームとチェック項目に触らない構成であることが条件です。

Flowの組み立て

Flowは次の順で組みました。

  1. Zoho Forms:Entry submitted(フォーム送信をきっかけに動く)
  2. Zoho CRM:Fetch module entry(対象の連絡先レコードを取得する)
  3. カスタム関数(職歴・チェック項目・ファイルをまとめて処理する)
  4. Zoho CRM:Update module entry(基本項目だけを更新する。サブフォームとチェック項目は対応づけない)

カスタム関数には、Flowから4つの値を渡します。

  • 2で取得した連絡先のレコードID
  • フォームのチェック項目の値(文字列)
  • フォームのアップロード項目(ファイルの一覧)
  • フォームの職歴(サブフォームの一覧)

Flowの基本的な使い方は「Zoho Flowとは」で紹介しています。

カスタム関数の中身

カスタム関数は「既存を読む→新しい行を足す→まとめて書き戻す」の順で組みます。以下は、実際の関数をもとに、処理を1本にまとめて書き直した例です。お使いの環境で動作を確認してから使ってください。

前提とする項目は次のとおりです(API名は例です)。

  • 連絡先のサブフォーム Work_History(中の項目:Company・Position・Start_Date・End_Date)
  • 連絡先の真偽値項目 Support_Request
  • Formsの職歴の各行:SingleLine2(勤務先)・SingleLine3(職種)・Date_ISO8601(開始日)・Date1_ISO8601(終了日)

Formsの項目名はフォームごとに違います。 上の名前はこの事例のフォームでの例です。必ず実際の送信データをログに出し、確認した名前に置き換えてから対応づけてください。名前がずれると、勤務先と職種が別の列に入ります。

void processApplicantEntry(string recordId, string supportFlag, list uploadedFiles, list newWorkRows)
{
	// 1. 既存の連絡先を取得する
	contact = zoho.crm.getRecordById("Contacts", recordId.toLong());
	mergedRows = List();

	// 2. 既存の職歴を、空の行を除いて残す
	existingRows = contact.get("Work_History");
	if(existingRows != null)
	{
		for each row in existingRows
		{
			company = ifnull(row.get("Company"), "");
			position = ifnull(row.get("Position"), "");
			if(company != "" || position != "")
			{
				keep = Map();
				// 既存の行はIDを付けて送る(IDのない行は新しい行として追加されるため、付けないと二重になる)
				keep.put("id", row.get("id"));
				keep.put("Company", row.get("Company"));
				keep.put("Position", row.get("Position"));
				keep.put("Start_Date", row.get("Start_Date"));
				keep.put("End_Date", row.get("End_Date"));
				mergedRows.add(keep);
			}
		}
	}

	// 3. フォームから届いた職歴を後ろに足す
	if(newWorkRows != null)
	{
		for each formRow in newWorkRows
		{
			addRow = Map();
			if(formRow.containKey("SingleLine2"))
			{
				addRow.put("Company", formRow.get("SingleLine2"));
			}
			if(formRow.containKey("SingleLine3"))
			{
				addRow.put("Position", formRow.get("SingleLine3"));
			}
			if(formRow.containKey("Date_ISO8601"))
			{
				addRow.put("Start_Date", formRow.get("Date_ISO8601"));
			}
			if(formRow.containKey("Date1_ISO8601"))
			{
				addRow.put("End_Date", formRow.get("Date1_ISO8601"));
			}
			if(!addRow.isEmpty())
			{
				mergedRows.add(addRow);
			}
		}
	}

	// 4. 文字列の "true" を真偽値に直す
	supportValue = false;
	if(supportFlag != null && supportFlag.trim().toLowerCase() == "true")
	{
		supportValue = true;
	}

	// 5. 資格証明書ファイルを取得し、名前を付け直して添付する
	if(uploadedFiles != null && !uploadedFiles.isEmpty())
	{
		fileUrl = uploadedFiles.get(0).get("path");
		fileObj = invokeurl
		[
			url :fileUrl
			type :GET
			connection:"zohoforms"
		];
		originalName = ifnull(uploadedFiles.get(0).get("name"), "");
		ext = "";
		dotIndex = originalName.lastIndexOf(".");
		if(dotIndex != -1)
		{
			ext = originalName.substring(dotIndex);
		}
		personName = ifnull(contact.get("Last_Name"), "") + ifnull(contact.get("First_Name"), "");
		if(personName == "")
		{
			personName = "連絡先";
		}
		fileObj.setFileName(personName + "_" + recordId + "_資格証明書" + ext);
		attachRes = zoho.crm.attachFile("Contacts", recordId.toLong(), fileObj);
		info "attach: " + attachRes;
	}

	// 6. 職歴(既存+新規)とチェック項目をまとめて書き戻す
	updateMap = Map();
	updateMap.put("Support_Request", supportValue);
	if(!mergedRows.isEmpty())
	{
		updateMap.put("Work_History", mergedRows);
	}
	updateRes = zoho.crm.updateRecord("Contacts", recordId.toLong(), updateMap);
	info "update: " + updateRes;
}

zohoforms は、Zoho Formsのファイルを取得するために作る接続(Connection)の名前です。お使いの環境で作った接続名に置き換えてください。Delugeの基本は「はじめてのDeluge」も参考になります。

書くときに気をつけた点

  • 空の行を除いてから残す:過去の不具合で中身のない行が残っていることがあります。勤務先と職種がどちらも空の行は、書き戻す対象から外しました
  • 既存の行はIDを付けて送る:Zoho CRMのAPIでは、IDのない行は新しい行として追加されます。既存の行をIDなしで書き戻すと二重になるため、読み出したIDをそのまま付けて送ります
  • 書き戻しは1回にまとめる:職歴とチェック項目を別々に更新すると、途中で失敗したときに半端な状態が残ります。1つの更新にまとめました
  • ログを細かく出す:受け取った値、既存の行数、追加した行、更新の結果を info で出しておくと、うまく動かないときに「どこで値が変わったか」を追えます。関数の実行ログは、CRMの設定画面の開発者向けツールから確認できます
  • ファイルがなくても職歴は更新する:資格証明書は任意項目でした。ファイルの有無で処理を分けても、職歴とチェック項目の更新は必ず通るようにしました

同じ職歴を二重に足さないか

この設計では、同じ人が同じ職歴を入れて再送信すると、同じ行が2行入ります。このお客様では、重複は許容し、担当者が確認する方針にしました。重複を自動で除きたい場合は、勤務先と開始日の組み合わせで既存の行と照合してから足す処理を加えます。重複をどこまで自動で処理するかの考え方は「CRMの重複チェックは「自動マージしない」設計にする」で扱っています。

テストで確かめる項目

本番に出す前に、次のパターンを試します。当社の設計では、次の5パターンを確認項目にしています。

  1. 既存の職歴が1行ある連絡先に、新しい職歴を1行送る(2行になるか)
  2. 既存の職歴が2行ある連絡先に、新しい職歴を2行送る(4行になるか)
  3. 職歴が空の連絡先に、初めて職歴を送る(1行で登録されるか)
  4. ファイルを付けずに職歴だけ送る(職歴とチェック項目が更新されるか)
  5. 既存と同じ職歴を送る(仕様どおり2行になるか)

この関数は既存の行を読み直して丸ごと書き戻すため、書き戻しのたびに既存の行が二重にならないかも、サンドボックスで行の数を数えて確かめます。確認するのは「行の数」だけではありません。勤務先・職種・開始日・終了日が正しい列に入っているかも1件ずつ見ます。Formsの日付項目は、開始日と終了日の項目名が似ていて取り違えやすいからです。

同じ問題を避けるためのチェックリスト

Zoho FormsからCRMへデータを入れるフォームを作るときは、次の点を先に確認しておくと安全です。

  1. CRM側にサブフォーム(行が増える表)の項目があるか。あるなら、標準の更新処理で対応づけない
  2. 真偽値・数値・日付の項目が、Formsからどんな形(文字列か)で届くか。ログで実際の値を見る
  3. 「新規登録」だけでなく「既存レコードの更新」になる場合があるか。あるなら、上書きされて困る項目を洗い出す
  4. 処理の順番を図に書く。後ろに置いた処理は、前の処理の結果を直せないことがある
  5. ファイルの名前の付け方(氏名・ID・書類名など)を決めておく

フォームの作り方そのものについては「Zoho Creatorのフォーム」でも紹介しています。

まとめ

標準の連携は、項目を対応づけるだけで動くのが良いところです。一方で、サブフォームのように「既存の内容を残しながら足す」処理は、標準の更新処理では扱いにくいことがあります。

この案件では、単純な項目はFlowの標準アクション、手順が必要な項目はカスタム関数、と役割を分ける設計にしました。既存の職歴を残したまま応募データを取り込む狙いです。どこを標準に任せ、どこを関数で書くかを最初に決めておくと、あとから項目が増えても直す場所が分かりやすくなります。

フォームとCRMの連携設計のご相談は、Zoho CRMカスタマイズ・設定代行サービスからどうぞ。

よくある質問

サブフォームの更新で既存の行が消えるのはなぜですか?

当社が確認したケースでは、標準の更新処理がサブフォームを渡された内容で上書きしていました。フォーム側に職歴の入力がない、または形式が合わない場合、空の内容で上書きされて既存の行が消えます。既存の行を残したいなら、更新前に読み出して新しい行と合わせてから書き戻す必要があります。

カスタム関数をFlowの最後に置けば、既存データを守れますか?

守れません。Forms送信→CRM更新→カスタム関数の順に動くため、関数が動く時点で既存の行はもう消えています。標準の更新処理からサブフォームの項目を外し、サブフォームはカスタム関数だけが触る形にするのが確実です。

Zoho Flowだけ、またはカスタム関数だけで全部処理してはだめですか?

可能ですが、項目が多いと保守が大変になります。姓名や連絡先のような単純な項目はFlowの標準アクションのほうが設定画面で確認しやすく、型変換や追記のように手順が必要な処理だけを関数に寄せると、問題が起きたときに原因を切り分けやすくなります。

Zoho Formsのチェック項目をCRMの真偽値項目に入れるとエラーになるのはなぜですか?

当社が確認したケースでは、チェック項目の値がFlowに渡る時点で文字列の"true"になっていました。CRM側の項目は真偽値(true/false)のため、そのまま対応づけると型が合わずにエラーになります。カスタム関数で文字列を真偽値に変換してから書き込みます。