きっかけ
kintoneカスタマイズスペシャリストの勉強(JavaScript)を始めて、APIトークンとかJSONの話が出てきました。 社内でkintoneとGASでAPI連携してデータの自取取得を試みている部署がるのを思い出して「そういえばこの前やり方聞かれたけど答えられなかったな。もし自動取得できたら自分の業務でもかなり時短できるぞ。ちょっと調べてみよう」と思ったのがきっかけです
効率化の必要性
昨年から社内ではSFAを入れ替えており、一定の進捗確認の仕組みはすでにありました。今年は「このレイアウトで数字を見たい」「商品別、販売経路別、●●別で見たい。あと時期の区切りは~~で」と様々な切り口から売上のトレンドを、しかもなるべくリアルタイムで知りたい。という要望も多くなりました。データはありますが、全部すぐに出せるほどは整ってないんですね。
対応するには、kintoneからcsv出力→スプレッドシートに張り付け→関数使って集計。が早いです。 早いんですが、複数の要望を定期的に集計。となるとまあまあ時間かかります。 ですので、自動化ができれば、私の負担が軽減されるだけでなく、営業組織全体の意思決定も迅速化にもつながるため、重要度も高いと考えました
API連携時のデータの抽出とスプレッドシート出力の実装手順
※kintoneからのAPIでのレコード取得は一度に500件が限度なのでループさせます。
※今回は営業の案件のデータで、一つの案件に複数商品(テーブルデータ)が含まれます。商品が複数ある場合、商品ごとで一行ずつ出力します。テーブルデータ以外はコピーして同じデータを出力します
※一部の商品(テーブルデータ)は、あるフィールドのフラグごとに出力する列を分けます。
※値が空だと列数がずれることがあるので、空欄判定をして"-"を出力します。
実装コード(項目名をxxxにしたり、一部端折ってます)
function fetchRecords(startIndex, limitnumber) { const subdomain = "xxxxxxxxxx"; var urlGetRecords = "https://" + subdomain + ".cybozu.com/k/v1/records.json"; var appId = xx; var apiToken = 'xxxxxxxxxx'; //xxxにフィールドコード名を入れる var paramFields = "&fields=$id," + encodeURIComponent("xxx,xxx,xxx,xxx,xxx,xxx,xxx,xxx,~~~~~~"); var paramQuery = "&query=" + encodeURIComponent("xxx in (条件) and xxx in (条件 order by $id desc limit " + limitnumber + " offset " + startIndex); var url = urlGetRecords + "?app=" + appId + paramFields + paramQuery; var res = JSON.parse(UrlFetchApp.fetch(url, { "method": "get", "headers": { "X-Cybozu-API-Token": apiToken } })); return res.records; } function convertToJapanTime(utcTimeString) { var utcDate = new Date(utcTimeString); // 日本時間に変換 var japanDate = Utilities.formatDate(utcDate, "GMT+9", "yyyy/MM/dd"); return japanDate; } function getNameFromRecord(record, field) { var data = record[field].value; return (data && data.name) ? data.name : "-"; } function myFunction4() { const limit = 500; const sheetOutpust = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('データ取得'); // ヘッダ行の生成 var headerRow = ["ヘッダ1", "ヘッダ2"~~~~~"ヘッダ43"]; var output = [headerRow]; // ヘッダ行を先頭に追加 var startIndex = 0; var records = fetchRecords(startIndex, limit); while (records.length > 0) { records.forEach(function (record) { var baseRow = []; //ユーザー選択や組織選択のデータ抽出の書き方 baseRow.push(record["xxx"].value && record["xxx"].value.length > 0 ? record["xxx"].value[0].name : "-"); //日付関連のデータ抽出の書き方 baseRow.push(convertToJapanTime(record["xxx"].value) || "-"); //文字列、レコード番号など単純にデータ取得する場合 baseRow.push(record["xxx"].value || "-"); //更新者・作成者の取得はこんな感じ var updaterName = getNameFromRecord(record, "xxx"); var creatorName = getNameFromRecord(record, "xxx"); baseRow.push(updaterName); baseRow.push(creatorName); //他データも同様に取得 //... //... // テーブルの情報はあるフィールドの値で場合分けして取得。テーブルデータの数だけ行を追加。テーブルデータ以外の共通の値はコピーして同じ情報を持つ。関係ないテーブルデータは"-"とする。 if (record["xxx"].value === "フラグ1") { // テーブルのデータが存在する場合 if ("xxx" in record && record["xxx"].value.length > 0) { record["テーブル"].value.forEach(function (tableData) { if (tableData.value["xxx"].value !== null) { var row = baseRow.slice(); // ベース行をコピー row.push(tableData.value["xxx"].value); row.push(tableData.value["xxx"].value); row.push(tableData.value["xxx"].value); row.push(tableData.value["xxx"].value); row.push(record["xxx"].value); row.push("-", "-", "-", "-", "-"); row.push("-", "-", "-", "-", "-"); row.push("-", "-"); output.push(row); } }); } else { // 何もしない } } else if (record["xxx"].value === "フラグ2") { // テーブル_1のデータが存在する場合 if ("テーブル_1" in record && record["テーブル_1"].value.length > 0) { record["テーブル_1"].value.forEach(function (tableData) { if (tableData.value["xxx"].value !== null) { var row = baseRow.slice(); // ベース行をコピー row.push("-", "-", "-", "-", "-"); row.push(tableData.value["xxx"].value); row.push(tableData.value["xxx"].value); row.push(tableData.value["xxx"].value); row.push(tableData.value["xxx"].value); row.push(record["xxx"].value); row.push("-", "-", "-", "-", "-"); row.push("-", "-"); output.push(row); } }); } else { //何もしない } } else if (record["xxx"].value === "フラグ3") { // テーブル_0のデータが存在する場合 if ("テーブル_0" in record && record["テーブル_0"].value.length > 0) { record["テーブル_0"].value.forEach(function (tableData) { if (tableData.value["xxx"].value !== null) { var row = baseRow.slice(); // ベース行をコピー row.push("-", "-", "-", "-", "-"); row.push("-", "-", "-", "-", "-"); row.push(tableData.value["xxx"].value); row.push(tableData.value["xxx"].value); row.push(tableData.value["xxx"].value); row.push(tableData.value["xxx"].value); row.push(record["xxx"].value); row.push("-", "-"); output.push(row); } }); } else { //何もしない } } //フラグ関係なく、テーブル情報があれば別行でデータ取得 if ("テーブル_2" in record && record["テーブル_2"].value.length > 0) { record["テーブル_2"].value.forEach(function (tableData) { if (tableData.value["xxx"].value !== null) { var row = baseRow.slice(); // ベース行をコピー row.push("-", "-", "-", "-", "-"); row.push("-", "-", "-", "-", "-"); row.push("-", "-", "-", "-", "-"); row.push(tableData.value["xxx"].value); row.push(tableData.value["xxx"].value); output.push(row); } }); } else { //何もしない } }); startIndex += limit; records = fetchRecords(startIndex, limit); } // スプレッドシートに出力 //console.log(output); var sheetOutput = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('データ取得'); sheetOutput.getRange(1, 1, output.length, output[0].length).setValues(output); // ヘッダ行も含めて出力 }
挑戦したことと学び
課題・問題点
学び
成果と今後の展望
成果
- 営業組織の一部門の進捗管理を自動化し、業務効率化を達成しました。営業担当者別の契約・解約・提案状況を顧客名、商品や個数・金額など約50項目を自動で出力します。
今後の展望
- 個人:カスタマイズスペシャリストの勉強を続けるとともに、JSを用いたフィールドの表示・非表示切り替えに取り組みます。
- 業務:営業企画で管理するものも自動化していきます。BIツールの活用や、他の業務領域での効率化を目指します。
最後に
読んでくださりありがとうございます。 現状はkintoneの記事が2つのみですが、kintone以外の取り組みも書いていこうと思いますので、よろしくお願いします。