関連レコードのデータを CSV 出力する方法

著者名:楊 思夢(サイボウズ株式会社)

目次

はじめに

こんにちは。サイボウズの楊です。
今回は関連レコードも CSV 出力したい!というニーズにお応えしたいと思います。

kintone アプリでは、他アプリのレコードを参照できる関連レコード一覧機能があります。
ひとつのフィールドをキーとし、関連するレコードを現在のレコード詳細画面に表示できます。
しかし、この場合関連レコードのデータは表示させているだけで、閲覧しているレコードにデータとして所持している訳ではありません。

また kintone にはデータを CSV 形式でダウンロードする機能があります。
レコードを CSV ファイルで書き出す場合、関連レコードはそのデータを保持していないため、通常は関連レコードを CSV 出力できません。

今回は、関連レコードを含めた CSV を出力する Tips を紹介できればと思います。

デモ環境

デモ環境で実際に動作を確認できます。

ログイン情報は cybozu developer network デモ環境 で確認してください。

完成イメージ

完成したアプリには、以下のように CSV ファイル出力ボタンが表示されます。

ダウンロードした CSV ファイルのイメージは以下のとおりです。

アプリの準備

今回使用するのはアプリストアにある「案件管理」と「顧客リスト」アプリを応用したものです。
のちほど「顧客リスト」アプリの関連レコードのフィールド設定で「案件管理」アプリが必要となりますので、先に「案件管理」のアプリの追加をしてください。

案件管理アプリ

フィールドコードが異なると JavaScript が正常に動作しないため注意して変更しましょう。
案件管理アプリのフィールドの設定は以下のとおりです。
なお、記載していないフィールドはデフォルトのままです。

フィールドタイプ フィールド名 フィールドコード
文字列(1 行) 会社名 company
ドロップダウン 製品名 product
計算 小計 subtotal

顧客リストアプリ

顧客リストアプリに「関連レコード」フィールドを追加します。

関連レコード一覧の設定の詳細は以下のとおりです。

フィールドコードが異なると JavaScript が正常に動作しないため注意して変更しましょう。
顧客リストアプリのフィールドの設定は以下のとおりです。
なお、記載していないフィールドはデフォルトのままです。

フィールドタイプ フィールド名 フィールドコード
文字列(1 行) 会社名 company
文字列(1 行) 部署名 division
文字列(1 行) 担当者名 assignee
文字列(1 行) TEL TEL
文字列(1 行) メールアドレス Mail
関連レコード一覧 関連レコード一覧 reference

サンプルコード

サンプルコードは以下のとおりです。
JS ファイルは URL 指定もしくはローカルから「顧客リスト」アプリに適用してください。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
/*
 * related records sample program
 * Copyright (c) 2014 Cybozu
 *
 * Licensed under the MIT License
*/

(() => {
  'use strict';

  kintone.events.on('app.record.index.show', (event) => {
    const records = event.records;
    const appId = kintone.app.getRelatedRecordsTargetAppId('reference');

    // ボタンの有無をチェック
    if (document.getElementById('btn-export-csv')) {
      return;
    }

    const spaceEl = kintone.app.getHeaderMenuSpaceElement();
    const CSVButtonEl = document.createElement('button');
    CSVButtonEl.textContent = 'CSVファイル出力';
    CSVButtonEl.id = 'btn-export-csv';
    spaceEl.appendChild(CSVButtonEl);

    CSVButtonEl.addEventListener('click', () => {
      getMakeCsv(appId, records).then((resp) => {
        downloadFile(resp);
      });
    });
  });

  // csvデータの作成
  const getMakeCsv = (appId, customerRecords) => {
    // 同じ会社名の関連レコードを取得
    const fetchRelatedRecords = (opt_index, opt_data) => {
      const index = opt_index || 0;
      const data = opt_data || [];
      const customerRecord = customerRecords[index];

      const company = customerRecord.company.value;
      const division = customerRecord.division.value;
      const assignee = customerRecord.assignee.value;
      const tel = customerRecord.TEL.value;
      const mail = customerRecord.Mail.value;

      const query = `company = "${company}"`; // 会社名をキーに該当関連レコードを取得

      const params = {
        app: appId,
        query: query
      };
      return kintone.api(kintone.api.url('/k/v1/records', true), 'GET', params)
        .then((resp) => {
          const row = [company, division, assignee, tel, mail];
          const relatedRecords = resp.records;
          if (relatedRecords.length > 0) {
            relatedRecords.forEach((relatedRecord) => {
              row.push(relatedRecord.product.value);
              row.push(relatedRecord.subtotal.value);
            });
          }
          data.push(row.join(','));
          if (customerRecords.length > index + 1) {
            return fetchRelatedRecords(index + 1, data);
          }
          return data;
        });
    };
    const header = ['会社名', '部署名', '担当者名', 'TEL', 'メールアドレス', '製品名', '小計'].join(',');
    let csvData = [header];

    return new kintone.Promise((resolve, reject) => {
      fetchRelatedRecords().then((resp) => {
        csvData = csvData.concat(resp);
        resolve(csvData);
      });
    });
  };

  // ダウンロード関数
  const downloadFile = (data) => {
    const csv = data.join('\r\n');
    // ファイル名
    const filename = `client_case${getTimeStamp()}.csv`;

    // Blob準備
    const bom = new Uint8Array([0xef, 0xbb, 0xbf]);
    const blob = new Blob([bom, csv], {type: 'text/csv'});

    if (window.navigator.msSaveBlob) {
      window.navigator.msSaveBlob(blob, filename);
    } else {
      const e = new MouseEvent('click', {view: window, bubbles: true, cancelable: true});
      const url = window.URL || window.webkitURL;
      const blobUrl = url.createObjectURL(blob);
      const a = document.createElementNS('http://www.w3.org/1999/xhtml', 'a');
      a.href = blobUrl;
      a.download = filename;
      a.dispatchEvent(e);
    }
  };

  // ファイル名に付与する日付の取得
  const getTimeStamp = () => {
    const d = new Date();
    const YYYY = d.getFullYear();
    let MM = d.getMonth() + 1;
    let DD = d.getDate();
    let hh = d.getHours();
    let mm = d.getMinutes();
    if (MM < 10) {
      MM = `0${MM}`;
    }
    if (DD < 10) {
      DD = `0${DD}`;
    }
    if (hh < 10) {
      hh = `0${hh}`;
    } else if (mm < 10) {
      mm = `0${mm}`;
    }
    return `${YYYY}${MM}${DD}${hh}${mm}`;
  };
})();

コードの説明

今回はレコードの詳細情報を取得し、CSV ファイルを手動で作成して、その CSV ファイルの中にデータを格納しています。
それでは簡単に説明します。

レコード情報と要素の取得

kintone.app.getRelatedRecordsTargetAppId('フィールドコード') で関連レコードのアプリ ID を取得します。
関連レコード一覧の参照先のアプリ ID を取得する

13
const appId = kintone.app.getRelatedRecordsTargetAppId('reference');

そして、kintone.app.getHeaderMenuSpaceElement() でボタン作成のためにレコード一覧のメニューの右側の要素を取得します。
レコード一覧のメニューの右側の要素を取得する

20
const spaceEl = kintone.app.getHeaderMenuSpaceElement();

customerRecords は、顧客管理アプリを一覧表示したときの event.records です。
index でひとつずつレコードを取り出し、各変数の中にフィールドの値を格納します。

39
const customerRecord = customerRecords[index];

query では、検索条件でキーとなる会社名を変数に格納します。
複数のレコードを取得する

47
const query = `company = "${company}"`; // 会社名をキーに該当関連レコードを取得

ボタンの生成と処理

document.getElementById('btn-export-csv') を if 文の条件とすることで、ボタンがない場合のみボタン要素を生成し、増殖バグを防ぎます。
増殖バグを防ぐ処理については、 第2回 レコード一覧画面にボタンを置いてみよう! を参照してください。

15
16
// ボタンの有無をチェック
if (document.getElementById('btn-export-csv')) {

以下でボタンを押した際に処理を行います。

26
CSVButtonEl.addEventListener('click', () => {

CSV ファイルの作成

CSV ファイルの各項目名(ヘッダー)は手動で作成する必要があります。
CSV はカンマ区切りなので、join(',') で配列の要素を結合した文字列を生成します。

70
const header = ['会社名', '部署名', '担当者名', 'TEL', 'メールアドレス', '製品名', '小計'].join(',');

関連レコードは kintone.api で取得します。
kintone.api は非同期実行なので、顧客ごとの関連レコードを取得するため fetchRelatedRecords を再帰的に呼び出します。
取得した関連レコードの内容を CSV の各行に追加します。

35
36
// 同じ会社名の関連レコードを取得
const fetchRelatedRecords = (opt_index, opt_data) => {

現在の日付をファイル名に

Date オブジェクトで現在の日時を取得します。

106
const d = new Date();

getMonth() は前の月を取得するため、注意して +1 します。

108
let MM = d.getMonth() + 1;

月や日付の桁数が一桁場合、前に 0 を足して二桁にします。

112
113
114
115
116
117
118
119
120
121
122
if (MM < 10) {
  MM = `0${MM}`;
}
if (DD < 10) {
  DD = `0${DD}`;
}
if (hh < 10) {
  hh = `0${hh}`;
} else if (mm < 10) {
  mm = `0${mm}`;
}

最後に YYYY + MM + DD + hh + mm の形式で返します。

123
return `${YYYY}${MM}${DD}${hh}${mm}`;

データのダウンロードについては 集計したデータをCSVでダウンロードするには を参照してください。

最後に

いかがでしたでしょうか。
これで関連レコードも CSV 出力できるようになりました。
また上記のテクニックを活用することで関連レコードに限らず、自由に他アプリからのデータを CSV 出力できます。
ぜひお試しください。

information

この Tips は、2022 年 10 月版 kintone で動作を確認しています。