Files
data-fetch-plugin/vue-project/my-kintone-plugin/src/js/KintoneIndexEventHandler.ts
2025-01-26 17:27:08 +09:00

317 lines
10 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import type { FieldLayout, FieldsJoinMapping, JoinTable, Record, RecordForParameter, SavedData, WhereCondition } from "@/types/model";
import type { isType, OneOf } from "./kintone-rest-api-client";
declare var KintoneRestAPIClient: typeof import("@kintone/rest-api-client").KintoneRestAPIClient;
export class KintoneIndexEventHandler {
private config: SavedData<FieldLayout>;
private currentApp: string;
constructor(config: SavedData<FieldLayout>, currentApp: string) {
this.config = config;
this.currentApp = currentApp;
}
public init(): void {
this.addButtonToView();
}
// ボタン追加
private addButtonToView(): void {
const headerSpace = kintone.app.getHeaderMenuSpaceElement();
if (!headerSpace) {
throw new Error('このページではヘッダー要素が利用できません。');
};
// ボタン追加
if (document.getElementById('btn-data-fetch')) return;
const kuc = Kucs['1.18.0'];
const button = new kuc.Button({
text: this.config.buttonName,
type: "submit",
id: 'btn-data-fetch',
});
// const button = document.createElement('button');
// button.id = 'btn-data-fetch';
// button.textContent = this.config.buttonName;
// button.style.margin = '0 8px';
button.addEventListener('click', () => this.handleButtonClick());
headerSpace.appendChild(button);
}
// ボタンクリック
private handleButtonClick = async (): Promise<void> => {
try {
console.log('データ収集開始...');
await this.execDataFectch();
} catch (error) {
console.error('Error during data processing:', error);
throw error;
}
}
/**
* 検索データ取得&作成処理
*/
private execDataFectch = async (): Promise<void> => {
const mainTable = this.config.joinTables[0];
const mainRecords = await this.fetchDataFromApp(mainTable);
// フィールド結合
let mainData = mainRecords.map((record) => {
const rightRecord: Record = {};
mainTable.fieldsMapping.forEach((mapping => {
rightRecord[this.fieldCode(mapping.rightField)] = record[this.fieldCode(mapping.leftField)]
}));
return rightRecord;
});
const joinTables = this.config.joinTables.filter((table, index) => index > 0);
for (const table of joinTables) {
const subDatas = await this.fetchDataFromApp(table);
mainData = this.leftJoin(mainData, subDatas, table);
};
//現在のデータをクリアする
await this.deleteCurrentRecords();
//データを更新する
await this.saveDataToCurrentApp(mainData);
}
/**
* Appからデータを取得する
* @param joinTable 対象アプリかられレコードを取得する
* @returns
*/
private fetchDataFromApp = async (joinTable: JoinTable<FieldLayout>): Promise<Record[]> => {
// Filter 条件作成
const filter = this.getWhereCondition(joinTable.whereConditions);
const fetchFields = joinTable.fieldsMapping.map(map => this.fieldCode(map.leftField));
if (joinTable.table) {
fetchFields.push(joinTable.table);
}
// KintoneRESTAPI
const client = new KintoneRestAPIClient();
const records = await client.record.getAllRecords({
app: joinTable.app,
fields: fetchFields,
condition: filter
});
//SubTableが含まれる場合、フラットなデータに変換する
return this.convertToFlatDatas(records, joinTable.table);
}
private getWhereCondition(whereCondifions: WhereCondition<FieldLayout>[]): string {
const conds = whereCondifions
.filter((cond) => this.fieldCode(cond.field) !== '');
const condition = conds.map((cond) => {
const condValue = this.getConditionValue(cond);
return `${this.fieldCode(cond.field)} ${cond.condition} ${condValue}`;
}).join(' and ');
return condition;
}
private getConditionValue(condi: WhereCondition<FieldLayout>): string {
const field = condi.field as OneOf;
const data = condi.data;
if (!data) return "";
if (isType.NUMBER(field) || isType.RECORD_NUMBER(field)) {
// For numbers, return as is
return data;
} else if (isType.DATE(field)) {
// If field is DATE, format as "yyyy-MM-dd" unless it's a reserved function
if (/^\d{4}-\d{2}-\d{2}$/.test(data) || data.match(/^\w+\(.*\)$/)) {
return `"${data}"`;
}
const date = new Date(data);
return `"${date.toISOString().split('T')[0]}"`;
} else if (isType.DATETIME(field) || isType.CREATED_TIME(field) || isType.UPDATED_TIME(field)) {
// If field is DATETIME, format as "yyyy-MM-ddTHH:mm:ssZ"
if (data.match(/^\w+\(.*\)$/)) {
return `"${data}"`;
}
const dateTime = new Date(data);
return `"${dateTime.toISOString()}"`;
} else if ((condi.condition === "in" || condi.condition === "not in" )) {
if( data.includes(",")){
// Handle "in" and "not in" with comma-separated strings
const items = data.split(",").map(item => `"${item.trim()}"`);
return `(${items.join(",")})`;
} else {
return `"${data}"`;
}
} else {
// Default case for other types (treat as text)
return `"${data}"`;
}
}
/**
* fieldからコードを取得する
* @param field
* @returns
*/
private fieldCode(field: any): string {
if (!field) {
return "";
}
if (typeof field === 'string' && field) {
return field;
} else if (typeof field === 'object' && 'code' in field) {
return field.code;
}
return "";
}
/**
* ネストされたサブテーブルデータをフラットなデータに変換し、親レコードを複製します。
* @param records レコードの配列
* @returns 変換後のフラットなレコード配列
*/
private convertToFlatDatas(records: Record[], subTable: string): Record[] {
if (!subTable) {
return records;
}
const flattenedData: Record[] = [];
records.forEach((record) => {
// テーブルフィールドが存在するかを確認
if (record[subTable]?.type === "SUBTABLE" && record[subTable].value.length > 0) {
// サブテーブル内の各レコードを処理
record[subTable].value.forEach((nested: Record) => {
// 親レコードのコピーを作成
const flatRecord = { ...record };
// サブテーブルフィールドを抽出してフラットな構造に追加
Object.entries(nested.value).forEach(([key, field]) => {
flatRecord[key] = field.value;
});
// テーブルフィールドを削除
delete flatRecord[subTable];
// 結果の配列に追加
flattenedData.push(flatRecord);
});
} else {
// サブテーブルが空の場合、親レコードをそのまま追加
const flatRecord = { ...record };
delete flatRecord[subTable];
flattenedData.push(flatRecord);
}
});
return flattenedData;
}
/**
* データLeftJoin処理
* @param mainData
* @param subData
* @param onConditions
* @param fieldsMapping
* @returns
*/
private leftJoin(
mainData: Record[],
subData: Record[],
joinTable: JoinTable<FieldLayout>
): Record[] {
const joinedRecords = mainData.map((mainRecord) => {
const matchedRecord = subData.find((subRecord) =>
joinTable.onConditions.every(
(cond) => mainRecord[this.fieldCode(cond.leftField)] === subRecord[this.fieldCode(cond.rightField)]
)
);
// マッチ出来ない場合、LEFTの列のみ返す
if (!matchedRecord) return mainRecord;
// フィールド結合
const combinedRecord: Record = { ...mainRecord };
joinTable.fieldsMapping.forEach((mapping) => {
combinedRecord[this.fieldCode(mapping.rightField)] = matchedRecord[this.fieldCode(mapping.leftField)];
});
return combinedRecord;
});
return joinedRecords;
}
/**
* 取得先はテーブルの場合の特別対応
*/
private leftJoinForTable(
mainData: Record[],
subData: Record[],
joinTable: JoinTable
): any[] {
return mainData.map((mainRecord) => {
const matchedRecord = subData.find((subRecord) => {
const subRows = subRecord[joinTable.table].value as Record[];
joinTable.onConditions.every(
(cond) => mainRecord[cond.leftField] === subRecord[cond.rightField]
)
}
);
// マッチ出来ない場合、LEFTの列のみ返す
if (!matchedRecord) return mainRecord;
// フィールド結合
const combinedRecord = { ...mainRecord };
joinTable.fieldsMapping.forEach((mapping) => {
combinedRecord[mapping.rightField] = matchedRecord[mapping.leftField];
});
return combinedRecord;
});
}
/**
* 現在アプリのすべてレコードを削除する
*/
private async deleteCurrentRecords(): Promise<void> {
const client = new KintoneRestAPIClient();
const currentRecords = await client.record.getAllRecords({
app: this.currentApp,
fields: ["$id"],
});
const deleteRecords = currentRecords.map(record => {
return { id: record.$id.value as string }
});
await client.record.deleteAllRecords({
app: this.currentApp,
records: deleteRecords
});
client.record.addAllRecords
}
/**
* 結合後のデータを現在のアプリに挿入する
* @param records
*/
private async saveDataToCurrentApp(records: Record[]): Promise<void> {
try {
const client = new KintoneRestAPIClient();
const result = await client.record.addAllRecords({
app: this.currentApp,
records: this.convertForUpdate(records)
});
} catch (error) {
console.error('データ作成時エラーが発生しました:', error);
throw error;
}
}
/**
* Recordを更新時の形式を変換する
* @param resords
* @returns
*/
private convertForUpdate(resords: Record[]): RecordForParameter[] {
return resords.map((record) =>
Object.fromEntries(
Object.entries(record).map(([fieldCode, { value }]) => [fieldCode, { value }])
)
);
}
}