kintoneからspreadsheetに情報を転記する（簡易版ver2）

# はじめに
[こちら](https://qiita.com/nintia8/items/3f38f31309a1ac4d48d4)の記事で紹介したツールの改良版です

前回から変更したポイント
- kintoneの全レコードが取得できるようにした
- 表を簡略化

# 初期設定
### spreadsheetのコピー
spreadsheetをコピーして使ってください([link](https://docs.google.com/spreadsheets/d/19nDiK_R6FUdQj-saKpor9UMy-wKxaEgOqIubhiMQOCs/edit?usp=sharing))

### kintoneアプリ情報の設定
「config」というシートに情報を入力してください
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/172695/a367bfe4-c53a-2286-a824-da629d863667.png)

1. 「実行」列にはチェックをつけてください。
2. 「アプリNo」列にkintoneのアプリ番号を入力してください([link](https://potara.net/2021/04/14/1801/#:~:text=%E3%82%A2%E3%83%97%E3%83%AAID,123%E3%81%8C%E3%82%A2%E3%83%97%E3%83%AAID%E3%81%A7%E3%81%99%E3%80%82))
3. 「アプリ名前」列にkintoneのアプリ名を入力してください([link](https://jp.cybozu.help/k/ja/user/app_settings/app_changename.html))


### ユーザー情報の入力
1. spreadsheetのスクリプトエディタを開いてください
<img width="651" alt="スクリーンショット 2019-06-18 16.59.46.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/172695/1db858b5-47cd-49cd-334c-a3b2a495e145.png">

2. ファイルの「プロジェクトのプロパティ」を開いてください
<img width="368" alt="スクリーンショット 2019-06-18 17.02.32.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/172695/7908b1f9-38d5-7bbb-449f-2f246b434630.png">

3. 以下の情報を設定してください。
    1. subdomain - サイボウズのURLのサブドメイン(https://◯◯.cybozu.com/の◯◯の部分)
    2. user - ログインする時のユーザー名
    3. pass - ログインする時のパスワード
<img width="674" alt="スクリーンショット 2019-06-18 17.03.40.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/172695/ea591bee-206f-a062-bad5-6833a358445c.png">

※ユーザー名、パスワードに使うアカウントは管理者権限が必要かも

### アプリシートの設定
- シート名をアプリ名と同じにしてください
- 各列の見出しにはアプリのフィールドコードを設定してください([link](https://jp.cybozu.help/k/ja/user/app_settings/form/autocalc/fieldcode.html))

## 実行
「概要」シートの猫の画像を押すと、処理が開始されます。

# ソース
```gas:main.js
function getKintone(applicationName) {
  let appList = getAppList();
  appList = appList.filter(app => {
    return !app.isIgnore() && !app.isDifferentName(applicationName);
  });
  
  const manager = getManeger(appList);
  appList.forEach(app => {
    app.setRecords(getAllRecords(manager, app.getIndex()));
    app.refreshSheet();
  });
}
```

```gas:sheet.js
var CONFIG_SHEET = {
  name: 'config',
  row: {
    data: 2,
  },
  column: {
    runFlag: 1,
    appId: 2,
    appName: 3,
  }
};

function refreshSheet(sheetName, outList) {

  const sheet = SpreadsheetApp.getActive().getSheetByName(sheetName);
  const row = 2;
  const column = 1;
  sheet.getRange(row, column, sheet.getLastRow(), outList[0].length).clearContent();
  sheet.getRange(row, column, outList.length, outList[0].length).setValues(outList);
}

function getSheetKeyList(sheetName){
  const sheet = SpreadsheetApp.getActive().getSheetByName(sheetName);
  return sheet.getRange('1:1').getValues()[0].filter(Boolean);
}

function getAppList(){
  const sheet = SpreadsheetApp.getActive().getSheetByName(CONFIG_SHEET.name);
  let data = sheet.getDataRange().getValues();
  data.shift();

  return data.map((row, index) => new App(row, index));
}
```

```gas:class/App.js
class App{
  constructor(row, index){
    this.index = index;
    this.runFlag = row[CONFIG_SHEET.column.runFlag - 1];
    this.appId = row[CONFIG_SHEET.column.appId - 1];
    this.appName = row[CONFIG_SHEET.column.appName - 1];
    this.records = undefined;
  }

  isIgnore(){
    return !this.runFlag;
  }

  isDifferentName(name){
    const isString = object => typeof object === 'string' || object instanceof String;
    return isString(name) && this.appName !== name;
  }

  getKintoneAppData(){
    return {
      'appid' : this.appId,
      'name' : this.appName,
    };
  }

  getIndex(){
    return this.index;
  }

  getSheetName(){
    return this.appName;
  }

  setRecords(records){
    this.records = records;
  }

  refreshSheet(){
    const outList = this.getOutList();
    refreshSheet(this.getSheetName(), outList);
  }

  getOutList(){
    const keyList = getSheetKeyList(this.getSheetName());

    if(this.records === undefined) return [['取得エラー']];

    return this.records.map(record => {
      return keyList.map(key => {
        const value = record[key].value;

        if (!Array.isArray(value)) return value;

        if(value[0] instanceof Object){
          return value.map(obj => JSON.stringify(obj)).join('|');
        };

        return value.join();
      });
    });
  }
}
```

```gas:kintone.js
function getManeger(appList){
  const subdomain = PropertiesService.getScriptProperties().getProperty('subdomain');
  const user = PropertiesService.getScriptProperties().getProperty('user');
  const pass = PropertiesService.getScriptProperties().getProperty('pass');
  const apps = appList.reduce((apps, app) => {
    apps[app.getIndex()] = app.getKintoneAppData();
    return apps;
  }, {});
  return new KintoneManager.KintoneManager(subdomain, apps, user, pass);
}

function getAllRecords(manager, appName, offset = 0, limit = 500, optRecords) {
  let allRecords = optRecords || [];
  const query = `order by $id desc limit ${limit}　offset ${offset}`;

  if (offset > 10000) {
    Logger.log('offsetが10000を超えたので取得を終了します。');
    return allRecords;
  }

  let response = manager.search(appName, query);
  let records = JSON.parse(response.getContentText()).records;

  allRecords = allRecords.concat(records);

  if (records.length === limit) {
    return getAllRecords(manager, appName, offset + limit, limit, allRecords);
  }

  return allRecords;
}
```