feat:lookup同期アクション
This commit is contained in:
1
plugin/kintone-addins/src/actions/auto-lookup.scss
Normal file
1
plugin/kintone-addins/src/actions/auto-lookup.scss
Normal file
@@ -0,0 +1 @@
|
||||
@import 'bootstrap/scss/bootstrap';
|
||||
272
plugin/kintone-addins/src/actions/auto-lookup.ts
Normal file
272
plugin/kintone-addins/src/actions/auto-lookup.ts
Normal file
@@ -0,0 +1,272 @@
|
||||
import {
|
||||
IAction,
|
||||
IActionResult,
|
||||
IActionNode,
|
||||
IActionProperty,
|
||||
IContext,
|
||||
IField,
|
||||
} from "../types/ActionTypes";
|
||||
|
||||
import { actionAddins } from ".";
|
||||
import type { Record} from "@kintone/rest-api-client/lib/src/client/types";
|
||||
import { KintoneRestAPIClient} from "@kintone/rest-api-client";
|
||||
import "./auto-lookup.scss";
|
||||
import "bootstrap/js/dist/modal";
|
||||
// import "bootstrap/js/dist/spinner";
|
||||
import {Modal} from "bootstrap"
|
||||
import $ from "jquery";
|
||||
|
||||
interface Props {
|
||||
displayName: string;
|
||||
lookupField: LookupField;
|
||||
condition: Condition;
|
||||
}
|
||||
|
||||
interface Condition {
|
||||
queryString: string;
|
||||
index: number;
|
||||
type: string;
|
||||
children: Child[];
|
||||
parent: null;
|
||||
logicalOperator: string;
|
||||
}
|
||||
|
||||
interface Child {
|
||||
index: number;
|
||||
type: string;
|
||||
parent: string;
|
||||
object: any;
|
||||
operator: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface LookupField {
|
||||
app: App;
|
||||
fields: Field[];
|
||||
}
|
||||
|
||||
interface Field {
|
||||
name: string;
|
||||
type: string;
|
||||
code: string;
|
||||
label: string;
|
||||
noLabel: boolean;
|
||||
required: boolean;
|
||||
lookup: Lookup;
|
||||
}
|
||||
|
||||
interface Lookup {
|
||||
relatedApp: RelatedApp;
|
||||
relatedKeyField: string;
|
||||
fieldMappings: FieldMapping[];
|
||||
lookupPickerFields: any[];
|
||||
filterCond: string;
|
||||
sort: string;
|
||||
}
|
||||
|
||||
interface FieldMapping {
|
||||
field: string;
|
||||
relatedField: string;
|
||||
}
|
||||
|
||||
interface RelatedApp {
|
||||
app: string;
|
||||
code: string;
|
||||
}
|
||||
|
||||
interface App {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
createdate: string;
|
||||
}
|
||||
|
||||
export class AutoLookUpAction implements IAction {
|
||||
name: string;
|
||||
actionProps: IActionProperty[];
|
||||
props: Props;
|
||||
constructor() {
|
||||
this.name = "ルックアップ更新";
|
||||
this.actionProps = [];
|
||||
this.props = {} as Props;
|
||||
this.register();
|
||||
}
|
||||
|
||||
/***
|
||||
* アクセスのメインの処理関数
|
||||
*/
|
||||
async process(
|
||||
prop: IActionNode,
|
||||
event: any,
|
||||
context: IContext
|
||||
): Promise<IActionResult> {
|
||||
this.actionProps = prop.actionProps;
|
||||
this.props = {
|
||||
...prop.ActionValue,
|
||||
condition: JSON.parse((prop.ActionValue as any).condition),
|
||||
} as Props;
|
||||
// console.log(context);
|
||||
|
||||
let result = {
|
||||
canNext: true,
|
||||
result: "",
|
||||
} as IActionResult;
|
||||
try {
|
||||
const lookUpFields = this.props.lookupField.fields.filter(
|
||||
(f) => f.lookup && f.lookup.relatedApp.app === String(kintone.app.getId())
|
||||
);
|
||||
if (!lookUpFields || lookUpFields.length===0) {
|
||||
throw new Error(
|
||||
`ルックアップの設定は不正です。${this.props.lookupField.fields[0].label} `
|
||||
);
|
||||
}
|
||||
const lookUpField = this.props.lookupField.fields[0];
|
||||
const key = event.record[lookUpField.lookup.relatedKeyField].value;
|
||||
const targetRecords = await this.getUpdateRecords(lookUpField, key);
|
||||
//更新対象がない時にスキップ
|
||||
if(targetRecords.length===0){
|
||||
return result;
|
||||
}
|
||||
const updateRecords = this.convertForLookup(targetRecords,lookUpField,key);
|
||||
console.log("updateRecords", updateRecords);
|
||||
this.showSpinnerModel(this.props.lookupField.app);
|
||||
await this.updateLookupTarget(updateRecords);
|
||||
this.showResult(this.props.lookupField.app,updateRecords.length);
|
||||
} catch (error) {
|
||||
console.error("ルックアップ更新中例外が発生しました。", error);
|
||||
if(error instanceof Error){
|
||||
event.error = error.message;
|
||||
}else{
|
||||
event.error = "ルックアップ更新中例外が発生しました。";
|
||||
}
|
||||
result.canNext = false;
|
||||
}
|
||||
console.log("autoLookupProps", this.props);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* REST API用クエリ作成
|
||||
* TODO:共通関数として作成
|
||||
* @param lookUpField
|
||||
* @param key
|
||||
* @returns
|
||||
*/
|
||||
makeQuery=(lookUpField:Field,key:any)=>{
|
||||
if(typeof key==='number'){
|
||||
return `${lookUpField.code} = ${key}`
|
||||
}
|
||||
if(typeof key==='string'){
|
||||
return `${lookUpField.code} = "${key}"`
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新対象のレコードを取得する
|
||||
*/
|
||||
getUpdateRecords = async (lookUpField:Field,key:any):Promise< Record[]>=>{
|
||||
const client=new KintoneRestAPIClient();
|
||||
const resp = await client.record.getAllRecords({
|
||||
app:this.props.lookupField.app.id,
|
||||
fields:["$id"],
|
||||
condition:this.makeQuery(lookUpField,key)
|
||||
});
|
||||
return resp;
|
||||
}
|
||||
|
||||
/**
|
||||
* ルックアップ更新用レコードに変換する
|
||||
* @param targetRecords 更新対象レコード
|
||||
* @param lookUpField ルックアップフィールド
|
||||
* @param key ルックアップフィールドの値
|
||||
* @returns
|
||||
*/
|
||||
convertForLookup = (targetRecords:Record[],lookUpField:Field,key:any):Array<any>=>{
|
||||
return targetRecords.map((r) => ({
|
||||
id: Number(r["$id"].value),
|
||||
record: { [lookUpField.code]: { value: key } },
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* ルックアップ先を更新する
|
||||
* @param updateRecords
|
||||
*/
|
||||
updateLookupTarget = async (updateRecords:Array<any>)=>{
|
||||
if (updateRecords && updateRecords.length > 0) {
|
||||
const client=new KintoneRestAPIClient();
|
||||
client.record.updateAllRecords({
|
||||
app:this.props.lookupField.app.id,
|
||||
records:updateRecords
|
||||
});
|
||||
// await kintone.api(kintone.api.url("/k/v1/records.json", true), "PUT", {
|
||||
// app: this.props.lookupField.app.id,
|
||||
// records: updateRecords
|
||||
// });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新中のダイアログ表示
|
||||
* @param app
|
||||
*/
|
||||
showSpinnerModel = (app:App) => {
|
||||
let dialog = $("#alcLookupModal");
|
||||
if(dialog.length===0){
|
||||
const modalHTML = `
|
||||
<div class="modal" id="alcLookupModal" tabindex="-1" aria-labelledby="staticBackdropLabel" aria-hidden="true">
|
||||
<div class="modal-dialog-centered">
|
||||
<div class="modal-dialog modal-content">
|
||||
<div class="modal-header">
|
||||
<h1 class="modal-title fs-5" id="alcLookupModalLabel">ルックアップ同期処理</h1>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="row" id="app${app.id}">
|
||||
<div class="spinner-border text-secondary col-1 " role="alert"></div>
|
||||
<div class="col">${app.name}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary" data-bs-dismiss="modal">OK</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
dialog = $(modalHTML).appendTo("body");
|
||||
dialog.get()[0].addEventListener('hidden.bs.modal',(ev)=>{
|
||||
Modal.getOrCreateInstance(dialog.get()[0]).dispose();
|
||||
$("#alcLookupModal").remove();
|
||||
});
|
||||
}else{
|
||||
const dialogBody=$("#alcLookupModal .modal-body");
|
||||
const htmlrow=`
|
||||
<div class="row" id="app${app.id}">
|
||||
<div class="spinner-border text-secondary col-1 " role="alert">
|
||||
</div>
|
||||
<div class="col">${app.name}</div>
|
||||
<div>`;
|
||||
dialogBody.append(htmlrow);
|
||||
}
|
||||
Modal.getOrCreateInstance(dialog.get()[0]).show();
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新結果を表示する
|
||||
* @param app 更新先アプリ情報
|
||||
* @param count 更新件数
|
||||
*/
|
||||
showResult=(app:App,count:number)=>{
|
||||
const dialogBody=$(`#alcLookupModal .modal-body #app${app.id}`);
|
||||
const html=` <div class="col-1 text-success">✔</div>
|
||||
<div class="col">${app.name}</div>
|
||||
<div class="col">更新件数:${count}件</div>`;
|
||||
dialogBody.html(html);
|
||||
}
|
||||
|
||||
register(): void {
|
||||
actionAddins[this.name] = this;
|
||||
}
|
||||
}
|
||||
|
||||
new AutoLookUpAction();
|
||||
74
plugin/kintone-addins/src/actions/current-field-get.ts
Normal file
74
plugin/kintone-addins/src/actions/current-field-get.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import {
|
||||
IAction,
|
||||
IActionResult,
|
||||
IActionNode,
|
||||
IActionProperty,
|
||||
IContext,
|
||||
} from "../types/ActionTypes";
|
||||
import { actionAddins } from ".";
|
||||
|
||||
|
||||
interface Props {
|
||||
displayName: string;
|
||||
field: Field;
|
||||
verName: VerName;
|
||||
}
|
||||
|
||||
interface VerName {
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface Field {
|
||||
name: string;
|
||||
type: string;
|
||||
code: string;
|
||||
label: string;
|
||||
noLabel: boolean;
|
||||
required: boolean;
|
||||
minLength: string;
|
||||
maxLength: string;
|
||||
expression: string;
|
||||
hideExpression: boolean;
|
||||
unique: boolean;
|
||||
defaultValue: string;
|
||||
}
|
||||
|
||||
export class CurrentFieldGetAction implements IAction {
|
||||
name: string;
|
||||
actionProps: IActionProperty[];
|
||||
currentFieldGetProps: Props;
|
||||
constructor() {
|
||||
this.name = "フィールドの値を取得する";
|
||||
this.actionProps = [];
|
||||
this.currentFieldGetProps = {} as Props;
|
||||
this.register();
|
||||
}
|
||||
|
||||
async process(
|
||||
prop: IActionNode,
|
||||
event: any,
|
||||
context: IContext
|
||||
): Promise<IActionResult> {
|
||||
this.currentFieldGetProps = prop.ActionValue as Props;
|
||||
this.actionProps = prop.actionProps;
|
||||
|
||||
let result = {
|
||||
canNext: true,
|
||||
result: "",
|
||||
} as IActionResult;
|
||||
|
||||
try {
|
||||
context.variables[this.currentFieldGetProps.verName.name] = context.record[this.currentFieldGetProps.field.code].value;
|
||||
} catch (error) {
|
||||
console.error("CurrentFieldGetAction error", error);
|
||||
result.canNext = false;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
register(): void {
|
||||
actionAddins[this.name] = this;
|
||||
}
|
||||
}
|
||||
|
||||
new CurrentFieldGetAction();
|
||||
166
plugin/kintone-addins/src/actions/data-mapping.ts
Normal file
166
plugin/kintone-addins/src/actions/data-mapping.ts
Normal file
@@ -0,0 +1,166 @@
|
||||
import {
|
||||
IAction,
|
||||
IActionResult,
|
||||
IActionNode,
|
||||
IActionProperty,
|
||||
IContext,
|
||||
} from "../types/ActionTypes";
|
||||
import { actionAddins } from ".";
|
||||
|
||||
interface Props {
|
||||
displayName: string;
|
||||
sources: Sources;
|
||||
dataMapping: DataMapping[];
|
||||
}
|
||||
|
||||
interface DataMapping {
|
||||
id: string;
|
||||
from: From;
|
||||
to: To;
|
||||
}
|
||||
|
||||
interface To {
|
||||
app: App;
|
||||
fields: Field[];
|
||||
isDialogVisible: boolean;
|
||||
}
|
||||
|
||||
interface Field {
|
||||
name: string;
|
||||
type: string;
|
||||
code: string;
|
||||
label: string;
|
||||
noLabel: boolean;
|
||||
required: boolean;
|
||||
minLength: string;
|
||||
maxLength: string;
|
||||
expression: string;
|
||||
hideExpression: boolean;
|
||||
unique: boolean;
|
||||
defaultValue: string;
|
||||
}
|
||||
|
||||
interface From {
|
||||
sharedText: string;
|
||||
_t: string;
|
||||
id: string;
|
||||
objectType: string;
|
||||
name: Name;
|
||||
actionName: string;
|
||||
displayName: string;
|
||||
}
|
||||
|
||||
interface Name {
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface Sources {
|
||||
app: App;
|
||||
}
|
||||
|
||||
interface App {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
createdate: string;
|
||||
}
|
||||
|
||||
export class DataMappingAction implements IAction {
|
||||
name: string;
|
||||
actionProps: IActionProperty[];
|
||||
dataMappingProps: Props;
|
||||
constructor() {
|
||||
this.name = "データマッピング";
|
||||
this.actionProps = [];
|
||||
this.dataMappingProps = {} as Props;
|
||||
this.register();
|
||||
}
|
||||
|
||||
async process(
|
||||
prop: IActionNode,
|
||||
event: any,
|
||||
context: IContext
|
||||
): Promise<IActionResult> {
|
||||
this.actionProps = prop.actionProps;
|
||||
this.dataMappingProps = prop.ActionValue as Props;
|
||||
console.log(prop.ActionValue);
|
||||
|
||||
// this.initTypedActionProps();
|
||||
let result = {
|
||||
canNext: true,
|
||||
result: "",
|
||||
} as IActionResult;
|
||||
try {
|
||||
const record = this.dataMappingProps.dataMapping
|
||||
.filter(
|
||||
(item) =>
|
||||
item.from.objectType === "variable" &&
|
||||
item.from.name.name &&
|
||||
item.to.app &&
|
||||
item.to.fields &&
|
||||
item.to.fields.length > 0
|
||||
)
|
||||
.reduce((accumulator, item) => {
|
||||
return {...accumulator, [item.to.fields[0].code]: {
|
||||
value: getValueByPath(context.variables, item.from.name.name),
|
||||
}};
|
||||
}, {});
|
||||
if (record && Object.keys(record).length > 0) {
|
||||
await kintone.api(kintone.api.url("/k/v1/record.json", true), "POST", {
|
||||
app: this.dataMappingProps.sources.app.id,
|
||||
record: record,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("DataMappingAction error", error);
|
||||
result.canNext = false;
|
||||
}
|
||||
console.log("dataMappingProps", this.dataMappingProps);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
register(): void {
|
||||
actionAddins[this.name] = this;
|
||||
}
|
||||
}
|
||||
|
||||
new DataMappingAction();
|
||||
|
||||
const getValueByPath = (obj: any, path: string) => {
|
||||
return path.split(".").reduce((o, k) => (o || {})[k], obj);
|
||||
};
|
||||
|
||||
type Resp = { records: RespRecordType[] };
|
||||
|
||||
type RespRecordType = {
|
||||
[key: string]: {
|
||||
type: string;
|
||||
value: any;
|
||||
};
|
||||
};
|
||||
|
||||
type Result = {
|
||||
type: string;
|
||||
value: any[];
|
||||
};
|
||||
|
||||
const selectData = async (appid: string, field: string): Promise<Result> => {
|
||||
return kintone
|
||||
.api(kintone.api.url("/k/v1/records", true), "GET", {
|
||||
app: appid ?? kintone.app.getId(),
|
||||
fields: [field],
|
||||
})
|
||||
.then((resp: Resp) => {
|
||||
const result: Result = { type: "", value: [] };
|
||||
resp.records.forEach((element) => {
|
||||
for (const [key, value] of Object.entries(element)) {
|
||||
if (result.type === "") {
|
||||
result.type = value.type;
|
||||
}
|
||||
result.value.push(value.value);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
});
|
||||
};
|
||||
374
plugin/kintone-addins/src/actions/data-processing.ts
Normal file
374
plugin/kintone-addins/src/actions/data-processing.ts
Normal file
@@ -0,0 +1,374 @@
|
||||
import {
|
||||
IAction,
|
||||
IActionResult,
|
||||
IActionNode,
|
||||
IActionProperty,
|
||||
IContext,
|
||||
} from "../types/ActionTypes";
|
||||
import { actionAddins } from ".";
|
||||
|
||||
interface Props {
|
||||
displayName: string;
|
||||
sources: Sources;
|
||||
condition: string;
|
||||
conditionO: Condition;
|
||||
verName: VerName;
|
||||
}
|
||||
|
||||
interface Condition {
|
||||
queryString: string;
|
||||
index: number;
|
||||
type: string;
|
||||
children: Child[];
|
||||
parent: null;
|
||||
logicalOperator: string;
|
||||
}
|
||||
|
||||
interface Child {
|
||||
index: number;
|
||||
type: string;
|
||||
parent: string;
|
||||
object: Object;
|
||||
operator: ChildOperator;
|
||||
value: Value;
|
||||
}
|
||||
|
||||
interface Value {
|
||||
sharedText: string;
|
||||
_t: string;
|
||||
objectType: string;
|
||||
actionName: string;
|
||||
displayName: string;
|
||||
name: Name;
|
||||
}
|
||||
|
||||
interface Name {
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface ChildOperator {
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface Object {
|
||||
sharedText: string;
|
||||
_t: string;
|
||||
name: string;
|
||||
objectType: string;
|
||||
type: string;
|
||||
code: string;
|
||||
label: string;
|
||||
noLabel: boolean;
|
||||
required: boolean;
|
||||
minLength: string;
|
||||
maxLength: string;
|
||||
expression: string;
|
||||
hideExpression: boolean;
|
||||
unique: boolean;
|
||||
defaultValue: string;
|
||||
}
|
||||
|
||||
interface VerName {
|
||||
name: string;
|
||||
actionName: string;
|
||||
displayName: string;
|
||||
vars: Var[];
|
||||
}
|
||||
|
||||
interface Var {
|
||||
id: string;
|
||||
field: Field2;
|
||||
logicalOperator: LogicalOperator;
|
||||
vName: string;
|
||||
}
|
||||
|
||||
interface LogicalOperator {
|
||||
operator: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
interface Field2 {
|
||||
sharedText: string;
|
||||
_t: string;
|
||||
name: string;
|
||||
objectType: string;
|
||||
type: string;
|
||||
code: string;
|
||||
label: string;
|
||||
noLabel: boolean;
|
||||
required: boolean;
|
||||
minLength: string;
|
||||
maxLength: string;
|
||||
expression: string;
|
||||
hideExpression: boolean;
|
||||
unique: boolean;
|
||||
defaultValue: string;
|
||||
}
|
||||
|
||||
interface Sources {
|
||||
app: App;
|
||||
fields: Field[];
|
||||
}
|
||||
|
||||
interface Field {
|
||||
name: string;
|
||||
type: string;
|
||||
code: string;
|
||||
label: string;
|
||||
noLabel: boolean;
|
||||
required: boolean;
|
||||
minLength: string;
|
||||
maxLength: string;
|
||||
expression: string;
|
||||
hideExpression: boolean;
|
||||
unique: boolean;
|
||||
defaultValue: string;
|
||||
}
|
||||
|
||||
interface App {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
createdate: string;
|
||||
}
|
||||
|
||||
export class DataProcessingAction implements IAction {
|
||||
name: string;
|
||||
actionProps: IActionProperty[];
|
||||
dataProcessingProps: Props | null;
|
||||
constructor() {
|
||||
this.name = "データ処理";
|
||||
this.actionProps = [];
|
||||
this.dataProcessingProps = null;
|
||||
this.register();
|
||||
}
|
||||
|
||||
async process(
|
||||
nodes: IActionNode,
|
||||
event: any,
|
||||
context: IContext
|
||||
): Promise<IActionResult> {
|
||||
this.actionProps = nodes.actionProps;
|
||||
this.dataProcessingProps = nodes.ActionValue as Props;
|
||||
this.dataProcessingProps.conditionO = JSON.parse(
|
||||
this.dataProcessingProps.condition
|
||||
);
|
||||
let result = {
|
||||
canNext: true,
|
||||
result: "",
|
||||
} as IActionResult;
|
||||
try {
|
||||
if (!this.dataProcessingProps) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const data = await selectData(
|
||||
varGet(
|
||||
this.dataProcessingProps.conditionO.queryString,
|
||||
context.variables
|
||||
)
|
||||
);
|
||||
console.log("data ", data);
|
||||
|
||||
context.variables[this.dataProcessingProps.verName.name] =
|
||||
this.dataProcessingProps.verName.vars.reduce((acc, f) => {
|
||||
const v = calc(f, data);
|
||||
if (v) {
|
||||
acc[f.vName] = calc(f, data);
|
||||
}
|
||||
return acc;
|
||||
}, {} as AnyObject);
|
||||
|
||||
console.log("context ", context);
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
event.error = error;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
register(): void {
|
||||
actionAddins[this.name] = this;
|
||||
}
|
||||
}
|
||||
|
||||
new DataProcessingAction();
|
||||
|
||||
const varGet = (str: string, vars: any) => {
|
||||
console.log(str);
|
||||
|
||||
const regex = /var\((.*?)\)/g;
|
||||
let match;
|
||||
while ((match = regex.exec(str)) !== null) {
|
||||
const varName = match[1];
|
||||
if (varName in vars) {
|
||||
str = str.replace(match[0], vars[varName]);
|
||||
} else {
|
||||
throw new Error(`変数${varName}が見つかりません`);
|
||||
}
|
||||
}
|
||||
console.log(str);
|
||||
return str;
|
||||
};
|
||||
|
||||
const selectData = async (query?: string) => {
|
||||
return kintone
|
||||
.api(kintone.api.url("/k/v1/records", true), "GET", {
|
||||
app: kintone.app.getId(),
|
||||
query: query,
|
||||
})
|
||||
.then((resp: Resp) => {
|
||||
const result: Result = {};
|
||||
resp.records.forEach((element) => {
|
||||
for (const [key, value] of Object.entries(element)) {
|
||||
if (!result[key]) {
|
||||
result[key] = { type: value.type, value: [] };
|
||||
}
|
||||
result[key].value.push(value.value);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
});
|
||||
};
|
||||
|
||||
type Resp = { records: RespRecordType[] };
|
||||
|
||||
type RespRecordType = {
|
||||
[key: string]: {
|
||||
type: string;
|
||||
value: any;
|
||||
};
|
||||
};
|
||||
|
||||
type Result = {
|
||||
[key: string]: {
|
||||
type: string;
|
||||
value: any[];
|
||||
};
|
||||
};
|
||||
|
||||
type AnyObject = {
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
const ERROR_TYPE = "ERROR_TYPE";
|
||||
|
||||
const calc = (f: Var, result: Result) => {
|
||||
const type = typeCheck(f.field.type);
|
||||
if (!type) {
|
||||
return ERROR_TYPE;
|
||||
}
|
||||
|
||||
const fun =
|
||||
calcFunc[
|
||||
`${type}_${Operator[f.logicalOperator.operator as keyof typeof Operator]}`
|
||||
];
|
||||
if (!fun) {
|
||||
return ERROR_TYPE;
|
||||
}
|
||||
const values = result[f.field.code].value;
|
||||
if (!values) {
|
||||
return null;
|
||||
}
|
||||
return fun(values);
|
||||
};
|
||||
|
||||
const typeCheck = (type: string) => {
|
||||
switch (type) {
|
||||
case "RECORD_NUMBER":
|
||||
case "NUMBER":
|
||||
return CalcType.NUMBER;
|
||||
case "SINGLE_LINE_TEXT":
|
||||
case "MULTI_LINE_TEXT":
|
||||
case "RICH_TEXT":
|
||||
return CalcType.STRING;
|
||||
case "DATE":
|
||||
return CalcType.DATE;
|
||||
case "TIME":
|
||||
return CalcType.TIME;
|
||||
case "DATETIME":
|
||||
case "UPDATED_TIME":
|
||||
return CalcType.DATETIME;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
enum Operator {
|
||||
SUM = "SUM",
|
||||
AVG = "AVG",
|
||||
MAX = "MAX",
|
||||
MIN = "MIN",
|
||||
COUNT = "COUNT",
|
||||
FIRST = "FIRST",
|
||||
}
|
||||
|
||||
enum CalcType {
|
||||
NUMBER = "number",
|
||||
STRING = "string",
|
||||
DATE = "date",
|
||||
TIME = "time",
|
||||
DATETIME = "datetime",
|
||||
}
|
||||
|
||||
const calcFunc: Record<string, (value: string[]) => string | null> = {
|
||||
[`${CalcType.NUMBER}_${Operator.COUNT}`]: (value: string[]) =>
|
||||
value.length.toString(),
|
||||
[`${CalcType.STRING}_${Operator.COUNT}`]: (value: string[]) =>
|
||||
value.length.toString(),
|
||||
[`${CalcType.DATE}_${Operator.COUNT}`]: (value: string[]) =>
|
||||
value.length.toString(),
|
||||
[`${CalcType.TIME}_${Operator.COUNT}`]: (value: string[]) =>
|
||||
value.length.toString(),
|
||||
[`${CalcType.DATETIME}_${Operator.COUNT}`]: (value: string[]) =>
|
||||
value.length.toString(),
|
||||
|
||||
[`${CalcType.NUMBER}_${Operator.SUM}`]: (value: string[]) =>
|
||||
value.reduce((acc, v) => acc + Number(v), 0).toString(),
|
||||
[`${CalcType.NUMBER}_${Operator.AVG}`]: (value: string[]) =>
|
||||
(value.reduce((acc, v) => acc + Number(v), 0) / value.length).toString(),
|
||||
[`${CalcType.NUMBER}_${Operator.MAX}`]: (value: string[]) =>
|
||||
Math.max(...value.map(Number)).toString(),
|
||||
[`${CalcType.NUMBER}_${Operator.MIN}`]: (value: string[]) =>
|
||||
Math.min(...value.map(Number)).toString(),
|
||||
|
||||
[`${CalcType.STRING}_${Operator.SUM}`]: (value: string[]) => value.join(" "),
|
||||
|
||||
[`${CalcType.DATE}_${Operator.MAX}`]: (value: string[]) =>
|
||||
value.reduce((maxDate, currentDate) =>
|
||||
maxDate > currentDate ? maxDate : currentDate
|
||||
),
|
||||
|
||||
[`${CalcType.DATE}_${Operator.MIN}`]: (value: string[]) =>
|
||||
value.reduce((minDate, currentDate) =>
|
||||
minDate < currentDate ? minDate : currentDate
|
||||
),
|
||||
|
||||
[`${CalcType.TIME}_${Operator.MAX}`]: (value: string[]) =>
|
||||
value.reduce((maxTime, currentTime) =>
|
||||
maxTime > currentTime ? maxTime : currentTime
|
||||
),
|
||||
[`${CalcType.TIME}_${Operator.MIN}`]: (value: string[]) =>
|
||||
value.reduce((minTime, currentTime) =>
|
||||
minTime < currentTime ? minTime : currentTime
|
||||
),
|
||||
|
||||
[`${CalcType.DATETIME}_${Operator.MAX}`]: (value: string[]) =>
|
||||
value.reduce((maxDateTime, currentDateTime) =>
|
||||
new Date(maxDateTime) > new Date(currentDateTime)
|
||||
? maxDateTime
|
||||
: currentDateTime
|
||||
),
|
||||
|
||||
[`${CalcType.DATETIME}_${Operator.MIN}`]: (value: string[]) =>
|
||||
value.reduce((minDateTime, currentDateTime) =>
|
||||
new Date(minDateTime) < new Date(currentDateTime)
|
||||
? minDateTime
|
||||
: currentDateTime
|
||||
),
|
||||
[`${CalcType.STRING}_${Operator.FIRST}`]: (value: string[]) => {
|
||||
return value[0];
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user