feat:データ集計処理実装完了
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -13,7 +13,6 @@ API_V1_AUTH_KEY = "X-Cybozu-Authorization"
|
|||||||
DEPLOY_MODE = "PROD" #DEV,PROD
|
DEPLOY_MODE = "PROD" #DEV,PROD
|
||||||
|
|
||||||
DEPLOY_JS_URL = "https://ka-addin.azurewebsites.net/alc_runtime.js"
|
DEPLOY_JS_URL = "https://ka-addin.azurewebsites.net/alc_runtime.js"
|
||||||
#DEPLOY_JS_URL = "https://ce1c-133-139-70-194.ngrok-free.app/alc_runtime.js"
|
|
||||||
|
|
||||||
KINTONE_FIELD_TYPE=["GROUP","GROUP_SELECT","CHECK_BOX","SUBTABLE","DROP_DOWN","USER_SELECT","RADIO_BUTTON","RICH_TEXT","LINK","REFERENCE_TABLE","CALC","TIME","NUMBER","ORGANIZATION_SELECT","FILE","DATETIME","DATE","MULTI_SELECT","SINGLE_LINE_TEXT","MULTI_LINE_TEXT"]
|
KINTONE_FIELD_TYPE=["GROUP","GROUP_SELECT","CHECK_BOX","SUBTABLE","DROP_DOWN","USER_SELECT","RADIO_BUTTON","RICH_TEXT","LINK","REFERENCE_TABLE","CALC","TIME","NUMBER","ORGANIZATION_SELECT","FILE","DATETIME","DATE","MULTI_SELECT","SINGLE_LINE_TEXT","MULTI_LINE_TEXT"]
|
||||||
|
|
||||||
|
|||||||
@@ -6,67 +6,19 @@ import {
|
|||||||
IContext,
|
IContext,
|
||||||
} from "../types/ActionTypes";
|
} from "../types/ActionTypes";
|
||||||
import { actionAddins } from ".";
|
import { actionAddins } from ".";
|
||||||
|
import {KintoneRestAPIClient} from '@kintone/rest-api-client';
|
||||||
|
import { Aggregator,Operator} from '../util/aggregates';
|
||||||
|
import { FieldForm } from "../types/FieldLayout";
|
||||||
|
|
||||||
interface Props {
|
interface IDataProcessingProps {
|
||||||
displayName: string;
|
displayName: string;
|
||||||
sources: Sources;
|
sources: Sources;
|
||||||
condition: string;
|
condition: string;
|
||||||
conditionO: Condition;
|
verName?: VerName;
|
||||||
verName: VerName;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Condition {
|
interface Condition {
|
||||||
queryString: string;
|
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 {
|
interface VerName {
|
||||||
@@ -78,170 +30,26 @@ interface VerName {
|
|||||||
|
|
||||||
interface Var {
|
interface Var {
|
||||||
id: string;
|
id: string;
|
||||||
field: Field2;
|
field: FieldForm;
|
||||||
logicalOperator: LogicalOperator;
|
logicalOperator: CalcOperator;
|
||||||
vName: string;
|
vName: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface LogicalOperator {
|
interface CalcOperator {
|
||||||
operator: string;
|
operator: Operator;
|
||||||
label: 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 {
|
interface Sources {
|
||||||
app: App;
|
app: App;
|
||||||
fields: Field[];
|
fields: FieldForm[];
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
interface App {
|
||||||
id: string;
|
id: string;
|
||||||
name: 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 = {
|
type Result = {
|
||||||
[key: string]: {
|
[key: string]: {
|
||||||
type: string;
|
type: string;
|
||||||
@@ -253,122 +61,125 @@ type AnyObject = {
|
|||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ERROR_TYPE = "ERROR_TYPE";
|
export class DataProcessingAction implements IAction {
|
||||||
|
name: string;
|
||||||
|
actionProps: IActionProperty[];
|
||||||
|
props: IDataProcessingProps ;
|
||||||
|
|
||||||
const calc = (f: Var, result: Result) => {
|
constructor() {
|
||||||
const type = typeCheck(f.field.type);
|
this.name = "データ処理";
|
||||||
if (!type) {
|
this.actionProps = [];
|
||||||
return ERROR_TYPE;
|
this.props = {
|
||||||
}
|
displayName:'',
|
||||||
|
condition:'',
|
||||||
const fun =
|
sources:{
|
||||||
calcFunc[
|
app:{
|
||||||
`${type}_${Operator[f.logicalOperator.operator as keyof typeof Operator]}`
|
id:""
|
||||||
];
|
|
||||||
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];
|
|
||||||
},
|
},
|
||||||
};
|
fields:[]
|
||||||
|
},
|
||||||
|
};
|
||||||
|
this.register();
|
||||||
|
}
|
||||||
|
|
||||||
|
async process(
|
||||||
|
nodes: IActionNode,
|
||||||
|
event: any,
|
||||||
|
context: IContext
|
||||||
|
): Promise<IActionResult> {
|
||||||
|
this.actionProps = nodes.actionProps;
|
||||||
|
|
||||||
|
this.props = nodes.ActionValue as IDataProcessingProps;
|
||||||
|
const condition = JSON.parse(this.props.condition) as Condition;
|
||||||
|
let result = {
|
||||||
|
canNext: true,
|
||||||
|
result: "",
|
||||||
|
} as IActionResult;
|
||||||
|
try {
|
||||||
|
if (!this.props) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
const query = this.getQuery(condition.queryString,context.variables);
|
||||||
|
const data = await this.selectData(query);
|
||||||
|
|
||||||
|
console.log("data ", data);
|
||||||
|
|
||||||
|
if(this.props.verName){
|
||||||
|
const varValues= this.props.verName.vars.reduce((acc, f) => {
|
||||||
|
const datas=data[f.field.code].value;
|
||||||
|
const agg = new Aggregator(datas,f.field);
|
||||||
|
const result = agg.calculate(f.logicalOperator.operator)
|
||||||
|
acc[f.vName]=result;
|
||||||
|
return acc;
|
||||||
|
}, {} as AnyObject);
|
||||||
|
context.variables[this.props.verName.name]=varValues;
|
||||||
|
console.log("context ", context);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("データ集計中例外が発生しました。", error);
|
||||||
|
if(error instanceof Error){
|
||||||
|
event.error = `データ集計中例外が発生しました。 ${error.message}`;
|
||||||
|
}else{
|
||||||
|
event.error = "データ集計中例外が発生しました。";
|
||||||
|
}
|
||||||
|
result.canNext = false;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param str
|
||||||
|
* @param vars
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
getQuery = (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;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* データを取得する
|
||||||
|
* @param query
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
selectData = async ( query?: string) => {
|
||||||
|
const api = new KintoneRestAPIClient();
|
||||||
|
const fields = this.props.sources.fields.map((field)=>field.code);
|
||||||
|
const resp = await api.record.getAllRecords({
|
||||||
|
app: this.props.sources.app.id,
|
||||||
|
fields:fields,
|
||||||
|
condition:query
|
||||||
|
});
|
||||||
|
const result: Result = {};
|
||||||
|
resp.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;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
register(): void {
|
||||||
|
actionAddins[this.name] = this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
new DataProcessingAction();
|
||||||
240
plugin/kintone-addins/src/types/FieldLayout.ts
Normal file
240
plugin/kintone-addins/src/types/FieldLayout.ts
Normal file
@@ -0,0 +1,240 @@
|
|||||||
|
// src/types/field.d.ts
|
||||||
|
|
||||||
|
export enum FieldType {
|
||||||
|
CALC = "CALC",
|
||||||
|
CATEGORY = "CATEGORY",
|
||||||
|
CHECK_BOX = "CHECK_BOX",
|
||||||
|
CREATED_TIME = "CREATED_TIME",
|
||||||
|
CREATOR = "CREATOR",
|
||||||
|
DATE = "DATE",
|
||||||
|
DATETIME = "DATETIME",
|
||||||
|
DROP_DOWN = "DROP_DOWN",
|
||||||
|
FILE = "FILE",
|
||||||
|
GROUP = "GROUP",
|
||||||
|
GROUP_SELECT = "GROUP_SELECT",
|
||||||
|
LINK = "LINK",
|
||||||
|
MODIFIER = "MODIFIER",
|
||||||
|
MULTI_LINE_TEXT = "MULTI_LINE_TEXT",
|
||||||
|
MULTI_SELECT = "MULTI_SELECT",
|
||||||
|
NUMBER = "NUMBER",
|
||||||
|
ORGANIZATION_SELECT = "ORGANIZATION_SELECT",
|
||||||
|
RADIO_BUTTON = "RADIO_BUTTON",
|
||||||
|
RECORD_NUMBER = "RECORD_NUMBER",
|
||||||
|
REFERENCE_TABLE = "REFERENCE_TABLE",
|
||||||
|
RICH_TEXT = "RICH_TEXT",
|
||||||
|
SINGLE_LINE_TEXT = "SINGLE_LINE_TEXT",
|
||||||
|
STATUS = "STATUS",
|
||||||
|
STATUS_ASSIGNEE = "STATUS_ASSIGNEE",
|
||||||
|
SUBTABLE = "SUBTABLE",
|
||||||
|
TIME = "TIME",
|
||||||
|
UPDATED_TIME = "UPDATED_TIME",
|
||||||
|
USER_SELECT = "USER_SELECT"
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum CalcFormat{
|
||||||
|
NUMBER = "NUMBER",
|
||||||
|
NUMBER_DIGIT = "NUMBER_DIGIT",
|
||||||
|
DATETIME = "DATETIME",
|
||||||
|
DATE = "DATE",
|
||||||
|
TIME = "TIME",
|
||||||
|
HOUR_MINUTE= "HOUR_MINUTE",
|
||||||
|
DAY_HOUR_MINUTE= "DAY_HOUR_MINUTE"
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FieldForm {
|
||||||
|
code: string;
|
||||||
|
type: FieldType;
|
||||||
|
required?: boolean;
|
||||||
|
noLabel?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OptionForm{
|
||||||
|
index: string;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EntityForm {
|
||||||
|
code: string;
|
||||||
|
type: 'USER' | 'ORGANIZATION' | 'GROUP' | 'FUNCTION';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CalcFieldForm extends FieldForm {
|
||||||
|
unit?: string;
|
||||||
|
expression?: string;
|
||||||
|
format?:CalcFormat;
|
||||||
|
unitPosition?: 'BEFORE' | 'AFTER';
|
||||||
|
displayScale?: string;
|
||||||
|
hideExpression?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CategoryFieldForm extends FieldForm {
|
||||||
|
enabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DateFieldForm extends FieldForm {
|
||||||
|
defaultValue?: string;
|
||||||
|
unique?: boolean;
|
||||||
|
defaultNowValue?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DatetimeFieldForm extends FieldForm {
|
||||||
|
defaultValue?: string;
|
||||||
|
unique?: boolean;
|
||||||
|
defaultNowValue?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DecimalFieldForm extends FieldForm {
|
||||||
|
maxValue?: string;
|
||||||
|
defaultValue?: string;
|
||||||
|
unitPosition?: 'BEFORE' | 'AFTER';
|
||||||
|
minValue?: string;
|
||||||
|
unit?: string;
|
||||||
|
unique?: boolean;
|
||||||
|
displayScale?: string;
|
||||||
|
digit?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EditorFieldForm extends FieldForm {
|
||||||
|
defaultValue?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EntityForm {
|
||||||
|
code: string;
|
||||||
|
type: 'USER' | 'ORGANIZATION' | 'GROUP' | 'FUNCTION';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FieldMappingForm {
|
||||||
|
relatedField: string;
|
||||||
|
field: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FileFieldForm extends FieldForm {
|
||||||
|
thumbnailSize?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GroupFieldForm extends FieldForm {
|
||||||
|
openGroup?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GroupSelectFieldForm extends FieldForm {
|
||||||
|
entities: EntityForm[];
|
||||||
|
defaultValue?: EntityForm[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LinkFieldForm extends FieldForm {
|
||||||
|
protocol?: 'WEB' | 'CALL' | 'MAIL';
|
||||||
|
defaultValue?: string;
|
||||||
|
minLength?: string;
|
||||||
|
unique?: boolean;
|
||||||
|
maxLength?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LookupFieldForm extends FieldForm {
|
||||||
|
lookup: {
|
||||||
|
lookupPickerFields: string[];
|
||||||
|
relatedKeyField: string;
|
||||||
|
relatedApp: {
|
||||||
|
app: string;
|
||||||
|
code: string;
|
||||||
|
};
|
||||||
|
fieldMappings: FieldMappingForm[];
|
||||||
|
sort?: string;
|
||||||
|
filterCond?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ModifiedAtFieldForm extends FieldForm {}
|
||||||
|
|
||||||
|
export interface ModifierFieldForm extends FieldForm {}
|
||||||
|
|
||||||
|
export interface MultipleCheckFieldForm extends FieldForm {
|
||||||
|
defaultValue?: string[];
|
||||||
|
options: {
|
||||||
|
[key: string]: OptionForm;
|
||||||
|
};
|
||||||
|
align?: 'HORIZONTAL' | 'VERTICAL';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MultipleLineTextFieldForm extends FieldForm {
|
||||||
|
defaultValue?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MultipleSelectFieldForm extends FieldForm {
|
||||||
|
defaultValue?: string[];
|
||||||
|
options: {
|
||||||
|
[key: string]: OptionForm;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface OrganizationSelectFieldForm extends FieldForm {
|
||||||
|
entities: EntityForm[];
|
||||||
|
defaultValue?: EntityForm[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RecordIdFieldForm extends FieldForm {}
|
||||||
|
|
||||||
|
export interface ReferenceTableFieldForm extends FieldForm {
|
||||||
|
referenceTable: {
|
||||||
|
condition: {
|
||||||
|
relatedField: string;
|
||||||
|
field: string;
|
||||||
|
};
|
||||||
|
size?: string;
|
||||||
|
relatedApp: {
|
||||||
|
app: string;
|
||||||
|
code: string;
|
||||||
|
};
|
||||||
|
sort?: string;
|
||||||
|
filterCond?: string;
|
||||||
|
displayFields: string[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SingleCheckFieldForm extends FieldForm {
|
||||||
|
defaultValue?: string;
|
||||||
|
options: {
|
||||||
|
[key: string]: OptionForm;
|
||||||
|
};
|
||||||
|
align?: 'HORIZONTAL' | 'VERTICAL';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SingleLineTextFieldForm extends FieldForm {
|
||||||
|
expression?: string;
|
||||||
|
defaultValue?: string;
|
||||||
|
minLength?: string;
|
||||||
|
unique?: boolean;
|
||||||
|
maxLength?: string;
|
||||||
|
hideExpression?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SingleSelectFieldForm extends FieldForm {
|
||||||
|
defaultValue?: string;
|
||||||
|
options: {
|
||||||
|
[key: string]: OptionForm;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StatusAssigneeFieldForm extends FieldForm {
|
||||||
|
enabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StatusFieldForm extends FieldForm {
|
||||||
|
enabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TableForm extends FieldForm {
|
||||||
|
fields: {
|
||||||
|
[key: string]: CalcFieldForm | CategoryFieldForm | DateFieldForm | DatetimeFieldForm | DecimalFieldForm | EditorFieldForm | FileFieldForm | GroupFieldForm | GroupSelectFieldForm | LinkFieldForm | LookupFieldForm | ModifiedAtFieldForm | ModifierFieldForm | MultipleCheckFieldForm | MultipleLineTextFieldForm | MultipleSelectFieldForm | OrganizationSelectFieldForm | RecordIdFieldForm | ReferenceTableFieldForm | SingleCheckFieldForm | SingleLineTextFieldForm | SingleSelectFieldForm | StatusAssigneeFieldForm | StatusFieldForm | TableForm | TimeFieldForm | UserSelectFieldForm;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TimeFieldForm extends FieldForm {
|
||||||
|
defaultValue?: string;
|
||||||
|
defaultNowValue?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserSelectFieldForm extends FieldForm {
|
||||||
|
entities: EntityForm[];
|
||||||
|
defaultValue?: EntityForm[];
|
||||||
|
}
|
||||||
121
plugin/kintone-addins/src/util/aggregates.ts
Normal file
121
plugin/kintone-addins/src/util/aggregates.ts
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
import { FieldForm,CalcFieldForm,FieldType, CalcFormat} from "../types/FieldLayout";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 集計操作子
|
||||||
|
*/
|
||||||
|
export enum Operator {
|
||||||
|
SUM = "SUM",
|
||||||
|
AVG = "AVG",
|
||||||
|
MAX = "MAX",
|
||||||
|
MIN = "MIN",
|
||||||
|
COUNT = "COUNT",
|
||||||
|
FIRST = "FIRST",
|
||||||
|
LAST = "LAST"
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 集計関数
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class Aggregator {
|
||||||
|
private data: string[];
|
||||||
|
private field: FieldForm;
|
||||||
|
|
||||||
|
constructor(data: string[], field: FieldForm) {
|
||||||
|
this.data = data;
|
||||||
|
this.field = field;
|
||||||
|
}
|
||||||
|
|
||||||
|
private toNumberArray(): number[] {
|
||||||
|
const numberArray = this.data.map(item => {
|
||||||
|
const num = Number(item);
|
||||||
|
if (isNaN(num)) {
|
||||||
|
throw new Error(`フィールド「${this.field.code} 」は数値に変換できないため、計算を実行できません。`);
|
||||||
|
}
|
||||||
|
return num;
|
||||||
|
});
|
||||||
|
|
||||||
|
return numberArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
private sum(): number | null {
|
||||||
|
if (this.data.length === 0) return null;
|
||||||
|
const numberArray = this.toNumberArray();
|
||||||
|
return numberArray.reduce((acc, val) => acc + val, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private avg(): number | null {
|
||||||
|
if (this.data.length === 0) return null;
|
||||||
|
const numberArray = this.toNumberArray();
|
||||||
|
const total = numberArray.reduce((acc, val) => acc + val, 0);
|
||||||
|
return total / numberArray.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
private max(): number | string | null {
|
||||||
|
if (this.data.length === 0) return null;
|
||||||
|
if (this.field.type === FieldType.NUMBER ) {
|
||||||
|
const numberArray = this.toNumberArray();
|
||||||
|
return Math.max(...numberArray);
|
||||||
|
}
|
||||||
|
if(this.field.type===FieldType.CALC){
|
||||||
|
const calcField = this.field as CalcFieldForm;
|
||||||
|
if(calcField.format===CalcFormat.NUMBER || calcField.format===CalcFormat.NUMBER_DIGIT){
|
||||||
|
const numberArray = this.toNumberArray();
|
||||||
|
return Math.max(...numberArray);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this.data.reduce((max, item) => (item > max ? item : max), this.data[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private min(): number | string | null {
|
||||||
|
if (this.data.length === 0) return null;
|
||||||
|
if (this.field.type === FieldType.NUMBER ) {
|
||||||
|
const numberArray = this.toNumberArray();
|
||||||
|
return Math.min(...numberArray);
|
||||||
|
}
|
||||||
|
if(this.field.type===FieldType.CALC){
|
||||||
|
const calcField = this.field as CalcFieldForm;
|
||||||
|
if(calcField.format===CalcFormat.NUMBER || calcField.format===CalcFormat.NUMBER_DIGIT){
|
||||||
|
const numberArray = this.toNumberArray();
|
||||||
|
return Math.min(...numberArray);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(this.data===null || this.data.length===0){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return this.data.reduce((min, item) => (item < min ? item : min), this.data[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private count(): number {
|
||||||
|
return this.data.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
private first(): string | null {
|
||||||
|
return this.data.length ? this.data[0] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private last(): string | null {
|
||||||
|
return this.data.length ? this.data[this.data.length - 1] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public calculate(operator: Operator): number | string | null {
|
||||||
|
switch (operator) {
|
||||||
|
case Operator.SUM:
|
||||||
|
return this.sum();
|
||||||
|
case Operator.AVG:
|
||||||
|
return this.avg();
|
||||||
|
case Operator.MAX:
|
||||||
|
return this.max();
|
||||||
|
case Operator.MIN:
|
||||||
|
return this.min();
|
||||||
|
case Operator.COUNT:
|
||||||
|
return this.count();
|
||||||
|
case Operator.FIRST:
|
||||||
|
return this.first();
|
||||||
|
case Operator.LAST:
|
||||||
|
return this.last();
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user