Compare commits

..

22 Commits

Author SHA1 Message Date
Mouriya
3540becf6f vite-plugin-css-injected-by-jsはメンテナンスされていないため、通常のソースマップ生成に影響を与えるので、vite-plugin-lib-injected-cssを使用してください 2024-07-02 12:15:18 +09:00
Tian Dai
63099eda8b Merge branch 'plugin-infar-vite' of ssh.dev.azure.com:v3/alicorn-dev/KintoneAppBuilder/KintoneAppBuilder into feature-data-mapping 2024-06-04 17:49:32 +09:00
Tian Dai
65d89b0462 Windowsのオペレーティングシステムの権限問題のため、Viteの自動URLオープン機能を無効にしました 2024-06-04 17:48:39 +09:00
Tian Dai
c6ded099fa vite devとvite serverはエイリアスではなく、置き換え可能で、問題を修正しました。 2024-06-04 17:41:09 +09:00
Tian Dai
c072233593 Merge branch 'plugin-infar-vite' into feature-data-mapping
# Conflicts:
#	plugin/kintone-addins/package.json
#	plugin/kintone-addins/vite.config.js
2024-06-04 17:21:43 +09:00
Tian Dai
4e296c1555 npm-run-all2を全オペレーティング・システムと依存性管理のサポートに置き換える。ラン・スクリプトをより組み合わせ可能なものに再編成する。 プロジェクトの dotenv サポートを提供する。 2024-06-04 16:16:36 +09:00
016fcaab29 feat:データマッピングアクション変更 2024-06-03 17:39:39 +09:00
Mouriya
bebc1ec9fa フィックス 2024-06-03 08:10:23 +09:00
Mouriya
f71c3d2123 挿入専用のデータ・マッピング・プラグインを追加 2024-06-03 08:08:06 +09:00
Mouriya
d79ce8d06b データ収集プラグインの追加 2024-06-03 07:08:04 +09:00
Mouriya
fc9c3a5e81 条件演算子をカスタマイズし、kintoneクエリ文字列に変換することができます。 2024-06-03 07:07:20 +09:00
Mouriya
6df72a1ae3 Merge commit '372dbe50f7254ed3142a85dac58e624fce695aa7' into feature-data-mapping 2024-06-03 02:16:50 +09:00
Mouriya
372dbe50f7 HTTPサーバーのホットリロード機能を追加。 2024-06-03 02:08:37 +09:00
Mouriya
68fde6d490 Merge commit 'b25c17ab53a848f5692f113c6c3c1177e310d87f' into feature-data-mapping 2024-06-03 00:41:01 +09:00
Mouriya
c398dee21e 誤ったmodelValueの使用法の修正 2024-05-27 19:52:16 +09:00
Mouriya
f2ab310b6d Merge commit 'a6cf95b76d56841e7a5d545c8f0835e44cf4dafc' into feature-data-mapping 2024-05-27 19:27:44 +09:00
Mouriya
ca0f24465b 外部アプリセレクタコンポーネントがある場合、このコンポーネントはアプリを選択せずにフィールドを選択できる。 2024-05-27 19:04:21 +09:00
Mouriya
3cc4b65460 アプリ選択コンポーネントがある場合、アプリまたはフィールドが選択されるまで、それらを無効にすることができる。 2024-05-27 19:03:08 +09:00
Mouriya
a6cf95b76d 新しいアプリ選択コンポーネント 2024-05-27 18:45:34 +09:00
Mouriya
484ab9fdae ファイル名とコンポーネント名を変更し、コンポーネントとして使用できるようにする。 2024-05-27 18:44:36 +09:00
Mouriya
78bba2502f データ・マッピング・コンポーネントの追加 2024-05-25 07:01:58 +09:00
Mouriya
c78b3cb5c0 AppFieldSelectコンポーネントは、パブリック部分をAppFieldSelectBoxとして抽出する。 2024-05-25 07:01:38 +09:00
69 changed files with 1637 additions and 4130 deletions

File diff suppressed because one or more lines are too long

View File

@@ -218,8 +218,6 @@ def deoployappfromkintone(app:str,revision:str,c:config.KINTONE_ENV):
r = httpx.post(url,headers=headers,data=json.dumps(data))
return r.json
# 既定項目に含めるアプリのフィールドのみ取得する
# スペース、枠線、ラベルを含まない
def getfieldsfromkintone(app:str,c:config.KINTONE_ENV):
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
params = {"app":app}
@@ -227,44 +225,6 @@ def getfieldsfromkintone(app:str,c:config.KINTONE_ENV):
r = httpx.get(url,headers=headers,params=params)
return r.json()
# フォームに配置するフィールドのみ取得する
# スペース、枠線、ラベルも含める
def getformfromkintone(app:str,c:config.KINTONE_ENV):
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
params = {"app":app}
url = f"{c.BASE_URL}{config.API_V1_STR}/form.json"
r = httpx.get(url,headers=headers,params=params)
return r.json()
def merge_kintone_fields(fields_response: dict, form_response: dict) -> dict:
fields_properties = fields_response.get('properties', {})
form_properties = form_response.get('properties', [])
merged_properties = {k: v for k, v in fields_properties.items()}
for index, form_field in enumerate(form_properties):
code = form_field.get('code')
if code:
if code and code not in merged_properties:
merged_properties[code] = form_field
else:
element_id = form_field.get('elementId')
if element_id:
key = element_id
form_field['code']=element_id
form_field['label']=form_field.get('type')
# else:
# key = f"{form_field.get('type')}_{index}"
merged_properties[key] = form_field
merged_response = {
'revision': fields_response.get('revision', ''),
'properties': merged_properties
}
return merged_response
def analysefields(excel,kintone):
updatefields={}
addfields={}
@@ -404,12 +364,6 @@ def updateappjscss(app,uploads,c:config.KINTONE_ENV):
r = httpx.put(url,headers=headers,data=json.dumps(data))
return r.json()
def getTempPath(filename):
scriptdir = Path(__file__).resolve().parent
rootdir = scriptdir.parent.parent.parent.parent
fpath = os.path.join(rootdir,"Temp",filename)
return fpath
def createappjs(domainid,app):
db = SessionLocal()
flows = get_flows_by_app(db,domainid,app)
@@ -418,10 +372,10 @@ def createappjs(domainid,app):
for flow in flows:
content[flow.eventid] = {'flowid':flow.flowid,'name':flow.name,'content':flow.content}
js = 'const alcflow=' + json.dumps(content)
# scriptdir = Path(__file__).resolve().parent
# rootdir = scriptdir.parent.parent.parent.parent
# fpath = os.path.join(rootdir,"Temp",f"alc_setting_{app}.js")
fpath = getTempPath(f"alc_setting_{app}.js")
scriptdir = Path(__file__).resolve().parent
rootdir = scriptdir.parent.parent.parent.parent
fpath = os.path.join(rootdir,"Temp",f"alc_setting_{app}.js")
print(rootdir)
print(fpath)
with open(fpath,'w') as file:
file.write(js)
@@ -509,7 +463,7 @@ async def app(request:Request,app:str,c:config.KINTONE_ENV=Depends(getkintoneenv
r = httpx.get(url,headers=headers,params=params)
return r.json()
except Exception as e:
raise APIException('kintone:app',request.url._url, f"Error occurred while get app({c.DOMAIN_NAME}->{app}):",e)
raise APIException('kintone:app',request.url._url, f"Error occurred while get app({c.DOMAIN_NAM}->{app}):",e)
@r.get("/allapps")
async def allapps(request:Request,c:config.KINTONE_ENV=Depends(getkintoneenv)):
@@ -519,30 +473,21 @@ async def allapps(request:Request,c:config.KINTONE_ENV=Depends(getkintoneenv)):
r = httpx.get(url,headers=headers)
return r.json()
except Exception as e:
raise APIException('kintone:allapps',request.url._url, f"Error occurred while get allapps({c.DOMAIN_NAME}):",e)
raise APIException('kintone:allapps',request.url._url, f"Error occurred while get allapps({c.DOMAIN_NAM}):",e)
@r.get("/appfields")
async def appfields(request:Request,app:str,env = Depends(getkintoneenv)):
try:
return getfieldsfromkintone(app,env)
except Exception as e:
raise APIException('kintone:appfields',request.url._url, f"Error occurred while get app fileds({env.DOMAIN_NAME}->{app}):",e)
@r.get("/allfields")
async def allfields(request:Request,app:str,env = Depends(getkintoneenv)):
try:
field_resp = getfieldsfromkintone(app,env)
form_resp = getformfromkintone(app,env)
return merge_kintone_fields(field_resp,form_resp)
except Exception as e:
raise APIException('kintone:allfields',request.url._url, f"Error occurred while get form fileds({env.DOMAIN_NAME}->{app}):",e)
raise APIException('kintone:appfields',request.url._url, f"Error occurred while get app fileds({env.DOMAIN_NAM}->{app}):",e)
@r.get("/appprocess")
async def appprocess(request:Request,app:str,env = Depends(getkintoneenv)):
try:
return getprocessfromkintone(app,env)
except Exception as e:
raise APIException('kintone:appprocess',request.url._url, f"Error occurred while get app process({env.DOMAIN_NAME}->{app}):",e)
raise APIException('kintone:appprocess',request.url._url, f"Error occurred while get app process({env.DOMAIN_NAM}->{app}):",e)
@r.get("/alljscss")
async def alljscs(request:Request,app:str,c:config.KINTONE_ENV=Depends(getkintoneenv)):
@@ -553,7 +498,7 @@ async def alljscs(request:Request,app:str,c:config.KINTONE_ENV=Depends(getkinton
r = httpx.get(url,headers=headers,params=params)
return r.json()
except Exception as e:
raise APIException('kintone:alljscss',request.url._url, f"Error occurred while get app js/css({c.DOMAIN_NAME}->{app}):",e)
raise APIException('kintone:alljscss',request.url._url, f"Error occurred while get app js/css({c.DOMAIN_NAM}->{app}):",e)
@r.post("/createapp",)
async def createapp(request:Request,name:str,c:config.KINTONE_ENV=Depends(getkintoneenv)):
@@ -569,7 +514,7 @@ async def createapp(request:Request,name:str,c:config.KINTONE_ENV=Depends(getkin
r = httpx.post(url,headers=headers,data=json.dumps(data))
return r.json
except Exception as e:
raise APIException('kintone:createapp',request.url._url, f"Error occurred while create app({c.DOMAIN_NAME}->{name}):",e)
raise APIException('kintone:createapp',request.url._url, f"Error occurred while create app({c.DOMAIN_NAM}->{name}):",e)
@r.post("/createappfromexcel",)
@@ -698,7 +643,7 @@ async def updateprocessfromexcel(request:Request,app:str,env = Depends(getkinton
if deploy:
result = deoployappfromkintone(app,revision,env)
except Exception as e:
raise APIException('kintone:updateprocessfromexcel',request.url._url, f"Error occurred while update process ({env.DOMAIN_NAME}->{app}):",e)
raise APIException('kintone:updateprocessfromexcel',request.url._url, f"Error occurred while update process ({env.DOMAIN_NAM}->{app}):",e)
return result
@@ -709,8 +654,7 @@ async def createjstokintone(request:Request,app:str,env:config.KINTONE_ENV = Dep
jscs=[]
files=[]
files.append(createappjs(env.DOMAIN_ID, app))
files.append(getTempPath('alc_runtime.js'))
files.append(getTempPath('alc_runtime.css'))
files.append('Temp\\alc_runtime.js')
for file in files:
upload = uploadkintonefiles(file,env)
if upload.get('fileKey') != None:
@@ -720,4 +664,4 @@ async def createjstokintone(request:Request,app:str,env:config.KINTONE_ENV = Dep
deoployappfromkintone(app,appjscs["revision"],env)
return appjscs
except Exception as e:
raise APIException('kintone:createjstokintone',request.url._url, f"Error occurred while create js ({env.DOMAIN_NAME}->{app}):",e)
raise APIException('kintone:createjstokintone',request.url._url, f"Error occurred while create js ({env.DOMAIN_NAM}->{app}):",e)

View File

@@ -5,14 +5,15 @@ PROJECT_NAME = "KintoneAppBuilder"
#SQLALCHEMY_DATABASE_URI = "postgres://maxz64:m@xz1205@alicornkintone.postgres.database.azure.com/postgres"
SQLALCHEMY_DATABASE_URI = "postgres://kabAdmin:P@ssw0rd!@kintonetooldb.postgres.database.azure.com/postgres"
#SQLALCHEMY_DATABASE_URI = "postgres://kabAdmin:P@ssw0rd!@kintonetooldb.postgres.database.azure.com/unittest"
API_V1_STR = "/k/v1"
API_V1_AUTH_KEY = "X-Cybozu-Authorization"
DEPLOY_MODE = "PROD" #DEV,PROD
DEPLOY_MODE = "DEV" #DEV,PROD
DEPLOY_JS_URL = "https://ka-addin.azurewebsites.net/alc_runtime.js"
#DEPLOY_JS_URL = "https://e84c-133-139-70-142.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"]

View File

@@ -25,7 +25,3 @@ python -m venv env
pip install -r requirements.txt
```
4. backend 起動
```bash
uvicorn app.main:app --reload
```

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,3 @@
#開発環境
#KAB_BACKEND_URL="https://kab-backend.azurewebsites.net/"
#単体テスト環境
#KAB_BACKEND_URL="https://kab-backend-unittest.azurewebsites.net/"
#ローカル開発環境
KAB_BACKEND_URL="http://127.0.0.1:8000/"
KAB_BACKEND_URL="https://kab-backend.azurewebsites.net/"
#KAB_BACKEND_URL="http://127.0.0.1:8000/"

View File

@@ -94,7 +94,6 @@ module.exports = configure(function (/* ctx */) {
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#devServer
devServer: {
// https: true
port:9001,
open: true, // opens browser window automatically
env: { ...dotenv },
},

View File

@@ -4,7 +4,7 @@
<div class="q-mb-xs q-ml-md text-primary">アプリ選択</div>
<div class="q-pa-md row" style="border: 1px solid rgba(0, 0, 0, 0.12); border-radius: 4px;">
<div v-if="selField?.app && !showSelectApp">{{ selField?.app?.name }}</div>
<div v-if="selField?.app && !showSelectApp">{{ selField.app?.name }}</div>
<q-space />
<div>
<q-btn outline dense label="選 択" padding="none sm" color="primary" @click="() => {
@@ -14,7 +14,7 @@
</div>
</div>
<div v-if="!showSelectApp && selField?.app?.name">
<div v-if="!showSelectApp && selField.app?.name">
<div>
<div class="row q-mb-md">
<!-- <div class="col"> -->
@@ -31,9 +31,9 @@
</div>
</div>
<div class="row">
<field-select ref="fieldDlg" name="フィールド" :type="selectType" :updateSelectFields="updateSelectFields"
:appId="selField?.app?.id" not_page :filter="fieldFilter"
:selectedFields="selField.fields" :fieldTypes="fieldTypes"></field-select>
<field-select ref="fieldDlg" name="フィールド" :type="selectType" :updateSelects="updateItems"
:appId="selField.app?.id" not_page :filter="fieldFilter"
:selFields="selField.fields"></field-select>
</div>
</div>
</div>
@@ -41,7 +41,7 @@
</div>
<show-dialog v-model:visible="showSelectApp" name="アプリ選択">
<show-dialog v-model:visible="showSelectApp" name="アプリ選択" @close="closeAppDlg">
<template v-slot:toolbar>
<q-input dense debounce="300" v-model="filter" placeholder="検索" clearable>
<template v-slot:before>
@@ -51,7 +51,7 @@
</template>
<AppSelectBox ref="appDlg" name="アプリ" type="single" :filter="filter"
:updateSelectApp="updateSelectApp"></AppSelectBox>
:updateExternalSelectAppInfo="updateExternalSelectAppInfo"></AppSelectBox>
</show-dialog>
</template>
@@ -59,6 +59,7 @@
import { defineComponent, ref, watchEffect, computed ,reactive} from 'vue';
import ShowDialog from './ShowDialog.vue';
import FieldSelect from './FieldSelect.vue';
import { useFlowEditorStore } from 'stores/flowEditor';
import AppSelectBox from './AppSelectBox.vue';
interface IApp {
id: string,
@@ -92,24 +93,40 @@ export default defineComponent({
type: String,
default: 'single'
},
fieldTypes:{
type:Array,
default:()=>[]
}
},
setup(props, { emit }) {
const appDlg = ref();
const fieldDlg = ref();
const showSelectApp = ref(false);
const selField = reactive(props.selectedField);
console.log(props.selectedField);
const store = useFlowEditorStore();
const isSelected = computed(() => {
return selField !== null && typeof selField === 'object' && ('app' in selField)
});
const updateSelectApp = (newAppinfo: IApp) => {
const closeAppDlg = (val: string) => {
if (val == 'OK') {
selField.app = appDlg.value.selected[0];
selField.fields = [];
showSelectApp.value = false;
}
};
const closeFieldDialog = (val: string) => {
if (val == 'OK') {
selField.fields = fieldDlg.value.selected;
}
};
const updateExternalSelectAppInfo = (newAppinfo: IApp) => {
selField.app = newAppinfo
}
const updateSelectFields = (newFields: IField[]) => {
const updateItems = (newFields: IField[]) => {
selField.fields = newFields
}
@@ -118,11 +135,15 @@ export default defineComponent({
});
return {
appDlg,
fieldDlg,
closeAppDlg,
closeFieldDialog,
showSelectApp,
isSelected,
updateSelectApp,
updateExternalSelectAppInfo,
filter: ref(),
updateSelectFields,
updateItems,
fieldFilter: ref(),
selField
};

View File

@@ -26,7 +26,7 @@ export default {
name: String,
type: String,
filter: String,
updateSelectApp: {
updateExternalSelectAppInfo: {
type: Function
}
},
@@ -42,8 +42,8 @@ export default {
const selected = ref([])
watchEffect(()=>{
if (selected.value && selected.value[0] && props.updateSelectApp) {
props.updateSelectApp(selected.value[0])
if (selected.value && selected.value[0] && props.updateExternalSelectAppInfo) {
props.updateExternalSelectAppInfo(selected.value[0])
}
});
onMounted(() => {

View File

@@ -1,6 +1,6 @@
<template>
<q-field labelColor="primary" class="condition-object" dense outlined :label="label" :disable="disabled"
:clearable="isSelected">
<q-field v-model="selectedObject" labelColor="primary" class="condition-object"
:clearable="isSelected" stack-label :dense="true" :outlined="true" >
<template v-slot:control >
<q-chip color="primary" text-color="white" v-if="isSelected && selectedObject.objectType==='field'" :dense="true" class="selected-obj">
{{ selectedObject.name }}
@@ -8,14 +8,13 @@
<q-chip color="info" text-color="white" v-if="isSelected && selectedObject.objectType==='variable'" :dense="true" class="selected-obj">
{{ selectedObject.name.name }}
</q-chip>
<div v-if="isSelected && selectedObject.objectType==='text'">{{ selectedObject?.sharedText }}</div>
</template>
<template v-slot:append>
<q-icon name="search" class="cursor-pointer" @click="showDg"/>
</template>
</q-field>
<show-dialog v-model:visible="show" name="設定項目" @close="closeDg" min-width="400px">
<!-- <template v-slot:toolbar>
<show-dialog v-model:visible="show" name="設定項目一覧" @close="closeDg" min-width="400px">
<template v-slot:toolbar>
<q-input dense debounce="200" v-model="filter" placeholder="検索" clearable>
<template v-slot:before>
<q-icon name="search" />
@@ -23,88 +22,47 @@
</q-input>
</template>
<condition-objects ref="appDg" name="フィールド" type="single" :filter="filter" :appId="store.appInfo?.appId" :vars="vars"></condition-objects>
-->
<DynamicItemInput v-model:selectedObject="selectedObject" :canInput="config.canInput"
:buttonsConfig="config.buttonsConfig" :appId="store.appInfo?.appId" :options="options" ref="inputRef" />
</show-dialog>
</template>
<script lang="ts">
import { defineComponent, reactive, ref, watchEffect, computed ,PropType} from 'vue';
import { defineComponent, reactive, ref ,watchEffect,computed} from 'vue';
import ShowDialog from '../ShowDialog.vue';
// import ConditionObjects from '../ConditionObjects.vue';
import DynamicItemInput from '../DynamicItemInput/DynamicItemInput.vue';
import ConditionObjects from '../ConditionObjects.vue';
import { useFlowEditorStore } from '../../stores/flowEditor';
import {IActionFlow,IActionNode,IActionVariable} from '../../types/ActionTypes';
import { IDynamicInputConfig } from 'src/types/ComponentTypes';
export default defineComponent({
name: 'ConditionObject',
components: {
ShowDialog,
DynamicItemInput,
// ConditionObjects
ConditionObjects
},
props: {
disabled: {
type: Boolean,
default: false
},
label: {
type: String,
default: undefined
},
config: {
type: Object as PropType<IDynamicInputConfig>,
default: () => {
return {
canInput: false,
buttonsConfig: [
{ label: 'フィールド', color: 'primary', type: 'FieldAdd' },
{ label: '変数', color: 'green', type: 'VariableAdd' },
]
};
}
},
options:
{
type:Array as PropType< string[]>,
default:()=>[]
},
modelValue: {
type: Object,
default: null
},
},
setup(props, { emit }) {
// const appDg = ref();
const inputRef=ref();
const appDg = ref();
const show = ref(false);
const selectedObject = ref(props.modelValue);
const store = useFlowEditorStore();
// const sharedText = ref(''); // 共享的文本状态
const isSelected = computed(()=>{
return selectedObject.value?.sharedText !== '';
return selectedObject.value!==null && typeof selectedObject.value === 'object' && ('name' in selectedObject.value)
});
// const isSelected = computed(()=>{
// return selectedObject.value!==null && typeof selectedObject.value === 'object' && ('name' in selectedObject.value)
// });
let vars:IActionVariable[] =[];
if(store.currentFlow!==undefined && store.activeNode!==undefined ){
vars =store.currentFlow.getVarNames(store.activeNode);
}
// const filter=ref('');
const filter=ref('');
const showDg = () => {
show.value = true;
};
const closeDg = (val:string) => {
if (val == 'OK') {
// selectedObject.value = appDg.value.selected[0];
selectedObject.value = inputRef.value.selectedObjectRef
selectedObject.value = appDg.value.selected[0];
}
};
@@ -113,20 +71,15 @@ export default defineComponent({
});
return {
inputRef,
store,
// appDg,
appDg,
show,
showDg,
closeDg,
selectedObject,
vars:reactive(vars),
isSelected,
buttonsConfig: [
{ label: 'フィールド', color: 'primary', type: 'FieldAdd' },
{ label: '変数', color: 'green', type: 'VariableAdd' },
]
// filter
filter
};
}
});
@@ -137,7 +90,6 @@ export default defineComponent({
max-height: 40px;
margin: 0 2px;
}
.selected-obj{
margin: 0 2px;
}

View File

@@ -67,21 +67,17 @@
<!-- condition -->
<div @click.stop @keypress.stop v-else >
<div class="row no-wrap items-center q-my-xs">
<ConditionObject v-bind="prop.node" v-model="prop.node.object" :config="leftDynamicItemConfig" class="col-4"/>
<ConditionObject v-bind="prop.node" v-model="prop.node.object" class="col-4"></ConditionObject>
<q-select v-model="prop.node.operator" :options="operators" class="operator" :outlined="true" :dense="true"></q-select>
<ConditionObject v-bind="prop.node" v-model="prop.node.value" :config="rightDynamicItemConfig" class="col-4"
:options="objectValueOptions(prop.node?.object?.options)"
/>
<!-- <ConditionObject v-bind="prop.node" v-model="prop.node.object" class="col-4"/> -->
<!-- <q-input v-if="!prop.node.object || !('options' in prop.node.object)"
<q-input v-if="!prop.node.object || !('options' in prop.node.object)"
v-model="prop.node.value"
class="condition-value" :outlined="true" :dense="true" ></q-input> -->
<!-- <q-select v-if="prop.node.object && ('options' in prop.node.object)"
class="condition-value" :outlined="true" :dense="true" ></q-input>
<q-select v-if="prop.node.object && ('options' in prop.node.object)"
v-model="prop.node.value"
:options="objectValueOptions(prop.node.object.options)"
clearable
value-key="index"
class="condition-value" :outlined="true" :dense="true" ></q-select> -->
class="condition-value" :outlined="true" :dense="true" ></q-select>
<q-btn flat round dense icon="more_horiz" size="sm" >
<q-menu auto-close anchor="top right">
<q-list>
@@ -120,7 +116,6 @@ import { finished } from 'stream';
import { defineComponent,ref,reactive, computed, inject } from 'vue';
import { INode,ConditionTree,GroupNode,ConditionNode, LogicalOperator,Operator,NodeType } from '../../types/Conditions';
import ConditionObject from './ConditionObject.vue';
import { IDynamicInputConfig } from 'src/types/ComponentTypes';
export default defineComponent( {
name: 'NodeCondition',
components: {
@@ -148,18 +143,17 @@ export default defineComponent( {
return opts;
});
const operatorSet = inject<Array<any>>('Operator')
const operators = ref(operatorSet ? operatorSet : Object.values(Operator));
const operator = inject('Operator')
const operators =computed(()=>{
return operator ? operator : Object.values(Operator);
});
const tree = reactive(props.conditionTree);
const conditionString = computed(()=>{
return tree.buildConditionString(tree.root);
});
const objectValueOptions=(options:any):any[]|null=>{
if(!options){
return null;
}
const objectValueOptions=(options:any):any[]=>{
const opts:any[] =[];
Object.keys(options).forEach((key) =>
{
@@ -226,14 +220,11 @@ export default defineComponent( {
ticked.value=[];
}
const expanded=computed(()=>tree.getGroups(tree.root));
// addCondition(tree.root);
const leftDynamicItemConfig = inject<IDynamicInputConfig>('leftDynamicItemConfig');
const rightDynamicItemConfig = inject<IDynamicInputConfig>('rightDynamicItemConfig');
return {
leftDynamicItemConfig,
rightDynamicItemConfig,
showingCondition,
conditionString,
tree,
@@ -265,12 +256,10 @@ export default defineComponent( {
max-height: 40px;
margin: 0 2px;
}
.operator{
min-width: 150px;
max-height: 40px;
margin: 0 2px;
text-align: center;
font-size: 12pt;
}

View File

@@ -1,161 +0,0 @@
<template>
<div class="q-mx-md" style="max-width: 600px;">
<!-- <q-card> -->
<div class="q-mb-md">
<q-input ref="inputRef" v-if="!optionsRef|| optionsRef.length===0"
outlined dense debounce="200" @update:model-value="updateSharedText"
v-model="sharedText" :readonly="!canInputFlag" autogrow>
<template v-slot:append>
<q-btn flat round padding="none" icon="cancel" @click="clearSharedText" color="grey-6" />
</template>
</q-input>
<q-select v-if="optionsRef && optionsRef.length>0"
:model-value="sharedText"
:options="optionsRef"
clearable
value-key="index"
outlined
dense
use-input
hide-selected
input-debounce="10"
fill-input
@input-value="setValue"
@clear="sharedText=null"
hide-dropdown-icon
:readonly="!canInputFlag"
>
</q-select>
</div>
<div class="row q-gutter-sm">
<q-btn v-for="button in buttonsConfig" :key="button.type" :color="button.color" @mousedown.prevent
@click="openDialog(button)" size="sm">
{{ button.label }}
</q-btn>
</div>
<show-dialog v-model:visible="dialogVisible" :name="currentDialogName" @close="closeDialog" min-width="400px">
<template v-slot:toolbar>
<q-input dense debounce="200" v-model="filter" clearable>
<template v-slot:before>
<q-icon name="search" />
</template>
</q-input>
</template>
<!-- asdf -->
<component :is="currentComponent" @select="handleSelect" :filter="filter" :appId="appId" />
</show-dialog>
<!-- </q-card> -->
</div>
</template>
<script lang="ts">
import { ref, inject, watchEffect, defineComponent,PropType } from 'vue';
import FieldAdd from './FieldAdd.vue';
import VariableAdd from './VariableAdd.vue';
// import FunctionAdd from './FunctionAdd.vue';
import ShowDialog from '../ShowDialog.vue';
import { IButtonConfig } from 'src/types/ComponentTypes';
export default defineComponent({
name: 'DynamicItemInput',
components: {
FieldAdd,
VariableAdd,
// FunctionAdd,
ShowDialog
},
props: {
canInput: {
type: Boolean,
default: false
},
appId: {
type: String,
},
selectedObject: {
default: {}
},
options:{
type:Array as PropType< string[]>
},
buttonsConfig: {
type: Array as PropType<IButtonConfig[]>,
default: () => [
{ label: 'フィールド', color: 'primary', type: 'FieldAdd' }
]
}
},
setup(props, { emit }) {
const filter = ref('');
const dialogVisible = ref(false);
const currentDialogName = ref('');
const selectedObjectRef = ref(props.selectedObject);
const currentComponent = ref('FieldAdd');
const sharedText = ref(props.selectedObject?.sharedText ?? '');
const inputRef = ref();
const canInputFlag = ref(props.canInput);
const editable = ref(false);
const openDialog = (button: IButtonConfig) => {
currentDialogName.value = button.label;
currentComponent.value = button.type;
dialogVisible.value = true;
editable.value = canInputFlag.value;
};
const closeDialog = () => {
dialogVisible.value = false;
};
const handleSelect = (value:any) => {
if (value && value._t && (value._t as string).length > 0) {
canInputFlag.value = editable.value;
}
selectedObjectRef.value={ sharedText: value._t, ...value };
sharedText.value = `${value._t}`;
// emit('update:selectedObject', { sharedText: sharedText.value, ...value });
dialogVisible.value = false;
};
const clearSharedText = () => {
sharedText.value = '';
selectedObjectRef.value={};
canInputFlag.value = true;
// emit('update:selectedObject', {});
}
const updateSharedText = (value:string) => {
sharedText.value = value;
selectedObjectRef.value= { sharedText: value,objectType:'text' }
// emit('update:selectedObject', { ...props.selectedObject, sharedText: value,objectType:'text' });
}
const setValue=(value:string)=>{
sharedText.value = value;
if(selectedObjectRef.value.sharedText!==value){
selectedObjectRef.value= { sharedText: value,objectType:'text' }
}
}
const optionsRef=ref(props.options);
return {
filter,
dialogVisible,
currentDialogName,
currentComponent,
canInputFlag,
openDialog,
closeDialog,
handleSelect,
clearSharedText,
updateSharedText,
setValue,
sharedText,
inputRef,
optionsRef,
selectedObjectRef
};
}
});
</script>

View File

@@ -1,41 +0,0 @@
<template>
<field-list v-model="selected" type="single" :filter="filter" :appId="sourceApp ? sourceApp : appId"
:fields="sourceFields" @update:modelValue="handleSelect" />
</template>
<script lang="ts">
import { computed, inject, ref } from 'vue';
import FieldList from '../FieldList.vue';
export default {
name: 'FieldAdd',
components: {
FieldList,
},
props: {
appId: Number,
filter: String
},
setup(props, { emit }) {
const sourceFields = inject<Array<unknown>>('sourceFields')
const sourceApp = inject<number>('sourceApp')
const appId = computed(() => {
if (sourceFields || sourceApp) {
return sourceApp.value
} else {
return props.appId
}
});
return {
sourceFields,
sourceApp,
selected: ref([]),
handleSelect: (newSelection: any[]) => {
if (newSelection.length > 0) {
const v = newSelection[0]
emit('select', { _t: `field(${appId.value},${v.name})`, ...v }); // 假设您只需要选择的第一个字段的名称
}
}
}
},
}
</script>

View File

@@ -1,42 +0,0 @@
<template>
<variable-list v-model="selected" type="single" :vars="vars" :filter="filter" @update:modelValue="handleSelect" />
</template>
<script lang="ts">
import { ref } from 'vue';
import VariableList from '../VariableList.vue';
import { useFlowEditorStore } from 'src/stores/flowEditor';
import { IActionVariable } from 'src/types/ActionTypes';
export default {
name: 'VariableAdd',
components: {
VariableList,
},
props: {
appId: Number,
filter: String
},
setup(props, { emit }) {
const store = useFlowEditorStore();
let vars: IActionVariable[] = [];
console.log(store.currentFlow !== undefined && store.activeNode !== undefined);
if (store.currentFlow !== undefined && store.activeNode !== undefined) {
vars = store.currentFlow.getVarNames(store.activeNode);
}
return {
vars,
selected: ref([]),
handleSelect: (newSelection: any[]) => {
if (newSelection.length > 0) {
const v = newSelection[0];
let name = v.name
if (typeof name === 'object') {
name = name.name
}
emit('select', { _t: `var(${name})`, ...v }); // 假设您只需要选择的第一个字段的名称
}
}
}
},
}
</script>

View File

@@ -32,7 +32,7 @@ export default {
]
const { state : rows, isReady: isLoaded, isLoading } = useAsyncState((args) => {
if (props.fields && Object.keys(props.fields).length > 0) {
if (props.fields) {
return props.fields.map(f => ({ name: f.label, objectType: 'field', ...f }));
} else {
return api.get('api/v1/appfields', {

View File

@@ -11,7 +11,6 @@
import { ref, onMounted, reactive, watchEffect } from 'vue'
import { api } from 'boot/axios';
export default {
name: 'fieldSelect',
props: {
@@ -29,14 +28,10 @@ export default {
type:Array,
default:()=>[]
},
fieldTypes:{
type:Array,
default:()=>[]
},
filter: String,
updateSelectFields: {
updateSelects: {
type: Function
},
filter: String,
},
setup(props) {
const isLoaded = ref(false);
@@ -48,39 +43,33 @@ export default {
const pageSetting = ref({
sortBy: 'desc',
descending: false,
page: 1,
page: 2,
rowsPerPage: props.not_page ? 0 : 5
// rowsNumber: xx if getting data from a server
});
const rows = reactive([]);
const selected = ref(props.selectedFields && props.selectedFields.length>0?props.selectedFields:[]);
watchEffect(() => {
props.updateSelects(selected);
});
onMounted(async () => {
const url = props.fieldTypes.includes('SPACER')?'api/v1/allfields':'api/v1/appfields';
const res = await api.get(url, {
const res = await api.get('api/v1/appfields', {
params: {
app: props.appId
}
});
let fields = res.data.properties;
console.log(fields);
Object.keys(fields).forEach((key) => {
const fld = fields[key];
if(props.fieldTypes.length===0 || props.fieldTypes.includes(fld.type)){
rows.push({ name: fld.label || fld.code, ...fld });
}else if(props.fieldTypes.includes("lookup") && ("lookup" in fld)){
rows.push({ name: fld.label || fld.code, ...fld });
}
// rows.push({name:fields[key].label,code:fields[key].code,type:fields[key].type});
rows.push({ name: fld.label, ...fld });
});
isLoaded.value = true;
});
watchEffect(()=>{
if (selected.value && selected.value[0] && props.updateSelectFields) {
props.updateSelectFields(selected)
}
});
return {
columns,
rows,

View File

@@ -1,17 +1,20 @@
<template>
<!-- <div class="q-pa-md q-gutter-sm" > -->
<q-dialog :model-value="visible" persistent bordered >
<q-card class="" style="min-width: 40vw; max-width: 80vw; max-height: 95vh;" :style="cardStyle">
<q-card style="min-width: 40vw; max-width: 80vw; max-height: 95vh;" :style="cardStyle">
<q-toolbar class="bg-grey-4">
<q-toolbar-title>{{ name }}</q-toolbar-title>
<q-space></q-space>
<slot name="toolbar"></slot>
<q-btn flat round dense icon="close" @click="CloseDialogue('Cancel')" />
</q-toolbar>
<q-card-section class="q-mt-md" :style="sectionStyle">
<q-card-section>
<!-- <div class="text-h6">{{ name }}</div> -->
</q-card-section>
<q-card-section class="q-pt-none" :style="sectionStyle">
<slot></slot>
</q-card-section>
<q-card-actions align="right" class="text-primary">
<q-card-actions align="right" class="text-primary q-mt-lg">
<q-btn flat label="確定" v-close-popup @click="CloseDialogue('OK')" />
<q-btn flat label="キャンセル" v-close-popup @click="CloseDialogue('Cancel')" />
</q-card-actions>

View File

@@ -1,6 +1,6 @@
<template>
<div class="q-pa-md">
<q-table flat bordered row-key="id" :selection="type" :selected="modelValue" :filter="filter"
<q-table flat bordered row-key="id" :selection="type" :selected="modelValue"
@update:selected="$emit('update:modelValue', $event)" :columns="columns" :rows="rows" />
</div>
</template>
@@ -19,8 +19,7 @@ export default {
reqired: true,
default: () => []
},
modelValue: Array,
filter: String
modelValue: Array
},
emits: [
'update:modelValue'

View File

@@ -3,11 +3,11 @@
<q-tree :nodes="store.eventTree.screens" node-key="eventId" children-key="events" no-connectors
v-model:expanded="store.expandedScreen" :dense="true" :ref="tree">
<template v-slot:header-EVENT="prop">
<div :ref="prop.node.eventId" class="row col items-center no-wrap event-node" @click="onSelected(prop.node)">
<div :ref="prop.node.eventId" class="row col items-center no-wrap event-node">
<q-icon v-if="prop.node.eventId" name="play_circle" :color="prop.node.hasFlow ? 'green' : 'grey'" size="16px"
class="q-mr-sm">
</q-icon>
<div class="no-wrap"
<div class="no-wrap" @click="onSelected(prop.node)"
:class="selectedEvent && prop.node.eventId === selectedEvent.eventId ? 'selected-node' : ''">{{
prop.node.label }}</div>
<q-space></q-space>
@@ -15,21 +15,25 @@
</div>
</template>
<template v-slot:header-CHANGE="prop">
<div class="row col items-start no-wrap event-node">
<div class="no-wrap"
:class="selectedEvent && prop.node.eventId === selectedEvent.eventId ? 'selected-node' : ''"
>{{ prop.node.label }}</div>
<div class="row col items-center no-wrap event-node">
<div class="no-wrap">{{ prop.node.label }}</div>
<q-space></q-space>
<q-icon name="add_circle" color="primary" size="16px" class="q-mr-sm"
@click="addChangeEvent(prop.node)"></q-icon>
</div>
</template>
<template v-slot:header-DELETABLE="prop">
<div class="row col items-start event-node" @click="onSelected(prop.node)">
<q-icon v-if="prop.node.eventId" name="play_circle" :color="prop.node.hasFlow ? 'green' : 'grey'" size="16px" class="q-mr-sm" />
<div class="no-wrap" :class="selectedEvent && prop.node.eventId === selectedEvent.eventId ? 'selected-node' : ''" >{{ prop.node.label }}</div>
<q-space></q-space>
<q-icon name="delete_forever" color="negative" size="16px" @click="deleteEvent(prop.node)"></q-icon>
<div class="row col items-center event-node">
<div class="row col items-center" @click="onSelected(prop.node)">
<q-icon v-if="prop.node.eventId" name="play_circle" :color="prop.node.hasFlow ? 'green' : 'grey'" size="16px"
class="q-mr-sm">
</q-icon>
<div>{{ prop.node.label }}</div>
</div>
<div>
<q-btn class="q-mr-sm delete-btn" flat fab-mini icon="delete_forever" padding="none" color="negative"
@click="deleteEvent(prop.node)"></q-btn>
</div>
</div>
</template>
</q-tree>

View File

@@ -48,8 +48,8 @@
</q-card-section>
</q-card>
</div>
<show-dialog v-model:visible="show" name="フィールド一覧" @close="closeAFBox">
<AppFieldSelectBox v-model:selectedField="selectedField" :selectType="selectType" ref="afBox" :fieldTypes="fieldTypes"/>
<show-dialog v-model:visible="show" name="フィールド一覧" @close="closeFieldDialog" ref="fieldDlg">
<AppFieldSelectBox v-model:selectedField="selectedField" :selectType="selectType" />
</show-dialog>
</template>
@@ -66,8 +66,7 @@ export interface IApp {
export interface IField {
name: string,
code: string,
type: string,
label?:string
type: string
}
export interface IAppFields {
@@ -102,20 +101,15 @@ export default defineComponent({
selectType: {
type: String,
default: 'single'
},
fieldTypes:{
type:Array,
default:()=>[]
}
},
setup(props, { emit }) {
const show = ref(false);
const afBox = ref();
const selectedField = ref<IAppFields>({
app: undefined,
fields: []
});
if (props.modelValue && 'app' in props.modelValue && 'fields' in props.modelValue) {
if (props.modelValue && "app" in props.modelValue && "fields" in props.modelValue) {
selectedField.value = props.modelValue as IAppFields;
}
const store = useFlowEditorStore();
@@ -129,28 +123,17 @@ export default defineComponent({
const removeField = (index: number) => {
selectedField.value.fields.splice(index, 1);
}
const closeAFBox = (val: string) => {
if (val == 'OK') {
console.log(afBox.value);
selectedField.value = afBox.value.selField;
}
};
watchEffect(() => {
emit('update:modelValue', selectedField.value);
});
return {
store,
afBox,
show,
showDg: () => { show.value = true },
selectedField,
clear,
removeField,
closeAFBox,
};
}
});

View File

@@ -18,13 +18,29 @@
</div>
</template>
<script lang="ts">
import { ConditionNode, ConditionTree, Operator, OperatorListItem } from 'app/src/types/Conditions';
import { computed, defineComponent, provide, reactive, ref, watchEffect } from 'vue';
import ConditionEditor from '../ConditionEditor/ConditionEditor.vue';
import { IActionProperty } from 'src/types/ActionTypes';
import { } from 'src/types/';
type Props = {
props?: {
name: string;
modelValue?: {
app: {
id: string;
name: string;
},
fields: {
type: string;
label: string;
code: string;
}[]
}
}
};
export default defineComponent({
@@ -35,7 +51,7 @@ export default defineComponent({
},
props: {
context: {
type: Array<IActionProperty>,
type: Array<Props>,
default: '',
},
displayName: {
@@ -68,29 +84,11 @@ export default defineComponent({
},
operatorList: {
type: Array,
},
inputConfig: {
type: Object,
default: () => ({
left: {
canInput: false,
buttonsConfig: [
{ label: 'フィールド', color: 'primary', type: 'FieldAdd' },
{ label: '変数', color: 'green', type: 'VariableAdd' },
]
},
right: {
canInput: true,
buttonsConfig: [
{ label: '変数', color: 'green', type: 'VariableAdd' },
]
},
})
}
},
setup(props, { emit }) {
const source = props.context.find(element => element.props.name === 'sources')
const source = props.context.find(element => element?.props?.name === 'sources')
if (source) {
if (props.sourceType === 'field') {
@@ -100,8 +98,6 @@ export default defineComponent({
}
}
provide('leftDynamicItemConfig', props.inputConfig.left);
provide('rightDynamicItemConfig', props.inputConfig.right);
provide('Operator', props.operatorList);
const btnDisable = computed(() => {

View File

@@ -16,61 +16,44 @@
</q-card>
</template>
</q-field>
<show-dialog v-model:visible="dgIsShow" name="データマッピング" @close="closeDg" min-width="55vw" min-height="60vh">
<show-dialog v-model:visible="dgIsShow" name="データマッピング" @close="closeDg" min-width="50vw" min-height="60vh">
<div class="">
<div class="q-mx-md">
<div class="row q-col-gutter-x-xs flex-center">
<div class="col-5">
<div class="q-mx-xs">ソース</div>
<div class="q-mx-xs">From</div>
</div>
<div class="col-1">
</div>
<!-- <div class="col-1">
</div> -->
<div class="col-5">
<div class="row justify-between q-mr-md">
<div class="">{{ sourceApp?.name }}</div>
<q-btn outline color="primary" size="xs" label="最新のフィールドを取得する"
@click="() => updateFields(sourceAppId!)" />
<div class="q-mx-xs">To</div>
</div>
<div class="col-1"><q-btn flat round dense icon="add" size="sm" @click="addMappingObject" />
</div>
</div>
<div class="col-1 q-pl-sm">
キー
</div>
</div>
<q-virtual-scroll style="max-height: 60vh;" :items="mappingProps" separator v-slot="{ item, index }">
<!-- <div class="q-my-sm" v-for="(item, index) in mappingProps" :key="item.id"> -->
<div class="row q-pa-sm q-col-gutter-x-md flex-center">
<div class="q-my-sm" v-for="(item, index) in mappingProps" :key="item.id">
<div class="row q-col-gutter-x-xs flex-center">
<div class="col-5">
<ConditionObject :config="config" v-model="item.from" :disabled="item.disabled"
:label="item.disabled ? '「Lookup」によってロックされる' : undefined" />
<ConditionObject v-model="item.from" />
</div>
<div class="col-1">
</div>
<!-- <div class="col-1">
</div> -->
<div class="col-5">
<q-field v-model="item.vName" type="text" outlined dense :disable="item.disabled" >
<!-- <template v-slot:append>
<q-field v-model="item.vName" type="text" outlined dense>
<template v-slot:append>
<q-icon name="search" class="cursor-pointer"
@click="() => { mappingProps[index].to.isDialogVisible = true }" />
</template> -->
</template>
<template v-slot:control>
<div class="self-center full-width no-outline" tabindex="0"
v-if="item.to.app?.name && item.to.fields?.length > 0 && item.to.fields[0].label">
{{ `${item.to.fields[0].label}` }}
<span class="text-red" v-if="item.to.fields[0].required">*</span>
<q-tooltip class="bg-yellow-2 text-black shadow-4" >
<div>アプリ : {{ item.to.app.name }}</div>
<div>フィールドのコード : {{ item.to.fields[0].code }}</div>
<div>フィールドのタイプ : {{ item.to.fields[0].type }}</div>
<div v-if="item.to.fields[0].required">必須項目</div>
<!-- <div>フィールド : {{ item.to.fields[0] }}</div>
<div>フィールド : {{ item.isKey }}</div> -->
</q-tooltip>
{{ `${item.to.app?.name} : ${item.to.fields[0].label}` }}
</div>
</template>
</q-field>
</div>
<div class="col-1">
<q-checkbox size="sm" v-model="item.isKey" :disable="item.disabled" />
<!-- <q-btn flat round dense icon="delete" size="sm" @click="() => deleteMappingObject(index)" /> -->
<q-btn flat round dense icon="delete" size="sm" @click="() => deleteMappingObject(index)" />
</div>
</div>
@@ -82,11 +65,6 @@
</FieldSelect>
<AppFieldSelectBox v-else v-model:selectedField="mappingProps[index].to" />
</show-dialog>
<!-- </div> -->
</q-virtual-scroll>
<div class="q-mt-lg q-ml-md row ">
<q-checkbox size="sm" v-model="createWithNull" label="キーが存在しない場合は新規に作成され、存在する場合はデータが更新されます。" />
</div>
</div>
</show-dialog>
@@ -95,15 +73,14 @@
<script lang="ts">
import { v4 as uuidv4 } from 'uuid';
import { computed, defineComponent, watch, isRef, reactive, ref, watchEffect } from 'vue';
import { computed, defineComponent, reactive, ref, watchEffect } from 'vue';
import ConditionObject from '../ConditionEditor/ConditionObject.vue';
import ShowDialog from '../ShowDialog.vue';
import AppFieldSelectBox from '../AppFieldSelectBox.vue';
import FieldSelect from '../FieldSelect.vue';
import { IApp, IField } from './AppFieldSelect.vue';
import { api } from 'boot/axios';
import IAppFields from './AppFieldSelect.vue';
type ContextProps = {
type Props = {
props?: {
name: string;
modelValue?: {
@@ -114,25 +91,15 @@ type ContextProps = {
}
}
};
type CurrentModelValueType = {
data: MappingValueType[];
createWithNull: boolean;
}
type MappingValueType = {
type ValueType = {
id: string;
from: { sharedText?: string };
to: {
app?: IApp,
fields: IField[],
from: object;
to: typeof IAppFields & {
isDialogVisible: boolean;
};
isKey: boolean;
disabled: boolean;
}
const blackListLabelName = ['レコード番号', '作業者', '更新者', '更新日時', '作成日時', '作成者']
const defaultMappingProp = () => ({ id: uuidv4(), to: { app: {}, fields: [], isDialogVisible: false } });
export default defineComponent({
name: 'DataMapping',
@@ -145,7 +112,7 @@ export default defineComponent({
},
props: {
context: {
type: Array<ContextProps>,
type: Array<Props>,
default: '',
},
displayName: {
@@ -157,7 +124,7 @@ export default defineComponent({
default: '',
},
modelValue: {
type: Object as () => CurrentModelValueType,
type: Object as () => ValueType[],
},
placeholder: {
type: String,
@@ -166,94 +133,55 @@ export default defineComponent({
onlySourceSelect: {
type: Boolean,
default: false
},
fieldTypes:{
type:Array,
default:()=>[]
},
}
},
setup(props, { emit }) {
const closeDg = () => {
emit('update:modelValue', mappingProps
);
}
const closeToDg = () => {
emit('update:modelValue', mappingProps
);
}
const mappingProps: ValueType[] = props.modelValue
? props.modelValue
: reactive([defaultMappingProp()]);
const deleteMappingObject = (index: number) => mappingProps.length === 1
? mappingProps.splice(0, mappingProps.length, defaultMappingProp())
: mappingProps.splice(index, 1);
const mappingObjectsInputDisplay = computed(() =>
mappingProps ?
mappingProps
.filter(item => item.from?.name && item.to.fields?.length > 0)
.map(item => {
const name = typeof item.from?.name === 'string'
? item.from.name
: item.from?.name.name;
return `[${name}] - (${item.to.app?.name} : ${item.to.fields[0].label})`;
})
: []
);
const source = props.context.find(element => element?.props?.name === 'sources')
const sourceApp = computed(() => source?.props?.modelValue?.app);
const sourceAppId = computed(() => sourceApp.value?.id);
const closeDg = () => {
emit('update:modelValue', { data: mappingProps.value, createWithNull: createWithNull.value });
}
const closeToDg = () => {
emit('update:modelValue', { data: mappingProps.value, createWithNull: createWithNull.value });
}
const mappingProps = ref(props.modelValue?.data ?? []);
const createWithNull = ref(props.modelValue?.createWithNull ?? false)
// 外部ソースコンポーネントの appid をリッスンし、変更されたときに現在のコンポーネントを更新します
watch(() => sourceAppId.value, async (newId,) => {
if (!newId) return;
updateFields(newId)
})
const updateFields = async (sourceAppId: string) => {
const ktAppFields = await api.get('api/v1/appfields', {
params: {
app: sourceAppId
}
}).then(res => {
return Object.values(res.data.properties)
// kintoneのデフォルトの非表示フィールドフィルタリング
.filter(f => !blackListLabelName.find(label => f.label === label))
.map(f => ({ name: f.label, objectType: 'field', ...f }))
.map(f => {
// 更新前の値を求める
const beforeData = mappingProps.value.find(m => m.to.fields[0].code === f.code)
return {
id: uuidv4(),
from: beforeData?.from ?? {}, // 以前のデータを入力します
to: {
app: sourceApp.value,
fields: [f],
isDialogVisible: false
},
isKey: beforeData?.isKey ?? false, // 以前のデータを入力します
disabled: false
}
})
})
// 「ルックアップ」によってロックされているフィールドを検索する
const lookupFixedField = ktAppFields
.filter(field => field.to.fields[0].lookup !== undefined)
.flatMap(field => field.to.fields[0].lookup.fieldMappings.map((m) => m.field))
// 「ルックアップ」でロックされたビューコンポーネントを非対話型に設定します
if (lookupFixedField.length > 0) {
ktAppFields.filter(f => lookupFixedField.includes(f.to.fields[0].code)).forEach(f => f.disabled = true)
}
mappingProps.value = ktAppFields
}
const mappingObjectsInputDisplay = computed(() =>
(mappingProps.value && Array.isArray(mappingProps.value)) ?
mappingProps.value
.filter(item => item.from?.sharedText && item.to.fields?.length > 0)
.map(item => {
return `field(${item.to.app?.id},${item.to.fields[0].label}) = ${item.from.sharedText} `;
})
: []
);
const btnDisable = computed(() => props.onlySourceSelect ? !(source?.props?.modelValue?.app?.id) : false);
watchEffect(() => {
emit('update:modelValue', { data: mappingProps.value, createWithNull: createWithNull.value });
});
//集計処理方法
watchEffect(() => {
emit('update:modelValue', mappingProps);
});
return {
uuidv4,
dgIsShow: ref(false),
@@ -261,24 +189,14 @@ export default defineComponent({
toDgIsShow: ref(false),
closeToDg,
mappingProps,
createWithNull,
updateFields,
// addMappingObject: () => mappingProps.push(defaultMappingProp()),
// deleteMappingObject,
addMappingObject: () => mappingProps.push(defaultMappingProp()),
deleteMappingObject,
mappingObjectsInputDisplay,
sourceApp,
sourceAppId,
btnDisable,
config: {
canInput: false,
buttonsConfig: [
{ label: 'フィールド', color: 'primary', type: 'FieldAdd' },
{ label: '変数', color: 'green', type: 'VariableAdd', editable: false },
]
}
btnDisable
};
},
});
</script>
<style lang="scss"></style>

View File

@@ -40,7 +40,7 @@
<div class="col-5">
<ConditionObject v-model="item.field" />
</div>
<div class="col-2 q-pa-sm">
<div class="col-2">
<q-select v-model="item.logicalOperator" :options="logicalOperators" outlined dense></q-select>
</div>
<div class="col-4">
@@ -78,8 +78,14 @@ type Props = {
type ProcessingObjectType = {
field?: {
sharedText: string;
objectType: 'field';
name: string | {
name: string;
};
objectType: string;
type: string;
code: string;
label: string;
noLabel: boolean;
};
logicalOperator?: string;
vName?: string;
@@ -133,60 +139,44 @@ export default defineComponent({
}
return null;
}));
provide('sourceApp', computed(() => source.props?.modelValue?.app?.id));
}
const actionName = props.context.find(element => element?.props?.name === 'displayName')
const processingProps: ValueType = props.modelValue && props.modelValue.vars
? reactive(props.modelValue)
? props.modelValue
: reactive({
name: '',
actionName: actionName?.props?.modelValue as string,
displayName: '結果(戻り値)',
vars: [
{
id: uuidv4(),
field:{
objectType:'field',
sharedText:''
}
}]
vars: [{ id: uuidv4() }]
});
const closeDg = () => {
emit('update:modelValue', processingProps);
emit('update:modelValue', processingProps
);
}
const processingObjects = processingProps.vars;
const deleteProcessingObject = (index: number) => {
if(processingObjects.length >0){
processingObjects.splice(index, 1);
}
if(processingObjects.length===0){
addProcessingObject();
}
}
const deleteProcessingObject = (index: number) => processingObjects.length === 1
? processingObjects.splice(0, processingObjects.length, { id: uuidv4() })
: processingObjects.splice(index, 1);
const processingObjectsInputDisplay = computed(() =>
processingObjects ?
processingObjects
.filter(item => item.field && item.logicalOperator && item.vName)
.map(item => {
return`var(${processingProps.name}.${item.vName}) = ${item.field?.sharedText}`
const name = typeof item.field?.name === 'string'
? item.field.name
: item.field?.name.name;
return item.logicalOperator.operator!==''?
`${processingProps.name}.${item.vName} = ${item.logicalOperator.operator}(${name})`
:`${processingProps.name}.${item.vName} = ${name}`
})
: []
);
const addProcessingObject=()=>{
processingObjects.push({
id: uuidv4(),
field:{
objectType:'field',
sharedText:''
}
});
}
//集計処理方法
const logicalOperators = ref([
{
@@ -228,7 +218,7 @@ export default defineComponent({
closeDg,
processingObjects,
processingProps,
addProcessingObject,
addProcessingObject: () => processingObjects.push({ id: uuidv4() }),
deleteProcessingObject,
logicalOperators,
processingObjectsInputDisplay,

View File

@@ -61,12 +61,12 @@ export default defineComponent({
if(store.eventTree.findEventById(addEventId)){
return;
}
customEvents.events.push({
eventId: addEventId,
label: displayName,
parentId: customButtonId,
header: 'DELETABLE'
});
customEvents.events.push(
new kintoneEvent(
displayName,
addEventId,
customButtonId)
);
}
}

View File

@@ -7,6 +7,9 @@
{{ selectedField.name }}
</q-chip>
</template>
<!-- <template v-slot:hint v-if="isSelected">
<div> 項目コード<q-chip size="sm" outline color="secondary" text-color="white">{{selectedField.code}}</q-chip></div>
</template> -->
<template v-slot:hint v-if="!isSelected">
{{ placeholder }}
</template>
@@ -15,15 +18,8 @@
<q-icon name="search" class="cursor-pointer" color="primary" @click="showDg" />
</template>
</q-field>
<show-dialog v-model:visible="show" name="フィールド一覧" @close="closeDg" min-width="400px">
<template v-slot:toolbar>
<q-input dense debounce="300" v-model="filter" placeholder="検索" clearable>
<template v-slot:before>
<q-icon name="search" />
</template>
</q-input>
</template>
<field-select ref="appDg" name="フィールド" :type="selectType" :appId="store.appInfo?.appId" :fieldTypes="fieldTypes" :filter="filter"></field-select>
<show-dialog v-model:visible="show" name="フィールド一覧" @close="closeDg" widht="400px">
<field-select ref="appDg" name="フィールド" type="single" :appId="store.appInfo?.appId"></field-select>
</show-dialog>
</div>
</template>
@@ -58,14 +54,6 @@ export default defineComponent({
type: String,
default: '',
},
selectType:{
type:String,
default:'single'
},
fieldTypes:{
type:Array,
default:()=>[]
},
hint: {
type: String,
default: '',
@@ -106,8 +94,7 @@ export default defineComponent({
showDg,
closeDg,
selectedField,
isSelected,
filter:ref(''),
isSelected
};
}
});

View File

@@ -33,10 +33,6 @@ export default defineComponent({
type: String,
default: '',
},
fieldTypes:{
type:Array,
default:()=>[]
},
hint: {
type: String,
default: '',

View File

@@ -29,7 +29,7 @@ export default defineComponent({
default:'',
},
modelValue: {
type: [Array,String],
type: Object,
default: null,
},
},

View File

@@ -113,19 +113,19 @@ export const useFlowEditorStore = defineStore('flowEditor', {
},
async deleteEvent(event: IKintoneEvent) {
deleteEvent(event: IKintoneEvent) {
const store = useFlowEditorStore();
if (event.flowData) {
const flow = event.flowData;
if (flow.id !== '') {
await flowCtrl.DeleteFlow(flow.id)
if (flow.id === '') {
return;
}
flowCtrl.DeleteFlow(flow.id)
eventTree.deleteEvent(event, store);
if(this.flows){
this.flows = this.flows.filter((f) => f.id !== flow.id);
}
}
eventTree.deleteEvent(event, store);
}
else {
} else {
eventTree.deleteEvent(event, store);
}
},

View File

@@ -1,50 +0,0 @@
export interface IApp {
id: string,
name: string
}
export interface IField {
label?:string;
code:string;
type?:string;
required?:boolean;
options?:string;
}
/**
* 選択されたフィールド
*/
export interface ISelectedField extends IField{
objectType:'Field'|'RefField';
}
export interface IAppFields {
app?: IApp,
name?:string;
fields: IField[]
}
/**
* 条件式の入力ボタンの属性定義
*/
export interface IButtonConfig{
label: string;
color: string;
type: 'FieldAdd' | 'VariableAdd' | 'FunctionAdd';
};
/**
* 条件入力項目の属性
*/
export interface IDynamicInputConfig{
canInput: boolean;
buttonsConfig: IButtonConfig[];
}
/**
* 条件式入力項目の属性
*/
export interface ICoditionConfig{
left:IDynamicInputConfig,
right:IDynamicInputConfig
}

View File

@@ -201,12 +201,11 @@ export class ConditionTree {
} else {
const condNode=node as ConditionNode;
if (condNode.object && condNode.operator ) {
// let value=condNode.value;
// if(value && typeof value ==='object' && ('label' in value)){
// value =condNode.value.label;
// }
return `${condNode.object.sharedText} ${typeof condNode.operator === 'object' ? condNode.operator.label : condNode.operator} ${condNode.value.sharedText}`;
// return `${typeof condNode.object.name === 'object' ? condNode.object.name.name : condNode.object.name} ${typeof condNode.operator === 'object' ? condNode.operator.label : condNode.operator} '${value}'`;
let value=condNode.value;
if(value && typeof value ==='object' && ('label' in value)){
value =condNode.value.label;
}
return `${typeof condNode.object.name === 'object' ? condNode.object.name.name : condNode.object.name} ${typeof condNode.operator === 'object' ? condNode.operator.label : condNode.operator} '${value}'`;
} else {
return '';
}
@@ -238,10 +237,11 @@ export class ConditionTree {
} else {
const condNode=node as ConditionNode;
if (condNode.object && condNode.operator ) {
if (!condNode.object.code || !condNode.value.sharedText){
return '';
let value=condNode.value;
if(value && typeof value ==='object' && ('label' in value)){
value =condNode.value.label;
}
return `${condNode.object.code} ${typeof condNode.operator === 'object' ? condNode.operator.value : condNode.operator} "${condNode.value.sharedText}"`;
return `${condNode.object.code} ${typeof condNode.operator === 'object' ? condNode.operator.value : condNode.operator} "${value}"`;
} else {
return '';
}

View File

@@ -95,7 +95,10 @@ export class KintoneEventManager {
const lastIndex = eventId.lastIndexOf('.');
const groupId = eventId.substring(0, lastIndex);
const eventNode = this.findEventById(groupId);
if (eventNode && (eventNode.header === 'EVENTGROUP' || eventNode.header === 'CHANGE')) {
if (
eventNode &&
(eventNode.header === 'EVENTGROUP' || eventNode.header === 'CHANGE')
) {
const groupEvent = eventNode as kintoneEventGroup;
const newEvent = {

6
node_modules/.package-lock.json generated vendored
View File

@@ -1,6 +0,0 @@
{
"name": "App Builder for kintone",
"lockfileVersion": 3,
"requires": true,
"packages": {}
}

15
node_modules/.yarn-integrity generated vendored
View File

@@ -1,15 +0,0 @@
{
"systemParams": "win32-x64-108",
"modulesFolders": [
"node_modules"
],
"flags": [],
"linkedModules": [
"@quasar\\quasar-ui-qactivity",
"docs"
],
"topLevelPatterns": [],
"lockfileEntries": {},
"files": [],
"artifacts": {}
}

View File

@@ -8,8 +8,7 @@
"name": "kintone-addins",
"version": "0.0.0",
"dependencies": {
"jquery": "^3.7.1",
"yarn": "^1.22.22"
"jquery": "^3.7.1"
},
"devDependencies": {
"@types/jquery": "^3.5.24",
@@ -796,19 +795,6 @@
"optional": true
}
}
},
"node_modules/yarn": {
"version": "1.22.22",
"resolved": "https://registry.npmjs.org/yarn/-/yarn-1.22.22.tgz",
"integrity": "sha512-prL3kGtyG7o9Z9Sv8IPfBNrWTDmXB4Qbes8A9rEzt6wkJV8mUvoirjU0Mp3GGAU06Y0XQyA3/2/RQFVuK7MTfg==",
"hasInstallScript": true,
"bin": {
"yarn": "bin/yarn.js",
"yarnpkg": "bin/yarn.js"
},
"engines": {
"node": ">=4.0.0"
}
}
}
}

View File

@@ -8,16 +8,14 @@
"watch": "vite build --watch --mode dev",
"server": "vite dev --mode dev",
"ngrok": "ngrok http 4173",
"build": "run-s b:production copyjs:windows copycss:windows",
"build:dev": "run-s b:dev copyjs:windows copycss:windows",
"build:linux": "run-s b:production copyjs:linux copycss:linux",
"build": "run-s b:production copy:windows",
"build:dev": "run-s b:dev copy:windows",
"build:linux": "run-s b:production copy:linux",
"build:linux-dev": "run-s b:dev copy:linux",
"b:production": "tsc & vite build --mode production",
"b:dev": "tsc & vite build --mode dev",
"copyjs:windows": "xcopy dist\\*.js ..\\..\\backend\\Temp\\ /E /I /Y",
"copyjs:linux": "cp -ur dist/*.js ../../backend/Temp",
"copycss:windows": "xcopy dist\\*.css ..\\..\\backend\\Temp\\ /E /I /Y",
"copycss:linux": "cp -ur dist/*.css ../../backend/Temp"
"b:production": "vite build --mode production",
"b:dev": "vite build --mode dev",
"copy:windows": "xcopy dist\\*.js ..\\..\\backend\\Temp\\ /E /I /Y",
"copy:linux": "cp -ur dist/*.js ../../backend/Temp"
},
"devDependencies": {
"@types/jquery": "^3.5.24",
@@ -26,13 +24,10 @@
"sass": "^1.69.5",
"typescript": "^5.0.2",
"vite": "^4.4.5",
"vite-plugin-checker": "^0.6.4"
"vite-plugin-checker": "^0.6.4",
"vite-plugin-lib-inject-css": "^2.1.1"
},
"dependencies": {
"@kintone/rest-api-client": "^5.5.2",
"@popperjs/core": "^2.11.8",
"@types/bootstrap": "^5.2.10",
"bootstrap": "^5.3.3",
"jquery": "^3.7.1"
}
}

View File

@@ -1 +0,0 @@
@import 'bootstrap/scss/bootstrap';

View File

@@ -1,272 +0,0 @@
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();

View File

@@ -0,0 +1,24 @@
.alc-button-normal {
display: inline-block;
box-sizing: border-box;
padding: 0 16px;
margin-left: 16px;
margin-top: 8px;
min-width: 100px;
outline: none;
border: 1px solid #e3e7e8;
background-color: #f7f9fa;
box-shadow: 1px 1px 1px #fff inset;
color: #3498db;
text-align: center;
line-height: 32px;
}
.alc-button-normal:hover {
background-color: #c8d6dd;
box-shadow: none;
cursor: pointer;
}
.alc-button-normal:active {
color: #f7f9fa;
background-color: #54b8eb;
}

View File

@@ -1,7 +1,8 @@
import { actionAddins } from ".";
import { IField, IAction,IActionResult, IActionNode, IActionProperty, IContext, IVarName } from "../types/ActionTypes";
import { IField, IAction,IActionResult, IActionNode, IActionProperty, IContext } from "../types/ActionTypes";
import { Formatter } from "../util/format";
import "./auto-numbering.css";
declare global {
interface Window { $format: any; }
@@ -13,7 +14,7 @@ interface IAutoNumberingProps{
format:string;
prefix:string;
suffix:string;
verName:IVarName;
verName:string;
}
export class AutoNumbering implements IAction{
@@ -29,7 +30,7 @@ export class AutoNumbering implements IAction{
format:'',
prefix:'',
suffix:'',
verName:{name:''}
verName:''
}
globalThis.window.$format=this.format;
this.register();
@@ -56,8 +57,8 @@ export class AutoNumbering implements IAction{
const docNum = await this.createNumber(this.props);
record[this.props.field.code].value=docNum;
//変数設定
if(this.props.verName && this.props.verName.name!==''){
context.variables[this.props.verName.name]=docNum;
if(this.props.verName){
context.variables[this.props.verName]=docNum;
}
result= {
canNext:true,

View File

@@ -1,24 +0,0 @@
.alc-button-normal {
display: inline-block;
box-sizing: border-box;
padding: 0 16px;
margin-left: 16px;
margin-top: 8px;
min-width: 100px;
outline: none;
border: 1px solid #e3e7e8;
background-color: #f7f9fa;
box-shadow: 1px 1px 1px #fff inset;
color: #3498db;
text-align: center;
line-height: 32px;
}
.alc-button-normal:hover {
background-color: #c8d6dd;
box-shadow: none;
cursor: pointer;
}
.alc-button-normal:active {
color: #f7f9fa;
background-color: #54b8eb;
}

View File

@@ -10,18 +10,12 @@ import "./button-add.css";
interface IButtonAddProps {
//ボタン表示名
buttonName: string;
space?:ISpace;
//配置位置
position: string;
//イベント名
eventName:string
}
interface ISpace{
type:string,
elementId:string
}
export class ButtonAddAction implements IAction {
name: string;
actionProps: IActionProperty[];
@@ -55,19 +49,20 @@ export class ButtonAddAction implements IAction {
}
this.props = actionNode.ActionValue as IButtonAddProps;
//ボタンを配置する
let buttonSpace;
if(this.props.space && this.props.space.elementId){
buttonSpace = kintone.app.record.getSpaceElement(this.props.space.elementId);
}else{
buttonSpace = kintone.app.record.getHeaderMenuSpaceElement();
const menuSpace = kintone.app.record.getHeaderMenuSpaceElement();
if(!menuSpace) return result;
if($("style#alc-button-add").length===0){
const css=`
`;
const style = $("<style id='alc-button-add'>/<style>");
style.text(css);
$("head").append(style);
}
if(!buttonSpace) return result;
const button =$(`<button id='${this.props.eventName}' class='alc-button-normal' >${this.props.buttonName}</button>`);
if(this.props.position==="一番左に追加する"){
$(buttonSpace).prepend(button);
$(menuSpace).prepend(button);
}else{
$(buttonSpace).append(button);
$(menuSpace).append(button);
}
const clickEventName = `${event.type}.customButtonClick.${this.props.eventName}`;
button.on("click",()=>{

View File

@@ -1,13 +1,13 @@
import { actionAddins } from ".";
import { IAction,IActionResult, IActionNode, IActionProperty, IContext, IVarName } from "../types/ActionTypes";
import { IAction,IActionResult, IActionNode, IActionProperty, IContext } from "../types/ActionTypes";
import { ConditionTree } from '../types/Conditions';
/**
* アクションの属性定義
*/
interface ICondition{
condition:string;
verName:IVarName;
verName:string;
}
/**
* 条件分岐アクション
@@ -21,7 +21,7 @@ export class ConditionAction implements IAction{
this.actionProps=[];
this.props={
condition:'',
verName:{name:''}
verName:''
}
//アクションを登録する
this.register();
@@ -58,8 +58,8 @@ export class ConditionAction implements IAction{
result:'いいえ'
}
}
if(this.props.verName && this.props.verName.name!==''){
context.variables[this.props.verName.name]=result.result;
if(this.props.verName){
context.variables[this.props.verName]=result.result;
}
return result;
}catch(error){

View File

@@ -1,75 +0,0 @@
import { actionAddins } from ".";
import { IAction,IActionResult, IActionNode, IActionProperty, IField} from "../types/ActionTypes";
/**
* アクションの属性定義
*/
interface IStrCountCheckProps{
field:IField;//チェックするフィールドの対象
message:string;//エラーメッセージ
maxLength:number;//
}
/**
* 正規表現チェックアクション
*/
export class StrCountCheckAciton implements IAction{
name: string;
actionProps: IActionProperty[];
props:IStrCountCheckProps;
constructor(){
this.name="文字数チェック";
this.actionProps=[];
this.props={
field:{code:''},
message:'',
maxLength:0
}
//アクションを登録する
this.register();
}
/**
* アクションの実行を呼び出す
* @param actionNode
* @param event
* @returns
*/
async process(actionNode:IActionNode,event:any):Promise<IActionResult> {
let result={
canNext:true,
result:false
};
try{
//属性設定を取得する
this.actionProps=actionNode.actionProps;
if (!('field' in actionNode.ActionValue) && !('message' in actionNode.ActionValue) && !('strExpression'in actionNode.ActionValue)) {
return result
}
this.props = actionNode.ActionValue as IStrCountCheckProps;
//条件式の計算結果を取得
const record = event.record;
const value = record[this.props.field.code].value;
const maxLength = this.props.maxLength;
if(value === undefined || value === '' ){
return result;
}else if(maxLength < value.length){
record[this.props.field.code].error = this.props.message;
}else{
result= {
canNext:true,
result:true
}
}
return result;
}catch(error){
event.error=error;
console.error(error);
result.canNext=false;
return result;
}
};
register(): void {
actionAddins[this.name]=this;
}
}
new StrCountCheckAciton();

View File

@@ -1,74 +0,0 @@
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();

View File

@@ -0,0 +1,182 @@
import {
IAction,
IActionResult,
IActionNode,
IActionProperty,
IContext,
} from "../types/ActionTypes";
import { actionAddins } from ".";
export type IApp = {
id: string;
name: string;
};
export type IField = {
name: string;
code: string;
type: string;
};
export type IAppFields = {
app?: IApp;
fields: IField[];
};
type ValueType = {
id: string;
from: {
objectType: "variable" | "field";
name: { name: string };
code: string;
};
to: IAppFields & {
isDialogVisible: boolean;
};
};
type Props = { app: IApp; field: ValueType[] };
export class DataMappingAction implements IAction {
name: string;
actionProps: IActionProperty[];
dataMappingProps: Props;
constructor() {
this.name = "DataMapping";
this.actionProps = [];
this.dataMappingProps = {} as Props;
this.register();
}
async process(
prop: IActionNode,
event: any,
context: IContext
): Promise<IActionResult> {
this.initActionProps(prop);
this.initTypedActionProps();
let result = {
canNext: true,
result: "",
} as IActionResult;
try {
for (const item of this.dataMappingProps.field) {
if (item.from.objectType === "variable") {
if (
item.from.name.name &&
item.to.app &&
item.to.fields &&
item.to.fields.length > 0
) {
const value = getValueByPath(
context.variables,
item.from.name.name
);
if (value) {
await kintone.api(
kintone.api.url("/k/v1/record.json", true),
"POST",
{
app: item.to.app.id,
record: {
[item.to.fields[0].code]: {
value: value,
},
},
}
);
}
}
} else if (item.from.objectType === "field") {
if (
item.from.code &&
item.to.app &&
item.to.fields &&
item.to.fields.length > 0
) {
const value = await selectData(
item.to.app.id,
item.to.fields[0].code
);
if (value && value.type === context.record[item.from.code].type) {
await kintone.api(
kintone.api.url("/k/v1/records.json", true),
"POST",
{
app: item.to.app.id,
records: value.value.map((v) => ({
[item.to.fields[0].code]: {
value: v,
},
})),
}
);
}
}
}
}
} catch (error) {
console.error("DataMappingAction error", error);
result.canNext = false;
}
console.log("dataMappingProps", this.dataMappingProps);
return result;
}
private initActionProps(nodes: IActionNode) {
this.actionProps = nodes.actionProps;
}
private initTypedActionProps() {
for (const action of this.actionProps) {
if (action.component === "DataMapping") {
this.dataMappingProps.field = action.props.modelValue as ValueType[];
} else if (action.component === "AppSelect") {
this.dataMappingProps.app = action.props.modelValue.app as IApp;
}
}
}
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;
});
};

View File

@@ -6,166 +6,130 @@ 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 IDataProcessingProps {
displayName: string;
sources: Sources;
condition: string;
verName?: VerName;
}
interface Condition {
queryString: string;
}
interface VerName {
type DataProcessingProps = {
app: {
id: string;
name: string;
actionName: string;
displayName: string;
vars: Var[];
}
};
conditionsQuery: string;
propcessing: {
varRootName: string;
fields: Field[];
};
};
interface Var {
id: string;
field: FieldForm;
logicalOperator: CalcOperator;
vName: string;
}
interface CalcOperator {
operator: Operator;
label: string;
}
interface Sources {
app: App;
fields: FieldForm[];
}
interface App {
id: string;
name?: string;
}
type Result = {
[key: string]: {
type Field = {
name: string;
code: string;
type: string;
value: any[];
};
};
type AnyObject = {
[key: string]: any;
varName: string;
operator: string;
};
export class DataProcessingAction implements IAction {
name: string;
actionProps: IActionProperty[];
props: IDataProcessingProps ;
dataProcessingProps: DataProcessingProps | null;
constructor() {
this.name = "データ処理";
this.actionProps = [];
this.props = {
displayName:'',
condition:'',
sources:{
app:{
id:""
},
fields:[]
},
};
this.dataProcessingProps = null;
this.register();
}
async process(
nodes: IActionNode,
event: any,
context: IContext
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;
this.initActionProps(nodes);
this.initTypedActionProps();
let result = {
canNext: true,
result: "",
} as IActionResult;
try {
if (!this.props) {
if (!this.dataProcessingProps) {
return result;
}
const query = this.getQuery(condition.queryString,context.variables);
const data = await this.selectData(query);
const data = await selectData(this.dataProcessingProps.conditionsQuery);
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);
context.variables[this.dataProcessingProps.propcessing.varRootName] =
this.dataProcessingProps.propcessing.fields.reduce((acc, f) => {
const v = calc(f, data);
if (v) {
acc[f.varName] = calc(f, data);
}
return acc;
}, {} as Var);
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;
console.error(error);
event.error=error;
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}が見つかりません`);
register(): void {
actionAddins[this.name] = this;
}
}
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
private initActionProps(nodes: IActionNode) {
this.actionProps = nodes.actionProps;
}
private initTypedActionProps() {
this.dataProcessingProps = {
app: {
id: "",
name: "",
},
conditionsQuery: "",
propcessing: {
varRootName: "",
fields: [],
},
};
for (const action of this.actionProps) {
if (action.component === "AppFieldSelect") {
this.dataProcessingProps.app.id = action.props.modelValue.app.id;
this.dataProcessingProps.app.name = action.props.modelValue.app.name;
} else if (action.component === "DataProcessing") {
this.dataProcessingProps.propcessing.varRootName =
action.props.modelValue.name;
for (const f of action.props.modelValue.vars) {
this.dataProcessingProps.propcessing.fields.push({
name: f.field.name,
code: f.field.code,
type: f.field.type,
varName: f.vName,
operator: f.logicalOperator.operator,
});
}
} else if (action.component === "ConditionInput") {
this.dataProcessingProps.conditionsQuery = JSON.parse(
action.props.modelValue
).queryString;
}
}
}
}
new DataProcessingAction();
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.forEach((element) => {
resp.records.forEach((element) => {
for (const [key, value] of Object.entries(element)) {
if (!result[key]) {
result[key] = { type: value.type, value: [] };
@@ -174,12 +138,143 @@ export class DataProcessingAction implements IAction {
}
});
return result;
});
};
register(): void {
actionAddins[this.name] = this;
}
type Resp = { records: RespRecordType[] };
type RespRecordType = {
[key: string]: {
type: string;
value: any;
};
};
type Result = {
[key: string]: {
type: string;
value: any[];
};
};
type Var = {
[key: string]: any;
};
const ERROR_TYPE = "ERROR_TYPE";
const calc = (field: Field, result: Result) => {
const type = typeCheck(field.type);
if (!type) {
return ERROR_TYPE;
}
new DataProcessingAction();
const fun =
calcFunc[`${type}_${Operator[field.operator as keyof typeof Operator]}`];
if (!fun) {
return ERROR_TYPE;
}
const values = result[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];
}
};

View File

@@ -1,334 +0,0 @@
import {
IAction,
IActionResult,
IActionNode,
IActionProperty,
IContext,
} from "../types/ActionTypes";
import { actionAddins } from ".";
import { KintoneRestAPIClient } from "@kintone/rest-api-client";
import { Lookup } from "@kintone/rest-api-client/lib/src/KintoneFields/types/property";
import { FieldForm, FieldType } from "../types/FieldLayout";
interface Props {
displayName: string;
sources: Sources;
dataMapping: DataMapping;
}
interface DataMapping {
data: Mapping[];
createWithNull: boolean;
}
interface Mapping {
id: string;
from: From;
to: To;
isKey: boolean;
}
interface To {
app: App;
fields:FieldForm[];
isDialogVisible: boolean;
}
interface From {
sharedText: string;
id: string;
objectType: 'variable'|'field'|'text';
}
interface IVar extends From{
name:{
name:string;
}
}
interface IFromField extends From,FieldForm{
}
interface Sources {
app: App;
}
interface App {
id: string;
name: string;
description: string;
createdate: string;
}
export class DataUpdateAction 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(context);
let result = {
canNext: true,
result: "",
} as IActionResult;
try {
const lookupFixedFieldCodes = await getLookupFixedFieldCodes(
this.dataMappingProps.sources.app.id
);
// createWithNull が有効な場合は、4 番目のパラメーターを true にして doUpdate 関数ブランチを実行します。
if (this.dataMappingProps.dataMapping.createWithNull) {
await doUpdate(
this.dataMappingProps.dataMapping.data,
this.dataMappingProps.sources.app.id,
context,
true, // キーがない場合、またはキーでターゲットが見つからない場合に、マッピング条件によって新しいレコードを作成するかどうかを決定するために使用されます。
lookupFixedFieldCodes
);
} else if (
// キーがないと更新対象を取得できないため、この時点でのみ更新が行われます。 doUpdate 関数の 4 番目のパラメーターは false です。
this.dataMappingProps.dataMapping.data
.map((m) => m.isKey)
.find((isKey) => isKey === true)
) {
await doUpdate(
this.dataMappingProps.dataMapping.data,
this.dataMappingProps.sources.app.id,
context,
false,
lookupFixedFieldCodes
);
} else {
await doCreate(
this.dataMappingProps.dataMapping.data,
this.dataMappingProps.sources.app.id,
context,
lookupFixedFieldCodes
);
}
} catch (error) {
console.error("DataMappingAction error", error);
result.canNext = false;
}
console.log("dataMappingProps", this.dataMappingProps);
return result;
}
register(): void {
actionAddins[this.name] = this;
}
}
new DataUpdateAction();
const getContextVarByPath = (obj: any, path: string) => {
return path.split(".").reduce((o, k) => (o || {})[k], obj);
};
interface UpdateRecord {
id: string;
record: {
[key: string]: {
value: any;
};
};
}
const client = new KintoneRestAPIClient();
const getFromValue=(item:Mapping,context:IContext)=>{
if (item.from.objectType === "variable") {
const rfrom =item.from as IVar;
return getContextVarByPath(context.variables,rfrom.name.name);
}else if(item.from.objectType === "field"){
const field = item.from as IFromField;
return context.record[field.code].value;
}
else {
return item.from.sharedText;
}
}
const doUpdate = async (
mappingData: Mapping[],
appId: string,
context: IContext,
needCreate: boolean,
lookupFixedFieldCodes: string[]
) => {
const targetField = await findUpdateField(mappingData, appId, context);
console.log(targetField);
if (targetField.records.length === 0 && needCreate) {
await doCreate(mappingData, appId, context, lookupFixedFieldCodes);
} else {
// マッピングデータを単純なオブジェクトに処理し、ソース値が変数の場合は変数を置き換えます。
const mappingRules = mappingData
.filter(
(m) =>
Object.keys(m.from).length > 0 &&
!lookupFixedFieldCodes.includes(m.to.fields[0].code)
)
.map((m) => {
if (m.from.objectType === "variable") {
const rfrom =m.from as IVar;
return {
value: getContextVarByPath(context.variables,rfrom.name.name),
code: m.to.fields[0].code,
};
}else if(m.from.objectType === "field"){
const field = m.from as IFromField;
return {
value: context.record[field.code].value,
code: m.to.fields[0].code,
}
}
else {
return {
value: m.from.sharedText,
code: m.to.fields[0].code,
};
}
});
const updateRecords: UpdateRecord[] = targetField.records.map(
(targetRecord) => {
const updateRecord: UpdateRecord["record"] = {};
// マッピング内のルールにヒットしたフィールドのみが更新されます。
for (const mapping of mappingRules) {
if (targetRecord[mapping.code]) {
updateRecord[mapping.code] = {
value: mapping.value,
};
}
}
return {
id: targetRecord.$id.value as string,
record: updateRecord,
};
}
);
console.log(updateRecords);
await client.record.updateRecords({
app: appId,
records: updateRecords,
});
}
};
const makeQuery=(field:FieldForm,key:any)=>{
if(field.type===FieldType.NUMBER || field.type===FieldType.RECORD_NUMBER){
return `${field.code} = ${Number(key)}`
}
if(typeof key==='string'){
return `${field.code} = "${key}"`
}
}
const findUpdateField = async (
mappingData: Mapping[],
appId: string,
context: IContext
) => {
const queryStr = mappingData
.filter((m) => m.to.app && m.to.fields && m.to.fields.length > 0 && m.isKey)
.map((m) => {
if (m.from.objectType === "variable") {
const vfrom = m.from as IVar;
return makeQuery(m.to.fields[0],getContextVarByPath(context.variables , vfrom.name.name));
}
else if(m.from.objectType === "field"){
const field = m.from as IFromField;
return makeQuery(m.to.fields[0],context.record[field.code].value);
}
else{
return makeQuery(m.to.fields[0],m.from.sharedText);
}
})
.join("&");
// 検索条件が空の場合は全レコードを返すため、検索対象が見つからない場合は検索は行われません。
if (queryStr.length === 0) {
return {
records: [],
};
} else {
return await client.record.getRecords({
app: appId,
// query: undefined
query: queryStr,
});
}
};
const doCreate = async (
mappingData: Mapping[],
appId: string,
context: IContext,
lookupFixedFieldCodes: string[]
) => {
const filterHandler = (item:Mapping)=>{
if(!item.to.fields || item.to.fields.length===0){
return false;
}
if(item.from.objectType === "variable" && (item.from as IVar).name.name ){
return true;
}
if(item.from.objectType === "field" && (item.from as IFromField).code){
return true;
}
if(item.from.objectType === "text" && item.from.sharedText!==null){
return true;
}
return false;
}
const record = mappingData
.filter(filterHandler)
.filter((item) => !lookupFixedFieldCodes.includes(item.to.fields[0].code))
.reduce((accumulator, item) => {
return {
...accumulator,
[item.to.fields[0].code]: {
value: getFromValue(item,context),
},
};
}, {});
if (record && Object.keys(record).length > 0) {
console.log(record);
await client.record.addRecord({
app:appId,
record:record
});
// await kintone.api(kintone.api.url("/k/v1/record.json", true), "POST", {
// app: appId,
// record: record,
// });
}
};
const getLookupFixedFieldCodes = async (appId: string) => {
return await client.app
.getFormFields({ app: appId })
.then((resp) =>
Object.values(resp.properties)
.filter((f) => (f as Lookup).lookup !== undefined)
.flatMap((f) => (f as Lookup).lookup.fieldMappings.map((m) => m.field))
);
};

View File

@@ -1,66 +0,0 @@
import { actionAddins } from ".";
import { IAction, IActionResult, IActionNode, IActionProperty, IField ,IContext, IVarName} from "../types/ActionTypes";
/**
* アクションの属性定義
*/
interface IDatetimeGetterProps {
/**変数の名前 */
verName:IVarName;
}
/**
* 現在日時を取得するアクション
*/
export class DatetimeGetterAction implements IAction {
name: string;
actionProps: IActionProperty[];
props: IDatetimeGetterProps;
constructor() {
this.name = "現在日時";
this.actionProps = [];
this.props = {
verName:{name:''}
}
this.register();
}
/**
* アクションの実行を呼び出す
* @param actionNode
* @param event
* @returns
*/
async process(actionNode: IActionNode, event: any,context:IContext): Promise<IActionResult> {
let result = {
canNext: true,
result: false
};
try {
//属性設定を取得する
this.actionProps = actionNode.actionProps;
if (!('verName' in actionNode.ActionValue) ) {
return result
}
this.props = actionNode.ActionValue as IDatetimeGetterProps;
let today = new Date();
if(this.props.verName && this.props.verName.name!==''){
context.variables[this.props.verName.name]=today.toISOString();
}
return result;
} catch (error) {
event.error = error;
console.error(error);
result.canNext = false;
return result;
}
};
register(): void {
actionAddins[this.name] = this;
}
}
new DatetimeGetterAction();

View File

@@ -103,5 +103,6 @@ export class FieldShownAction implements IAction{
register(): void {
actionAddins[this.name]=this;
}
}
new FieldShownAction();

View File

@@ -1,543 +0,0 @@
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 dateTimeMilliSecond = inputValue.match(/(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}).(\d{2,3})Z$/);//時刻入りのUTCの日付形式(ミリ秒)
let dateTime = inputValue.match(/(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})Z$/);//時刻入りの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 && dateTimeMilliSecond === 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桁の数字-12桁の数字-12桁の数字
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<string | boolean>{
//ユーザー名を格納する変数
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<string | boolean>{
//組織名を格納する変数
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<string | boolean>{
//グループ名を格納する変数
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<IActionResult> {
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[] = [];
//入力エラー(空白文字の混入)がないことをチェック
let notInputError=this.checkInputBlank(fieldType,fieldValue,fieldCode,fieldRequired,event);
//条件式の結果がtrue、入力エラー空白文字の混入がない場合、挿入する値をフィールドタイプ別にチェックする
if(conditionResult && notInputError){
//文字列型のフィールドに挿入しようとしている値が適切の場合、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}];
}
}
}
//保存成功イベントの場合、kintone async/await による非同期処理でフィールドに値を挿入する
if(!event.type.includes('success')){
//条件式の結果がtrueかつ挿入する値が変換できた場合、フィールドラジオボタン・ドロップダウン・チェックボックス・複数選択・文字列一行・文字列複数行・リッチエディタ・数値・日付・日時・時刻にセット
if(conditionResult && (correctFormattedValue || correctValues)){
//条件式の結果がtureかつ、値を正しい形式に変換できた場合、フィールドに値をセットする
if(correctFormattedValue){
event.record[fieldCode].value = correctFormattedValue;
//条件式の結果がtureかつ、値を正しい形式配列に変換できた場合、フィールドに値配列をセットする
}else if(correctValues.length > 0){
event.record[fieldCode].value = correctValues;
}
}
}else{
//kintone async/await による非同期処理(保存成功時イベントREST API処理時)
async function updateRecord(fieldCode:string,event:any,insertValue:any) {
return new Promise((resolve, reject) => {
var updatedRecord = {
app: event.appId,
id: event.recordId,
record: {[fieldCode]:{"value":insertValue}}
};
kintone.api(kintone.api.url('/k/v1/record', true), 'PUT', updatedRecord, (resp) => {
resolve(resp);
}, (error) => {
reject(error);
});
});
}
//条件式の結果がtrueかつ挿入する値が変換できた場合、フィールドラジオボタン・ドロップダウン・チェックボックス・複数選択・文字列一行・文字列複数行・リッチエディタ・数値・日付・日時・時刻にセット
if(conditionResult && (correctFormattedValue || correctValues)){
//条件式の結果がtureかつ、値を正しい形式に変換できた場合、フィールドに値をセットする
if(correctFormattedValue){
event.record[fieldCode].value = correctFormattedValue;
//条件式の結果がtureかつ、値を正しい形式配列に変換できた場合、フィールドに値配列をセットする
}else if(correctValues.length > 0){
event.record[fieldCode].value = correctValues;
}
}
//kintone async/await による非同期処理(レコード更新)
await updateRecord(fieldCode,event,correctFormattedValue);
};
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();

View File

@@ -1,76 +0,0 @@
import { actionAddins } from ".";
import { IField,IAction,IActionResult, IActionNode, IActionProperty,IContext,IVarName} from "../types/ActionTypes";
//右UI画面propertyのname:型:
interface ILoginUserGetterProps{
//変数名にセット:値がオブジェクトの形式
verName:IVarName;
}
//IActionインタフェースを実装する新しいクラスActionを作成
export class LoginUserGetterAction implements IAction{
name: string;
//importから導入顕示定義
actionProps: IActionProperty[];
//上方のinterface Propsから、props受ける属性を定義
props:ILoginUserGetterProps;
//関数定義に必要な類名を構築:
constructor(){
//pgAdminDBに登録したアクション名(name/subtitle)一致する必要がある:
this.name="ログインユーザー取得";
this.actionProps=[];
this.register();
//プロパティ属性初期化:
this.props={
verName:{name:''}
}
//リセット上記登録表:
this.register();
}
/**
* アクションの処理を実装する
* @param actionNode アクションノード
* @param event Kintoneのイベント
* @param context コンテキスト(レコード、変数情報を持っている)
* @returns
*/
  //非同期処理ある関数下のある属性:
async process(actionNode:IActionNode,event:any,context:IContext):Promise<IActionResult> {
let result={
canNext:true,
result:false
};
try{
//属性設定を取得する:
this.actionProps=actionNode.actionProps;
//プロパティ設定のデータ型は必要な情報含めますか全部不存在時return:
if (!('verName' in actionNode.ActionValue)) {
return result
}
//既定のプロパティのインタフェースへ変換:
this.props = actionNode.ActionValue as ILoginUserGetterProps;
//////////////////////////////////////////////////////////////////////////////////////
if(this.props.verName&&this.props.verName.name!==''){
context.variables[this.props.verName.name]=kintone.getLoginUser();
// window.alert("変数名"+this.props.verName.name+"の値は"+context.variables[this.props.verName.name]+"です");
// event.record["変数運用先のユーザ選択フィールド"].value= [{code:context.variables[this.props.verName.name].code}];
}
//////////////////////////////////////////////////////////////////////////////////////
result= {
canNext:true,
result:true
}
return result;
//////////////////////////////////////////////////////////////////////////////////////
}catch(error:any){;
event.error=error.message;
return {
canNext:false,
result:false
}
}
}
//////////////////////////////////////////////////////////////////////////////////////
register(): void {
actionAddins[this.name]=this;
}
}
new LoginUserGetterAction();

View File

@@ -1,86 +0,0 @@
import { actionAddins } from ".";
import { IAction, IActionResult, IActionNode, IActionProperty, IField } from "../types/ActionTypes";
/**
* アクションの属性定義
*/
interface IMailCheckProps {
field: IField;//チェックするフィールドの対象
message: string;//エラーメッセージ
emailCheck:string;
}
/**
* メールアドレスチェックアクション
*/
export class MailCheckAction implements IAction {
name: string;
actionProps: IActionProperty[];
props: IMailCheckProps;
constructor() {
this.name = "メールアドレスチェック";
this.actionProps = [];
this.props = {
field: { code: '' },
message: '',
emailCheck:''
}
//アクションを登録する
this.register();
}
/**
* アクションの実行を呼び出す
* @param actionNode
* @param event
* @returns
*/
async process(actionNode: IActionNode, event: any): Promise<IActionResult> {
let result = {
canNext: true,
result: false
};
try {
//属性設定を取得する
this.actionProps = actionNode.actionProps;
if (!('field' in actionNode.ActionValue) && !('message' in actionNode.ActionValue) && !('emailCheck' in actionNode.ActionValue)) {
return result
}
this.props = actionNode.ActionValue as IMailCheckProps;
//条件式の計算結果を取得
const record = event.record;
const value = record[this.props.field.code].value;
if (this.props.emailCheck === '厳格') {
if (!/^[a-zA-Z0-9_-¥.]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/.test(value)) {
record[this.props.field.code].error = this.props.message;
}
else {
record[this.props.field.code].error = null;
}
} else if (this.props.emailCheck === 'ゆるめ') {
if (!/^[^@]+@[^@]+$/.test(value)) {
record[this.props.field.code].error = this.props.message;
}
else {
record[this.props.field.code].error = null;
}
} else {
result = {
canNext: true,
result: true
}
}
return result;
} catch (error) {
event.error = error;
console.error(error);
result.canNext = false;
return result;
}
};
register(): void {
actionAddins[this.name] = this;
}
}
new MailCheckAction();

View File

@@ -1,72 +0,0 @@
import { actionAddins } from ".";
import { IAction,IActionResult, IActionNode, IActionProperty, IField} from "../types/ActionTypes";
/**
* アクションの属性定義
*/
interface IRegularCheckProps{
field:IField;//チェックするフィールドの対象
message:string;//エラーメッセージ
regExpression:string;//正規表現式
}
/**
* 正規表現チェックアクション
*/
export class RegularCheckAction implements IAction{
name: string;
actionProps: IActionProperty[];
props:IRegularCheckProps;
constructor(){
this.name="正規表現チェック";
this.actionProps=[];
this.props={
field:{code:''},
message:'',
regExpression:''
}
//アクションを登録する
this.register();
}
/**
* アクションの実行を呼び出す
* @param actionNode
* @param event
* @returns
*/
async process(actionNode:IActionNode,event:any):Promise<IActionResult> {
let result={
canNext:true,
result:false
};
try{
//属性設定を取得する
this.actionProps=actionNode.actionProps;
if (!('field' in actionNode.ActionValue) && !('message' in actionNode.ActionValue) && !('regExpression'in actionNode.ActionValue)) {
return result
}
this.props = actionNode.ActionValue as IRegularCheckProps;
//条件式の計算結果を取得
const record = event.record;
const value = record[this.props.field.code].value;
const regex = new RegExp(this.props.regExpression);
if(!regex.test(value)){
record[this.props.field.code].error=this.props.message;
}else{
result= {
canNext:true,
result:true
}
}
return result;
}catch(error){
event.error=error;
console.error(error);
result.canNext=false;
return result;
}
};
register(): void {
actionAddins[this.name]=this;
}
}
new RegularCheckAction();

View File

@@ -1,211 +0,0 @@
import { actionAddins } from ".";
import { IField, IAction,IActionResult, IActionNode, IActionProperty } from "../types/ActionTypes";
import { Formatter } from "../util/format";
//右UI画面propertyのname:型:
interface IStringJoinProps{
//保存先フィールド
saveField:IField;
//結合元フィールド1
joinField1:IField;
//結合元フィールド2
joinField2:IField;
//区切り文字
delimiter:string;
}
//IActionインタフェースを実装する新しいクラスActionを作成
export class StringJoinAction implements IAction{
name: string;
//importから導入顕示定義
actionProps: IActionProperty[];
//上方のinterface Propsから、props受ける属性を定義
props:IStringJoinProps;
//関数定義に必要な類名を構築:
constructor(){
//pgAdminDBに登録したアクション名(name/subtitle)一致する必要がある:
this.name="文字結合";
this.actionProps=[];
this.register();
//プロパティ属性初期化:
this.props={
saveField:{code:''},
joinField1:{code:''},
joinField2:{code:''},
delimiter:''
}
//リセット上記登録表:
this.register();
}
/**
* アクションの処理を実装する
* @param actionNode アクションノード
* @param event Kintoneのイベント
* @param context コンテキスト(レコード、変数情報を持っている)
* @returns
*/
//非同期処理ある関数下のある属性:
async process(actionNode:IActionNode,event:any):Promise<IActionResult> {
let result={
//後継処理不可:
canNext:false,
result:false
};
try{
//属性設定を取得する:
this.actionProps=actionNode.actionProps;
//プロパティ設定のデータ型は必要な情報含めますか全部不存在時return:
if (!('saveField' in actionNode.ActionValue) && !('joinField1' in actionNode.ActionValue) && !('joinField2' in actionNode.ActionValue)) {
return result
}
//既定のプロパティのインタフェースへ変換:
this.props = actionNode.ActionValue as IStringJoinProps;
const record = event.record;
//kintoneフィールドタイプ取得
const joinField1type=this.props.joinField1.type;
const joinField2type=this.props.joinField2.type;
const saveFieldtype=this.props.saveField.type;
//////////////////////////////////////////////////////////////////////////////////////////////////////
// //保存先フィールドは文字列フィールドではない場合、エラー発生:
if(!(saveFieldtype==='SINGLE_LINE_TEXT'||saveFieldtype==='MULTI_LINE_TEXT'||saveFieldtype==='RICH_TEXT')){
event.error='[エラーメッセージ]:結合保存先対応不可。結合しません';
if (event.type.includes('success')){
window.alert("[windows alert]"+event.error);
}
result = {
canNext: false,
result: false
}
return result;
}
//////////////////////////////////////////////////////////////////////////////////////////
//値取得方法定義:
function getValue(value:string,type:string|undefined,fieldCode:string,event:any){
if(event.record[fieldCode]?.value===undefined||event.record[fieldCode]?.value===null){
event.record[fieldCode].value='';
}
//作成者、更新者:
if(type==='CREATOR'||type==='MODIFIER'){
value = event.record[fieldCode]?.value.name;
//日時、作成日時、更新日時:
}else if(type==='DATETIME'||type==='CREATED_TIME'||type==='UPDATED_TIME'){
if(event.record[fieldCode]?.value!==undefined && event.record[fieldCode]?.value!==''){
value = Formatter.dateFormat(new Date(event.record[fieldCode]?.value),'yyyy-MM-dd HH:mm');
}else{
value=event.record[fieldCode]?.value;
}
//ユーザ選択、組織選択、グループ選択、添付ファイル名、作業者、カテゴリー:
}else if(type==='USER_SELECT'||type==='ORGANIZATION_SELECT'||type==='GROUP_SELECT'||type==='FILE'||type==='STATUS_ASSIGNEE'){
if(event.record[fieldCode]?.value===undefined || event.record[fieldCode]?.value===''){
value = event.record[fieldCode]?.value;
}else{
const mototext=event.record[fieldCode]?.value;
let arr=[];
for(let i=0;i<mototext.length;i++){
arr.push(mototext[i].name);
}
//配列要素を,で連結して文字列を作成:
value=arr.join();
}
//カテゴリー、チェックボックス、複数選択:
}else if(type==='CATEGORY'||type==='CHECK_BOX'||type==='MULTI_SELECT'){
if(event.record[fieldCode]?.value===undefined || event.record[fieldCode]?.value===''){
value = event.record[fieldCode]?.value;
}else{
const mototext=event.record[fieldCode]?.value;
let arr=[];
for(let i=0;i<mototext.length;i++){
arr.push(mototext[i]);
}
//配列要素を,で連結して文字列を作成:
value=arr.join();
}
//詳細画面プロセス実行後のステータス:
}else if(type==='STATUS'&&event.type.includes('process')){
value = event.nextStatus.value;
}else{
value = event.record[fieldCode]?.value;
}
if (value===undefined || value===null){
value='';
}
return value;
}
//////////////////////////////////////////////////////////////////////
//値取得方法呼出:
let joinValue1:string='';
joinValue1=getValue(joinValue1,joinField1type,this.props.joinField1.code,event);
/////////////////////////////////////////////////////////////////////////////////////
let joinValue2:string='';
joinValue2=getValue(joinValue2,joinField2type,this.props.joinField2.code,event);
//////////////////////////////////////////////////////////////////////////////////////////////////////////
const conString = this.props.delimiter;
let saveValue:string='';
//前後結合元が空白なら区切り文字も空白にする(例:1-8の8無いなら1。1-8の1無いなら8。1空白8の8無いなら1。結合元全部空白なら全部空白):
if(joinValue1===''&&joinValue2===''){
saveValue='';
}else if(joinValue1===''&&joinValue2!==''){
saveValue=joinValue2;
}else if(joinValue2===''&&joinValue1!==''){
saveValue=joinValue1;
}else if(joinValue1!==''&&joinValue2!==''){
saveValue=`${joinValue1}${conString}${joinValue2}`
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
//新規/更新/一覧保存成功後の以外のeventでPUT使用しない
if (!event.type.includes('success')){
//保存先フィールドに値セット:
record[this.props.saveField.code].value=saveValue;
//window.alert("文字結合行いました。"+this.props.joinField1.name+":"+joinValue1+","+this.props.joinField2.name+":"+joinValue2+"。");
}else{
//kintone async/await による非同期処理(成功イベントREST API処理時)
async function updateRecord(fieldCode:string,event:any) {
return new Promise((resolve, reject) => {
var updatedRecord = {
app: event.appId,
id: event.recordId,
record: {[fieldCode]:{"value":saveValue}}
};
kintone.api(kintone.api.url('/k/v1/record', true), 'PUT', updatedRecord, (resp) => {
resolve(resp);
}, (error) => {
reject(error);
});
});
}
//kintone保存先フィールド存在確認
record[this.props.saveField.code].value=saveValue;
//kintone async/await による非同期処理:
await updateRecord(this.props.saveField.code,event);
//一覧画面更新成功後手動リロードください:
if (event.type.includes('index')){
//window.alert("文字結合行いました。"+this.props.joinField1.name+":"+joinValue1+","+this.props.joinField2.name+":"+joinValue2+"。一覧画面更新成功後自動リロードしません。必要に応じて手動リロードください。");
window.alert("文字結合には、一覧画面更新成功後自動リロードしません。必要に応じて手動リロードください。");
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
result= {
canNext:true,
result:true
}
return result;
}catch(error){;
if (event.type.includes('success')){
window.alert("[windows alert]処理中異常が発生しました。結合しません。システム担当者へお問合せください。errorメッセージ"+error)
}
event.error="[エラーメッセージ]処理中異常が発生しました。結合しません。システム担当者へお問合せください。errorメッセージ"+error;
return {
canNext:false,
result:false
}
}
}
///////////////////////////////////////////////////////////////////////////////////////////////
register(): void {
actionAddins[this.name]=this;
}
}
new StringJoinAction();

View File

@@ -1,96 +0,0 @@
import { actionAddins } from ".";
import { IAction,IActionResult, IActionNode, IActionProperty, IField, IContext } from "../types/ActionTypes";
/**
* アクションの属性定義
*/
interface FullWidthProps{
field:IField
}
/**
* 全角チェックアクション
*/
export class FullWidthAction implements IAction{
name: string;
actionProps: IActionProperty[];
props:FullWidthProps;
constructor(){
this.name="全角チェック"; /* pgadminのnameと同様 */
this.actionProps=[];
this.props={
field:{code:''}
}
//アクションを登録する
this.register();
}
/**
* アクションの実行を呼び出す
* @param actionNode
* @param event
* @returns
*/
async process(actionNode:IActionNode,event:any,context:IContext):Promise<IActionResult> {
let result={
canNext:true,
result:false
};
try{
//属性設定を取得する
this.actionProps=actionNode.actionProps;
if (!('field' in actionNode.ActionValue) ) {
return result
}
this.props = actionNode.ActionValue as FullWidthProps;
//条件式の計算結果を取得
const record = event.record;
const value = record[this.props.field.code]?.value;
//条件分岐
//未入力時は何も処理をせず終了
if(value===undefined || value===''){
record[this.props.field.code].error=null;
}
//半角が含まれていた場合resultがfalse
if(!this.containsFullWidthChars(value) && !(value === undefined || value ==='')){
//エラー時に出力される文字設定
record[this.props.field.code].error="半角が含まれています";
//次の処理を中止する値設定
result.canNext=false;
}
else{
record[this.props.field.code].error=null;
}
//resultプロパティ指定
result= {
canNext:true,
result:true
}
return result;
//例外処理
}catch(error){
event.error=error;
console.error(error);
result.canNext=false;
return result;
}
}
// 全て全角の文字列の場合はtrue、そうでない場合はfalse
containsFullWidthChars(text: string): boolean {
// 半角英数字カナ記号を除外
//全角かどうか
const checkRegex="^[^\x01-\x7E\uFF61-\uFF9F]+$";
//正規表現オブジェクト生成
const fullWidthRegex = new RegExp(checkRegex);
//正規表現チェック
return fullWidthRegex.test(text);
}
//戻り値を持たないためvoid型
register(): void {
actionAddins[this.name]=this;
}
}
new FullWidthAction();

View File

@@ -1,92 +0,0 @@
import { actionAddins } from ".";
import { IAction,IActionResult, IActionNode, IActionProperty, IField, IContext } from "../types/ActionTypes";
/**
* アクションの属性定義
*/
interface HalfWidthProps{
field:IField
}
/**
* 半角チェックアクション
*/
export class HalfWidthAction implements IAction{
name: string;
actionProps: IActionProperty[];
props:HalfWidthProps;
constructor(){
this.name="半角チェック"; /* pgadminのnameと同様 */
this.actionProps=[];
this.props={
field:{code:''}
}
//アクションを登録する
this.register();
}
/**
* アクションの実行を呼び出す
* @param actionNode
* @param event
* @returns
*/
async process(actionNode:IActionNode,event:any,context:IContext):Promise<IActionResult> {
let result={
canNext:true,
result:false
};
try{
//属性設定を取得する
this.actionProps=actionNode.actionProps;
if (!('field' in actionNode.ActionValue) ) {
return result
}
this.props = actionNode.ActionValue as HalfWidthProps;
//条件式の計算結果を取得
const record = event.record;
const value = record[this.props.field.code]?.value;
//条件分岐
//未入力時は何も処理をせず終了
if(value===undefined || value===''){
record[this.props.field.code].error=null;
}
//全角が含まれていた場合保存処理中止(エラー処理)
if(!this.containsHalfWidthChars(value)){
//エラー時に出力される文字設定
record[this.props.field.code].error="全角が含まれています";
//次の処理を中止する値設定
result.canNext=false;
}
else{
record[this.props.field.code].error=null;
}
//半角の場合問題なく実行
//resultプロパティ指定
result= {
canNext:true,
result:true
}
return result;
//例外処理
}catch(error){
event.error=error;
console.error(error);
result.canNext=false;
return result;
}
}
// 全て全角の文字列の場合はtrue、そうでない場合はfalse
containsHalfWidthChars(text: string): boolean {
const checkRegex = "^[\x01-\x7E\uFF61-\uFF9F]+$";
//正規表現オブジェクト生成
const halfWidthRegex = new RegExp(checkRegex);
//正規表現チェック
return halfWidthRegex.test(text);
}
//戻り値を持たないためvoid型
register(): void {
actionAddins[this.name]=this;
}
}
new HalfWidthAction();

View File

@@ -1,70 +0,0 @@
import { actionAddins } from ".";
import { IAction,IActionResult, IActionNode, IActionProperty, IField, IContext, IVarName} from "../types/ActionTypes";
/**
* アクションの属性定義
*/
interface IGetValueProps{
field:IField;//チェックするフィールドの対象
verName:IVarName;
}
/**
* 正規表現チェックアクション
*/
export class GetValueAciton implements IAction{
name: string;
actionProps: IActionProperty[];
props:IGetValueProps;
constructor(){
this.name="値を取得する";
this.actionProps=[];
this.props={
field:{code:''},
verName:{name:''}
}
//アクションを登録する
this.register();
}
/**
* アクションの実行を呼び出す
* @param actionNode
* @param event
* @returns
*/
async process(actionNode:IActionNode,event:any,context:IContext):Promise<IActionResult> {
let result={
canNext:true,
result:false
};
try{
//属性設定を取得する
this.actionProps=actionNode.actionProps;
if (!('field' in actionNode.ActionValue) && !('verName' in actionNode.ActionValue)) {
return result
}
this.props = actionNode.ActionValue as IGetValueProps;
//条件式の計算結果を取得
const record = event.record;
const value = record[this.props.field.code].value;
if(this.props.verName && this.props.verName.name!==''){
context.variables[this.props.verName.name] = value;
}
result = {
canNext:true,
result:true
}
return result;
}catch(error){
event.error=error;
console.error(error);
result.canNext=false;
return result;
}
};
register(): void {
actionAddins[this.name]=this;
}
}
new GetValueAciton();

View File

@@ -1,3 +1,9 @@
// export const sum = (a: number, b: number) => {
// if ('development' === process.env.NODE_ENV) {
// console.log('boop');
// }
// return a + b;
// };
import $ from 'jquery';
import { ActionProcess } from './types/action-process';
import { ActionFlow } from './types/ActionTypes';
@@ -42,12 +48,7 @@ $(function (){
const flow=ActionFlow.fromJSON(flowinfo.content);
if(flow!==undefined){
const process = new ActionProcess(event.type,flow,event);
process.exec().then((res)=>{
const record = event.record;
kintone.app.record.set({record});
}).catch((err)=>{
console.error(err);
});
process.exec();
}
return event;
});
@@ -63,7 +64,7 @@ $(function (){
await process.exec();
}
const record = event.record;
kintone.app.record.set({record});
kintone.app.record.set({record})
});
});
}

View File

@@ -89,13 +89,6 @@ export interface IField{
name?:string;
code:string;
type?:string;
required?:boolean;
options?:string;
}
//変数のインターフェース
export interface IVarName{
name:string;
fields?:IVarName[];
}
/**

View File

@@ -347,9 +347,6 @@ export class ConditionTree {
if(!object || typeof object!=="object"){
return object;
}
if("sharedText" in object){
return object.sharedText;
}
if("label" in object){
return object.label;
}

View File

@@ -1,240 +0,0 @@
// 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[];
}

View File

@@ -7,20 +7,7 @@ import '../actions/error-show';
import '../actions/button-add';
import '../actions/condition-action';
import '../actions/data-processing';
import '../actions/data-update';
import '../actions/current-field-get';
import '../actions/regular-check';
import '../actions/mail-check';
import '../actions/counter-check';
import '../actions/datetime-getter';
import '../actions/insert-value';
import '../actions/value-getter';
import '../actions/string-join';
import '../actions/validation-fullwidth';
import '../actions/validation-halfwidth';
import '../actions/login-user-getter';
import '../actions/auto-lookup';
import '../actions/data-mapping';
import { ActionFlow,IActionFlow, IActionResult,IContext } from "./ActionTypes";
export class ActionProcess{

View File

@@ -1,121 +0,0 @@
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;
}
}
}

View File

@@ -1,18 +1,17 @@
// vite.config.js
import { defineConfig, loadEnv } from "vite";
import checker from "vite-plugin-checker";
// import { libInjectCss } from 'vite-plugin-lib-inject-css';
import { libInjectCss } from 'vite-plugin-lib-inject-css';
export default ({ mode }) => {
process.env = { ...process.env, ...loadEnv(mode, process.cwd()) };
const sourcemap = process.env.VITE_SOURCE_MAP=== 'false'?false:process.env.VITE_SOURCE_MAP;
// console.log(process.env);
return defineConfig({
plugins: [
checker({
typescript: true,
}),
// libInjectCss(),
libInjectCss(),
],
build: {
cssCodeSplit: false,
@@ -20,10 +19,10 @@ export default ({ mode }) => {
input: "src/index.ts", // entry file
output: {
entryFileNames: "alc_runtime.js",
assetFileNames:'alc_runtime.css'
// assetFileNames:'alc_kintone_style.css'
},
},
sourcemap: sourcemap,
sourcemap: process.env.VITE_SOURCE_MAP,
},
server: {
port: process.env.VITE_PORT,

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
"id": "",
"actionNodes": [
{
"id": "822b5b2c-cfb7-447a-ae77-01bf3a530fae",
"id": "c5cd772a-04be-418e-a811-3787f98a2285",
"name": "app.record.create.show",
"title": "レコード追加画面",
"subTitle": "レコード追加画面を表示した後",
@@ -12,12 +12,12 @@
"actionProps": [],
"ActionValue": {},
"nextNodeIds": {
"": "3a762b11-8e15-4353-8148-306bfed6cd5e"
"": "1eb097b1-9d08-462e-97b0-6e3e1232edef"
}
},
{
"id": "3a762b11-8e15-4353-8148-306bfed6cd5e",
"name": "ルックアップ更新",
"id": "1eb097b1-9d08-462e-97b0-6e3e1232edef",
"name": "属性UIテスト用",
"inputPoint": "",
"outputPoints": [],
"actionProps": [
@@ -28,73 +28,85 @@
"displayName": "表示名",
"placeholder": "表示を入力してください",
"hint": "",
"modelValue": "元データ更新時にルックアップ先が同期する"
"modelValue": "属性UIテスト用"
}
},
{
"component": "AppFieldSelect",
"props": {
"displayName": "更新先選択",
"displayName": "フィールド選択(複数)",
"modelValue": {
"app": {
"id": "247",
"name": "商品購買記録",
"description": "",
"createdate": "2024/06/24 02:49:12"
"id": "64",
"name": "日報テスト",
"description": "日々の業務内容、報告事項、所感などを記載していくアプリです。\n記録を行うだけでなく、あとからの振り返りやメンバー間のコミュニケーションにも活用できます。",
"createdate": "2023/07/15 10:15:03"
},
"fields": [
{
"name": "商品ID",
"type": "SINGLE_LINE_TEXT",
"code": "商品ID",
"label": "商品ID",
"noLabel": false,
"required": true,
"lookup": {
"relatedApp": {
"app": "246",
"code": ""
},
"relatedKeyField": "商品ID",
"fieldMappings": [
{
"field": "商品名",
"relatedField": "商品名"
},
{
"field": "単価",
"relatedField": "価格"
}
],
"lookupPickerFields": [
"商品名",
"価格"
],
"filterCond": "",
"sort": "レコード番号 desc"
}
"name": "ステータス",
"type": "STATUS",
"code": "ステータス",
"label": "ステータス",
"enabled": false
}
]
},
"name": "lookupField",
"placeholder": "",
"fieldTypes": [
"lookup"
],
"hint": "更新先のルックアップフィールドを選択する"
"name": "selectFields",
"placeholder": "アプリ選択後、フィールドを選んでください",
"selectType": "multiple"
}
},
{
"component": "ConditionInput",
"component": "AppFieldSelect",
"props": {
"displayName": "更新条件",
"modelValue": "{\"index\":0,\"type\":\"root\",\"children\":[{\"index\":1,\"type\":\"condition\",\"parent\":\"root\",\"object\":{},\"operator\":\"=\",\"value\":\"\"}],\"parent\":null,\"logicalOperator\":\"AND\"}",
"name": "condition",
"placeholder": "条件式を設定してください"
"displayName": "フィールド選択(単一)",
"modelValue": {
"app": {
"id": "58",
"name": "日報",
"description": "",
"createdate": "2023/07/13 19:05:26"
},
"fields": [
{
"name": "所感、学び",
"type": "MULTI_LINE_TEXT",
"code": "文字列__複数行__0",
"label": "所感、学び",
"noLabel": false,
"required": false,
"defaultValue": ""
}
]
},
"name": "selectField",
"placeholder": "アプリ選択後、フィールドを選んでください",
"selectType": "single"
}
},
{
"component": "ColorPicker",
"props": {
"displayName": "色選択",
"modelValue": "#f50000",
"name": "color",
"placeholder": "カラーを選択してください"
}
},
{
"component": "NumInput",
"props": {
"displayName": "数値入力フィールド",
"modelValue": 100,
"name": "num",
"max": 100,
"min": 0,
"placeholder": "数値を入力してください"
}
}
],
"prevNodeId": "822b5b2c-cfb7-447a-ae77-01bf3a530fae",
"prevNodeId": "c5cd772a-04be-418e-a811-3787f98a2285",
"nextNodeIds": {}
}
]

View File

@@ -1,24 +1,55 @@
[
{
"component": "AppFieldSelect",
"component": "InputText",
"props": {
"displayName": "更新先選択",
"modelValue": {},
"name": "lookupField",
"placeholder": "",
"fieldTypes": [
"lookup"
],
"hint": "更新先のルックアップフィールドを選択する"
"displayName": "文字入力",
"modelValue": "",
"name": "str",
"placeholder": "文字を入力してください",
"maxLength":"20",
"hint":"文字列入力<br>入力ルール指定可能。ルールの設定例:[val=>!!val||'必須入力です']",
"rules":"[val=>!!val||'必須入力です']"
}
},
{
"component": "ConditionInput",
"component": "AppFieldSelect",
"props": {
"displayName": "更新条件",
"displayName": "フィールド選択(複数)",
"modelValue": {},
"name": "selectFields",
"placeholder": "アプリ選択後、フィールドを選んでください",
"selectType":"multiple"
}
},
{
"component": "AppFieldSelect",
"props": {
"displayName": "フィールド選択(単一)",
"modelValue": {},
"name": "selectField",
"placeholder": "アプリ選択後、フィールドを選んでください",
"selectType":"single"
}
},
{
"component": "ColorPicker",
"props": {
"displayName": "色選択",
"modelValue": "",
"name": "condition",
"placeholder": "条件式を設定してください"
"name": "color",
"placeholder": "カラーを選択してください"
}
},
{
"component": "NumInput",
"props": {
"displayName": "数値入力フィールド",
"modelValue": "",
"name": "num",
"max":100,
"min":0,
"placeholder": "数値を入力してください",
"rules":"[val=>!!val ||'数値を入力してください',val=>val<=100 && val>=1 || '1-100の範囲内の数値を入力してください']"
}
}
]

View File

@@ -1,4 +0,0 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1