レコードの複製

にメンテナンス済み

kintone の標準機能にもレコードの再利用(コピー)機能はありますが、「特定のフィールドだけ値を変えてコピーしたい」「ボタン一つで複製を完了させたい」「サブテーブルの内容も含めて完全にコピーしたい」といった業務要件には対応しきれないことがあります。

JavaScript カスタマイズと REST API を組み合わせれば、より柔軟なレコード複製を実現できます。

この記事では、レコード詳細画面にコピーボタンを設置し、REST API を使ってレコードを複製する方法を、基本パターンから応用パターンまで紹介します。

基本: 現在のレコードを複製する

コピーボタンの設置と基本的な複製

レコード詳細画面のスペースフィールドに「レコードをコピー」ボタンを設置し、クリック時に現在のレコードを複製する基本パターンです。

record-copy-basic.js
(() => {
  'use strict';

  // コピー対象外のシステムフィールド
  const EXCLUDED_FIELDS = [
    '$id',
    '$revision',
    '作成者',
    '更新者',
    '作成日時',
    '更新日時',
    'レコード番号',
  ];

  /**
   * レコードオブジェクトからコピー用のデータを生成する
   * @param { Object } record - kintone のレコードオブジェクト
   * @returns { Object } コピー用レコードデータ
   */
  const buildCopyRecord = (record) => {
    const copyRecord = {};

    Object.entries(record).forEach(([fieldCode, field]) => {
      // システムフィールドを除外
      if (EXCLUDED_FIELDS.includes(fieldCode)) return;

      // ステータス・カテゴリーフィールドを除外
      if (['STATUS', 'STATUS_ASSIGNEE', 'CATEGORY'].includes(field.type)) return;

      // CALC(計算)フィールドを除外(自動計算されるため)
      if (field.type === 'CALC') return;

      copyRecord[fieldCode] = { value: field.value };
    });

    return copyRecord;
  };

  const events = [
    'app.record.detail.show',
    'mobile.app.record.detail.show',
  ];

  kintone.events.on(events, (event) => {
    // スペースフィールドにボタンを設置
    const space = kintone.app.record.getSpaceElement('コピーボタン');
    if (!space || space.childNodes.length > 0) return event;

    const button = document.createElement('button');
    button.textContent = 'レコードをコピー';
    button.className = 'kintoneplugin-button-dialog-ok';
    button.style.marginTop = '8px';

    button.addEventListener('click', async () => {
      if (!confirm('このレコードを複製しますか?')) return;

      try {
        button.disabled = true;
        button.textContent = 'コピー中...';

        const record = event.record;
        const appId = kintone.app.getId();
        const copyRecord = buildCopyRecord(record);

        // REST API でレコードを追加
        const response = await kintone.api(
          kintone.api.url('/k/v1/record.json', true),
          'POST',
          { app: appId, record: copyRecord }
        );

        alert(`レコードを複製しました(ID: ${response.id})`);

        // 新しいレコードの詳細画面に遷移
        const baseUrl = location.pathname.replace(/\/show.*/, '');
        location.href = `${baseUrl}/show#record=${response.id}`;
      } catch (error) {
        console.error('レコードのコピーに失敗しました', error);
        alert('レコードのコピーに失敗しました: ' + error.message);
      } finally {
        button.disabled = false;
        button.textContent = 'レコードをコピー';
      }
    });

    space.appendChild(button);
    return event;
  });
})();
スペースフィールドの準備

上記のコードを動作させるには、フォーム設定でスペースフィールドを配置し、要素 ID に コピーボタン を設定する必要があります。

スペースフィールドの活用
kintone のスペースフィールドを活用して、ボタン、ステータス表示、入力フォーム、グラフなどのカスタム要素を画面内に配置する方法を解説します。kintone.app.record.getSpaceElement の基本的な使い方から実践

特定フィールドの値を変更してコピー

ステータスやフラグを初期化する

コピー元のレコードには「承認済み」や「完了」のステータスが設定されていることがあります。コピー時にこれらを初期値に戻すパターンです。

record-copy-with-reset.js
(() => {
  'use strict';

  const EXCLUDED_FIELDS = [
    '$id', '$revision', '作成者', '更新者', '作成日時', '更新日時', 'レコード番号',
  ];

  // コピー時に値をリセットするフィールド
  const RESET_FIELDS = {
    '対応ステータス': '未対応',
    '承認フラグ': [],
    '担当者': [],
    '対応日': '',
    '備考': '',
  };

  const buildCopyRecord = (record) => {
    const copyRecord = {};

    Object.entries(record).forEach(([fieldCode, field]) => {
      if (EXCLUDED_FIELDS.includes(fieldCode)) return;
      if (['STATUS', 'STATUS_ASSIGNEE', 'CATEGORY', 'CALC'].includes(field.type)) return;

      // リセット対象のフィールドは指定した初期値を設定
      if (fieldCode in RESET_FIELDS) {
        copyRecord[fieldCode] = { value: RESET_FIELDS[fieldCode] };
        return;
      }

      copyRecord[fieldCode] = { value: field.value };
    });

    return copyRecord;
  };

  const events = ['app.record.detail.show'];

  kintone.events.on(events, (event) => {
    const space = kintone.app.record.getSpaceElement('コピーボタン');
    if (!space || space.childNodes.length > 0) return event;

    const button = document.createElement('button');
    button.textContent = 'ステータスを初期化してコピー';
    button.className = 'kintoneplugin-button-dialog-ok';

    button.addEventListener('click', async () => {
      if (!confirm('ステータスを初期化してレコードを複製しますか?')) return;

      try {
        button.disabled = true;
        const copyRecord = buildCopyRecord(event.record);

        const response = await kintone.api(
          kintone.api.url('/k/v1/record.json', true),
          'POST',
          { app: kintone.app.getId(), record: copyRecord }
        );

        alert(`レコードを複製しました(ID: ${response.id})`);
        location.reload();
      } catch (error) {
        console.error(error);
        alert('コピーに失敗しました: ' + error.message);
      } finally {
        button.disabled = false;
      }
    });

    space.appendChild(button);
    return event;
  });
})();
チェック

RESET_FIELDS オブジェクトでリセット対象とその初期値を定義しておくと、フィールドの追加・変更が容易です。ドロップダウンフィールドの場合は '未対応' のように文字列、チェックボックスやユーザー選択の場合は [](空配列)を指定します。

サブテーブルを含むレコードの複製

サブテーブルを含むレコードをコピーする場合、テーブルの各行から id を除去する必要があります。id は既存の行を識別するためのものであり、新しいレコードに含めると意図しない動作になる可能性があります。

record-copy-with-subtable.js
/**
 * サブテーブルフィールドからコピー用のデータを生成する
 * 各行の id を除去し、value のみを保持する
 * @param { Object[] } tableValue - サブテーブルの value 配列
 * @returns { Object[] } コピー用のサブテーブルデータ
 */
const copySubtable = (tableValue) => {
  return tableValue.map((row) => {
    const newRow = {};

    Object.entries(row.value).forEach(([fieldCode, field]) => {
      // CALC フィールドはコピー不要
      if (field.type === 'CALC') return;
      newRow[fieldCode] = { value: field.value };
    });

    return { value: newRow };
  });
};

/**
 * レコードオブジェクトからコピー用のデータを生成する(サブテーブル対応版)
 * @param { Object } record - kintone のレコードオブジェクト
 * @returns { Object } コピー用レコードデータ
 */
const buildCopyRecord = (record) => {
  const EXCLUDED_FIELDS = [
    '$id', '$revision', '作成者', '更新者', '作成日時', '更新日時', 'レコード番号',
  ];

  const copyRecord = {};

  Object.entries(record).forEach(([fieldCode, field]) => {
    if (EXCLUDED_FIELDS.includes(fieldCode)) return;
    if (['STATUS', 'STATUS_ASSIGNEE', 'CATEGORY', 'CALC'].includes(field.type)) return;

    // サブテーブルの場合は id を除去してコピー
    if (field.type === 'SUBTABLE') {
      copyRecord[fieldCode] = { value: copySubtable(field.value) };
      return;
    }

    copyRecord[fieldCode] = { value: field.value };
  });

  return copyRecord;
};
添付ファイルフィールドの注意点

添付ファイルフィールド(FILE タイプ)を含むレコードをコピーする場合、ファイルの実体はコピーされません。ファイルキー(fileKey)はコピー元のレコードに紐づいているため、コピー先で参照しても正しく動作しない場合があります。ファイルも含めて完全にコピーしたい場合は、ファイル API でダウンロードしてから再アップロードする必要があります。

ファイルのアップロード・ダウンロード
kintone REST API を使って添付ファイルのアップロード・ダウンロードを行う方法を解説します。ファイルキーの取得、Blob を使ったダウンロード、JavaScript からのファイル添付レコード登録まで紹介します。

複数レコードの一括コピー

一覧画面でチェックボックスを使って選択したレコードを、別のアプリに一括コピーするパターンです。

bulk-record-copy.js
(() => {
  'use strict';

  const DEST_APP_ID = 20; // コピー先のアプリID

  // コピー対象のフィールドマッピング(コピー元 → コピー先)
  const FIELD_MAPPING = {
    '案件名': '案件名',
    '顧客名': '顧客名',
    '金額': '金額',
    '担当者': '担当者',
  };

  const events = ['app.record.index.show'];

  kintone.events.on(events, (event) => {
    if (document.getElementById('bulk-copy-button')) return event;

    const headerSpace = kintone.app.getHeaderMenuSpaceElement();
    if (!headerSpace) return event;

    const button = document.createElement('button');
    button.id = 'bulk-copy-button';
    button.textContent = '選択レコードを別アプリにコピー';
    button.className = 'kintoneplugin-button-dialog-ok';

    button.addEventListener('click', async () => {
      // 一覧画面で表示されているレコードを取得
      const records = event.records;
      if (!records || records.length === 0) {
        alert('コピー対象のレコードがありません');
        return;
      }

      if (!confirm(`${records.length}件のレコードをコピーしますか?`)) return;

      try {
        button.disabled = true;
        button.textContent = 'コピー中...';

        // コピー用のレコードデータを生成
        const copyRecords = records.map((record) => {
          const newRecord = {};
          Object.entries(FIELD_MAPPING).forEach(([srcField, destField]) => {
            if (record[srcField]) {
              newRecord[destField] = { value: record[srcField].value };
            }
          });
          return newRecord;
        });

        // 100件ずつ分割して登録(REST API の上限)
        for (let i = 0; i < copyRecords.length; i += 100) {
          const chunk = copyRecords.slice(i, i + 100);
          await kintone.api(
            kintone.api.url('/k/v1/records.json', true),
            'POST',
            { app: DEST_APP_ID, records: chunk }
          );
        }

        alert(`${records.length}件のレコードをコピーしました`);
      } catch (error) {
        console.error(error);
        alert('コピーに失敗しました: ' + error.message);
      } finally {
        button.disabled = false;
        button.textContent = '選択レコードを別アプリにコピー';
      }
    });

    headerSpace.appendChild(button);
    return event;
  });
})();
チェック

FIELD_MAPPING でコピー元とコピー先のフィールドコードを対応付けることで、フィールドコードが異なるアプリ間でもコピーが可能です。

まとめ

  • buildCopyRecord 関数でシステムフィールドやステータスフィールドを自動的に除外してコピーデータを生成できる
  • サブテーブルを含むレコードのコピーでは、各行の id を除去する必要がある
  • RESET_FIELDS オブジェクトを使えば、コピー時に特定フィールドの値を初期化できる
  • 添付ファイルフィールドの実体はコピーされないため、ファイル API による再アップロードが必要
  • 100 件を超えるレコードの一括コピーは、100 件ずつ分割してリクエストする
  • FIELD_MAPPING を定義すれば、異なるフィールドコードを持つアプリ間のコピーも可能

関連記事

スペースフィールドの活用
kintone のスペースフィールドを活用して、ボタン、ステータス表示、入力フォーム、グラフなどのカスタム要素を画面内に配置する方法を解説します。kintone.app.record.getSpaceElement の基本的な使い方から実践
レコードの追加(1件)
kintone REST APIを使用して、レコードを1件追加する方法を紹介します。Node.jsやGASでのサンプルコードも掲載しています。
レコードの追加(複数件)
kintone REST APIを使用して、レコードを複数件追加する方法を紹介します。Node.jsやGASでのサンプルコードも掲載しています。
ファイルのアップロード・ダウンロード
kintone REST API を使って添付ファイルのアップロード・ダウンロードを行う方法を解説します。ファイルキーの取得、Blob を使ったダウンロード、JavaScript からのファイル添付レコード登録まで紹介します。
レコード一覧・詳細画面にボタンを設置するサンプル
kintoneカスタマイズを行う際によく使われる、レコード一覧・詳細画面にボタンを設置する方法を紹介します。

練習問題

サブテーブルを含むレコードをREST APIでコピーする際に、各行から除去すべきプロパティはどれですか?

レコードのコピー時に除外すべきフィールドタイプとして、適切でないものはどれですか?

#kintone #JavaScript #TypeScript #REST API