테이블 간 조인 관계 조회 기능 추가
- 두 테이블 간의 조인 관계를 조회하는 API를 추가하였습니다. 이 API는 메인 테이블과 디테일 테이블을 파라미터로 받아, 해당 테이블 간의 조인 관계를 반환합니다. - DataflowService에 조인 관계 조회 메서드를 구현하여, 회사 코드에 따라 적절한 조인 정보를 필터링합니다. - 프론트엔드에서 조인 관계를 캐시하여, 반복적인 API 호출을 줄이고 성능을 개선하였습니다. 이로 인해 마스터-디테일 저장 기능의 효율성이 향상되었습니다.
This commit is contained in:
parent
8c0572e0ac
commit
d429e237ee
|
|
@ -759,3 +759,45 @@ export async function getAllRelationships(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 두 테이블 간의 조인 관계 조회 (마스터-디테일 저장용)
|
||||||
|
*/
|
||||||
|
export async function getJoinRelationship(
|
||||||
|
req: AuthenticatedRequest,
|
||||||
|
res: Response
|
||||||
|
): Promise<void> {
|
||||||
|
try {
|
||||||
|
const { mainTable, detailTable } = req.params;
|
||||||
|
const companyCode = req.user?.companyCode || "*";
|
||||||
|
|
||||||
|
if (!mainTable || !detailTable) {
|
||||||
|
res.status(400).json({
|
||||||
|
success: false,
|
||||||
|
message: "메인 테이블과 디테일 테이블이 필요합니다.",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// DataflowService에서 조인 관계 조회
|
||||||
|
const { DataflowService } = await import("../services/dataflowService");
|
||||||
|
const dataflowService = new DataflowService();
|
||||||
|
|
||||||
|
const result = await dataflowService.getJoinRelationshipBetweenTables(
|
||||||
|
mainTable,
|
||||||
|
detailTable,
|
||||||
|
companyCode
|
||||||
|
);
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
data: result,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("조인 관계 조회 실패:", error);
|
||||||
|
res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
message: error instanceof Error ? error.message : "조인 관계 조회 실패",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import {
|
||||||
executeOptimizedButton,
|
executeOptimizedButton,
|
||||||
executeSimpleDataflow,
|
executeSimpleDataflow,
|
||||||
getJobStatus,
|
getJobStatus,
|
||||||
|
getJoinRelationship,
|
||||||
} from "../controllers/buttonDataflowController";
|
} from "../controllers/buttonDataflowController";
|
||||||
import { authenticateToken } from "../middleware/authMiddleware";
|
import { authenticateToken } from "../middleware/authMiddleware";
|
||||||
|
|
||||||
|
|
@ -61,6 +62,13 @@ router.post("/execute-simple", executeSimpleDataflow);
|
||||||
// 백그라운드 작업 상태 조회
|
// 백그라운드 작업 상태 조회
|
||||||
router.get("/job-status/:jobId", getJobStatus);
|
router.get("/job-status/:jobId", getJobStatus);
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 🔥 테이블 관계 조회 (마스터-디테일 저장용)
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// 두 테이블 간의 조인 관계 조회
|
||||||
|
router.get("/join-relationship/:mainTable/:detailTable", getJoinRelationship);
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// 🔥 레거시 호환성 (기존 API와 호환)
|
// 🔥 레거시 호환성 (기존 API와 호환)
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
|
||||||
|
|
@ -337,6 +337,110 @@ export class DataflowService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 두 테이블 간의 조인 관계 조회 (마스터-디테일 저장용)
|
||||||
|
* @param mainTable 메인 테이블명 (마스터)
|
||||||
|
* @param detailTable 디테일 테이블명 (리피터)
|
||||||
|
* @param companyCode 회사코드
|
||||||
|
* @returns 조인 컬럼 매핑 정보
|
||||||
|
*/
|
||||||
|
async getJoinRelationshipBetweenTables(
|
||||||
|
mainTable: string,
|
||||||
|
detailTable: string,
|
||||||
|
companyCode: string
|
||||||
|
): Promise<{
|
||||||
|
found: boolean;
|
||||||
|
mainColumn?: string;
|
||||||
|
detailColumn?: string;
|
||||||
|
relationshipType?: string;
|
||||||
|
}> {
|
||||||
|
try {
|
||||||
|
logger.info(
|
||||||
|
`DataflowService: 테이블 간 조인 관계 조회 - 메인: ${mainTable}, 디테일: ${detailTable}`
|
||||||
|
);
|
||||||
|
|
||||||
|
// 양방향 조회 (from → to 또는 to → from)
|
||||||
|
let queryText = `
|
||||||
|
SELECT
|
||||||
|
from_table_name,
|
||||||
|
from_column_name,
|
||||||
|
to_table_name,
|
||||||
|
to_column_name,
|
||||||
|
relationship_type,
|
||||||
|
settings
|
||||||
|
FROM table_relationships
|
||||||
|
WHERE is_active = 'Y'
|
||||||
|
AND (
|
||||||
|
(from_table_name = $1 AND to_table_name = $2)
|
||||||
|
OR (from_table_name = $2 AND to_table_name = $1)
|
||||||
|
)
|
||||||
|
`;
|
||||||
|
const params: any[] = [mainTable, detailTable];
|
||||||
|
|
||||||
|
// 관리자가 아닌 경우 회사코드 제한
|
||||||
|
if (companyCode !== "*") {
|
||||||
|
queryText += ` AND (company_code = $3 OR company_code = '*')`;
|
||||||
|
params.push(companyCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
queryText += ` LIMIT 1`;
|
||||||
|
|
||||||
|
const result = await queryOne<{
|
||||||
|
from_table_name: string;
|
||||||
|
from_column_name: string;
|
||||||
|
to_table_name: string;
|
||||||
|
to_column_name: string;
|
||||||
|
relationship_type: string;
|
||||||
|
settings: any;
|
||||||
|
}>(queryText, params);
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
logger.info(
|
||||||
|
`DataflowService: 테이블 간 조인 관계 없음 - ${mainTable} ↔ ${detailTable}`
|
||||||
|
);
|
||||||
|
return { found: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 방향에 따라 컬럼 매핑 결정
|
||||||
|
// mainTable이 from_table이면 그대로, 아니면 반대로
|
||||||
|
let mainColumn: string;
|
||||||
|
let detailColumn: string;
|
||||||
|
|
||||||
|
if (result.from_table_name === mainTable) {
|
||||||
|
// from → to 방향: mainTable.from_column → detailTable.to_column
|
||||||
|
mainColumn = result.from_column_name;
|
||||||
|
detailColumn = result.to_column_name;
|
||||||
|
} else {
|
||||||
|
// to → from 방향: mainTable.to_column → detailTable.from_column
|
||||||
|
mainColumn = result.to_column_name;
|
||||||
|
detailColumn = result.from_column_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 쉼표로 구분된 다중 컬럼인 경우 첫 번째 컬럼만 사용
|
||||||
|
// (추후 다중 컬럼 지원 필요시 확장)
|
||||||
|
if (mainColumn.includes(",")) {
|
||||||
|
mainColumn = mainColumn.split(",")[0].trim();
|
||||||
|
}
|
||||||
|
if (detailColumn.includes(",")) {
|
||||||
|
detailColumn = detailColumn.split(",")[0].trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
`DataflowService: 조인 관계 발견 - ${mainTable}.${mainColumn} → ${detailTable}.${detailColumn}`
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
found: true,
|
||||||
|
mainColumn,
|
||||||
|
detailColumn,
|
||||||
|
relationshipType: result.relationship_type,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("DataflowService: 테이블 간 조인 관계 조회 실패", error);
|
||||||
|
return { found: false };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 연결 타입별 관계 조회
|
* 연결 타입별 관계 조회
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -1237,6 +1237,9 @@ export class ButtonActionExecutor {
|
||||||
console.log("🔎 [handleSave] formData 키 목록:", Object.keys(context.formData));
|
console.log("🔎 [handleSave] formData 키 목록:", Object.keys(context.formData));
|
||||||
console.log("🔎 [handleSave] formData 전체:", context.formData);
|
console.log("🔎 [handleSave] formData 전체:", context.formData);
|
||||||
|
|
||||||
|
// 🆕 마스터-디테일 저장: 테이블 간 조인 관계 캐시
|
||||||
|
const joinRelationshipCache: Record<string, { mainColumn: string; detailColumn: string } | null> = {};
|
||||||
|
|
||||||
for (const [fieldKey, fieldValue] of Object.entries(context.formData)) {
|
for (const [fieldKey, fieldValue] of Object.entries(context.formData)) {
|
||||||
console.log(`🔎 [handleSave] 필드 검사: ${fieldKey}`, {
|
console.log(`🔎 [handleSave] 필드 검사: ${fieldKey}`, {
|
||||||
type: typeof fieldValue,
|
type: typeof fieldValue,
|
||||||
|
|
@ -1266,6 +1269,34 @@ export class ButtonActionExecutor {
|
||||||
itemCount: parsedData.length,
|
itemCount: parsedData.length,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 🆕 마스터-디테일 조인 관계 조회 (메인 테이블 → 리피터 테이블)
|
||||||
|
let joinRelationship: { mainColumn: string; detailColumn: string } | null = null;
|
||||||
|
const cacheKey = `${tableName}:${repeaterTargetTable}`;
|
||||||
|
|
||||||
|
if (tableName && repeaterTargetTable && tableName !== repeaterTargetTable) {
|
||||||
|
// 캐시에서 먼저 확인
|
||||||
|
if (cacheKey in joinRelationshipCache) {
|
||||||
|
joinRelationship = joinRelationshipCache[cacheKey];
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
const joinResponse = await apiClient.get(
|
||||||
|
`/button-dataflow/join-relationship/${tableName}/${repeaterTargetTable}`
|
||||||
|
);
|
||||||
|
if (joinResponse.data?.success && joinResponse.data?.data?.found) {
|
||||||
|
joinRelationship = {
|
||||||
|
mainColumn: joinResponse.data.data.mainColumn,
|
||||||
|
detailColumn: joinResponse.data.data.detailColumn,
|
||||||
|
};
|
||||||
|
console.log(`🔗 [handleSave] 조인 관계 발견: ${tableName}.${joinRelationship.mainColumn} → ${repeaterTargetTable}.${joinRelationship.detailColumn}`);
|
||||||
|
}
|
||||||
|
} catch (joinError) {
|
||||||
|
console.warn(`⚠️ [handleSave] 조인 관계 조회 실패:`, joinError);
|
||||||
|
}
|
||||||
|
// 결과를 캐시에 저장 (없어도 null로 저장하여 재조회 방지)
|
||||||
|
joinRelationshipCache[cacheKey] = joinRelationship;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 🆕 범용 폼 모달의 공통 필드 추출 (order_no, manager_id 등)
|
// 🆕 범용 폼 모달의 공통 필드 추출 (order_no, manager_id 등)
|
||||||
// "범용_폼_모달" 키에서 공통 필드를 가져옴
|
// "범용_폼_모달" 키에서 공통 필드를 가져옴
|
||||||
const universalFormData = context.formData["범용_폼_모달"] as Record<string, unknown> | undefined;
|
const universalFormData = context.formData["범용_폼_모달"] as Record<string, unknown> | undefined;
|
||||||
|
|
@ -1303,6 +1334,18 @@ export class ButtonActionExecutor {
|
||||||
}
|
}
|
||||||
console.log("📋 [handleSave] 최종 공통 필드 (규칙 기반 자동 추출):", commonFields);
|
console.log("📋 [handleSave] 최종 공통 필드 (규칙 기반 자동 추출):", commonFields);
|
||||||
|
|
||||||
|
// 🆕 마스터-디테일 조인: 메인 테이블의 조인 컬럼 값을 commonFields에 추가
|
||||||
|
if (joinRelationship) {
|
||||||
|
const mainColumnValue = context.formData[joinRelationship.mainColumn];
|
||||||
|
if (mainColumnValue !== undefined && mainColumnValue !== null && mainColumnValue !== "") {
|
||||||
|
// 리피터 테이블의 조인 컬럼에 메인 테이블의 값 주입
|
||||||
|
commonFields[joinRelationship.detailColumn] = mainColumnValue;
|
||||||
|
console.log(`🔗 [handleSave] 조인 컬럼 값 주입: ${joinRelationship.detailColumn} = ${mainColumnValue}`);
|
||||||
|
} else {
|
||||||
|
console.warn(`⚠️ [handleSave] 조인 컬럼 값이 없음: ${joinRelationship.mainColumn}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (const item of parsedData) {
|
for (const item of parsedData) {
|
||||||
// 메타 필드 제거 (eslint 경고 무시 - 의도적으로 분리)
|
// 메타 필드 제거 (eslint 경고 무시 - 의도적으로 분리)
|
||||||
|
|
||||||
|
|
@ -1455,15 +1498,51 @@ export class ButtonActionExecutor {
|
||||||
|
|
||||||
if (!Array.isArray(rows) || rows.length === 0) continue;
|
if (!Array.isArray(rows) || rows.length === 0) continue;
|
||||||
|
|
||||||
|
// 🆕 마스터-디테일 조인 관계 조회 (메인 테이블 → RepeatScreenModal 테이블)
|
||||||
|
let joinRelationship: { mainColumn: string; detailColumn: string } | null = null;
|
||||||
|
if (tableName && targetTable && tableName !== targetTable) {
|
||||||
|
const cacheKey = `${tableName}:${targetTable}`;
|
||||||
|
if (cacheKey in joinRelationshipCache) {
|
||||||
|
joinRelationship = joinRelationshipCache[cacheKey];
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
const joinResponse = await apiClient.get(
|
||||||
|
`/button-dataflow/join-relationship/${tableName}/${targetTable}`
|
||||||
|
);
|
||||||
|
if (joinResponse.data?.success && joinResponse.data?.data?.found) {
|
||||||
|
joinRelationship = {
|
||||||
|
mainColumn: joinResponse.data.data.mainColumn,
|
||||||
|
detailColumn: joinResponse.data.data.detailColumn,
|
||||||
|
};
|
||||||
|
console.log(`🔗 [handleSave] RepeatScreenModal 조인 관계 발견: ${tableName}.${joinRelationship.mainColumn} → ${targetTable}.${joinRelationship.detailColumn}`);
|
||||||
|
}
|
||||||
|
} catch (joinError) {
|
||||||
|
console.warn(`⚠️ [handleSave] RepeatScreenModal 조인 관계 조회 실패:`, joinError);
|
||||||
|
}
|
||||||
|
joinRelationshipCache[cacheKey] = joinRelationship;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 🆕 조인 컬럼 값 준비 (메인 테이블에서 가져옴)
|
||||||
|
let joinColumnData: Record<string, any> = {};
|
||||||
|
if (joinRelationship) {
|
||||||
|
const mainColumnValue = context.formData[joinRelationship.mainColumn];
|
||||||
|
if (mainColumnValue !== undefined && mainColumnValue !== null && mainColumnValue !== "") {
|
||||||
|
joinColumnData[joinRelationship.detailColumn] = mainColumnValue;
|
||||||
|
console.log(`🔗 [handleSave] RepeatScreenModal 조인 컬럼 값: ${joinRelationship.detailColumn} = ${mainColumnValue}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
console.log(`📦 [handleSave] ${targetTable} 테이블 저장:`, rows);
|
console.log(`📦 [handleSave] ${targetTable} 테이블 저장:`, rows);
|
||||||
|
|
||||||
for (const row of rows) {
|
for (const row of rows) {
|
||||||
const { _isNew, _targetTable, id, ...dataToSave } = row;
|
const { _isNew, _targetTable, id, ...dataToSave } = row;
|
||||||
|
|
||||||
// 사용자 정보 추가 + 채번 규칙 값 병합
|
// 사용자 정보 추가 + 채번 규칙 값 병합 + 조인 컬럼 값 추가
|
||||||
const dataWithMeta = {
|
const dataWithMeta = {
|
||||||
...dataToSave,
|
...dataToSave,
|
||||||
...numberingFields, // 채번 규칙 값 (shipment_plan_no 등)
|
...numberingFields, // 채번 규칙 값 (shipment_plan_no 등)
|
||||||
|
...joinColumnData, // 🆕 조인 컬럼 값 (마스터-디테일 관계)
|
||||||
created_by: context.userId,
|
created_by: context.userId,
|
||||||
updated_by: context.userId,
|
updated_by: context.userId,
|
||||||
company_code: context.companyCode,
|
company_code: context.companyCode,
|
||||||
|
|
@ -1497,6 +1576,96 @@ export class ButtonActionExecutor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 🆕 v2-repeat-container 데이터 저장 처리 (_repeatContainerTables에 그룹화된 데이터)
|
||||||
|
const repeatContainerTables = context.formData._repeatContainerTables as Record<string, any[]> | undefined;
|
||||||
|
if (repeatContainerTables && Object.keys(repeatContainerTables).length > 0) {
|
||||||
|
console.log("📦 [handleSave] v2-RepeatContainer 데이터 저장 시작:", Object.keys(repeatContainerTables));
|
||||||
|
|
||||||
|
for (const [targetTable, rows] of Object.entries(repeatContainerTables)) {
|
||||||
|
if (!Array.isArray(rows) || rows.length === 0) continue;
|
||||||
|
|
||||||
|
// 🆕 마스터-디테일 조인 관계 조회
|
||||||
|
let joinRelationship: { mainColumn: string; detailColumn: string } | null = null;
|
||||||
|
if (tableName && targetTable && tableName !== targetTable) {
|
||||||
|
const cacheKey = `${tableName}:${targetTable}`;
|
||||||
|
if (cacheKey in joinRelationshipCache) {
|
||||||
|
joinRelationship = joinRelationshipCache[cacheKey];
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
const joinResponse = await apiClient.get(
|
||||||
|
`/button-dataflow/join-relationship/${tableName}/${targetTable}`
|
||||||
|
);
|
||||||
|
if (joinResponse.data?.success && joinResponse.data?.data?.found) {
|
||||||
|
joinRelationship = {
|
||||||
|
mainColumn: joinResponse.data.data.mainColumn,
|
||||||
|
detailColumn: joinResponse.data.data.detailColumn,
|
||||||
|
};
|
||||||
|
console.log(`🔗 [handleSave] RepeatContainer 조인 관계 발견: ${tableName}.${joinRelationship.mainColumn} → ${targetTable}.${joinRelationship.detailColumn}`);
|
||||||
|
}
|
||||||
|
} catch (joinError) {
|
||||||
|
console.warn(`⚠️ [handleSave] RepeatContainer 조인 관계 조회 실패:`, joinError);
|
||||||
|
}
|
||||||
|
joinRelationshipCache[cacheKey] = joinRelationship;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 조인 컬럼 값 준비
|
||||||
|
let joinColumnData: Record<string, any> = {};
|
||||||
|
if (joinRelationship) {
|
||||||
|
const mainColumnValue = context.formData[joinRelationship.mainColumn];
|
||||||
|
if (mainColumnValue !== undefined && mainColumnValue !== null && mainColumnValue !== "") {
|
||||||
|
joinColumnData[joinRelationship.detailColumn] = mainColumnValue;
|
||||||
|
console.log(`🔗 [handleSave] RepeatContainer 조인 컬럼 값: ${joinRelationship.detailColumn} = ${mainColumnValue}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`📦 [handleSave] ${targetTable} 테이블 저장 (RepeatContainer): ${rows.length}건`);
|
||||||
|
|
||||||
|
for (const row of rows) {
|
||||||
|
const { _isDirty, _sectionIndex, _targetTable, id, ...dataToSave } = row;
|
||||||
|
|
||||||
|
// 변경되지 않은 행은 건너뛰기
|
||||||
|
if (_isDirty === false) {
|
||||||
|
console.log(`⏭️ [handleSave] ${targetTable} 변경 없음 건너뜀 (index: ${_sectionIndex})`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 사용자 정보 추가 + 조인 컬럼 값 추가
|
||||||
|
const dataWithMeta = {
|
||||||
|
...dataToSave,
|
||||||
|
...joinColumnData, // 조인 컬럼 값
|
||||||
|
created_by: context.userId,
|
||||||
|
updated_by: context.userId,
|
||||||
|
company_code: context.companyCode,
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!id) {
|
||||||
|
// INSERT (id가 없으면 새 레코드)
|
||||||
|
console.log(`📝 [handleSave] ${targetTable} INSERT (RepeatContainer):`, dataWithMeta);
|
||||||
|
const insertResult = await apiClient.post(
|
||||||
|
`/table-management/tables/${targetTable}/add`,
|
||||||
|
dataWithMeta,
|
||||||
|
);
|
||||||
|
console.log(`✅ [handleSave] ${targetTable} INSERT 완료:`, insertResult.data);
|
||||||
|
} else {
|
||||||
|
// UPDATE (id가 있으면 기존 레코드)
|
||||||
|
const originalData = { id };
|
||||||
|
const updatedData = { ...dataWithMeta, id };
|
||||||
|
console.log(`📝 [handleSave] ${targetTable} UPDATE (RepeatContainer):`, { originalData, updatedData });
|
||||||
|
const updateResult = await apiClient.put(`/table-management/tables/${targetTable}/edit`, {
|
||||||
|
originalData,
|
||||||
|
updatedData,
|
||||||
|
});
|
||||||
|
console.log(`✅ [handleSave] ${targetTable} UPDATE 완료:`, updateResult.data);
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error(`❌ [handleSave] ${targetTable} 저장 실패 (RepeatContainer):`, error.response?.data || error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 🆕 v3.9: RepeatScreenModal 집계 저장 처리
|
// 🆕 v3.9: RepeatScreenModal 집계 저장 처리
|
||||||
const aggregationConfigs = context.formData._repeatScreenModal_aggregations as Array<{
|
const aggregationConfigs = context.formData._repeatScreenModal_aggregations as Array<{
|
||||||
resultField: string;
|
resultField: string;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue