kintone 見積書アプリから Google スプレッドシートで帳票出力してみよう!

著者名:Mamoru Fujinoki(Fuji Business International)

目次

caution
警告

2020年8月改訂のセキュアコーディング ガイドライン に抵触する内容が含まれています。
認証情報が漏洩した場合の影響を考慮して慎重に検討してください。
該当箇所は、JavaScript プログラムの 6-7 行目です。

はじめに

今回は、kintone アプリの見積書から、Google スプレッドシートとデータを連携して見積書を出力できるようにしてみたいと思います。
Google スプレッドシートと連携すると、帳票のテンプレートを使用して簡単にカスタマイズできる上、Email に添付することも可能となります。

概要

連携の流れは以下となっています。

  1. kintone から Google のクライアント ID をもとに OAuth 認証
  2. kintone から Google のアクセストークン取得
  3. アクセストークンを利用して Google Sheets API を実行
  4. kintone アプリのレコードから Google スプレッドシートにデータを登録

kintone アプリの作成

アプリの追加

今回は、kintone アプリの「商品見積書パック」を元にアプリを設定・変更します。
kintone アプリストアより、左サイドメニュー上部のアプリストア検索欄に「見積書」と入力し、検索します。
「商品見積書パック」が表示されますので、「このアプリを追加」をクリックします。

「見積書」アプリを開いて、右上のギアアイコンをクリックして、アプリの設定画面に移行します。

フィールドの追加

下記テーブルを参考に、フォームに対してフィールドを追加します。

フィールドの種類 フィールド名 フィールドコード 備考
日付 見積日 見積日 -
文字列(1行) 見積番号 見積番号 -
文字列(1行) 顧客番号 顧客番号 追加フィールド
日付 有効期限 有効期限 追加フィールド
文字列(1行) 宛名 宛名 -
文字列(1行) 会社名 会社名 追加フィールド
文字列(1行) 郵便番号 郵便番号 追加フィールド
文字列(1行) 都道府県 都道府県 追加フィールド
文字列(1行) 住所 住所 追加フィールド
文字列(1行) 電話番号 電話番号 追加フィールド
テーブル 見積明細 見積明細 -
文字列(1行) 商品名 商品名 テーブル内フィールド
数値 単価 単価 テーブル内フィールド
数値 数量 数量 テーブル内フィールド
計算 小計 小計 計算式:単価*数量
テーブル内フィールド
計算 合計金額 合計金額 計算式:SUM(小計)
文字列(複数行) 備考 備考 -
スペース - print_button_space ボタン用スペース
追加フィールド

追加フィールドは以下のように配置します。

フィールドの設定、変更後、「フォームを保存」し、最後に「アプリを更新」します。

これで、kintone アプリの設定は終了です。

Google スプレッドシートの設定

1. スプレッドシートの作成

Google.com (External link) より、ログインし、右上 Google アプリメニューより、スプレッドシートを選択します。

「+」サインをクリックして、新しいスプレッドシートを作成します。

2. アドオンの設定

今回はスプレッドシートのテンプレートアドオンを利用して、帳票を作成します。
「アドオン」メニューより、「アドオンを取得」を選択します。

検索文字列に「Template Gallery」と入力し、表示された「Template Gallery for Sheets」アドオンをクリックします。

インストール画面が表示されるので、インストールをクリックします。
インストール完了後、画面にしたがって Google アカウントへのアクセスを許可してください。

「アドオン」メニューより「Template Gallery for Sheets」−「Browse Template」を選択し、「Category」から、「Business」−「Invoices」をクリックします。
表示された Invoice テンプレートより、今回は「Billing Invoice」を選択します。
画面に従い、Google Drive にテンプレートをコピーし、「Open File」ボタンをクリックします。

「Invoice」タブのシートを選択し、ファイル名を変更し、必要に応じ、帳票タイトルや見出しを日本語に変更します。

また、この Google スプレッドシートの URL から、スプレッドシート ID を取得します。

以上で Google スプレッドシートの設定は終了です。

Google API の認証情報の設定

1. プロジェクトの作成

次に Google API を利用するために Google API の認証情報を設定します。
まず、 Google Cloud のデベロッパーコンソール (External link) にログインします。
画面が表示されたら、「プロジェクトの選択」をクリックします。

右上の「新しいプロジェクト」をクリックして、新規プロジェクトを作成します。

プロジェクト名を入力し、「作成」ボタンをクリックすると、新規プロジェクトが作成されます。
ここではプロジェクト名を「kintone-google-spreadsheet」にします。

2. Gmail API の有効化

再び、プロジェクト選択の画面を開き、新規作成したプロジェクトを開きます。

次に左サイドメニューより「ライブラリ」を選択し、表示された画面の左サイドメニューから Google Workspace カテゴリーを選択し、「Google Sheets API」を選択します。

「有効にする」をクリックして、Google Sheets API を有効にします。

3. 認証情報の作成

OAuth 同意画面を設定します。
左サイドメニュより「OAuth 同意画面」を選択し、右画面より「User Type」を指定し、「作成」ボタンをクリックします。
今回は検証用として「外部」を選択します。利用状況に応じて適切に選択してください。

アプリケーション名を入力します。
ここでは「kintone-google-spreadsheet」にします。
ユーザーサポートメールをドロップダウンメニューから選択し、デベロッパーの連絡先情報にメールアドレスを入力(ユーザーサポートメールのメールアドレスと同じで OK です)し、「保存して次へ」ボタンをクリックします。

4. クライアントID の作成

次は、OAuth クライアント ID を作成します。
左サイドメニューより「認証情報」を選択し、右側の「+認証情報を作成」をクリックし、「OAuth クライアント ID」を選択します。

アプリケーションの種類に「Web アプリケーション」を選択し、名前を自由に入力し、「承認済みの JavaScript 生成元」にご自身の kintone 環境の URL を入力し、「作成」ボタンをクリックします。
クライアント ID が作成されますので、メモしておきます。(後のプログラミングで使用します)

5. APIキーの作成

再び、「認証情報を作成」メニューから、今度は、「API キー」を選択します。

API キーが作成されますので、メモしておきます。

左サイドメニューの「認証情報」をクリックすると生成した認証情報が確認できます。

以上で Google API の認証情報の設定は終了です。

プログラム

今回使用するプログラム

URL指定で追加
  • https://apis.google.com/js/api.js
  • https://accounts.google.com/gsi/client
アップロードして追加

次の内容を「sample-google-sheets.js」として保存します。
Google API の認証情報の設定を記述している為、 プログラムの修正 を参照して環境に合わせて修正してください。

  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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
/*
* kintone 見積書アプリから Google スプレッドシートで帳票出力してみよう
* Copyright (c) 2022 Cybozu
*
* Licensed under the MIT License
* https://opensource.org/license/mit/
*/

/* global gapi */
/* global google */

(() => {
  'use strict';
  // API キー
  const API_KEY = 'GOOGLE_API_KEY';
  // クライアントID
  const CLIENT_ID = 'GOOGLE_CLIENT_ID';
  // スプレッドシートID
  const SHEET_ID = 'GOOGLE_SHEET_ID';

  // ログイン状態監視用
  let tokenClient = false,
    gapiInited = false,
    gisInited = false;

  // ログイン準備ができたらログインボタン表示
  const checkBeforeStart = () => {
    if (gapiInited && gisInited) {
      document.getElementById('login_button').style.visibility = 'visible';
    }
  };

  const loadGapi = () => {
    gapi.load('client', intializeGapiClient);
  };

  const intializeGapiClient = async () => {
    await gapi.client.init({
      apiKey: API_KEY,
      discoveryDocs: ['https://www.googleapis.com/discovery/v1/apis/sheets/v4/rest'],
    });
    gapiInited = true;
    checkBeforeStart();
  };

  const loadGsi = () => {
    tokenClient = google.accounts.oauth2.initTokenClient({
      client_id: CLIENT_ID,
      scope: 'https://www.googleapis.com/auth/spreadsheets',
      callback: '', // defined later
    });
    gisInited = true;
    checkBeforeStart();
  };

  // Googleログイン準備
  const initClient = () => {
    loadGapi();
    loadGsi();
  };

  // Googleログインボタンクリック
  const handleLoginClick = () => {
    tokenClient.callback = async (resp) => {
      if (resp.error !== undefined) {
        throw resp;
      }
      document.getElementById('logout_button').style.visibility = 'visible';
      document.getElementById('print_button').style.visibility = 'visible';
      document.getElementById('login_button').textContent = 'Refresh';
    };
    if (gapi.client.getToken() === null) {
      tokenClient.requestAccessToken({prompt: 'consent'});
    } else {
      tokenClient.requestAccessToken({prompt: ''});
    }
  };

  // ログアウト
  const handleLogoutClick = () => {
    const token = gapi.client.getToken();
    if (token !== null) {
      google.accounts.oauth2.revoke(token.access_token);
      gapi.client.setToken('');
      document.getElementById('login_button').textContent = 'Googleログイン';
      document.getElementById('logout_button').style.visibility = 'hidden';
      document.getElementById('print_button').style.visibility = 'hidden';
    }
  };

  // 見積書印刷
  const printInvoice = (event) => {
    // レコードのデータの取得
    const record = kintone.app.record.get().record;
    if (!record) {
      return;
    }

    // API リクエスト
    // リクエストパラメータの設定
    const params = {
      // スプレッドシートID
      spreadsheetId: SHEET_ID
    };

    // 商品リストをSpreadSheet用の配列に変更
    const productArray = record.見積明細.value.map((item) => [
      item.value.商品名.value,
      null,
      null,
      null,
      null,
      item.value.数量.value,
      item.value.単価.value,
    ]);

    // スプレッドシートに出力するデータの設定
    const batchUpdateSpreadsheetRequestBody = {
      valueInputOption: 'RAW',
      data: [
        {
          range: '\'Invoice\'!A1:A6',
          majorDimension: 'COLUMNS',
          values: [
            [
              record.宛名.value + '様',
              record.会社名.value,
              record.郵便番号.value,
              record.都道府県.value,
              record.住所.value,
              record.電話番号.value
            ]
          ]
        },
        {
          range: '\'Invoice\'!F5:H7',
          majorDimension: 'ROWS',
          values: [
            [
              record.見積番号.value,
              null,
              record.見積日.value
            ],
            [],
            [
              record.顧客番号.value,
              null,
              record.有効期限.value
            ]
          ]
        },
        {
          range: '\'Invoice\'!A12:A17',
          majorDimension: 'COLUMNS',
          values: [
            [
              record.宛名.value + '様',
              record.会社名.value,
              record.郵便番号.value,
              record.都道府県.value,
              record.住所.value,
              record.電話番号.value
            ]
          ]
        },
        {
          range: '\'Invoice\'!F12:F17',
          majorDimension: 'COLUMNS',
          values: [
            [
              record.宛名.value + '様',
              record.会社名.value,
              record.郵便番号.value,
              record.都道府県.value,
              record.住所.value,
              record.電話番号.value
            ]
          ]
        },
        {
          range: '\'Invoice\'!A39:A39',
          majorDimension: 'COLUMNS',
          values: [
            [record.備考.value]
          ]
        },
        {
          range: '\'Invoice\'!A21:G38',
          majorDimension: 'ROWS',
          values: productArray
        }
      ]
    };

    // スプレッドシートへの出力実行
    const request = gapi.client.sheets.spreadsheets.values.batchUpdate(params, batchUpdateSpreadsheetRequestBody);
    request.then((response) => {
      alert('帳票出力に成功しました。');
    }, (reason) => {
      alert('エラー: ' + reason.result.error.message);
    });
  };

  // レコード詳細画面の表示後イベント
  kintone.events.on('app.record.detail.show', (event) => {
    // ボタン増殖回避
    if (document.getElementById('print_button') !== null) {
      return event;
    }

    // Googleログインボタン作成
    const loginButton = document.createElement('button');
    loginButton.id = 'login_button';
    loginButton.textContent = 'Googleログイン';
    loginButton.style.visibility = 'hidden';

    // Googleログアウトボタン作成
    const logoutButton = document.createElement('button');
    logoutButton.id = 'logout_button';
    logoutButton.textContent = 'Googleログアウト';
    logoutButton.style.visibility = 'hidden';

    // 帳票出力ボタン作成
    const printButton = document.createElement('button');
    printButton.id = 'print_button';
    printButton.textContent = '帳票出力';
    printButton.style.visibility = 'hidden';

    // 各ボタンに機能を持たせる
    logoutButton.addEventListener('click', handleLogoutClick);
    loginButton.addEventListener('click', handleLoginClick);
    printButton.addEventListener('click', () => {
      printInvoice(event);
    });

    // スペースフィールドに各ボタンを設置
    kintone.app.record.getSpaceElement('print_button_space').appendChild(loginButton);
    kintone.app.record.getSpaceElement('print_button_space').appendChild(logoutButton);
    kintone.app.record.getSpaceElement('print_button_space').appendChild(printButton);

    // Googleログイン準備
    initClient();
    return event;
  });
})();

プログラムの修正

Google スプレッドシートの設定 Google API の認証情報の設定 で取得した各設定値をセットします。

  • API_KEY:Google API キー
  • CLIENT_ID:クライアント ID
  • SHEET_ID :スプレッドシート ID の値
 6
 7
 8
 9
10
11
// API キー
const API_KEY = 'GOOGLE_API_KEY';
// クライアントID
const CLIENT_ID = 'GOOGLE_CLIENT_ID';
// スプレッドシートID
const SHEET_ID = 'GOOGLE_SHEET_ID';

スコープとディスカバリードックの情報の詳細は Google Sheets API のガイド (External link) を参照してください。

解説

Google API 認証

Google API の JavaScript クライアントと Google Identity Services ライブラリをロードし、ログイン時の処理を記述します。
JavaScript クライアントと Google Identity Services ライブラリの詳細に関しましては、 Google Identity Services JavaScript SDK (External link) を参照してください。

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
// ログイン状態監視用
let tokenClient = false,
  gapiInited = false,
  gisInited = false;

// ログイン準備ができたらログインボタン表示
const checkBeforeStart = () => {
  if (gapiInited && gisInited) {
    document.getElementById('login_button').style.visibility = 'visible';
  }
};

const loadGapi = () => {
  gapi.load('client', intializeGapiClient);
};

const intializeGapiClient = async () => {
  await gapi.client.init({
    apiKey: API_KEY,
    discoveryDocs: ['https://www.googleapis.com/discovery/v1/apis/sheets/v4/rest'],
  });
  gapiInited = true;
  checkBeforeStart();
};

const loadGsi = () => {
  tokenClient = google.accounts.oauth2.initTokenClient({
    client_id: CLIENT_ID,
    scope: 'https://www.googleapis.com/auth/spreadsheets',
    callback: '', // defined later
  });
  gisInited = true;
  checkBeforeStart();
};

// Googleログイン準備
const initClient = () => {
  loadGapi();
  loadGsi();
};

// Googleログインボタンクリック
const handleLoginClick = () => {
  tokenClient.callback = async (resp) => {
    if (resp.error !== undefined) {
      throw resp;
    }
    document.getElementById('logout_button').style.visibility = 'visible';
    document.getElementById('print_button').style.visibility = 'visible';
    document.getElementById('login_button').textContent = 'Refresh';
  };
  if (gapi.client.getToken() === null) {
    tokenClient.requestAccessToken({prompt: 'consent'});
  } else {
    tokenClient.requestAccessToken({prompt: ''});
  }
};

// ログアウト
const handleLogoutClick = () => {
  const token = gapi.client.getToken();
  if (token !== null) {
    google.accounts.oauth2.revoke(token.access_token);
    gapi.client.setToken('');
    document.getElementById('login_button').textContent = 'Googleログイン';
    document.getElementById('logout_button').style.visibility = 'hidden';
    document.getElementById('print_button').style.visibility = 'hidden';
  }
};
レコード詳細画面の表示後イベント

レコード詳細画面の表示後イベントは以下の関数内に記述します。

197
198
// レコード詳細画面の表示後イベント
kintone.events.on('app.record.detail.show', (event) => {});

Google ログインボタン、Google ログアウトボタン、見積書データを出力する帳票出力ボタンをスペースフィールドに設置します。
イベントリスナーにはボタンのクリックした際に呼び出される関数を設定しています。

209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
// Googleログアウトボタン作成
const logoutButton = document.createElement('button');
logoutButton.id = 'logout_button';
logoutButton.textContent = 'Googleログアウト';
logoutButton.style.visibility = 'hidden';

// 帳票出力ボタン作成
const printButton = document.createElement('button');
printButton.id = 'print_button';
printButton.textContent = '帳票出力';
printButton.style.visibility = 'hidden';

// 各ボタンに機能を持たせる
logoutButton.addEventListener('click', handleLogoutClick);
loginButton.addEventListener('click', handleLoginClick);
printButton.addEventListener('click', () => {
  printInvoice(event);
});

// スペースフィールドに各ボタンを設置
kintone.app.record.getSpaceElement('print_button_space').appendChild(loginButton);
kintone.app.record.getSpaceElement('print_button_space').appendChild(logoutButton);
kintone.app.record.getSpaceElement('print_button_space').appendChild(printButton);
Google スプレッドシートへ出力

まずは、見積書アプリで設定したレコードのデータを取得します。

86
87
// レコードのデータの取得
const record = kintone.app.record.get().record;

スプレッドシート ID を変数に設定します。

91
92
93
94
95
96
// API リクエスト
// リクエストパラメータの設定
const params = {
  // スプレッドシートID
  spreadsheetId: SHEET_ID
};

見積書のサブテーブルの商品リストを Array として設定します。

 98
 99
100
101
102
103
104
105
106
107
// 商品リストをSpreadSheet用の配列に変更
const productArray = record.見積明細.value.map((item) => [
  item.value.商品名.value,
  null,
  null,
  null,
  null,
  item.value.数量.value,
  item.value.単価.value,
]);

次に Google スプレッドシートに出力するデータのパラメーターを JSON 形式で設定します。

109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
// スプレッドシートに出力するデータの設定
const batchUpdateSpreadsheetRequestBody = {
  valueInputOption: 'RAW',
  data: [
    {
      range: '\'Invoice\'!A1:A6',
      majorDimension: 'COLUMNS',
      values: [
        [
          record.宛名.value + '様',
          record.会社名.value,
          record.郵便番号.value,
          record.都道府県.value,
          record.住所.value,
          record.電話番号.value
        ]
      ]
    },
    {
      range: '\'Invoice\'!F5:H7',
      majorDimension: 'ROWS',
      values: [
        [
          record.見積番号.value,
          null,
          record.見積日.value
        ],
        [],
        [
          record.顧客番号.value,
          null,
          record.有効期限.value
        ]
      ]
    },
    {
      range: '\'Invoice\'!A12:A17',
      majorDimension: 'COLUMNS',
      values: [
        [
          record.宛名.value + '様',
          record.会社名.value,
          record.郵便番号.value,
          record.都道府県.value,
          record.住所.value,
          record.電話番号.value
        ]
      ]
    },
    {
      range: '\'Invoice\'!F12:F17',
      majorDimension: 'COLUMNS',
      values: [
        [
          record.宛名.value + '様',
          record.会社名.value,
          record.郵便番号.value,
          record.都道府県.value,
          record.住所.value,
          record.電話番号.value
        ]
      ]
    },
    {
      range: '\'Invoice\'!A39:A39',
      majorDimension: 'COLUMNS',
      values: [
        [record.備考.value]
      ]
    },
    {
      range: '\'Invoice\'!A21:G38',
      majorDimension: 'ROWS',
      values: productArray
    }
  ]
};

上記で設定したリクエストを実行します。
今回は、batchUpdate 関数を使って、スプレッドシートに出力しています。

187
188
189
190
191
192
193
// スプレッドシートへの出力実行
const request = gapi.client.sheets.spreadsheets.values.batchUpdate(params, batchUpdateSpreadsheetRequestBody);
request.then((response) => {
  alert('帳票出力に成功しました。');
}, (reason) => {
  alert('エラー: ' + reason.result.error.message);
});

スプレッドシート出力データのパラメーター設定および API に関する詳細は、 Google Sheets API Reference のspreadsheets.values の項目 (External link) を参照してください。

プログラムの配置

これらのプログラムを「アプリの設定 > JavaScript/CSS でカスタマイズ」下に配置します。

動作確認

見積書で商品を選択するため、kintone の商品リストアプリを開いて、いくつか商品を追加します。

見積書アプリに新規レコードを追加します。

保存後、「Google ログインボタン」が表示されます。

「Google ログインボタン」をクリックすると Google アカウントへの認証画面が表示されるので、メールアドレス、パスワードを入力して Google アカウントにログインします。
Google スプレッドシートを作成したアカウントを選択してください。

また、認証画面を表示するには、お使いのブラウザーのポップアップ設定を有効にしてください。

以下のような警告が表示された場合は「cybozu.com(安全ではないページ)に移動」を選択してください。

「帳票出力」ボタンが画面右上に表示されるのでクリックして見積書を Google スプレッドシートに出力します。

見積書 Google スプレッドシートを開くと kintone 見積書アプリで作成したデータが出力されています。
「ファイル」メニューから印刷したり、PDF で保存して Email で顧客へ送信したりできます。

まとめ

今回は、kintone のアプリストアから商品見積書パックを使って、見積書を Google スプレッドシートに帳票出力してみました。
他にもいろいろなテンプレートを利用して Google スプレッドシートを帳票作成ツールとして kintone の機能を補う形で活用できると思いますのでぜひ、試してみてください。

参照サイト

information

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