株式会社etika(エティカ)CRMサポートセンターです。

週報メールの集計ズレとは、Zoho CRMから自動送信している定期レポートの数字が、実際の件数と合わなくなる不具合です。この記事では、起業支援メディア運営企業のZoho CRMで起きた「今月累計:0件」の事例をもとに、原因の切り分け方と直し方を説明します。

この会社では、サービスを契約している顧客ごとに、毎週「先週の件数」「今月累計」「先月の件数」をメールで送っています。送信はDeluge(デリュージ:Zohoの独自スクリプト言語。基本ははじめてのDelugeで紹介しています)のスケジュール関数です。契約を更新した月に、その顧客へ「今月累計:0件」と届く問題が起きました。

結論(先に要点)

  • 集計を「月次の計上レコードの終了日が今月か」で判定していたため、契約更新の月に0件になった
  • 件数の元になるログから、日付の範囲で直接数えるように変えると、契約更新の影響を受けにくくなる見込み
  • ログから数えると旧案件・新案件の両方に同じ数字が出るので、送信先を決めるフラグも合わせて用意する

症状:契約を更新した顧客だけ「今月累計0件」になる

0件になるのは、契約更新の月に旧案件が送信対象に残るケースです。

この会社では、契約の単位ごとに「案件」(Deals)のレコードを作ります。契約を更新すると、新しい契約期間の案件を別に作ります。案件には、月ごとの売上と件数を持つ「月次の計上レコード」(カスタムモジュール)が関連リストでぶら下がっています。

契約更新の月は、データが次の状態になっていました。

ステータス 紐づく計上レコード
旧案件 提供中(週報の送信対象) 前月分まで
新案件 まだ提供中ではない(送信対象外) 今月分から

週報は「ステータスが提供中の案件」に送ります。送信対象は旧案件です。ところが、旧案件には今月の計上レコードがありません。今月の件数は新案件の計上レコード側にあります。その結果、旧案件の「今月累計」は0件になっていました。

原因:集計の基準が「計上レコードの終了日」だった

週報の関数は、今月累計を次の手順で出していました。

  1. 送信対象の案件(ステータスが提供中)をCOQLで取得する
  2. 案件に紐づく計上レコードを関連リストで取得する
  3. 計上レコードの終了日が今月のものだけを選び、その「消化数」を足す

コードにすると、集計部分はこの形です(項目名は一般的な名前に置き換えています)。

// 変更前:計上レコードの終了日で今月分かどうかを判定していた
thisMonthStr = zoho.currentdate.toString("yyyy-MM");
thisMonthCount = 0;
for each rec in relatedRecords
{
	endDate = rec.get("End_Date");
	if(endDate != null && rec.get("Consumed_Count") != null)
	{
		if(endDate.toString("yyyy-MM") == thisMonthStr)
		{
			thisMonthCount = thisMonthCount + rec.get("Consumed_Count").toNumber();
		}
	}
}

この作りだと、次のどれかに当てはまると「今月累計」が0件になります。

  • 送信対象の案件に、終了日が今月の計上レコードがない(契約更新の月はこれに当たる)
  • 今月の計上レコードはあるが、消化数が空、または0のまま
  • 今月の計上レコードはあるが、終了日が空

問題は「案件の状態(提供中かどうか)」と「集計の基準(計上レコードの終了日)」が別々に動いていたことです。普段の月は両方がそろっているので、ずれに気づけません。契約更新の月だけ、ずれが表に出ます。

同じ理由で「先月の件数」も、新案件に切り替わった翌月は0件になり得ます。新案件には先月分の計上レコードがないためです。

「今月累計0件」の原因はどう切り分けるか

同じような症状が出たら、次の順番で確かめると早く原因にたどり着けます。

  1. 0件の顧客に共通点がないかを見る。 契約更新・プラン変更・担当の付け替えなど、レコードが入れ替わる操作が直前にあったかを確認します
  2. 送信対象になった案件のIDをログで確かめる。 週報の関数に info で案件IDを出しておくと、どの案件の数字が送られたかが分かります
  3. その案件に、今月分の集計元のレコードがあるかを見る。 関連リストに今月のレコードがなければ、集計の基準がずれています
  4. 今月分のレコードがあるなら、件数の項目が空や0になっていないかを見る。 入力や更新の仕組みが別にあるなら、そちらが止まっていないかも確認します
  5. 件数の元になるログを、同じ期間で直接数えてみる。 ここで件数が出るなら、データはあり、集計の経路だけが問題だと切り分けられます

今回の事例では、コードを読んで3の状態(旧案件に今月の計上レコードがない)になっていることを確かめました。5は、直した後の確認としても使えます。

直し方:件数の元になるログから直接数える

件数のログを計上レコードでまとめてから数えていた経路が契約更新で途切れていたため、ログから直接数えて週報に出す形に直したことを示した図

ここで示すコードは、当社が同じ会社で組んだ別の関数(先週分の件数を集計する関数)と同じ方式で書いた例です。週報の関数にこの形で組み込んで本番で動かした結果は、まだ確かめていません。お客様の環境で動作確認してから使ってください。

計上レコードの消化数は、もともと「件数のログ」を月ごとにまとめた値です。そこで、計上レコードを経由せず、ログのモジュールから直接数えるように変えます。今回の会社でも、別の関数(先週の件数を案件に書き込む関数)はすでにログから数えていました。週報の「今月累計」だけが計上レコード経由になっていたのです。

前提として、ログのモジュールに次の項目があるとします。

項目(API名) 中身
Item_Name どの提供物への件数か(案件と結びつけるキー)
Requested_At 発生日時(日時型)
Is_Excluded 集計から外すログのフラグ(重複・取り消しなど)

手順

  1. 週報の関数で、今月1日から今日までの範囲を日時の文字列にする
  2. 案件から、ログと結びつけるキー(ここでは提供物の名前)を取り出す
  3. COQLでログを200件ずつ取得し、件数を足していく
  4. more_records が false になったら止める
  5. 足した件数を「今月累計」としてメール本文に入れる

コード

週報の関数のうち、今月累計を出していた部分を、次のコードに置き換える例です。itemName には案件から取り出したキーが入ります。文字列をつないでクエリを作っているため、名前に '(引用符)が含まれるとクエリが壊れます。名前に引用符が入りうる場合は、ルックアップのIDで絞る形にしてください。connection には、CRMのモジュールを読める接続(スコープ ZohoCRM.coql.READ と、対象モジュールの読み取り)を指定してください。URLの zohoapis.jp は、お使いのデータセンターに合わせて変えます。

// 変更後:件数のログから、今月1日〜今日の件数を直接数える
today = zoho.currentdate;
fromIso = today.toStartOfMonth().toString("yyyy-MM-dd'T'00:00:00+09:00");
toIso = today.toString("yyyy-MM-dd'T'23:59:59+09:00");

thisMonthCount = 0;
offset = 0;
pages = {1,2,3,4,5,6,7,8,9,10};   // 200件 × 10ページ = 最大2,000件まで数える

for each p in pages
{
	query = "select id from Request_Logs where (((Item_Name = '" + itemName + "') and (Is_Excluded = false)) and ((Requested_At >= '" + fromIso + "') and (Requested_At <= '" + toIso + "'))) limit 200 offset " + offset;
	queryMap = Map();
	queryMap.put("select_query", query);

	resp = invokeurl
	[
		url: "https://www.zohoapis.jp/crm/v8/coql"
		type: POST
		parameters: queryMap.toString()
		connection: "crm_conn"
	];

	// 該当が0件のときは data が返らないため、ここで抜ける
	if(resp == null || resp.get("data") == null)
	{
		break;
	}

	rows = resp.get("data");
	thisMonthCount = thisMonthCount + rows.size();
	offset = offset + rows.size();

	if(resp.get("info") == null || resp.get("info").get("more_records") != true)
	{
		break;
	}
}
info itemName + " の今月累計: " + thisMonthCount;

ポイントは3つです。

  • Delugeにはwhile文がないため、決まった数の要素を持つリストで回します。 上の例は最大10ページです。1か月の件数がそれを超える見込みなら、要素を増やしてください
  • 0件のときの扱いを先に書きます。 当社の環境では、該当がないとき、COQLは本文のない応答を返しました。data が取れないことを前提に、0件として抜けるようにします(ウィジェットから同じ現象に当たった例はCOQLの件数制限と空応答(204)の落とし穴で紹介しています)
  • 提供物の名前など文字列をキーにする場合は、表記ゆれに注意します。 名前を変えるとログと結びつかなくなります。ルックアップ(他のレコードへの参照)でつなげられるなら、IDで絞るほうが安全です

COQLの1回の取得件数は、公式ドキュメントでは既定200件・最大2,000件とされています(COQL Overview)。件数だけが欲しい場合は COUNT() を使う方法もあります。ただし、集計関数を使える項目の種類には制限があると書かれているため、対象の項目で使えるか確かめてから採用してください(Get Records through COQL)。

ログから数えると、どちらの案件に送ればよいか

ログから直接数えると、別の問題が出てきます。契約更新の月に、旧案件と新案件の両方が「提供中」になっていると、両方に同じ件数が出て、週報が2通届きます。提供物の名前が同じなら、どちらから数えても同じ数字になるためです。

そこで、送信先を決める項目を案件に足します。

項目(API名) 型 使い方
Is_Report_Target チェックボックス 週報を送る案件だけオン。契約更新時に旧案件をオフ、新案件をオンにする
Previous_Deal ルックアップ(案件) 新案件から旧案件をたどれるようにする

送信対象を取るCOQLに、このフラグを条件として足します。

queryMap = Map();
queryMap.put("select_query", "select id, Account_Name, Item_Name from Deals where ((Stage = '提供中') and (Is_Report_Target = true))");

提供物の名前が契約更新で変わる場合は、旧案件の名前も新案件から数える対象に入れます。Previous_Deal をたどって旧案件のキーを取り出し、上の集計をキーの数だけ回して合算します。

フラグの切り替えは人の操作です。切り替え忘れを防ぐには、新案件のステータスが「提供中」になったときに、旧案件のフラグを自動でオフにするワークフローを用意しておく方法もあります(当社の提案段階の案です)。

運用で補うこと

集計の基準を直したうえで、運用でも次の2点を決めておくと、再発を防ぎやすくなります。

  1. 契約更新の手順に「ステータスとフラグの切り替え」を入れる。 旧案件は契約終了日で提供中から外す、新案件は開始日で提供中にする、をチェックリストにします
  2. 月初の1回目の週報の前に、二重送信と0件がないかを見る。 送信対象の案件と件数を info で出し、件数が0の案件だけ目で確かめます

本番に戻す前に何をテストするか

週報のように顧客へ直接届くメールは、修正後に必ず本番と同じデータで確かめます。送信先を自分のアドレスに差し替え、次の3パターンを用意して実行してください。

  1. 月初の実行:今月のログがまだ少ない(または0件)状態で、0件の扱いが正しいか
  2. 契約更新の月:旧案件と新案件が両方ある状態で、フラグがオンの1件だけに送られ、件数がログと一致するか
  3. 計上レコードがない案件:計上レコードを持たない案件でも、ログから件数が出るか

3パターンとも、メールの数字とログを直接数えた数字が合っていれば、送信先を元に戻します。

まとめ

週報の「今月累計0件」は、コードを読んだ限りでは、データが欠けていたのではなく、集計の経路が契約更新に追いつけていなかったことが原因でした。直し方は、ここで示した考え方とコード例を、実際のデータで確かめながら適用してください。

  • 件数は、まとめた値(計上レコード)ではなく、元のログから数える
  • 送信先は、ステータスだけでなくフラグで明示的に決める
  • 顧客に届く数字は、普段と違う月(月初・契約更新)でテストする

自動送信のレポートは、作った時点の業務の流れを前提にしています。契約の形や更新の手順が変わったときは、集計の基準も合わせて見直してください。日付を基準に動く自動処理の別の落とし穴は、日付ベースのワークフローが発火しないときの代わりの設計で紹介しています。

CRMサポートセンターでは、Zoho CRMの自動化(Delugeの関数・ワークフロー)の不具合調査や改修をお手伝いしています(Zoho CRMカスタマイズ・設定代行サービス)。「数字が合わない」「どこで止まっているか分からない」といったご相談も、お気軽にお寄せください。

よくある質問

計上レコードの「消化数」を毎月きちんと入れれば、今の作りのままでも直りますか?

当月の計上レコードが旧案件に紐づいていない限り、入力しても0件のままです。契約更新の月は計上レコードが新案件側にあるため、運用を徹底しても同じ形で再発します。集計の基準そのものを変えれば、再発を防げる見込みです。

COQLのCOUNTで件数だけ取れば、ページ送りは要らないのでは?

公式ドキュメントでは、COUNTなどの集計関数を使える項目の種類に制限が書かれています(数値・ルックアップ・選択リスト)。対象の項目で使えるかを確かめてから使ってください。本記事のコードは、レコードIDを取得して数える方法にしています。

旧案件のステータスを契約終了と同時に変えれば済みませんか?

ステータスの切り替えが毎回正しく行われれば再発は減ります。ただ、人の操作に依存するため、切り替え忘れや前後のずれで同じ症状が出ます。集計の基準を直したうえで、運用ルールは補助として使うのがおすすめです。