diff --git a/frontend/src/components/right/DataMapping.vue b/frontend/src/components/right/DataMapping.vue index bbe937b..4ab715b 100644 --- a/frontend/src/components/right/DataMapping.vue +++ b/frontend/src/components/right/DataMapping.vue @@ -96,7 +96,7 @@ import FieldSelect from '../FieldSelect.vue'; import IAppFields from './AppFieldSelect.vue'; import { api } from 'boot/axios'; -type Props = { +type ContextProps = { props?: { name: string; modelValue?: { @@ -108,12 +108,12 @@ type Props = { } }; -type Value = { - data: ValueType[]; +type CurrentModelValueType = { + data: MappingValueType[]; createWithNull: boolean; } -type ValueType = { +type MappingValueType = { id: string; from: object; to: typeof IAppFields & { @@ -122,6 +122,7 @@ type ValueType = { isKey: boolean; } +const blackListLabelName = ['レコード番号','作業者','更新者','更新日時','作成日時','作成者'] export default defineComponent({ name: 'DataMapping', @@ -134,7 +135,7 @@ export default defineComponent({ }, props: { context: { - type: Array, + type: Array, default: '', }, displayName: { @@ -146,7 +147,7 @@ export default defineComponent({ default: '', }, modelValue: { - type: Object as () => Value, + type: Object as () => CurrentModelValueType, }, placeholder: { type: String, @@ -162,31 +163,30 @@ export default defineComponent({ 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', mappingProps.value - ); + emit('update:modelValue', { data: mappingProps.value, createWithNull: createWithNull.value }); } const closeToDg = () => { - emit('update:modelValue', mappingProps.value - ); + emit('update:modelValue', { data: mappingProps.value, createWithNull: createWithNull.value }); } const mappingProps = computed(() => props.modelValue?.data ?? []); - const createWithNull = computed(() => props.modelValue?.createWithNull) + const createWithNull = ref(props.modelValue?.createWithNull ?? false) watch(() => sourceAppId.value, async (newId, oldId) => { if (!newId) return; - const a = await api.get('api/v1/appfields', { + const ktAppFields = await api.get('api/v1/appfields', { params: { app: newId } }).then(res => { return Object.values(res.data.properties) + .filter(f => !blackListLabelName.find(label => f.label === label)) .map(f => ({ name: f.label, objectType: 'field', ...f })) .map(f => { return { @@ -197,7 +197,7 @@ export default defineComponent({ fields: [f], isDialogVisible: false }, - isKey: true + isKey: false } }) }) @@ -206,21 +206,18 @@ export default defineComponent({ const createWithNull = props.modelValue?.createWithNull ?? false if (modelValueData.length === 0 || newId !== oldId) { - // emit('update:modelValue', a); - emit('update:modelValue', { data: a, createWithNull: createWithNull }); + emit('update:modelValue', { data: ktAppFields, createWithNull: createWithNull }); return; } const modelValueFieldNames = modelValueData.map(item => item.to.fields[0].name); - const newFields = a.filter(field => !modelValueFieldNames.includes(field.to.fields[0].name)); + const newFields = ktAppFields.filter(field => !modelValueFieldNames.includes(field.to.fields[0].name)); - const updatedModelValue = [...modelValueData, ...newFields]; - - emit('update:modelValue', { data: updatedModelValue, createWithNull: createWithNull }); - // emit('update:modelValue', updatedModelValue); + const updatedModelValueData = [...modelValueData, ...newFields]; + emit('update:modelValue', { data: updatedModelValueData, createWithNull: createWithNull }); }) - console.log(mappingProps.value); + console.log(createWithNull.value); // const deleteMappingObject = (index: number) => mappingProps.length === 1 // ? mappingProps.splice(0, mappingProps.length, defaultMappingProp()) @@ -243,7 +240,7 @@ export default defineComponent({ //集計処理方法 watchEffect(() => { - emit('update:modelValue', mappingProps.value); + emit('update:modelValue', { data: mappingProps.value, createWithNull: createWithNull.value }); }); return { uuidv4, diff --git a/plugin/kintone-addins/src/actions/data-mapping.ts b/plugin/kintone-addins/src/actions/data-mapping.ts index ec08137..73bc119 100644 --- a/plugin/kintone-addins/src/actions/data-mapping.ts +++ b/plugin/kintone-addins/src/actions/data-mapping.ts @@ -6,54 +6,57 @@ import { IContext, } from "../types/ActionTypes"; import { actionAddins } from "."; +import { KintoneRestAPIClient } from "@kintone/rest-api-client"; interface Props { displayName: string; sources: Sources; - dataMapping: DataMapping[]; + dataMapping: DataMapping; } interface DataMapping { + data: Mapping[]; + createWithNull: boolean; +} + +interface Mapping { id: string; from: From; to: To; + isKey: boolean; } interface To { app: App; - fields: Field[]; + fields: { + name: string; + type: string; + code: string; + label: string; + noLabel: boolean; + required: boolean; + minLength: string; + maxLength: string; + expression: string; + hideExpression: boolean; + unique: boolean; + defaultValue: string; + }[]; isDialogVisible: boolean; } -interface Field { - name: string; - type: string; - code: string; - label: string; - noLabel: boolean; - required: boolean; - minLength: string; - maxLength: string; - expression: string; - hideExpression: boolean; - unique: boolean; - defaultValue: string; -} - interface From { sharedText: string; _t: string; id: string; objectType: string; - name: Name; + name: { + name: string; + }; actionName: string; displayName: string; } -interface Name { - name: string; -} - interface Sources { app: App; } @@ -84,32 +87,37 @@ export class DataMappingAction implements IAction { this.actionProps = prop.actionProps; this.dataMappingProps = prop.ActionValue as Props; console.log(prop.ActionValue); - - // this.initTypedActionProps(); let result = { canNext: true, result: "", } as IActionResult; try { - const record = this.dataMappingProps.dataMapping - .filter( - (item) => - item.from.objectType === "variable" && - item.from.name.name && - item.to.app && - item.to.fields && - item.to.fields.length > 0 - ) - .reduce((accumulator, item) => { - return {...accumulator, [item.to.fields[0].code]: { - value: getValueByPath(context.variables, item.from.name.name), - }}; - }, {}); - if (record && Object.keys(record).length > 0) { - await kintone.api(kintone.api.url("/k/v1/record.json", true), "POST", { - app: this.dataMappingProps.sources.app.id, - record: record, - }); + // createWithNull が有効な場合は、4 番目のパラメーターを true にして doUpdate 関数ブランチを実行します。 + if (this.dataMappingProps.dataMapping.createWithNull === true) { + await doUpdate( + this.dataMappingProps.dataMapping.data, + this.dataMappingProps.sources.app.id, + context, + true // キーがない場合、またはキーでターゲットが見つからない場合に、マッピング条件によって新しいレコードを作成するかどうかを決定するために使用されます。 + ); + } 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 + ); + } else { + await doCreate( + this.dataMappingProps.dataMapping.data, + this.dataMappingProps.sources.app.id, + context + ); } } catch (error) { console.error("DataMappingAction error", error); @@ -127,40 +135,136 @@ export class DataMappingAction implements IAction { new DataMappingAction(); -const getValueByPath = (obj: any, path: string) => { +const getContextVarByPath = (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; +interface UpdateRecord { + id: string; + record: { + [key: string]: { + value: any; + }; }; -}; +} -type Result = { - type: string; - value: any[]; -}; +const client = new KintoneRestAPIClient(); -const selectData = async (appid: string, field: string): Promise => { - 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); +const doUpdate = async ( + mappingData: Mapping[], + appId: string, + context: any, + needCreate: boolean +) => { + const targetField = await findUpdateField(mappingData, appId, context); + console.log(targetField); + if (targetField.records.length === 0 && needCreate) { + await doCreate(mappingData, appId, context); + } else { + // マッピングデータを単純なオブジェクトに処理し、ソース値が変数の場合は変数を置き換えます。 + const mappingRules = mappingData + .filter((m) => Object.keys(m.from).length > 0) + .map((m) => { + if (m.from.objectType === "variable") { + return { + value: getContextVarByPath(context.variables, m.from.name.name), + code: m.to.fields[0].code, + }; + } else { + return { + value: m.from.sharedText, + code: m.to.fields[0].code, + }; } }); - return result; + + 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 findUpdateField = async ( + mappingData: Mapping[], + appId: string, + context: any +) => { + 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") { + return `${m.to.fields[0].code} = "${getContextVarByPath( + context.variables, + m.from.name.name + )}"`; + } else { + return `${m.to.fields[0].code}=${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: any +) => { + const record = mappingData + .filter( + (item) => + item.from.objectType === "variable" && + item.from.name.name && + item.to.app && + item.to.fields && + item.to.fields.length > 0 + ) + .reduce((accumulator, item) => { + return { + ...accumulator, + [item.to.fields[0].code]: { + value: getContextVarByPath(context.variables, item.from.name.name), + }, + }; + }, {}); + if (record && Object.keys(record).length > 0) { + await kintone.api(kintone.api.url("/k/v1/record.json", true), "POST", { + app: appId, + record: record, + }); + } };