お世話になります。
一覧に設置したボタンをクリックした際にレコード更新で行き詰まってます。
条件に一致したレコードのフィールドを更新したいのですが、うまくいきません。
①本アプリ内で更新するレコードを条件で取得
②取得したレコードの内、フィールド値が振替の場合、別アプリから値を取得し、入力する
③取得したレコードのフィールド値が振替でない場合、”振込”を入力する
という流れです。
別のアプリから取得する場合、値が入りません。上記でいうと②の場合手前のlogまでは
取得できているのですが、レコードに書き込みに行きません。
elseの処理で任意の言葉を入れるレコードは正常に登録ができています。
ご教授のほど宜しくお願いいたします。
kintoneUtility.rest.getAllRecordsByQuery(param).then(function(resp) {
//////// 空更新オブジェクトの生成
var param = {
"app": appId,
"records": [],
'query': ' ステータス = "作業者なし"',
isGuest: false
};
console.log(resp)
resp.records.forEach(function(record) {
if (record.サービス.value)return
if(record.種類.value==="振替"){
var FNo =record.加入者コード.value;
var query = 'ファクターNo = "' + FNo + '"';
var NssParam = {
app: 156,
query: query,
fields: ['ファクターNo','商品'],
};
kintoneUtility.rest.getAllRecordsByQuery(NssParam).then(function(NssResp) {
var service=null;
for (var i = 0; i < NssResp.records.length; i++) {
if (NssResp['records'][i]['ファクターNo']['value'] === record.加入者コード.value ) {
service = NssResp['records'][i]['商品']['value']
}
}
console.log(service)
var newRec = {
//未収サービスにセット
"サービス": { value: service },
};
param.records.push({
"id": record['$id']['value'],
"record": newRec
});
})
}else{
var newRec = {
//未収サービスにセット
"サービス": { value: "振込" },
};
param.records.push({
"id": record['$id']['value'],
"record": newRec
});
}
});
kintoneUtility.rest.putAllRecords(param).then(function(resp) {
// success
console.log(resp);
location.reload(true)
}, function(error) {
// error
console.log(param);
console.log(error);
});
})
見た限りだと、非同期処理(kintoneUtility)の完了を待たずにresp.records.forEachのループが次のループに移っているのが原因かと思います。
プログラムは上から実行されるのが原則ですが、非同期処理(今回の場合だとレコードの取得)はプログラムとは別に実行される(イメージとして、プログラムの文としては「レコードの取得を行う命令を行う」までが実行文で、その命令という処理が完了したため結果を待たずに次の処理へ移る)ので、完了を待つ処理を追加するか、先にレコードの取得を終わらせる必要があります(このあたりは非同期処理やPromiseといった言葉で調べて下さい)。
①最初のgetAllRecordsByQuery後、forEachループを始めるより先にアプリID156のレコードも取得(クエリは指定しない)し、両方のレコード取得完了後、最初に取得したレコードでループを回し、レコードの種類が「振替」だった場合は後に取得したレコードから「ファクターNoと加入者コードが一致する」レコードを探す(ループを回すかfind等を使う)
→恐らくパフォーマンスで優れるのはこちらかと思います。
②async/awaitを使い、最初に取得したレコードでforループを回してawaitでレコードの取得を行う(forEachはawait非対応のため)
→パフォーマンスは良くないのと多数のAPIを実行することになります。
どちらかの処理を行う必要があります。
また、kintone Utility Library for JavaScriptは現在非推奨で、現在はkintone/rest-api-client が推奨されています。
はしもと様 ご返信ありがとうございます!
ご教授頂いた内容一度調べてみます。
kintone Utility Library for JavaScriptは非推奨なんですね・・・
moment()もそうですが、非推奨になっていくとド素人には新しい代替機能に追いつきませんね。
ともかくまずは、調べてみます!
今後とも宜しくお願いいたします
はしもと様
今回一覧画面でのボタンを押した後の動作となりますが
私のjsのパターンですと「async () =>」「await」を入れる場所に悩んでおります。
https://developer.cybozu.io/hc/ja/articles/900001244323#examples
jsが動くのがボタンを押したときなので
myIndexButton.onclick = async () => { となるのでしょうか?
await は下記で考えておりましたが理解はあっておりますでしょうか?
await kintoneUtility.rest.getAllRecordsByQuery(NssParam).then(function(NssResp) {
どうぞ宜しくお願いいたします。
青山昌司 さま
async/awaitの方法でされるのですね。その場合は
kintoneUtility.rest.getAllRecordsByQuery(param).then(async function(resp) {
この場所にasyncを入れた上で
resp.records.forEach(function(record) {
↓
for (let j = 0; j < resp.records.length; j++) {
とループ方法を変更する必要があります(forEachはawait非対応)。awaitの位置は合っています。
かなり強引な方法ですが
kintoneUtility.rest.getAllRecordsByQuery(param).then(async function(resp) {
var param = {
app: appId,
records: []
};
let updateRecords = resp.records.filter((record) => !record['サービス'].value);
await Promise.all(updateRecords.filter((record) => {
return record['種類'].value === '振替';
}).map(async (record) => {
let nssParam = {
app: 156,
query: `ファクターNo = "${record['加入者コード'].value}"`,
fields: ['ファクターNo', '商品']
};
let nssRecords = await kintoneUtility.rest.getAllRecordsByQuery(nssParam);
if (nssRecords.records.length) {
param.records.push({
id: record['$id'].value,
record: {
'サービス': {
value: nssRecords.records[0]['商品'].value
}
}
});
}
return;
}));
updateRecords.filter((record) => {
return record['種類'].value !== '振替';
}).forEach((transferRecord) => {
param.records.push({
id: transferRecord['$id'].value,
record: {
'サービス': {
value: '振込'
}
}
});
});
こんな方法でも可能かと思います。しかし、ループ回数分レコードを取得することになるので、負荷やパフォーマンスの面からは推奨できません。
はしもと様
async/awaitの方法のご教授ありがとうございます。
ご教授いただいきました②forEach⇒forに変えることで実装できました。
①や強引な方法とおっしゃっている③(最後のコード部分)方は改めて勉強したいと思います。
①については何か参考になるリンクやコード等参考になるものがございましたらご教授いただきたいです。
ご教授いただきました①②③(最後のコード部分)ですと
推奨順は①②③の認識であっておりますでしょうか。
③を推奨できないとのことですが、逆に使う理由(メリット)としては何かございますでしょうか
こういう場合は③がいいよみたいなものがありましたらご教授いただければと思います。
おこがましいお願いばかりで申し訳ございませんがどうぞ宜しくお願いいたします。
青山昌司 さま
実装できたようで何よりです。以下に解説していますが、基本的に①の方法を覚えれば問題ありません。またすぐに理解する必要もなく、使っている内にどの方法が良いかは分かってくるかと思います。
推奨順としては① > ② = ③です。ただし、③は②と同じことをしているだけで、良い方法ではないので覚えなくて問題ありません。①はnssParamを使ったレコードの取得APIが1回で済むのに対し、②は最初に取得したレコードのループでAPIを実行しているため、取得したレコードの件数分レコードの取得APIを実行することになります。そして、そのループ内で都度「レコードの取得APIが完了するまで次のループに移らない」ようになっているため、最初に取得するレコードの数が多ければ多いほど時間が掛かり、かつサーバーに負荷が掛かります(1日に実行できるAPIにも上限があります)。
①はレコード取得API(param)→レコード取得API(NssParam)→ループ処理でレコードの取得は2回のみ
②はレコード取得API(param)→ループ1回目→レコード取得API(NssParam)→ループ2回目→レコード取得API(NssParam)→ループ3回目→レコード取得API(NssParam)…でレコードの取得はループ回数分
以上のようなイメージになります。②が有効なケースとして、このまま使用するのではなく、ループ中にレコードの更新をさせるのであれば有効かもしれません(提示されたコードだとループが終わった後、ループ中にpushで作ったリクエストボディの配列を使ってまとめてレコードの更新をしていますが、そうではなくループ中にレコードの更新APIも実行)。
ループ1回目→レコード取得API→ループ2回目→レコード取得API→ループ終了→レコード更新API
現在の形はこうなっていますが、これを
ループ1回目→レコード取得API→レコード更新API→ループ2回目→レコード取得API→レコード更新API→ループ3回目
といったようにするイメージです。そうすることで2回目のレコード取得APIで1回目のループで更新したレコードを取得できたり、エラーでループが止まってしまった場合、それまでのループで更新できた部分は保証されるといった利点があります(私はkintoneと外部サービスを繋ぐ時に使っています)。
async/awaitが、という話ではなく、ループ中に他のレコードを取得するAPIを実行することが推奨されません。
長くなりましたが、基本的には①の方法を推奨します。①の方法だと以下のような形になるかと思います。動作確認等はしておりませんのでご了承下さい。
kintoneUtility.rest.getAllRecordsByQuery(param).then(async (resp) => {
let nssParam = {
app: 156,
fields: ['ファクターNo', '商品']
};
let nssRecords = await kintoneUtility.rest.getAllRecordsByQuery(nssParam);
let param = {
app: appId,
records: resp.records.filter((record) => !record['サービス'].value).map((record) => {
let param = {
id: record['$id'].value,
record: {}
};
if (record['種類'].value === '振替') {
param.record['サービス'] = {
value: nssRecords.records.find((nssRecord) => nssRecord['ファクターNo'].value === record['加入者コード'].value)[0]?.['商品'].value
};
} else {
param.record['サービス'] = {
value: '振込'
};
}
return param;
})
};
kintoneUtility.rest.putAllRecords(param).then((resp) => {
location.reload();
}).catch((error) => console.error(error));
});
お世話になっております。
丁寧な解説ありがとうございます。すごい勉強になります!
これから①の方法を習得できるようにまずは組み直してみます。
取り急ぎ御礼まで。
今後とも何とぞよろしくおねがいします。m(_ _)m
はしもと様
①への組み換えができましたので改めてお礼申し上げます。
1点不明な箇所がございまして
最後にプッシュする際に他のフィールドも更新をさせたいのですが、
ユーザー選択の場合
https://developer.cybozu.io/hc/ja/articles/202166160-%E3%83%AC%E3%82%B3%E3%83%BC%E3%83%89%E3%81%AE%E7%99%BB%E9%8C%B2-POST-#step2
を参考に変更してみたのですが、うまく入りません
通常?record.営業担当.value[0]={code : ‘s.aoyama’};のように書いていますが
[0]のような表記は必要ないのでしょうか?
度々の質問で恐縮ですがどうぞよろしくおねがいします。
param.records.push({
id: record['$id'].value,
record: {
'サービス': {
value: NssValue
},
'てすと': {
value: NssValue
},
'営業担当': {
value: [{code:"s.aoyama"}]
}
},
});
青山昌司 さま
ご記載の方法で問題ないと思います。
[0]と書くのは、「既に存在する配列」にアクセスするときのインデックス番号です。ユーザー選択フィールドは値が配列になるため、配列へアクセスする時にインデックス番号が必要になります。
配列もオブジェクトと同じようなものと捉えると分かりやすいかもしれません。
let arr = ['a', 'b', 'c']
という配列があった場合
let arr = {
0: 'a',
1: 'b',
2: 'c'
}
と同じ意味になります。そのため配列の1番目にアクセスしたい時は「arr[0]」と書き、2番目を書き換えたい時は「arr[1] = ‘d’」と書き換えます。
レコードを書き換える場合、既にrecord.営業担当.value = []という配列が存在しているため、
record.営業担当.value[0]={code : 's.aoyama'}
と1番目の要素を書き換えていることになります。
新しく配列を宣言したり作る場合は当然使えないので、
'営業担当': {
value: [
{
code: 'code'
},
{
code: '2人目のcode'
}
]
}
と記載する必要があります。または
let arr = [];
arr[0] = {
code: 'code'
};
/* 略 */
record: {
'営業担当': {
value: arr
}
}
と記載します。
ただ、営業担当の値は必ず「s.aoyama」でしょうか?取得したレコードのユーザー選択フィールドにする場合は
'営業担当': {
value: record['ユーザー選択'].value
}
とします。
はしもと様
ご返信ありがとうございます。
[0]の件は理解しました。編集時の動作などで[0][1]などのインデックスを指定して登録していたので
ユーザー選択の場合必要と考えていました。
今回のケースはユーザー選択に未登録の状態でボタンを押した時にユーザー選択をしたいと考えていますので
他アプリなどから取得した値ではなく単純にkintoneのユーザーコードを使ってダイレクトに
書き込みたいです。念の為別のユーザーコードへ書き換えていますが書き込まれない状況です。
切り分け作業としまして念の為、下記でやってっみましたが、文字列フィールドには正しく入りました。
let user=kintone.getLoginUser().code
param.records.push({
id: record['$id'].value,
record: {
'サービス': {
value: NssValue
},
'てすと': {
value: user
},
'営業担当': {
value: [{code: user}]
}
},
青山昌司 さま
画像でコンソールに出している部分ですが、こちらは「最初に取得したレコードのユーザー選択フィールド」なので、判断できません。正しくAPIが実行されたか確認するのであれば更新されたレコードを直接確認し、リクエストボディが正しいかを確認するのであれば
console.log(param.records)
ではないかと思います。
はしもと様
ご返信ありがとうございます。写真143行目にログを設置いたしました。
logで見る限りは取得できていそうに思えるのですがいかがでしょうか。
お手すきな際にご確認いただければと思います。
青山昌司 さま
問題なさそうですね。ただし「取得できている」のではなく「レコードを更新するリクエストに追加できている」という表現になります。実際に実行してエラーは出ましたか?
はしもと様
表現の訂正ありがとうございます!
実際に実行してもエラーは発生しませんので一度運用に合わせて組み直したものを簡易版に変更し組みましたが
変わりませんでした。そもそも私の組み方がおかしいかもしれません。
念の為クリックを押した後のコードを記載いたします。
誤りがございましたらご指摘いただければ幸いです。
文字列フィールドとユーザー選択フィールドのみにして検証しています。
文字列には"m.aoyama"は正しく書き込みできています。
myIndexButton.onclick = function() {
varappId = kintone.app.getId();
varparam = {
app:appId,
query:'ステータス = "作業者なし"',
fields: ['$id','加入者コード','種類','サービス','商品名','請求番号','てすと','営業担当'],
totalCount:true,
isGuest:false
};
kintoneUtility.rest.getAllRecordsByQuery(param).then(asyncfunction(resp) {
varparam = {
app:appId,
records: []
};
letupdateRecords = resp.records.filter((record) => !record['サービス'].value);
awaitPromise.all(updateRecords.filter((record) => {
returnrecord['種類'].value === '振替';
}).map(async (record) => {
letnssParam = {
app:155,
query:`ファクターNo = "${record['加入者コード'].value}"`,
fields: ['ファクターNo','商品'],
};
letnssRecords = awaitkintoneUtility.rest.getAllRecordsByQuery(nssParam);
if (nssRecords.records.length) {
param.records.push({
id:record['$id'].value,
record: {
'てすと': {//文字フィールド
value:"m.aoyama"
},
'営業担当': {//ユーザー選択フィールド
value: [{code:"m.aoyama"}]
}
}
});
}
return;
}));
kintoneUtility.rest.putAllRecords(param).then(function(resp) {
// success
console.log(resp);
location.reload(true)
}, function(error) {
// error
console.log(param);
console.log(error);
});
})
}//onclick
文字がひっついて見えるのでキャプチャーも添付します
はしもと様
1点進展がございました。
アプリ設定で新たに「ユーザー選択フィールド」を追加したところ(フィールドコード:ユーザー選択)
こちらへは書き込みができました。
となるとコード的には問題ないと理解していますが、考えられる要因って検討付きますでしょうか?
念の為、他のプラグインやJSは削除し実行してみましたがフィールドコード’営業担当’へは書き込みできない状況です。
青山昌司 さま
私の方も次に考えられるものが1つしかなかったのですが、フィールドの編集権限が掛けられていると思います。設定のアクセス権の中にあるかと思います。