diff --git a/plugin/kintone-addins/src/actions/insert-value.ts b/plugin/kintone-addins/src/actions/insert-value.ts new file mode 100644 index 0000000..51b2524 --- /dev/null +++ b/plugin/kintone-addins/src/actions/insert-value.ts @@ -0,0 +1,504 @@ + +import { actionAddins } from "."; +import { IAction,IActionResult, IActionNode, IActionProperty, IField, IContext } from "../types/ActionTypes"; +import { ConditionTree } from '../types/Conditions'; + +/** + * アクションの属性定義 + */ +interface IInsertValueProps{ + field:IField; + condition:string; + value:string; + show:string; +} +/** + * + */ +export class InsertValueAction implements IAction{ + name: string; + actionProps: IActionProperty[]; + props:IInsertValueProps; + constructor(){ + this.name="値を挿入する";// DBに登録したアクション名 + this.actionProps=[]; + //プロパティ属性の初期化 + this.props={ + field:{code:''}, + condition:'', + value:'', + show:'' + } + //アクションを登録する + this.register(); + } + + /** + * 空白文字を空白文字が非対応のフィールドに挿入しようとしていないか、必須項目フィールドに挿入しようとしていないかチェックする + * @param {string} inputValue - 挿入する値 + * @return {boolean} -入力値が有効な日付形式の場合はtrueを返し、そうでない場合は例外を発生させる + */ + checkInputBlank(fieldType :string | undefined,inputValue :string,fieldCode :string,fieldRequired :boolean | undefined,event :any): boolean{ + //正規表現チェック + let blankCheck= inputValue.match(/^(\s| )+?$/);//半角スペース・タブ文字・改行・改ページ・全角スペース + + if(blankCheck !== null){ + //空白文字を空白文字が非対応のフィールドに挿入しようとしている場合、例外を発生させる + if(fieldType === "NUMBER" || fieldType === "DATE" || fieldType === "DATETIME" || fieldType === "TIME" || fieldType === "USER_SELECT" + || fieldType === "ORGANIZATION_SELECT" || fieldType === "GROUP_SELECT" || fieldType === "RADIO_BUTTON" || fieldType === "DROP_DOWN" || fieldType === "CHECK_BOX" || fieldType === "MULTI_SELECT"){ + event.record[fieldCode]['error'] = "「"+fieldCode+"」"+"フィールドには、空白文字は挿入できません。"; //レコードにエラーを表示 + throw new Error("「"+fieldCode+"」"+"フィールドには、空白文字は挿入できません。「値を挿入する」コンポーネントの処理を中断しました。"); + + //空白文字を必須項目フィールドに挿入しようとしている場合、例外を発生させる + }else if(fieldRequired){ + event.record[fieldCode]['error'] = "「"+fieldCode+"」"+"フィールドは必須項目のため、空白文字は挿入できません。"; //レコードにエラーを表示 + throw new Error("「"+fieldCode+"」"+"フィールドは必須項目のため、空白文字は挿入できません。「値を挿入する」コンポーネントの処理を中断しました。"); + } + } + + return true; + } + + /** + * 入力値が半角数字かチェックする関数 + * @param {string} inputValue - 挿入する値 + * @return {boolean} -入力値が有効な数値の場合はtrueを返し、そうでない場合は例外を発生させる + */ + checkInputNumber(inputValue :string,fieldCode :string,event :any): boolean{ + let inputNumberValue = Number(inputValue);//数値型に変換 + + //有限数かどうか判定s + if(!isFinite(inputNumberValue)){ + event.record[fieldCode]['error'] = "「"+fieldCode+"」"+"フィールドに入れようとした値は、無効な日付形式です。"; //レコードにエラーを表示 + throw new Error("「"+fieldCode+"」"+"フィールドに入れようとした値は、有効な数値ではありません。「値を挿入する」コンポーネントの処理を中断しました。"); + } + return true; + } + + /** 入力値が有効な日付形式かチェックする関数 + * @param {string} inputValue - 挿入する値 + * @return {boolean} -入力値が有効な日付形式の場合はtrueを返し、そうでない場合は例外を発生させる + */ + checkInputDate(inputValue :string,fieldCode :string,event :any): boolean{ + //正規表現チェック + let twoDigitMonthDay = inputValue.match(/(\d{4})-(\d{2})-(\d{2})$/);//4桁の数字-2桁の数字-2桁の数字 + let singleDigitMonthDay = inputValue.match(/(\d{4})-(\d{1})-(\d{1})$/);//4桁の数字-1桁の数字-2桁の数字 + let singleDigitMonth = inputValue.match(/(\d{4})-(\d{1})-(\d{2})$/);//4桁の数字-1桁の数字-2桁の数字 + let singleDigitDay = inputValue.match(/(\d{4})-(\d{2})-(\d{1})$/);//4桁の数字-2桁の数字-1桁の数字 + let dateTime = inputValue.match(/(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}).000Z/);//時刻入りのUTCの日付形式 + let date; + //date型に変換 + date = new Date(inputValue); + + //date型変換できたか確認 + if(date !== undefined && !isNaN(date.getDate())){ + //正規表現チェック確認 + if(twoDigitMonthDay === null && singleDigitMonth === null && singleDigitDay === null && singleDigitMonthDay === null && dateTime === null){ + event.record[fieldCode]['error'] = "「"+fieldCode+"」"+"フィールドに入れようとした値は、無効な日付形式です。"; //レコードにエラーを表示 + throw new Error("「"+fieldCode+"」"+"フィールドに入れようとした値は、無効な日付形式です。「値を挿入する」コンポーネントの処理を中断しました。"); + } + } + return true; + } + + /** 入力値が有効な時刻形式かチェックする関数 + * @param {string} inputValue - 挿入する値 + * @return {boolean} -入力値が有効な日付形式の場合はtrueを返し、そうでない場合は例外を発生させる + */ + checkInputTime(inputValue :string,fieldCode :string,event :any): boolean{ + //正規表現チェック + let timeFormat =inputValue.match(/^([0-9]|1[0-9]|2[0-3]):[0-5][0-9]$/); + + //正規表現チェック確認 + if(timeFormat === null){ + event.record[fieldCode]['error'] = "「"+fieldCode+"」"+"フィールドに入れようとした値は、無効な時刻形式です。"; //レコードにエラーを表示 + throw new Error("「"+fieldCode+"」"+"フィールドに入れようとした値は、無効な時刻形式です。「値を挿入する」コンポーネントの処理を中断しました。"); + } + return true; + } + + /** + * 入力値のフィールドタイプがDATETIMEであれば、時刻ありの日付形式変換し、DATEであれば時刻なしの日付形式変換する関数 + * @param {string} inputValue -挿入する値 + * @param {string} fieldType -フィールドタイプ + * @return {string} -入力値が日付形式に変換できた場合は文字列を返し、そうでない場合は例外を発生させる + */ + changeDateFormat(inputValue :string, fieldType :string,fieldCode :string,event :any): string{ + let dateTime; + let date; + + //挿入する値をdate型に変換 + date = new Date(inputValue); + //date型変換できたか確認 + if(date !== undefined && !isNaN(date.getDate())){ + + //日時フィールドの場合、時刻ありの日付形式変換 + if(fieldType === "DATETIME"){ + dateTime =date.toISOString(); + return dateTime; + } + + //日付フィールドの場合、時刻なしの日付形式変換 + let dateArray=inputValue.match(/(\d{4})-(\d{1,2})-(\d{1,2})$/);//4桁の数字-1~2桁の数字-1~2桁の数字 + if(dateArray !== null){ + let yearIndex = 1; + let monthIndex = 2; + let dayIndex = 3; + let dateFormatted=`${dateArray[yearIndex]}-${dateArray[monthIndex]}-${dateArray[dayIndex]}` + return dateFormatted; + } + + } + + event.record[fieldCode]['error'] = "「"+fieldCode+"」"+"フィールドに入れようとした値は、無効な日付形式です。"; //レコードにエラーを表示 + throw new Error("「"+fieldCode+"」"+"フィールドに入れようとした値は、無効な日付形式です。「値を挿入する」コンポーネントの処理を中断しました。"); + } + + /** + * 入力値がフィールドタイプ(ラジオボタン・ドロップダウン・複数選択・ドロップダウン)の選択肢に存在する値かチェックする関数 + * @param {string} inputValue - 挿入する値 + * @return {boolean} -入力値が有効な値の場合はtrueを返し、そうでない場合は例外を発生させる + */ + checkInputOption(inputValue :string,fieldOptions :string | undefined,fieldCode :string,event :any): boolean { + + //入力値が選択肢に存在する値かチェックし、存在したらtrueを返す + if(fieldOptions !== undefined){ + let options = Object.keys(fieldOptions); + for(var optionsIndex in options){ + if(options[optionsIndex] === inputValue){ + return true; + } + } + } + + event.record[fieldCode]['error']="「"+fieldCode+"」"+"には、存在しない値を挿入しようとしたため、処理を中断しました。"; + throw new Error("「"+fieldCode+"」"+"には、存在しない値を挿入しようとしたため、処理を中断しました。「値を挿入する」コンポーネントの処理を中断しました。"); + } + + /** + * 入力値がフィールドタイプ(ユーザー選択)で、ユーザー情報に存在する値かチェックする関数 + * @param {string} inputValue - 挿入する値 + * @return {string | boolean} 入力値が登録されているユーザー情報から見つかった場合、trueを返し、見つからなかった場合、falseを返す + */ + async setInputUser(inputValue :string): Promise{ + + //ユーザー名を格納する変数 + let usersName; + const usersInfoColumnIndex=0; + + try{ + //APIでユーザー情報を取得する + const resp =await kintone.api(kintone.api.url('/v1/users', true), 'GET', {codes:[inputValue ]}) + + //入力されたログイン名(メールアドレス)がユーザー情報に登録されている場合、そのユーザー名を取得する + if (resp.users[usersInfoColumnIndex].code === inputValue) { + usersName=resp.users[usersInfoColumnIndex].name; + } + + //ユーザー名が取得できた場合、ログイン名とユーザー名をフィールドにセットする + if(usersName === undefined){ + throw new Error(); + } + }catch{ + return false; + } + + return usersName; + } + + /** + * 入力値がフィールドタイプ(組織選択)で、組織情報に存在する値かチェックする関数 + * @param {string} inputValue - 挿入する値 + * @return {string | boolean} 入力値が登録されている組織情報から見つかった場合、trueを返し、見つからなかった場合、falseを返す + */ + async setInputOrganization(inputValue :string): Promise{ + + //組織名を格納する変数 + let organizationName; + const organizationsInfoColumnIndex=0; + + try{ + //APIで組織情報を取得する + const resp =await kintone.api(kintone.api.url('/v1/organizations.json', true), 'GET', {codes:[inputValue ]}) + + //入力された組織コードが組織情報に登録されている場合、その組織名を取得する + if (resp.organizations[organizationsInfoColumnIndex].code === inputValue) { + organizationName=resp.organizations[organizationsInfoColumnIndex].name; + } + + //組織名が取得できた場合、組織コードと組織名をフィールドにセットする + if(organizationName === undefined){ + throw new Error(); + } + }catch{ + return false; + } + + return organizationName; + } + + /** + * 入力値がフィールドタイプ(グループ選択)で、グループ情報に存在する値かチェックする関数 + * @param {string} inputValue - 挿入する値 + * @return {string | boolean} 入力値が登録されているグループ情報から見つかった場合、trueを返し、見つからなかった場合、falseを返す + */ + async setInputGroup(inputValue :string): Promise{ + + //グループ名を格納する変数 + let groupsName; + const groupsInfoColumnIndex=0; + + try{ + //APIでグループ情報を取得する + const resp =await kintone.api(kintone.api.url('/v1/groups.json', true), 'GET', {codes:[inputValue ]}) + + //入力されたグループコードがグループ情報に登録されている場合、そのグループ名を取得する + if (resp.groups[groupsInfoColumnIndex].code === inputValue) { + groupsName=resp.groups[groupsInfoColumnIndex].name; + } + + //グループ名が取得できた場合、グループコードとグループ名をフィールドにセットする + if(groupsName === undefined){ + throw new Error(); + } + }catch{ + return false; + } + + return groupsName; + } + + + + /** + * アクションの実行を呼び出す + * @param actionNode + * @param event + * @returns + */ + async process(actionNode:IActionNode,event:any,context:IContext):Promise { + + let result={ + canNext:true, + result:false + }; + try{ + //属性設定を取得する + this.actionProps = actionNode.actionProps; + if (!('field' in actionNode.ActionValue) && !('value' in actionNode.ActionValue)) { + return result + } + + const fieldColumnIndex=1; + const valueColumnIndex=3; + + //プロパティで選択されたフィールド + const field=this.actionProps[fieldColumnIndex].props.modelValue.type; + //プロパティの挿入する値 + const value=this.actionProps[valueColumnIndex].props.modelValue; + + //条件式の結果を取得 + const conditionResult = this.getConditionResult(context); + if(!conditionResult){ + return result; + } + + //プロパティの値を挿入するフィールドが未選択の場合、例外を発生させる + if(field === null){ + throw new Error("「値を挿入する」コンポーネントで、値を挿入するフィールドが指定されていなかったため、処理が中断されました。"); + } + + //プロパティの値を挿入するフィールドが非対応フィールドの場合、例外を発生させる + //添付ファイル・テーブル・カテゴリー・ステータス・作成者・更新者・作業者・リビジョン番号・レコード番号・レコードID・計算・作成日時・更新日時フィールドが選択されている場合、例外を発生させる + if(field === "FILE" || field === "SUBTABLE" || field === "CATEGORY" || field === "STATUS" + || field === "STATUS_ASSIGNEE" || field === "CREATOR" || field === "MODIFIER" || field === "__REVISION__" + || field === "RECORD_NUMBER"|| field === "__ID__" || field ==="CALC" || field === "CREATED_TIME" || field === "UPDATED_TIME" ){ + throw new Error("「値を挿入する」コンポーネントで、選択されたフィールドは、値を挿入するコンポーネントでは非対応のフィールドのため、処理を中断しました。"); + } + + //プロパティの挿入する値が未入力の場合、例外を発生させる + if(value === ""){ + throw new Error("「値を挿入する」コンポーネントで、フィールドに挿入する値が指定されていなかったため、処理が中断されました。"); + } + + //既定のプロパティのインターフェースへ変換する + this.props = actionNode.ActionValue as IInsertValueProps; + + //挿入する値を取得 + let fieldValue = this.props.value; + //フィールドの種類を取得 + const fieldType = this.props.field.type; + //フィールドが必須項目なのか取得 + const fieldRequired=this.props.field.required; + //挿入するフィールドのコードを取得 + const fieldCode=this.props.field.code; + //挿入する値の形式(手入力か変数)を取得 + const insertValueType=this.props.show; + //ラジオボタン・チェックボックス・複数選択・ドロップダウンの選択肢を取得 + let fieldOptions =this.props.field.options; + //変数の値を格納する + let variableValue; + + //変数の場合、値が取得できるかチェック + if(insertValueType === "変数" && conditionResult){ + variableValue = context.variables[fieldValue]; + + if(variableValue === undefined){ + throw new Error("「"+fieldCode+"」"+"フィールドに入れようとした変数は、無効な入力形式です。"); + } + fieldValue = variableValue; + } + + //入力値チェック後、形式変換、型変換した値を格納する変数 + let correctFormattedValue; + //入力値チェック後、形式変換、型変換した値を格納する配列 + let correctValues :string[] = []; + //空白文字チェックの結果が負の場合、true + let checkInputError=this.checkInputBlank(fieldType,fieldValue,fieldCode,fieldRequired,event); + + //条件式の結果がtrue、空白文字チェックでエラーがない場合、挿入する値をフィールドタイプ別にチェックする + if(conditionResult && !checkInputError){ + + //文字列型のフィールドに挿入しようとしている値が適切の場合、correctFormattedValueに代入する + if(fieldType === "SINGLE_LINE_TEXT" || fieldType === "MULTI_LINE_TEXT" || fieldType === "RICH_TEXT" || fieldType === "LINK" ){ + correctFormattedValue = fieldValue; + + //数値型のフィールドに挿入しようとしている値が適切の場合、数値型に型変換してcorrectFormattedValueに代入する + }else if(fieldType === "NUMBER" ){ + if(this.checkInputNumber(fieldValue,fieldCode,event)){//入力値チェック + correctFormattedValue = Number(fieldValue);//型変換 + } + + //日付・日時型のフィールドに挿入しようとしている値が適切の場合、指定の日付・日時に形式変換してcorrectFormattedValueに代入する + }else if(fieldType === "DATE" || fieldType === "DATETIME" ){ + if(this.checkInputDate(fieldValue,fieldCode,event)){//入力値チェック + let formattedDate = this.changeDateFormat(fieldValue,fieldType,fieldCode,event) + if(formattedDate){ + correctFormattedValue = formattedDate + } + } + + //時刻フィールドに挿入しようとしている値が適切の場合、correctFormattedValueに代入する + }else if(fieldType === "TIME"){ + if(this.checkInputTime(fieldValue,fieldCode,event)){//入力値チェック + correctFormattedValue = fieldValue; + } + + //ラジオボタン・ドロップダウンのフィールドの選択肢と入力値が一致した場合、correctFormattedValueに代入する + }else if(fieldType === "RADIO_BUTTON" || fieldType === "DROP_DOWN"){ + if(this.checkInputOption(fieldValue,fieldOptions,fieldCode,event)){//入力値チェック + correctFormattedValue = fieldValue; + } + + //チェックボックス・複数選択のフィールドの選択肢と入力値が一致した場合、correctValuesの配列に代入する + }else if(fieldType === "CHECK_BOX" || fieldType === "MULTI_SELECT" ){ + if(this.checkInputOption(fieldValue,fieldOptions,fieldCode,event)){//入力値チェック + correctValues[0] = fieldValue; + } + + //ユーザー情報フィードに挿入しようとした値が適切な場合、correctFormattedValueに代入する + }else if(fieldType === "USER_SELECT"){ + //挿入する値がユーザー情報から見つかれば、ユーザー名を格納 + let users=await this.setInputUser(fieldValue); + + //ユーザー名が格納できている場合、ログイン名とユーザー名をcorrectFormattedValueに代入する + if(!users){ + event.record[fieldCode]['error']="ユーザー選択に、挿入しようとしたユーザー情報は見つかりませんでした。「値を挿入する」コンポーネントの処理を中断しました。"; + throw new Error("ユーザー選択に、挿入しようとしたユーザー情報は見つかりませんでした。「値を挿入する」コンポーネントの処理を中断しました。"); + }else{ + correctFormattedValue=[{ code: fieldValue, name: users }]; + } + + //組織情報フィードに挿入しようとした値が適切な場合、correctFormattedValueに代入する + }else if(fieldType === "ORGANIZATION_SELECT"){ + //挿入する値が組織情報から見つかれば、組織名を格納 + let organizations=await this.setInputOrganization(fieldValue); + + //組織名が格納できている場合、組織コードと組織名をcorrectFormattedValueに代入する + if(!organizations){ + event.record[fieldCode]['error']="組織選択フィールドに、挿入しようとした組織情報は見つかりませんでした。「値を挿入する」コンポーネントの処理を中断しました。"; + throw new Error("組織選択フィールドに、挿入しようとした組織情報は見つかりませんでした。「値を挿入する」コンポーネントの処理を中断しました。"); + }else{ + correctFormattedValue=[{ code: fieldValue, name: organizations}]; + } + + //グループ情報フィードに挿入しようとした値が適切な場合、correctFormattedValueに代入する + }else if(fieldType === "GROUP_SELECT"){ + //挿入する値がグループ情報から見つかれば、グループ名を格納 + let groups=await this.setInputGroup(fieldValue); + + //グループ名が格納できている場合、グループコードとグループ名をcorrectFormattedValueに代入する + if(!groups){ + event.record[fieldCode]['error']="グループ選択フィールドに、挿入しようとしたグループ情報は見つかりませんでした。「値を挿入する」コンポーネントの処理を中断しました。"; + throw new Error("グループ選択フィールドに、挿入しようとしたグループ情報は見つかりませんでした。「値を挿入する」コンポーネントの処理を中断しました。"); + }else{ + correctFormattedValue=[{ code: fieldValue, name: groups}]; + } + } + } + + //条件式の結果がtrueかつ挿入する値が変換できた場合、フィールド(ラジオボタン・ドロップダウン・チェックボックス・複数選択・文字列一行・文字列複数行・リッチエディタ・数値・日付・日時・時刻)にセット + if(conditionResult && (correctFormattedValue || correctValues)){ + //条件式の結果がtureかつ、値を正しい形式に変換できた場合、フィールドに値をセットする + if(correctFormattedValue){ + event.record[fieldCode].value = correctFormattedValue; + //条件式の結果がtureかつ、値を正しい形式(配列)に変換できた場合、フィールドに値(配列)をセットする + }else if(correctValues.length > 0){ + event.record[fieldCode].value = correctValues; + } + } + + result= { + canNext:true, + result:true + } + return result; + }catch(error:any){ + event.record; + event.error=error.message; + console.error(error); + result.canNext=true;//次のノードは処理を続ける + return result; + } + + } + + /** + * + * @param context 条件式を実行する + * @returns + */ + getConditionResult(context:any):boolean{ + //プロパティ`condition`から条件ツリーを取得する + const tree =this.getCondition(this.props.condition); + if(!tree){ + //条件を設定されていません + return true; + } + return tree.evaluate(tree.root,context); + } + + /** + * @param condition 条件式ツリーを取得する + * @returns + */ + getCondition(condition:string):ConditionTree|null{ + try{ + const tree = new ConditionTree(); + tree.fromJson(condition); + if(tree.getConditions(tree.root).length>0){ + return tree; + }else{ + return null; + } + }catch(error){ + return null; + } + } + + register(): void { + actionAddins[this.name]=this; + } + +} +new InsertValueAction(); \ No newline at end of file diff --git a/plugin/kintone-addins/src/types/ActionTypes.ts b/plugin/kintone-addins/src/types/ActionTypes.ts index 2ad4c90..ad2ec32 100644 --- a/plugin/kintone-addins/src/types/ActionTypes.ts +++ b/plugin/kintone-addins/src/types/ActionTypes.ts @@ -89,6 +89,8 @@ export interface IField{ name?:string; code:string; type?:string; + required?:boolean; + options?:string; } /** diff --git a/plugin/kintone-addins/src/types/action-process.ts b/plugin/kintone-addins/src/types/action-process.ts index 5638597..130e2a9 100644 --- a/plugin/kintone-addins/src/types/action-process.ts +++ b/plugin/kintone-addins/src/types/action-process.ts @@ -10,6 +10,7 @@ import '../actions/regular-check'; import '../actions/mail-check'; import '../actions/counter-check'; import '../actions/datetime-getter'; +import '../actions/insert-value'; import { ActionFlow,IActionFlow, IActionResult,IContext } from "./ActionTypes"; export class ActionProcess{