アプリ間のデータ連携
kintone を業務で活用していると、「案件管理アプリのデータを日報アプリにコピーしたい」「マスターアプリの情報を参照して自動入力したい」といった、複数アプリ間でデータを連携したい場面が頻繁に発生します。
kintone の標準機能ではルックアップや関連レコード一覧でアプリ間の参照ができますが、より柔軟なデータ連携を実現するには JavaScript カスタマイズと REST API の組み合わせが有効です。
この記事では、kintone の複数アプリ間でデータを連携する代表的なパターンと、その実装方法を紹介します。
基本:別アプリのレコードを取得する
アプリ間連携の基本は、REST API で別アプリのレコードを取得することです。kintone.api を使えば、現在のアプリだけでなく、任意のアプリのレコードにアクセスできます。
/**
* 指定したアプリからレコードを取得する
* @param { Object } params
* @param { string | number } params.app - 取得先のアプリID
* @param { string } [params.query] - クエリ
* @param { string[] } [params.fields] - 取得するフィールドコード
* @returns { Promise<{ records: Record<string, any>[] }> }
*/
const getRecords = (params) => {
const { app, query = '', fields = [] } = params;
const body = { app, query };
if (fields.length > 0) {
body.fields = fields;
}
return kintone.api(kintone.api.url('/k/v1/records.json', true), 'GET', body);
};
連携先のアプリ ID はコード内にハードコーディングせず、定数として管理することをお勧めします。環境(開発・本番)によってアプリ ID が異なる場合があるため、変更しやすい形にしておくと保守性が向上します。
パターン 1:マスターアプリからのデータ参照
商品マスターや顧客マスターのようなマスターアプリからデータを取得し、別のアプリのフィールドに自動入力するパターンです。ルックアップで実現できる範囲を超える、複雑な条件での参照が必要な場合に有効です。
(() => {
'use strict';
// マスターアプリのID
const PRODUCT_MASTER_APP_ID = 10;
/**
* 商品コードから商品マスターのレコードを取得する
* @param { string } productCode - 商品コード
* @returns { Promise<Record<string, any> | null> }
*/
const getProduct = async (productCode) => {
const query = `商品コード = "${productCode}" limit 1`;
const { records } = await kintone.api(
kintone.api.url('/k/v1/records.json', true),
'GET',
{ app: PRODUCT_MASTER_APP_ID, query }
);
return records.length > 0 ? records[0] : null;
};
// 商品コードが変更されたら、マスターから情報を取得して自動入力
const events = [
'app.record.create.change.商品コード',
'app.record.edit.change.商品コード',
];
kintone.events.on(events, async (event) => {
const record = event.record;
const productCode = record['商品コード'].value;
if (!productCode) {
record['商品名'].value = '';
record['単価'].value = '';
return event;
}
const product = await getProduct(productCode);
if (product) {
record['商品名'].value = product['商品名'].value;
record['単価'].value = product['単価'].value;
record['カテゴリ'].value = product['カテゴリ'].value;
} else {
record['商品名'].value = '';
record['単価'].value = '';
event.error = `商品コード「${productCode}」に一致する商品が見つかりません`;
}
return event;
});
})();
ルックアップフィールドでは「コピー元のフィールド」が固定ですが、JavaScript カスタマイズでは複数の条件を組み合わせた検索や、取得後の加工処理を自由に実装できます。
パターン 2:レコードの保存時に別アプリへ自動登録
注文アプリにレコードを追加した際に、自動的に在庫アプリのデータを更新する、あるいは履歴アプリにログを残すといったパターンです。
(() => {
'use strict';
const HISTORY_APP_ID = 20;
/**
* 履歴アプリにレコードを追加する
* @param { Object } params
* @param { string } params.action - 操作種別
* @param { string } params.detail - 詳細
* @param { string | number } params.sourceRecordId - 元レコードID
* @returns { Promise<{ id: string, revision: string }> }
*/
const addHistory = (params) => {
const { action, detail, sourceRecordId } = params;
return kintone.api(kintone.api.url('/k/v1/record.json', true), 'POST', {
app: HISTORY_APP_ID,
record: {
操作種別: { value: action },
詳細: { value: detail },
元レコードID: { value: String(sourceRecordId) },
操作者: { value: [{ code: kintone.getLoginUser().code }] },
操作日時: { value: new Date().toISOString() },
},
});
};
// レコード保存成功後に履歴を追加
const events = [
'app.record.create.submit.success',
'mobile.app.record.create.submit.success',
];
kintone.events.on(events, async (event) => {
const record = event.record;
try {
await addHistory({
action: '新規作成',
detail: `案件「${record['案件名'].value}」が作成されました`,
sourceRecordId: event.recordId,
});
} catch (error) {
console.error('履歴の追加に失敗しました:', error);
}
return event;
});
})();
submit
イベント内で別アプリへの登録を行うと、元のレコードの保存が失敗した場合でも登録が実行されてしまいます。データの整合性を保つには、submit.success
イベント(保存成功後)を使用するか、bulkRequest でアトミックに実行することを検討してください。
パターン 3:アプリ間の集計
別アプリのデータを集計して、現在のアプリに表示するパターンです。例えば、顧客アプリのレコード詳細画面に、その顧客の注文合計金額を表示するケースです。
(() => {
'use strict';
const ORDER_APP_ID = 30;
/**
* 指定した顧客コードの注文合計金額を取得する
* @param { string } customerCode - 顧客コード
* @returns { Promise<number> } 合計金額
*/
const getTotalAmount = async (customerCode) => {
const query = `顧客コード = "${customerCode}" and ステータス in ("完了")`;
const { records } = await kintone.api(
kintone.api.url('/k/v1/records.json', true),
'GET',
{
app: ORDER_APP_ID,
query,
fields: ['金額'],
}
);
return records.reduce((sum, record) => sum + Number(record['金額'].value), 0);
};
const events = ['app.record.detail.show', 'mobile.app.record.detail.show'];
kintone.events.on(events, async (event) => {
const record = event.record;
const customerCode = record['顧客コード'].value;
if (!customerCode) {
return event;
}
const totalAmount = await getTotalAmount(customerCode);
// スペースフィールドに集計結果を表示
const spaceEl = kintone.app.record.getSpaceElement('集計表示エリア');
if (spaceEl) {
spaceEl.innerHTML = '';
const div = document.createElement('div');
div.style.cssText = 'padding: 8px; background: #f0f9ff; border-radius: 4px; font-weight: bold;';
div.textContent = `注文合計金額: ¥${totalAmount.toLocaleString()}`;
spaceEl.appendChild(div);
}
return event;
});
})();
取得対象のレコード数が多い場合は、records.json の 500
件制限に注意してください。全件取得が必要な場合は、カーソル API やレコード ID
を使ったシーク法を検討しましょう。
パターン 4:レコードの一括コピー
あるアプリのレコードを別のアプリに一括でコピーするパターンです。例えば、テンプレートアプリから実際のプロジェクトアプリにタスクを複製する場合などに使用します。
(() => {
'use strict';
const TEMPLATE_APP_ID = 40;
const TASK_APP_ID = kintone.app.getId();
/**
* テンプレートからタスクを一括コピーする
* @param { string } templateCategory - テンプレートのカテゴリ
* @param { string } projectName - プロジェクト名
*/
const copyFromTemplate = async (templateCategory, projectName) => {
// Step 1: テンプレートのレコードを取得
const query = `カテゴリ = "${templateCategory}" order by 表示順 asc`;
const { records: templates } = await kintone.api(
kintone.api.url('/k/v1/records.json', true),
'GET',
{ app: TEMPLATE_APP_ID, query }
);
if (templates.length === 0) {
throw new Error(`テンプレート「${templateCategory}」が見つかりません`);
}
// Step 2: コピー用のレコードデータを構築
const newRecords = templates.map((template) => ({
タスク名: { value: template['タスク名'].value },
説明: { value: template['説明'].value },
担当者: { value: template['デフォルト担当者'].value },
期限日数: { value: template['標準日数'].value },
プロジェクト名: { value: projectName },
ステータス: { value: '未着手' },
}));
// Step 3: 100件ずつ分割して登録(REST API の上限対応)
const chunkSize = 100;
for (let i = 0; i < newRecords.length; i += chunkSize) {
const chunk = newRecords.slice(i, i + chunkSize);
await kintone.api(kintone.api.url('/k/v1/records.json', true), 'POST', {
app: TASK_APP_ID,
records: chunk,
});
}
return newRecords.length;
};
kintone.events.on(['app.record.index.show'], (event) => {
// ヘッダーにコピーボタンを設置
if (document.getElementById('copy-template-btn')) {
return event;
}
const headerEl = kintone.app.getHeaderMenuSpaceElement();
const button = document.createElement('button');
button.id = 'copy-template-btn';
button.textContent = 'テンプレートからタスクを作成';
button.className = 'kintoneplugin-button-dialog-ok';
button.addEventListener('click', async () => {
const category = window.prompt('テンプレートのカテゴリを入力してください');
const project = window.prompt('プロジェクト名を入力してください');
if (!category || !project) return;
try {
button.disabled = true;
button.textContent = 'コピー中...';
const count = await copyFromTemplate(category, project);
window.alert(`${count} 件のタスクを作成しました`);
location.reload();
} catch (error) {
console.error(error);
window.alert(`エラーが発生しました: ${error.message}`);
} finally {
button.disabled = false;
button.textContent = 'テンプレートからタスクを作成';
}
});
headerEl.appendChild(button);
return event;
});
})();
アプリ間連携の設計ポイント
アプリ ID の一元管理
複数のアプリを連携する場合、アプリ ID をファイルの先頭で定数として定義します。
// アプリID定数(環境に応じて変更)
const APP_IDS = Object.freeze({
顧客マスター: 10,
商品マスター: 11,
注文管理: 20,
在庫管理: 21,
履歴: 30,
});
エラーハンドリングの統一
アプリ間連携では、連携先アプリの API 呼び出しが失敗するケースを必ず考慮します。
/**
* アプリ間連携で推奨されるエラーハンドリングパターン
*/
const safeApiCall = async (apiFunc, fallbackMessage) => {
try {
return await apiFunc();
} catch (error) {
console.error(fallbackMessage, error);
// 連携先のエラーが元のレコード操作に影響しないようにする
return null;
}
};
連携先アプリへのアクセス権がない場合、REST API はエラーを返します。連携を設計する際は、カスタマイズを利用するすべてのユーザーが連携先アプリの閲覧権限(または書き込み権限)を持っていることを確認してください。
まとめ
- kintone の REST API を使えば、JavaScript カスタマイズから任意のアプリのデータにアクセスできる
- マスターデータの参照は、フィールド変更イベントで別アプリを検索し、結果を自動入力するパターンが基本
- レコード保存時の自動連携は
submit.successイベントを使うことでデータの整合性を保てる - アプリ間の集計処理は、スペースフィールドを活用して結果を表示する
- 連携先のアプリ ID は定数として一元管理し、エラーハンドリングも統一する
- データの整合性が重要な場合は bulkRequest によるアトミックな操作を検討する