地方の中小企業のIT導入・活用コンサルティングを提供する株式会社etika(エティカ)CRMサポートセンターです。

「商談のメモ欄に会議の議事録を貼ったら、金額や次回アクションが自動で項目に入ってほしい」——Zoho CRMを使い込むほど出てくる要望です。この記事では、商談にメモ(議事録)を追加すると、ワークフローがDelugeカスタム関数を呼び出し、商談の5項目を自動更新する仕組みを、実際に動かしたコードそのままで解説します。

結論(先に要点)

  • メモ本文を「■ラベル:値」の決まった形式で書く
  • 商談のメモ作成をトリガーにワークフローで関数を発火させる
  • 関数はメモを解析し、Amount(総額)・Closing_Date(完了予定日)・Stage(ステージ)・次回アクション・要約の5項目をupdateRecordでまとめて更新する

何ができるようになるか

営業担当者が商談画面のメモに、次のような議事録を貼り付けます。

■総額:1,200万円
■完了予定日:2026/09/30
■ステージ:交渉
■次回アクション:見積の再提示と決裁者同席の打ち合わせ設定
■要約:
・現行の運用課題を確認
・価格は概ね合意、残るは決裁プロセス
・9月内の契約を目標に合意

保存すると数秒後、商談の「総額」「完了予定日」「ステージ」「次回アクション」「商談要約」が自動で埋まります。手入力の転記作業がゼロになり、入力漏れも防げます。

議事録メモが商談の5項目に自動反映される流れ

全体の構成

  1. カスタム項目の準備:商談モジュールに「次回アクション(1行テキスト)」「商談要約(複数行テキスト)」を用意します(総額・完了予定日・ステージは標準項目)。
  2. ワークフロールール:モジュール「商談」、実行タイミング「メモを追加したとき」で発火するルールを作成します。
  3. カスタム関数:ルールのアクションに、下記のDeluge関数を紐づけます。引数dealIdに「商談 - 商談ID」をマッピングします。

無料相談

「メモを書くだけで商談が埋まる」この仕組み、自社の商談項目に合わせて作れます。実現できるか気軽に聞いてみませんか?

議事録×CRM自動化を無料で相談する

Delugeカスタム関数(全文)

// 種別: ワークフローカスタム関数(モジュール: 商談)
// 引数: dealId (String) … WFの「引数を編集」で「商談 - 商談ID」をマッピング
try
{
    // 1) 商談に紐づくメモから議事録メモを取得
    notes = zoho.crm.getRelatedRecords("Notes", "Deals", dealId.toLong(), 1, 50);
    content = "";
    for each n in notes
    {
        t = ifnull(n.get("Note_Title"), "");
        c = ifnull(n.get("Note_Content"), "");
        if(t.contains("商談議事録") || c.contains("■総額"))
        {
            content = c;
            break;
        }
    }
    if(content == "")
    {
        return;
    }
    // 1.5) リッチテキスト対策: HTMLで保存されている場合はタグを除去
    //      改行文字リテラルはDeluge非対応のためbase64で生成("Cg==" = \n)
    nl = zoho.encryption.base64Decode("Cg==");
    if(content.contains("<"))
    {
        content = content.replaceAll("<br>", nl);
        content = content.replaceAll("<br/>", nl);
        content = content.replaceAll("<br />", nl);
        content = content.replaceAll("<[^>]*>", "");
        content = content.replaceAll("&nbsp;", " ");
        content = content.replaceAll("&amp;", "&");
    }
    // 2) 「■ラベル:値」を ■ 区切りでパース
    updateMap = Map();
    segments = content.toList("■");
    for each seg in segments
    {
        s = seg.trim();
        if(s == "") { s = "-"; }
        sep = ":";
        if(s.contains(":")) { sep = ":"; }
        label = ifnull(s.getPrefix(sep), "").trim();
        value = ifnull(s.getSuffix(sep), "").trim();
        if(label == "総額")
        {
            mult = 1;
            numText = value;
            if(value.contains("億")) { numText = ifnull(value.getPrefix("億"), ""); mult = 100000000; }
            else if(value.contains("万")) { numText = ifnull(value.getPrefix("万"), ""); mult = 10000; }
            num = numText.replaceAll("[^0-9.]", "");
            if(num != "" && num.length() < 12)
            {
                updateMap.put("Amount", num.toDecimal() * mult);
            }
        }
        else if(label == "完了予定日")
        {
            d = value.replaceAll("/", "-");
            if(d.contains(nl)) { d = ifnull(d.getPrefix(nl), d).trim(); }
            if(d.matches("[0-9]{4}-[0-9]{1,2}-[0-9]{1,2}"))
            {
                updateMap.put("Closing_Date", d);
            }
        }
        else if(label == "ステージ")
        {
            if(value.contains(nl)) { value = ifnull(value.getPrefix(nl), value).trim(); }
            // ★jp orgではStageは「表示名」で渡す("受注" 等)
            stageMap = Map();
            stageMap.put("提案", "提案");
            stageMap.put("交渉", "交渉");
            stageMap.put("受注", "受注");
            if(stageMap.containKey(value))
            {
                updateMap.put("Stage", stageMap.get(value));
            }
        }
        else if(label == "次回アクション")
        {
            // 1行テキスト項目のため先頭行のみ
            if(value.contains(nl)) { value = ifnull(value.getPrefix(nl), value).trim(); }
            updateMap.put("Next_Action", value);      // ← 実際のAPI名に置き換える
        }
        else if(label == "要約")
        {
            if(value != "")
            {
                updateMap.put("Meeting_Summary", value);  // ← 実際のAPI名に置き換える
            }
        }
    }
    // 3) 1項目でも取れたら商談を更新(失敗してもthrowせずログのみ)
    if(updateMap.size() > 0)
    {
        resp = zoho.crm.updateRecord("Deals", dealId.toLong(), updateMap);
        info resp;
        return;
    }
    return;
}
catch (e)
{
    info e;
    return;
}

Next_Action Meeting_Summary の部分は、ご自身の環境で作成したカスタム項目のAPI名に置き換えてください(API名は「設定 > APIとSDK > API名」で確認できます)。

実装でつまずきやすい3つのポイント

この関数には、公式ドキュメントには載っていない実測ベースの工夫が3つ入っています。

Deluge実装でハマる3つのポイント

1. 改行での分割はできない → 「■」区切りにする

Delugeでメモをパースするときにありがちなのがcontent.toList("\n")で行分割する方法ですが、Delugeでは改行での分割が期待通りに動きません。そこで本関数は各項目の頭に「■」を付けたフォーマットにして、toList("■")で分割しています。改行に依存しないので、要約のような複数行テキストもそのまま1つの値として扱えます。

2. 改行文字は base64 で生成する

Delugeは"\n"のような改行文字リテラルを書けません。そこでzoho.encryption.base64Decode("Cg==")Cg==はLFのbase64)で改行文字を生成し、変数nlとして使い回しています。

3. ステージは「表示名」で更新する

日本語環境(jp DC)のZoho CRMでは、APIでステージ(Stage)を更新するとき、内部値の"Closed Won"ではなく**表示名の"受注"**を渡す必要があります。ここは別記事で詳しく扱います。

まとめ

議事録メモから商談を自動更新する仕組みは、「決まったフォーマットで書く」「メモ追加で発火」「まとめてupdateRecord」という3点さえ押さえれば、標準機能とDelugeだけで実現できます。AI議事録ツールの出力をこのフォーマットに整えれば、そのままCRM入力の自動化まで一気通貫でつながります。

無料相談

議事録の転記や商談更新の手入力を、Delugeで自動化しませんか?実現可否の見極めから無料でご相談いただけます。

議事録×CRM自動化を無料で相談する