kintoneでレコード全件について関連レコードの項目を集計して更新する機能を泥臭くカスタマイズする【最終回】

↓前回は、ベタ書き＆for文を使って1件ずつ、3件の更新をしてみました👀✨✨

https://qiita.com/juri_don/items/934bdb88a29e1b662ccd

今回は1件ずつではなく、一括更新できる kintone REST API Clientのメソッドを使って、全件更新するチャレンジ！してみたいと思います。



**記号の見方**
丸コピペされるのを防ぐために、罠を埋め込んでみました👀
デバッグしたり、参考ページを見て直してから使ってね！

🙄考えるところ
🛠️手を動かすところ
👻コードに罠が埋め込まれているよのマーク


# 🙄一括更新API？

前回までは`updateRecord`メソッドを使ってましたが、
今回は一括更新用の`updateRecords`メソッド(※最後にsがついてる)を使って更新することを考えます。

## 🙄1件更新と、一括更新のリクエストパラメータの違い

1件ずつの更新と特に異なっているところは、
リクエストパラメータに全レコード分の更新情報を入れなきゃいけないところです。

例えば、レコード番号20のレコードを更新したい時のリクエストパラメータは
↓こんな感じでした。

```js
{
  app: kintone.app.getId(),
  id: 20,
  record: {
    Aプラン小計: {
      value: 123,
    },
}
```

それが、一括更新となるとこんな感じになります。
`record` が `records` になって、更新したいレコードを配列に入れるような形になっています。

```js
{
  app: kintone.app.getId(),
  records: [
    { id: 20, record: { Aプラン小計: { value: 123 } } },
    { id: 19, record: { Aプラン小計: { value: 234 } } },
    { id: 18, record: { Aプラン小計: { value: 456 } } },
  ],
}
```

となると、
前回ループの中で組み立てたパラメータは1件ずつの更新用。
一括更新となると作り変えないといけません。


## 🙄1件ずつ更新のアルゴリズム（前回までのアルゴリズム）
前回まではこんなアルゴリズムで更新していました。


下記をレコード番号20, 19, 18の分（3回ループ）
↓
レコード番号で絞り込んだ集計元のレコードを取得（getRecords）
↓
レコード番号ごとに合計費用を集計する
↓
レコード1件更新用リクエストパラメータを作成する
↓
1件更新する（putRecord）
↓
次のループへ

## 🙄一括更新のアルゴリズム（案）

今回は、まず一括更新用のリクエストパラメータをループで作成して
それから`putRecords`で一括更新🙄
となりそうです。
じゃあこんな感じになるかな・・・・

レコード数分ループ（全部で20件なので20回ループかな）
↓
レコード番号で絞り込んだ集計元のレコードを取得（getRecords）
↓
レコード番号ごとに合計費用を集計する
↓
レコード一括更新用リクエストパラメータに更新する内容を追加する
↓
次のループへ
↓
ループ終了

↓
一括更新（putRecords）

🤔むー。気になるところはあるけど・・・・
とりあえず作ってみよっか。

## 🛠️一括更新のリクエストパラメータを作る準備

では、レコード数分ループしてみたいと思います。

レコード数分ループ（全部で20件なので20回ループかな）
↓
レコード番号で絞り込んだ集計元のレコードを取得（getRecords）
↓
レコード番号ごとに合計費用を集計する

`event.records`　で今一覧に表示されている分のレコードが取得できるので、
良いか悪いかは置いておいて、ひとまず、`event.records` のレコード数分ループすることにしましょう。

```js
// 表示されているレコード
const records = event.records; // 今一覧に表示されているレコードたちを取得
for (const r of records) { // 20件分ループかな
  const query =
    '顧客管理レコード番号_関連レコード紐付け用="' +
    r["レコード番号"].value +
    '" and 提案プラン in ("Aプラン")';
  const outputFields = ["合計費用"];
  const params = {
    app: relatedAppId,
    query: query,
    fields: outputFields,
  };
  // 関連レコードの案件を取得する
  const res = await client.record.getRecords(params);
  // 合計金額集計
  const amount = res.records.reduce(
    (preV, p) => preV + parseFloat(p[outputFields[0]].value),
    0
  );
  
}
```

これで、一覧に表示される分のAプラン小計がamountに入るはず。
コレを使って、リクエストパラメータを作ろう👀

## 🛠️一括更新のリクエストパラメータを作って一括更新する

ループの前に最初に`param`を準備しておいて、ループ中に`record`1件分ずつの更新用パラメータを入れていこう。

```js
// ループの前にリクエストパラメータ定義
const param = {
  app: kintone.app.getId(),
  records: [],
};
```

↓ループ内でリクエストパラメータの`records`に追加

```js
// リクエストパラメータのrecordsに追加する
param.records.push({
  id: r["レコード番号"].value,
  record: { Aプラン小計: { value: amount } },
});

```

↓作成したparamで一括更新する

```js
const res2 = await client.record.updateRecords(param);
```

これで、今表示されている一覧分のレコードが表示されるはず💪
※画面のリロードお忘れなく。（前回記事なども参考にしてくださいね）


# 🙄でも、遅い。ループ内で毎回getRecordsするのは無駄じゃない？

というわけでこれで更新できるのですが、
レコード20件分のループ内で毎回`getRecords`を呼ぶのは無駄です。
毎回`wait`させられるので処理が遅くなりますし、
まだまだ20件とかだから良いかもだけど、これがレコード500件分のループなんてことになれば激遅アプリになってしまいます。

というわけで、getRecordsをループで毎回するのではなくて、
ループが始まる前に関連レコードを全件呼び出してしまいましょう👀

紐づくレコード番号（顧客管理レコード番号_関連レコード紐付け用）と、合計費用の列だけあるような全レコードを呼び出しましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/536148/de68f8d6-183d-af7b-9bbf-7f9d0b0b0fa2.png)

## 🛠️👻ループの前に関連レコードで紐付いているアプリのレコードを全件呼び出す

紐付け用のレコード番号と、合計費用だけで良くて、プランAの案件で絞り込むので・・・・
プランAの案件情報をすべて取ってくるパラメータはこんな感じになるかな👀

罠を仕込んでいますが、気づけるかな👀💦💦
罠というか、私が躓いてしまったところですが。
↓こちらのドキュメントと見比べて直してみてね✨✨罠についての質問は受け付けません👀ｷﾘｯ

https://github.com/kintone/js-sdk/blob/master/packages/rest-api-client/docs/record.md#getAllRecords

```js
const query = '提案プラン in ("Aプラン")';
const outputFields = [
  "顧客管理レコード番号_関連レコード紐付け用",
  "合計費用",
];
const params = {
  app: relatedAppId,
  query: query,
  fields: outputFields,
};
// 関連レコード(案件レコード)を取得する（案件：projectなのでpRecordsとしてみた）
const pRecords = await client.record.getAllRecords(params);
```

ここで、`pRecords`の中身がこんな感じだとOK。10件だとBプランの分も取ってきちゃってることと思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/536148/a432e0b4-a653-0393-4939-39ccdaf875b0.png)

## 🙄🛠️👻とってきた案件のレコードをレコード番号毎に集計する

↓前回までの集計はこんな感じで計算していました。

```js
// 合計金額集計
const amount = res.records.reduce(
  (preV, p) => preV + parseFloat(p[outputFields[0]].value),
  0
);
```

これは`reduce()`メソッドを使って合計値を計算するという技を使っております。
今回は、`pRecords`というレコードの情報（オブジェクト配列）の値を合計したいので
MDNの[オブジェクトの配列の値の合計値を出す](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce#%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%AE%E9%85%8D%E5%88%97%E3%81%AE%E5%80%A4%E3%81%AE%E5%90%88%E8%A8%88%E5%80%A4%E3%82%92%E5%87%BA%E3%81%99)を参考にコードを組み立てます。

↓MDNのサンプルコード

```js
let initialValue = 0
let sum = [{x: 1}, {x: 2}, {x: 3}].reduce(function (previousValue, currentValue) {
    return previousValue + currentValue.x
}, initialValue)

console.log(sum) // logs 6
```


`[{x: 1}, {x: 2}, {x: 3}]`の部分を`pRecords`に変更して・・・、
足したいのは`合計費用`フィールドの値(value)なので

```js
let sum = pRecords.reduce(function (previousValue, currentValue) {
  return previousValue + currentValue["合計費用"].value;
}, initialValue)

```

しかし、これだと、紐付け用のレコード番号関係なく全部足されちゃうので、条件をつけましょう。
forループ`for (const r of records) {～}`内で見ているレコード`r`の**レコード番号**と、
`pRecords`の**顧客管理レコード番号_関連レコード紐付け用**の値が同じ場合のみ足すようにします。
同じではない場合は前回値`previousValue`をreturnします。
これでプランAのレコード番号ごとの合計費用が計算できます。

というわけで↓を参考に、変数名を揃えたり、型に気をつけたりして合計値計算頑張ってみてください👀👻

```js
let sum = pRecords.reduce(function (previousValue, currentValue) {
  if (currentValue["顧客管理レコード番号_関連レコード紐付け用"].value === r["レコード番号"].value) {
    return previousValue + currentValue["合計費用"].value;
  }
  return previousValue;
}, initialValue)
```

# 🛠️パラメータの完成

あとは、putRecords用のリクエストパラメータにレコード番号と合計費用の情報を追加したらOK。

```js
param.records.push({
  id: r["レコード番号"].value,
  record: { Aプラン小計: { value: amount } },
});
```

最終的な`param`の中身はこんな感じになるかと思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/536148/459ab671-f62f-f2e8-a285-57010c26bae2.png)

コレで上手く更新されることと・・・・思います👀！

# 🙄さいごに・・・

ちょっと思うところがあって、全コードをあえて載せないようにしたり、機能毎にバラバラに書いたり、
コピペじゃ動かない罠も仕込むという記事にしました。
というわけで、👻アイコンがついているところは試行錯誤して動くようにしてみてくださいね！

それと、下記の部分、「一覧に表示されているレコード」ですので、
全レコードについて集計をしたい場合は変更する必要があります。

event.recordsではない他の方法🙄泥臭く、考えてみてくださいね！

```js
// 表示されているレコード
const records = event.records;
for (const r of records) {
```