diff --git a/document/検索取得プラグイン開発_結合テスト仕様書.xlsx b/document/検索取得プラグイン開発_結合テスト仕様書.xlsx new file mode 100644 index 0000000..25128f6 Binary files /dev/null and b/document/検索取得プラグイン開発_結合テスト仕様書.xlsx differ diff --git a/vue-project/my-kintone-plugin/src/components/Config.vue b/vue-project/my-kintone-plugin/src/components/Config.vue index 166f8fc..228b351 100644 --- a/vue-project/my-kintone-plugin/src/components/Config.vue +++ b/vue-project/my-kintone-plugin/src/components/Config.vue @@ -18,7 +18,8 @@ diff --git a/vue-project/my-kintone-plugin/src/components/basic/PluginInput.vue b/vue-project/my-kintone-plugin/src/components/basic/PluginInput.vue index 8a33ade..5f8c742 100644 --- a/vue-project/my-kintone-plugin/src/components/basic/PluginInput.vue +++ b/vue-project/my-kintone-plugin/src/components/basic/PluginInput.vue @@ -7,6 +7,7 @@ diff --git a/vue-project/my-kintone-plugin/src/components/basic/PluginTableActionIconGroup.vue b/vue-project/my-kintone-plugin/src/components/basic/PluginTableActionIconGroup.vue index 47db08d..3bea9ab 100644 --- a/vue-project/my-kintone-plugin/src/components/basic/PluginTableActionIconGroup.vue +++ b/vue-project/my-kintone-plugin/src/components/basic/PluginTableActionIconGroup.vue @@ -14,7 +14,7 @@ const props = withDefaults( defineProps<{ canAdd?: boolean; canDelete?: boolean; - tableId: number; + tableId: string; }>(), { canAdd: true, @@ -25,7 +25,12 @@ const props = withDefaults( const savedData = inject('savedData') as SavedData; const onClick = (type: 'add' | 'remove') => { if (type === 'add') { - savedData.joinTables.push(createEmptyJoinTable()); + const currentIndex = savedData.joinTables.findIndex((t) => t.id === props.tableId); + if (currentIndex !== -1) { + savedData.joinTables.splice(currentIndex + 1, 0, createEmptyJoinTable()); + } else { + savedData.joinTables.push(createEmptyJoinTable()); + } } else if (savedData.joinTables.length > 1) { savedData.joinTables = savedData.joinTables.filter((t) => t.id !== props.tableId); } diff --git a/vue-project/my-kintone-plugin/src/components/basic/PluginTableArea.vue b/vue-project/my-kintone-plugin/src/components/basic/PluginTableArea.vue index ee9a072..a577d1f 100644 --- a/vue-project/my-kintone-plugin/src/components/basic/PluginTableArea.vue +++ b/vue-project/my-kintone-plugin/src/components/basic/PluginTableArea.vue @@ -16,15 +16,15 @@ - + - + - +
@@ -43,7 +43,6 @@ import { } from '@/js/helper'; import { types } from '@/js/kintone-rest-api-client'; import type { CachedData, CachedSelectedAppData, FieldsInfo, JoinTable, SavedData } from '@/types/model'; -import type { KucEvent } from '@/types/my-kintone'; import { computed, inject, provide, reactive, ref, watch } from 'vue'; const savedData = inject('savedData') as SavedData; @@ -69,7 +68,6 @@ watch( () => tableOptions.value, () => { if (!props.table.table) return; - debugger componentKey.value += 1; }, { @@ -87,13 +85,14 @@ watch( const fields = await loadAppFieldsAndLayout(newVal); tableOptions.value = getTableFieldsDropdownItems(fields, types.SUBTABLE); selectedAppData.appFields = fields; + props.table.meta = fields.fields; !!oldVal && resetTable(props.table); loading.value = false; }, { immediate: true }, ); -const selectTable = (e: KucEvent) => { +const selectTable = () => { resetConditions(props.table); }; diff --git a/vue-project/my-kintone-plugin/src/components/basic/PluginTableConditionRow.vue b/vue-project/my-kintone-plugin/src/components/basic/PluginTableConditionRow.vue index ba53863..982f3a3 100644 --- a/vue-project/my-kintone-plugin/src/components/basic/PluginTableConditionRow.vue +++ b/vue-project/my-kintone-plugin/src/components/basic/PluginTableConditionRow.vue @@ -4,10 +4,9 @@ diff --git a/vue-project/my-kintone-plugin/src/components/basic/PluginTableConnectRow.vue b/vue-project/my-kintone-plugin/src/components/basic/PluginTableConnectRow.vue index 74fb64a..3c41243 100644 --- a/vue-project/my-kintone-plugin/src/components/basic/PluginTableConnectRow.vue +++ b/vue-project/my-kintone-plugin/src/components/basic/PluginTableConnectRow.vue @@ -3,32 +3,66 @@ diff --git a/vue-project/my-kintone-plugin/src/components/basic/TableCombobox.vue b/vue-project/my-kintone-plugin/src/components/basic/TableCombobox.vue index 2148d7c..b4bd8f4 100644 --- a/vue-project/my-kintone-plugin/src/components/basic/TableCombobox.vue +++ b/vue-project/my-kintone-plugin/src/components/basic/TableCombobox.vue @@ -1,7 +1,7 @@ diff --git a/vue-project/my-kintone-plugin/src/components/basic/conditions/TableConditionValue.vue b/vue-project/my-kintone-plugin/src/components/basic/conditions/TableConditionValue.vue index fad7907..4128c61 100644 --- a/vue-project/my-kintone-plugin/src/components/basic/conditions/TableConditionValue.vue +++ b/vue-project/my-kintone-plugin/src/components/basic/conditions/TableConditionValue.vue @@ -15,24 +15,33 @@ diff --git a/vue-project/my-kintone-plugin/src/js/conditions.ts b/vue-project/my-kintone-plugin/src/js/conditions.ts index f80cd68..0ffd963 100644 --- a/vue-project/my-kintone-plugin/src/js/conditions.ts +++ b/vue-project/my-kintone-plugin/src/js/conditions.ts @@ -1,5 +1,6 @@ import type { FieldsInfo } from '@/types/model'; import type { FieldType } from './kintone-rest-api-client'; +import { getFieldObj } from './helper'; // conditionValue = '' | 'eq' | 'ne' // conditionItem = { value: 'eq', label: '=(等しい)', type: 'input', func: (a: string, b: string) => a === b } @@ -39,9 +40,11 @@ const fieldConditions: FieldConditions = { } as const; // fieldCode -> conditionList: ConditionItem[] -export const getAvailableCondition = (fieldCode: string, { fields }: FieldsInfo) => { - if (!fieldCode || !fields) return; - const conditions = fieldConditions[fields[fieldCode]?.type] || []; +export const getAvailableCondition = (fieldCode: string, fieldsInfo: FieldsInfo, subTableCode: string | '') => { + if (!fieldCode || !fieldsInfo.fields) return; + const fieldObj = getFieldObj(fieldCode, fieldsInfo, subTableCode); + if (!fieldObj) return; + const conditions = fieldConditions[fieldObj.type] || []; return conditions.map((condition) => conditionMap[condition]); }; diff --git a/vue-project/my-kintone-plugin/src/js/helper.ts b/vue-project/my-kintone-plugin/src/js/helper.ts index 2e47cea..b249e1d 100644 --- a/vue-project/my-kintone-plugin/src/js/helper.ts +++ b/vue-project/my-kintone-plugin/src/js/helper.ts @@ -1,17 +1,37 @@ -import type { FieldsInfo, JoinTable, WhereCondition } from '@/types/model'; -import { client, isType, type FieldType, type App, type Layout } from './kintone-rest-api-client'; -import type { DropdownItem } from 'kintone-ui-component'; +import type { FieldsInfo, FieldsJoinMapping, JoinTable, WhereCondition } from '@/types/model'; +import { + client, + isType, + type FieldType, + type App, + type Layout, + type OneOf, + type Properties, +} from './kintone-rest-api-client'; +import type { ComboboxItem, DropdownItem } from 'kintone-ui-component'; +import { isForceDisable, type LeftCalcJoinType, type RightCalcJoinType } from './join'; export const EMPTY_OPTION = { value: '', label: '--------', } as DropdownItem; -export const getEmptyWhereCondition = () => ({ field: '', condition: '', data: '' }) as WhereCondition; -export const getEmptyOnCondition = () => ({ leftField: '', rightField: '' }); -export const getEmptyFieldsMapping = () => ({ leftField: '', rightField: '' }); +export function generateId(): string { + const timestamp = new Date().getTime().toString(36); + const randomNum = Math.random().toString(36).substring(2, 11); + return `${timestamp}-${randomNum}`; +} -export function createEmptyJoinTable(id = Number(new Date())) { +export function search(list: Array, id: string) { + return list.find((item) => item.id === id); +} + +export const getEmptyWhereCondition = () => + ({ field: '', condition: '', data: '', id: generateId() }) as WhereCondition; +export const getEmptyOnCondition = () => ({ leftField: '', rightField: '', id: generateId() }) as FieldsJoinMapping; +export const getEmptyFieldsMapping = () => ({ leftField: '', rightField: '', id: generateId() }) as FieldsJoinMapping; + +export function createEmptyJoinTable(id = generateId()) { return resetTable({ id, app: '' } as JoinTable); } @@ -49,25 +69,37 @@ export const loadAppFieldsAndLayout = async (appId: string | number = kintone.ap } as FieldsInfo; }; -type Param = { subTableCode?: string; filterType?: FieldType; defaultLabel?: string }; +type FilterType = Array; +type Param = { + subTableCode: string | undefined; + filterType?: FilterType; + baseFilter: FieldType[] | undefined; + defaultLabel?: string; + defaultDisableCallback?: (field: OneOf) => boolean; +}; export const getFieldsDropdownItems = ( { fields, layout }: FieldsInfo, - { subTableCode, filterType, defaultLabel }: Param = {}, + { subTableCode, filterType, baseFilter, defaultLabel, defaultDisableCallback }: Param, ) => { // get used field codes let fieldOrder: string[]; let fieldMap = fields; if (subTableCode) { const subTableFields = layout.find((each) => each.type === 'SUBTABLE' && each.code === subTableCode) as any; - fieldOrder = subTableFields?.fields.map((field: { code: string }) => field.code) || []; + fieldOrder = + subTableFields?.fields.map((field: OneOf) => { + if (!baseFilter) return field.code; + return baseFilter.find((t) => t === field.type) ? field.code : ''; + }) || []; + const subTableFieldMap = fieldMap[subTableCode] as { fields: Record } | undefined; fieldMap = subTableFieldMap?.fields || {}; } else { - fieldOrder = extractNoSubTableFields(layout); + fieldOrder = extractNoSubTableFields(layout, baseFilter); } // create labels - const labels = [ + const labels: ComboboxItem[] = [ { value: EMPTY_OPTION.value, label: defaultLabel || EMPTY_OPTION.label, @@ -75,15 +107,34 @@ export const getFieldsDropdownItems = ( ]; return fieldOrder.reduce((acc, fieldCode) => { const field = fieldMap[fieldCode]; - if (!fieldCode || (filterType && !isType[filterType](field))) return acc; + if (!fieldCode) return acc; acc.push({ value: fieldCode, label: field.label + '(FC: ' + fieldCode + ')', + disabled: (defaultDisableCallback && defaultDisableCallback(field)) || (filterType && !checkFilterType(field, filterType)), }); return acc; }, labels); }; +const checkFilterType = (field: OneOf, filterType: FilterType) => { + if (!filterType.length) return true; // [] means no filter + return !!filterType.find((type) => { + if (isRightCalcJoinType(type)) { + return isType.CALC(field) && type.format.find((e) => e === field.format); + } + return isType[type](field); + }); +}; + +export function isLeftCalcJoinType(obj: FilterType | LeftCalcJoinType): obj is LeftCalcJoinType { + return !Array.isArray(obj); +} + +function isRightCalcJoinType(obj: FieldType | RightCalcJoinType): obj is RightCalcJoinType { + return typeof obj === 'object' && obj.type === 'CALC'; +} + export const getTableFieldsDropdownItems = ({ fields }: FieldsInfo, filterType?: FieldType) => { return Object.keys(fields).reduce( (acc, fieldCode) => { @@ -99,19 +150,35 @@ export const getTableFieldsDropdownItems = ({ fields }: FieldsInfo, filterType?: ); }; -const extractNoSubTableFields = (layout: Layout) => { +const extractNoSubTableFields = (layout: Layout, baseFilter: FieldType[] | undefined) => { return layout.reduce((acc, each) => { if (each.type === 'SUBTABLE') { return acc; } else if (each.type === 'ROW') { - acc.push(...each.fields.map((field: any) => field.code)); + acc.push(...each.fields.map((field: any) => { + if (!baseFilter) return field.code; + return baseFilter.find((t) => t === field.type) ? field?.code || '' : '' + })); } else if (each.type === 'GROUP') { - acc.push(...extractNoSubTableFields(each.layout)); + acc.push(...extractNoSubTableFields(each.layout, baseFilter)); } return acc; }, [] as string[]); }; -// if (isType.SUBTABLE(field)) { -// console.log(field.fields); -// } +export function getFieldObj(fieldCode: string, { fields }: FieldsInfo, subTableCode?: string) { + const meta = getMeta(fields, subTableCode); + return meta[fieldCode]; +} + +export function getMeta(fields: Properties, subTableCode?: string) { + if (!fields || !subTableCode) { + return fields; + } + let meta = fields; + const table = meta[subTableCode]; + if (isType.SUBTABLE(table)) { + meta = table.fields; + } + return meta; +} diff --git a/vue-project/my-kintone-plugin/src/js/join.ts b/vue-project/my-kintone-plugin/src/js/join.ts new file mode 100644 index 0000000..5acddf2 --- /dev/null +++ b/vue-project/my-kintone-plugin/src/js/join.ts @@ -0,0 +1,59 @@ +import type { CalcType } from '@/types/my-kintone'; +import { isType, type FieldType, type OneOf } from './kintone-rest-api-client'; + + +export type LeftCalcJoinType = Record; + +export function isForceDisable(field: OneOf) { + if (isType.CALC(field)) { + return field.format === 'DAY_HOUR_MINUTE' || field.format === 'HOUR_MINUTE'; + } + return false; +} + +const calcJoinType = { + NUMBER: ['SINGLE_LINE_TEXT', 'NUMBER'], + NUMBER_DIGIT: ['SINGLE_LINE_TEXT', 'NUMBER'], + DATE: ['DATE'], + TIME: ['TIME'], + DATETIME: ['DATETIME'], + HOUR_MINUTE: [], + DAY_HOUR_MINUTE: [], +} as LeftCalcJoinType; + +const availableLeftJoinType = { + SINGLE_LINE_TEXT: ['SINGLE_LINE_TEXT'], + NUMBER: ['NUMBER'], + CALC: calcJoinType, + DATE: ['DATE'], + TIME: ['TIME'], + DATETIME: ['DATETIME'], + LINK: ['LINK'], +} as Record; + +// undefined means all +export function getRightAvailableJoinType(left?: OneOf | '') { + if (left === undefined) { + return Object.keys(availableRightJoinType) as FieldType[]; + } + return left ? availableLeftJoinType[left.type] : []; +} + +export type RightCalcJoinType = { type: 'CALC'; format: CalcType[] }; + +const availableRightJoinType = { + SINGLE_LINE_TEXT: ['SINGLE_LINE_TEXT', { type: 'CALC', format: ['NUMBER', 'NUMBER_DIGIT'] }], + NUMBER: ['NUMBER', { type: 'CALC', format: ['NUMBER', 'NUMBER_DIGIT'] }], + DATE: ['DATE', { type: 'CALC', format: ['DATE'] }], + TIME: ['TIME', { type: 'CALC', format: ['TIME'] }], + DATETIME: ['DATETIME', { type: 'CALC', format: ['DATETIME'] }], + LINK: ['LINK'], +} as Record>; + +// undefined means all +export function getLeftAvailableJoinType(right?: OneOf | '') { + if (right === undefined) { + return Object.keys(availableLeftJoinType) as FieldType[]; + } + return right ? availableRightJoinType[right.type] : []; +} diff --git a/vue-project/my-kintone-plugin/src/js/kintone-rest-api-client.ts b/vue-project/my-kintone-plugin/src/js/kintone-rest-api-client.ts index 43f1ad9..fd58f7f 100644 --- a/vue-project/my-kintone-plugin/src/js/kintone-rest-api-client.ts +++ b/vue-project/my-kintone-plugin/src/js/kintone-rest-api-client.ts @@ -10,7 +10,7 @@ export type App = { export type Properties = Awaited>['properties']; export type Layout = Awaited>['layout']; -type OneOf = Properties[string]; +export type OneOf = Properties[string]; export type FieldType = OneOf['type']; const typeNames = [ @@ -54,7 +54,7 @@ export const types = typeNames.reduce( type ExtractOneOf = Extract; function createTypeGuard(type: T) { - return (value: OneOf): value is ExtractOneOf => value.type === type; + return (value: OneOf): value is ExtractOneOf => value?.type === type; } export const isType = Object.fromEntries( diff --git a/vue-project/my-kintone-plugin/src/types/model.d.ts b/vue-project/my-kintone-plugin/src/types/model.d.ts index e35fdfa..62791ac 100644 --- a/vue-project/my-kintone-plugin/src/types/model.d.ts +++ b/vue-project/my-kintone-plugin/src/types/model.d.ts @@ -2,24 +2,27 @@ import type { ConditionValue } from '@/js/conditions'; import type { Layout, Properties } from '@/js/kintone-rest-api-client'; import type { DropdownItem } from 'kintone-ui-component'; -export interface FieldsJoinMapping { - leftField: string; - rightField: string; +export interface FieldsJoinMapping { + id: string; + leftField: FieldType; + rightField: FieldType; } -export interface WhereCondition { - field: string; +export interface WhereCondition { + id: string; + field: FieldType; condition: ConditionValue; data: string; } -export interface JoinTable { - id: number; // 用于唯一区分 +export interface JoinTable { + id: string; app: string; // 取得元アプリ table: string; // テーブル - onConditions: FieldsJoinMapping[]; // 連結条件 - fieldsMapping: FieldsJoinMapping[]; // 取得フィールド - whereConditions: WhereCondition[]; // 絞込条件 + onConditions: FieldsJoinMapping[]; // 連結条件 + fieldsMapping: FieldsJoinMapping[]; // 取得フィールド + whereConditions: WhereCondition[]; // 絞込条件 + meta?: Properties; } // 存储的数据格式 diff --git a/vue-project/my-kintone-plugin/src/types/my-kintone.d.ts b/vue-project/my-kintone-plugin/src/types/my-kintone.d.ts index 7e32aee..394f20c 100644 --- a/vue-project/my-kintone-plugin/src/types/my-kintone.d.ts +++ b/vue-project/my-kintone-plugin/src/types/my-kintone.d.ts @@ -1,5 +1,5 @@ -export interface KucEvent { - detail: { - value: string; - }; +export interface KucEvent { + detail: T; } + +export type CalcType = 'NUMBER' | 'NUMBER_DIGIT' | 'DATETIME' | 'DATE' | 'TIME' | 'HOUR_MINUTE' | 'DAY_HOUR_MINUTE';