別アプリへの登録後にテーブル行を追加するとエラーが出る

お世話になります。

背景・実現したいこと

様々な方のやり方等を参考に、 アプリAに登録するとアプリBに自動で

登録、更新を実現できました。

登録したアプリAのレコードでテーブル行を追加し、保存するとエラー

になります。どうエラーを回避すればいいでしょうか?

登録済みのレコード内容を更新するだけの場合は問題ないです。

エラー情報

Failed to load resource: the server responded with a status of 404 (Not Found)

Uncaught Object

  1. code: “GAIA_RE01”
  2. id: “8eGrxB5fFBX92NCnqJov”
  3. message: “The specified record (ID: 0) is not found.”

Failed to load resource: the server responded with a status of 520 (520)

利用したソースコード

(function() {
“use strict”;

kintone.events.on(“app.record.create.submit.success”, function(event) {

var postApp = 348; // 別アプリのID
var recId = event.recordId; // テーブルを登録したレコードを更新するためのID
var subTable = ‘table1’; //サブテーブルのフィールドコード
var headers = [ //コピーするサブテーブル外のフィールドのフィールドコード
‘From’, ‘To’,
];

var record = event.record;

// テーブルを更新するための値
var table = event.record.table1.value;
var array = [];

return kintone.api(kintone.api.url(‘/k/v1/records’, true), ‘POST’, {
app: postApp,
records: event.record[subTable].value.map(function (row) {
headers.forEach(function (header) {
row.value[header] = event.record[header];
});
return row.value;
})
}).then(function(resp) {
// ↓↓↓↓↓↓↓↓↓↓↓↓登録が成功したレコードID をテーブルに登録するための処理
console.log(resp);
for (var i = 0; i < table.length; i++) {
array.push({
“id”:table[i].id,
“value”:{
“id”: {
“value”: resp.ids[i]
}
}
});
}

var body = {
“app”: kintone.app.getId(),
“id”: recId,
“record” :{
“table1”:{
“value”: array
}
}
};

return kintone.api(kintone.api.url(‘/k/v1/record’, true), ‘PUT’, body).then(function(resp) {

// success
console.log(resp);

}, function(error) {
// error
console.log(error);
});

});

});

// 更新時の処理
kintone.events.on(“app.record.edit.submit.success”, function(event) {

var postApp = 348; // 別アプリのID
var recId = event.recordId; // テーブルを登録したレコードを更新するためのID
var subTable = ‘table1’; //サブテーブルのフィールドコード
var headers = [ //コピーするサブテーブル外のフィールドのフィールドコード
‘From’,‘To’,
];

var record = event.record;

console.log(event.record[subTable].value.map(function (row) {
headers.forEach(function (header) {
row.value[header] = event.record[header];
});
return row.value;
}));

return kintone.api(kintone.api.url(‘/k/v1/records’, true), ‘PUT’, {
app: postApp,
records:
event.record[subTable].value.map(function (row) {
headers.forEach(function (header) {
row.value[header] = event.record[header];
});
return {
id: row.value.id.value,
record: row.value
};
})
});
});

Tofuさん

お世話になっております。

app.record.edit.submit.successにおいて、テーブルの既存行であればPUTでよいですが、テーブルの新規行についてはPOSTにする必要があるのではないでしょうか。

江田篤史様

この場合どのように追記すればいいのでしょうか?

またJavascriptについて詳しくなく、コード教えていただければ幸いです。

よろしくお願いいたします。

Tofuさん

お世話になっております。

idを条件にしてレコード取得(GET)を行い、レコードが見つかったものに関しては更新(PUT)、見つからなかったものに関しては新規追加(POST)を行うと良いかと思います。
https://developer.cybozu.io/hc/ja/articles/202331474#step2

kintoneカスタマイズを理解するというcybozu developer networkの目的があるそうなので、現時点でのコード提示は控えさせていただきます。
もし、実装してみてわからないことがあればお力になれればと思いますので、その際にご質問いただきたいです。

江田篤史様

お世話になります。

どうしてもわからなかったので、下記リンクをそのまま利用させていただく

事にしたのですが、自分に置き換えたところ登録はできましたが、

登録時にはidが帰らず、更新時にはPUTが働きません。

エラーも出てこないようですが、

kintone.events.on([‘app.record.create.submit.success’],function(event){

の部分が作成時のみに限定されているからで、edit.submit.successに

も追加すると、idが返るものの更新ではなく、新規に追加となります。

PUT文の前に別途Kintone.event.on('app.record.edit.submit.success~を置いた方が

良いのでしょうか?

https://developer.cybozu.io/hc/ja/community/posts/900002373806-%E3%83%87%E3%83%BC%E3%82%BF%E3%81%AE%E5%88%A5%E3%82%A2%E3%83%97%E3%83%AA%E7%99%BB%E9%8C%B2%E5%BE%8C%E3%81%AE%E7%99%BB%E9%8C%B2%E5%85%88id%E3%81%AE%E5%8F%96%E5%BE%97%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6

失礼しました、上記のリンクはあくまで登録後にidを登録する目的だけですね。

その場合、編集が成功した時のイベントで、if文で行idがあるときはPUTでないときはPOSTで

作ればいいのでしょうか?

特にPOSTする際には最初の内容を繰り返すことになると思いますが、全文を書けばできそう

ですが、短く書く方法もあるのでしょうか?

参考となるリンク等教えていただければ幸いです。

 

続けてすみません、編集が成功した時にPUTで更新で、エラーの際にPOSTでも

良いのでしょうか?

もう少し考えてからでもいいと思うのですが、考えても全く分からず完全に

お手上げ状態です。何とぞよろしくお願いいたします。

Tofuさん

お世話になっております。

返信遅くなりました。

レコード取得をして既存行かどうか判断すると良いと提案しましたが、Tofuさんの実装をもとにするともう少し簡単に実装できそうでした。
Aアプリのサブテーブル内にidという、Bアプリのレコード番号保存用のフィールドを用意しているかと思います。
各行について、このidフィールドに値がセットされているかどうかだけで、既存行かどうかの判断はつくかと思いました。
混乱を招くような提案をしてしまい、申し訳ございません。(kintone組み込みのテーブル行のidのみで紐付けしていると勘違いしておりました。)

> if文で行idがあるときはPUTでないときはPOSTで作ればいいのでしょうか?
'app.record.edit.submit.success’イベント内では、POSTとPUTのどちらか片方だけを実行するのではなく、両方実行することになるかと思います。
テーブルの中身を新規行と既存行に分けて、新規行たちはPOST、既存行たちはPUTすると良いかと思います。
テーブルの中身を分けるには、filterメソッドなどを使うとよいかと思います。
https://www.javadrive.jp/javascript/array/index18.html
分ける条件は上述の通りで、idフィールドに値がセットされているかで分けるとよいかと思います。

> POSTする際には最初の内容を繰り返すことになると思いますが、全文を書けばできそうですが、短く書く方法もあるのでしょうか?
同じ処理を複数回使いたいときは、関数として切り出すのが良いです。
https://www.sejuku.net/blog/31671
ただ、単純に’app.record.create.submit.success’と同じ処理を繰り返すだけではうまくいかない気もしました。
POSTしたデータだけでテーブルを上書きすると、既存行が消えてしまうかもしれないと思いました。
ドキュメントを見ると、kintone組み込みのテーブル行のidを指定すれば指定行のみ書き換えられるようにも見えますが、やってみないとわからないですね。
https://developer.cybozu.io/hc/ja/articles/202166330
万全を期すのであれば、POST用とPUT用で分けたデータを再度合体させてから、テーブルの上書きを行うと良いと思います。

> 編集が成功した時にPUTで更新で、エラーの際にPOSTでも良いのでしょうか?
はい、それでも可能かと思います。
ただ、現状のコードのようにレコードの一括更新だと、一部のレコードの更新処理が失敗した場合に全部レコードの更新処理がキャンセルされます。
https://developer.cybozu.io/hc/ja/articles/201941784#step2
もし、そのような実装をするのであれば、レコード1件ずつで更新処理をするのが良いかと思います。
https://developer.cybozu.io/hc/ja/articles/201941784#step1

江田篤史様

ご返信ありがとうございます。

まだよくわかっていないのですが、if文でfilter関数で[]かどうかで判断するのでしょうか?

 

あと仮に1件ずつ更新する場合は、for文で更新していくのでしょうか?

イメージ

if (filter関数で[]でない){

for (行数のカウント){

PUT文

} else {

POST文

}}

何か参考となるコードを教えていただけませんでしょうか?

よろしくお願いいたします。

 

Tofuさん

お世話になっております。

コードは下記のようになるかと思います。

// 既存行 idがセットされている
var existingRows = event.record[subTable].value.filter(function (row) {
  return row.value.id.value;
});
// 新規行 idがセットされていない
var newRows = event.record[subTable].value.filter(function (row) {
  return !row.value.id.value;
});

return kintone.Promise.all([
  // 既存行はPUT
  kintone.api(kintone.api.url('/k/v1/records', true), 'PUT', {
    app: postApp,
    records: existingRows.map(function (row) {
      headers.forEach(function (header) {
        row.value[header] = event.record[header];
      });
      return {
        id: row.value.id.value,
        record: row.value
      };
    })
  }),
  // 新規行はPOST
  kintone.api(kintone.api.url('/k/v1/records', true), 'POST', {
    app: postApp,
    records: newRows.map(function (row) {
      headers.forEach(function (header) {
        row.value[header] = event.record[header];
      });
      return row.value;
    })
  })
]).then(function (responses) {
  //登録が成功したレコードID をテーブルに登録するための処理(createのときのものとは少し変える必要があるかと思います)
  // ...
  return event;
});

> あと仮に1件ずつ更新する場合は、for文で更新していくのでしょうか?

はい、for文かforEach()でループを回しながら更新することになります。
ただ、Tofuさんの設計の場合は「row.value.id.value」で判断つくので、上述の方法が容易かつリクエスト数も少なく済むと思います。

江田篤史様

ご返信、コードありがとうございます。

更新できました。

また、やはりというか、(createのときのものとは少し変える必要があるかと思います)

部分ですが、どう変えればいいのでしょうか?

body内、PUT文も問題ないように思いますが…

よろしくお願いいたします。

Tofuさん

お世話になっております。
返信遅くなりました。

以前「POSTしたデータだけでテーブルを上書きすると、既存行が消えてしまうかもしれないと思いました。」とコメントした件、やはり既存行は消えますね。

既存行はkintone組み込みのテーブル行idのみ指定し、新規行はkintone組み込みのテーブル行idと登録したいvalue.id.valueを指定して、PUTを行うと良いかと思います。

event.record[subTable].valueでループを回し、条件分岐しながらデータ(Tofuさんのコードでいうarray)を構築すると良いと思います。

イメージとして、下記のような構造になるかと思います。

[
  { //既存行。kintone組み込みのテーブル行idのみ指定
    id: **** , //event.record[subTable].valueから取り出したデータを使う
  },
  { //新規行。kintone組み込みのテーブル行idと、登録したいvalue.id.valueを指定
    id: **** , //event.record[subTable].valueから取り出したデータを使う
    value: {
      id: {
        value: **** , //POSTのレスポンス(responses[1])から取り出したデータを使う
      }
    }
  },
  //...
]

REST APIによる登録・更新用のデータ構造は、下記を参考にすると良いかと思います。
https://developer.cybozu.io/hc/ja/articles/202166330

また、REST API用ではありませんが、下記を読むとテーブル操作を理解しやすいかと思います。
REST APIでは、更新にkintone組み込みのテーブル行idを利用できることや、typeの指定が不要などの若干の違いはありますが、基本的なテーブル操作の理解の助けにはなるかと思います。
https://developer.cybozu.io/hc/ja/articles/360027244231

江田様

お世話になります、色々と勉強しながら進めておりますが、まだ難しいです。
前回のご返信から入れなおしてみましたが、まだエラーがでております。
コードは下記で試しております。

kintone.events.on(“app.record.edit.submit.success”, function(event) {
var postApp = 348;
var recId = event.recordId;
var subTable = ‘table1’;
var headers =[‘From’, ‘To’];
var record = event.record;
var table = record.table1.value;
var array =[];

//行idがないときPOST、ある時PUT
var existingRows = event.record[subTable].value.filter(function (row) {
return row.value.id.value;
});

// 新規行 idがセットされていない
var newRows = event.record[subTable].value.filter(function (row) {
return !row.value.id.value;
});

return kintone.Promise.all([
kintone.api(kintone.api.url(‘/k/v1/records’, true), ‘PUT’, {
app: postApp,
id: resp.id.value,
records: existingRows.map(function (row) {
headers.forEach(function (header) {
row.value[header] = event.record[header];
});
return {
id: row.value.id.value,
record: row.value
};
})
}),
kintone.api(kintone.api.url(‘/k/v1/records’, true), ‘POST’, {
app: postApp,
id: event.record[subTable].value,
records: newRows.map(function (row) {
headers.forEach(function (header) {
row.value[header] = event.record[header];
});
return row.value;
})
})
]).then(function (responses) {
var body = {
“app”: kintone.app.getId(),
“id”: recId,
“record” :{
“table1”:{
“value”: array
}
}
};

return kintone.api(kintone.api.url(‘/k/v1/record’, true), ‘PUT’, body).then(function(resp) {

// success
console.log(resp);

}, function(error) {
// error
console.log(error);
});
});
});

恐縮ですが、どう直すべきか再度コメント頂ければ幸いです。

Tofuさん

お世話になっております.

別アプリへのリクエストではなく,自アプリのテーブルを更新する際のリクエストボディを修正すると良いかと思います.

return kintone.Promise.all([
  kintone.api(kintone.api.url('/k/v1/records', true), 'PUT', {
    app: postApp,
    records: existingRows.map(function (row) {
      headers.forEach(function (header) {
        row.value[header] = event.record[header];
      });
      return {
        id: row.value.id.value,
        record: row.value
      };
    })
  }),
  kintone.api(kintone.api.url('/k/v1/records', true), 'POST', {
    app: postApp,
    records: newRows.map(function (row) {
      headers.forEach(function (header) {
        row.value[header] = event.record[header];
      });
      return row.value;
    })
  })
]).then(function (responses) {
  var body = {
    "app": kintone.app.getId(),
    "id": recId,
    "record": {
      "table1": {
        "value": event.record[subTable].value.map(function (row) {
          if (row.value.id.value) { //既存行
            return { //kintone組み込みのテーブル行idのみ指定
              id: row.id //event.record[subTable].valueから取り出したデータを使う
            }
          } else { //新規行
            //配列responses[1].idsの内の何番目の要素を使えば良いか調べておく
            var postIndex = newRows.findIndex(function (newRow) {
              return row.id === newRow.id;
            });
            return { //kintone組み込みのテーブル行idと、登録したいvalue.id.valueを指定
              id: row.id, //event.record[subTable].valueから取り出したデータを使う
              value: {
                id: {
                  value: responses[1].ids[postIndex] //POSTのレスポンス(responses[1])から取り出したデータを使う
                }
              }
            }
          }
        })
      }
    }
  };

  return kintone.api(kintone.api.url('/k/v1/record', true), 'PUT', body).then(function (resp) {
    // success
    console.log(resp);
  }).catch(function (error) {
    // error
    console.log(error);
  });
});

江田篤史様

お世話になります。
何とか実装でき、操作完了できました!
削除部分については手作業で対応し、利用する関係者間で了解、
何とか目途がつき、利用開始出来そうです。

考え方が難しく、1行ずつ読み解くのに時間がかかりましたが、
達成でき、安心いたしました。
本当にありがとうございました。

Tofuさん

お世話になっております。

解決したようで良かったです。

もし削除を自動で行うのであれば、'app.record.edit.show’で編集開始時のテーブル情報を変数に保持しておき、'app.record.edit.submit.success’時点のテーブル情報と比較することで、削除された行を判別することは可能かと思います。

ご参考になればと思います。