CATEGORIES:JAVA KINTONE
kintoneで同じビルの過去レコードから項目をまとめてコピーする

kintoneの編集画面にボタンを置き、同じビル名の過去レコードを一覧表示して、選んだレコードの項目をまとめてコピーする方法。管理会社の連絡先や入館方法のように、同じビルなら毎回同じ内容になる項目を、一つずつ手入力する手間を省く。

使い方は次のとおり。

  1. ビル名だけ入れて保存したレコードを編集画面で開く
  2. 「同じビルのレコードを検索」を押す
  3. 同じビル名のレコードが一覧で表示される
  4. 使いたいレコードの「このレコードの内容をコピー」を押す
  5. 項目が画面に入るので、確認して保存する

kintone標準の「再利用」でレコードを複製する方法もあるが、全項目がコピーされる。新しいレコードで入れ直すべき項目まで入ってしまい、それを消して回るのが手間になる。この方法なら、ビルで共通の項目だけを引き継げる。

候補を一覧で見比べてから選べるのと、コピーする項目をコード上の配列で管理できるのが特徴。項目が増えても、配列にフィールドコードを足すだけで済む。

添付ファイルはこの方法ではコピーできないので、別の記事で紹介する。

処理の流れは次のとおり。

  1. 編集画面を開いたとき、許可したユーザーだけスペースに検索ボタンを置く
  2. ボタンを押すと、画面のビル名で同じアプリ内を検索する(自分自身は除く)
  3. 見つかったレコードを、コピーボタン付きで一覧表示する
  4. コピーボタンを押すと、そのレコードの項目を画面に入れる(保存はしない)

ポイントは次の5つ。

ポイント内容
画面の最新の値を使うビル名は kintone.app.record.get() でボタンを押した瞬間の値を取る。event.record は編集画面を開いた時点の値なので、開いてからビル名を直しても反映されない
部分一致で検索するlike は部分一致。ビルはオーナーが変わると名称が変わることがあり、開設から数年たつと完全一致では見つからなくなる。部分一致にしておけば、「○○ビル」で「○○ビル本館」や「新○○ビル」もヒットする
対象外のレコードを除くこのサンプルではビル名が「・」で始まるレコードは一覧に出さない(運用上の目印)。kintoneの like は % のようなワイルドカードが使えず「○○で始まる」を検索条件で書けないので、取得後にJavaScriptの startsWith() で除いている
2回に分けて反映する項目が多いと一度の kintone.app.record.set() でうまく反映されないことがあった。前半を入れたあと、setTimeout で0.15秒空けて後半を入れている(原因は未特定)
複数の値を持つフィールドチェックボックスやユーザー選択の値は配列。そのまま代入するとコピー元と同じ配列を指すので、slice() で複製してから入れる

画面に入れるだけで保存はしないので、内容を確認してから保存できる。間違えたレコードをコピーした場合は、保存せずにキャンセルすれば元に戻る。

JavaScript全体(アプリの設定 →「JavaScript / CSSでカスタマイズ」でアップロードする。フォームには要素ID copy_space のスペースを置く)

/*
 * 同じビルの過去レコードから項目をまとめてコピーする
 *
 * 編集画面にボタンを置き、同じビル名のレコードを一覧表示する。
 * 一覧のボタンを押すと、そのレコードの項目を画面に入れる(保存はユーザーが行う)。
 */
(function () {
  "use strict";

  // ------------------------------------------------------------
  // 設定
  // ------------------------------------------------------------
  const FIELD_BUILDING = "ビル名";        // 検索に使うフィールド
  const SPACE_ID = "copy_space";          // ボタンを置くスペースの要素ID
  const ALLOW_USERS = ["user_a", "user_b", "user_c"];   // ボタンを使えるユーザー(ログイン名)

  // コピーする項目(前半)
  const FIELDS_FIRST = [
    "管理会社名",
    "管理会社_担当者名",
    "管理会社_電話番号",
    "管理会社_メールアドレス"
  ];

  // コピーする項目(後半)。0.15秒空けてから反映する
  const FIELDS_SECOND = [
    "入館方法",
    "作業届の提出方法",
    "入居フロア",
    "関係ユーザー",       // ユーザー選択(複数の値を持つ)
    "備考"
  ];

  // 値をコピーする。配列(チェックボックス、ユーザー選択など)は複製してから渡す
  function copyValue(value) {
    return Array.isArray(value) ? value.slice() : value;
  }

  // ------------------------------------------------------------
  // 1. 編集画面を開いたときに、検索ボタンを置く
  // ------------------------------------------------------------
  kintone.events.on("app.record.edit.show", function (event) {

    // 許可したユーザー以外には何も表示しない
    const loginUser = kintone.getLoginUser().code;
    if (!ALLOW_USERS.includes(loginUser)) {
      return event;
    }

    const spaceEl = kintone.app.record.getSpaceElement(SPACE_ID);
    if (!spaceEl) {
      return event;
    }

    // 二重に表示されないよう、いったん空にする
    spaceEl.innerHTML = "";

    const button = document.createElement("button");
    button.textContent = "同じビルのレコードを検索";
    button.style.marginBottom = "10px";

    const listArea = document.createElement("div");   // 検索結果を表示する場所

    spaceEl.appendChild(button);
    spaceEl.appendChild(listArea);

    // ----------------------------------------------------------
    // 2. 検索ボタンを押したとき
    // ----------------------------------------------------------
    button.onclick = function () {

      // ボタンを押した時点の画面の値を取る(event.record は画面を開いた時点の値)
      const current = kintone.app.record.get();
      const buildingName = current.record[FIELD_BUILDING].value;
      const currentRecordId = current.record.$id.value;

      if (!buildingName) {
        alert("ビル名が未入力です");
        return;
      }

      // ビル名に " が含まれていてもクエリが壊れないようにする
      const safeValue = buildingName.replace(/"/g, '\\"');

      // 同じアプリ内で、ビル名が部分一致し、自分以外のレコード
      const query = `${FIELD_BUILDING} like "${safeValue}" and $id != ${currentRecordId}`;

      kintone.api(kintone.api.url("/k/v1/records", true), "GET", {
        app: kintone.app.getId(),
        query: query
      }, function (resp) {

        listArea.innerHTML = "";

        // ビル名が「・」で始まるレコードは対象外
        // (kintone の like は「○○で始まる」を指定できないので、取得後に除く)
        const records = (resp.records || []).filter(function (rec) {
          return !(rec[FIELD_BUILDING].value || "").trim().startsWith("・");
        });

        if (records.length === 0) {
          listArea.textContent = "該当ビルはありません";
          return;
        }

        // --------------------------------------------------------
        // 3. 見つかったレコードを一覧表示する
        // --------------------------------------------------------
        records.forEach(function (rec) {

          const row = document.createElement("div");
          row.style.backgroundColor = "#f9faf7";
          row.style.border = "1px solid #ccc";
          row.style.padding = "8px";
          row.style.margin = "5px";

          const title = document.createElement("div");
          title.textContent = `レコード番号: ${rec.$id.value} / ビル名: ${rec[FIELD_BUILDING].value}`;

          const copyButton = document.createElement("button");
          copyButton.textContent = "このレコードの内容をコピー";

          // ------------------------------------------------------
          // 4. コピーボタンを押したとき、項目を画面に入れる
          // ------------------------------------------------------
          copyButton.onclick = function () {

            // 前半
            const first = kintone.app.record.get();
            FIELDS_FIRST.forEach(function (code) {
              first.record[code].value = copyValue(rec[code].value);
            });
            kintone.app.record.set(first);

            // 後半。一度に全部入れると反映されない項目があったので、少し空けて入れる
            setTimeout(function () {
              const second = kintone.app.record.get();
              FIELDS_SECOND.forEach(function (code) {
                second.record[code].value = copyValue(rec[code].value);
              });
              kintone.app.record.set(second);
            }, 150);
          };

          row.appendChild(title);
          row.appendChild(copyButton);
          listArea.appendChild(row);
        });

      }, function (error) {
        console.error(error);
        alert("検索エラー");
      });
    };

    return event;
  });
})();

自分の環境に合わせるときの変更箇所は次のとおり。コード先頭の「設定」だけ変えれば動く。

変えたいもの変更する場所
検索に使うフィールドFIELD_BUILDING
ボタンの置き場所SPACE_ID(フォームのスペースの要素ID)
ボタンを使える人ALLOW_USERS(ログイン名)
コピーする項目FIELDS_FIRST と FIELDS_SECOND にフィールドコードを追加・削除
対象外にする目印startsWith("・") の ・
後半を反映するまでの間setTimeout の 150(ミリ秒)

フィールドコードは記事用に置き換えている。実際には約50項目をコピーしている。

  • 新規作成画面では動かない。レコード番号がまだなく、「自分以外」の条件が作れないため。ビル名だけ入れて一度保存してから使う
  • 添付ファイルフィールドは kintone.app.record.set() では変更できないので、コピーする項目に入れない。添付ファイルのコピーは別の記事で紹介する
  • コピーする項目に、フォームに存在しないフィールドコードを書くとエラーで止まる。フィールドコードを変更したときは配列も直す
  • 検索は最大100件まで
  • コピーボタンを押すと、対象の項目はコピー元の値で上書きされる。先に入力していた内容も消えるので、保存前に確認する

本番に反映する前に、テスト用のアプリで動作を確認するのを推奨。

NOTICES

  • 記事内容は実装させたものがほとんどですが自己責任で参考にしてください。

TO HEADER