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_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"]
|
||||
|
||||
|
||||
@@ -6,67 +6,19 @@ import {
|
||||
IContext,
|
||||
} from "../types/ActionTypes";
|
||||
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;
|
||||
sources: Sources;
|
||||
condition: string;
|
||||
conditionO: Condition;
|
||||
verName: VerName;
|
||||
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 {
|
||||
@@ -78,69 +30,55 @@ interface VerName {
|
||||
|
||||
interface Var {
|
||||
id: string;
|
||||
field: Field2;
|
||||
logicalOperator: LogicalOperator;
|
||||
field: FieldForm;
|
||||
logicalOperator: CalcOperator;
|
||||
vName: string;
|
||||
}
|
||||
|
||||
interface LogicalOperator {
|
||||
operator: string;
|
||||
interface CalcOperator {
|
||||
operator: Operator;
|
||||
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;
|
||||
fields: FieldForm[];
|
||||
}
|
||||
|
||||
interface App {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
createdate: string;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
type Result = {
|
||||
[key: string]: {
|
||||
type: string;
|
||||
value: any[];
|
||||
};
|
||||
};
|
||||
|
||||
type AnyObject = {
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
export class DataProcessingAction implements IAction {
|
||||
name: string;
|
||||
actionProps: IActionProperty[];
|
||||
dataProcessingProps: Props | null;
|
||||
props: IDataProcessingProps ;
|
||||
|
||||
constructor() {
|
||||
this.name = "データ処理";
|
||||
this.actionProps = [];
|
||||
this.dataProcessingProps = null;
|
||||
this.props = {
|
||||
displayName:'',
|
||||
condition:'',
|
||||
sources:{
|
||||
app:{
|
||||
id:""
|
||||
},
|
||||
fields:[]
|
||||
},
|
||||
};
|
||||
this.register();
|
||||
}
|
||||
|
||||
@@ -150,55 +88,55 @@ export class DataProcessingAction implements IAction {
|
||||
context: IContext
|
||||
): Promise<IActionResult> {
|
||||
this.actionProps = nodes.actionProps;
|
||||
this.dataProcessingProps = nodes.ActionValue as Props;
|
||||
this.dataProcessingProps.conditionO = JSON.parse(
|
||||
this.dataProcessingProps.condition
|
||||
);
|
||||
|
||||
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.dataProcessingProps) {
|
||||
if (!this.props) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const data = await selectData(
|
||||
varGet(
|
||||
this.dataProcessingProps.conditionO.queryString,
|
||||
context.variables
|
||||
)
|
||||
);
|
||||
const query = this.getQuery(condition.queryString,context.variables);
|
||||
const data = await this.selectData(query);
|
||||
|
||||
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);
|
||||
}
|
||||
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);
|
||||
event.error = error;
|
||||
console.error("データ集計中例外が発生しました。", error);
|
||||
if(error instanceof Error){
|
||||
event.error = `データ集計中例外が発生しました。 ${error.message}`;
|
||||
}else{
|
||||
event.error = "データ集計中例外が発生しました。";
|
||||
}
|
||||
result.canNext = false;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
register(): void {
|
||||
actionAddins[this.name] = this;
|
||||
}
|
||||
}
|
||||
|
||||
new DataProcessingAction();
|
||||
|
||||
const varGet = (str: string, vars: any) => {
|
||||
/**
|
||||
*
|
||||
* @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) {
|
||||
@@ -213,15 +151,21 @@ const varGet = (str: string, vars: any) => {
|
||||
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) => {
|
||||
/**
|
||||
* データを取得する
|
||||
* @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.records.forEach((element) => {
|
||||
resp.forEach((element) => {
|
||||
for (const [key, value] of Object.entries(element)) {
|
||||
if (!result[key]) {
|
||||
result[key] = { type: value.type, value: [] };
|
||||
@@ -230,145 +174,12 @@ const selectData = async (query?: string) => {
|
||||
}
|
||||
});
|
||||
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;
|
||||
register(): void {
|
||||
actionAddins[this.name] = this;
|
||||
}
|
||||
}
|
||||
|
||||
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];
|
||||
},
|
||||
};
|
||||
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